borp 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- node-version: [18.x, 20.x, 21.x]
19
+ node-version: [18.x, 20.x, 21.x, 22.x]
20
20
  os: [ubuntu-latest, windows-latest]
21
21
  exclude:
22
22
  - os: windows-latest
@@ -39,4 +39,4 @@ jobs:
39
39
 
40
40
  - name: Run tests
41
41
  run: |
42
- npm run unit -- --reporter spec
42
+ npm run unit
package/README.md CHANGED
@@ -18,6 +18,15 @@ borp --coverage
18
18
 
19
19
  # with check coverage active
20
20
  borp --coverage --check-coverage --lines 95
21
+
22
+ # with a node_modules located reporter
23
+ borp --reporter foo
24
+
25
+ # with a node_modules located reporter writing to stderr
26
+ borp --reporter foo:stderr
27
+
28
+ # with a local custom reporter
29
+ borp --reporter ./lib/some-reporter.mjs
21
30
  ```
22
31
 
23
32
  Borp will automatically run all tests files matching `*.test.{js|ts}`.
@@ -98,7 +107,7 @@ Note the use of `incremental: true`, which speed up compilation massively.
98
107
  * `--ignore` or `-i`, ignore a glob pattern, and not look for tests there
99
108
  * `--expose-gc`, exposes the gc() function to tests
100
109
  * `--pattern` or `-p`, run tests matching the given glob pattern
101
- * `--reporter` or `-r`, set up a reporter, use a colon to set a file destination. Default: `spec`.
110
+ * `--reporter` or `-r`, set up a reporter, use a colon to set a file destination. Reporter may either be a module name resolvable by standard `node_modules` resolution, or a path to a script relative to the process working directory (must be an ESM script). Default: `spec`.
102
111
  * `--no-typescript` or `-T`, disable automatic TypeScript compilation if `tsconfig.json` is found.
103
112
  * `--post-compile` or `-P`, the path to a file that will be executed after each typescript compilation.
104
113
  * `--check-coverage`, enables c8 check coverage; default is false
package/borp.js CHANGED
@@ -114,7 +114,19 @@ try {
114
114
 
115
115
  for (const input of args.values.reporter) {
116
116
  const [name, dest] = input.split(':')
117
- const Ctor = reporters[name] || await import(name).then((m) => m.default || m)
117
+ let Ctor
118
+ if (Object.prototype.hasOwnProperty.call(reporters, name) === true) {
119
+ Ctor = reporters[name]
120
+ } else {
121
+ try {
122
+ // Try to load a custom reporter from a file relative to the process.
123
+ const modPath = join(process.cwd(), name.replace(/^['"]/, '').replace(/['"]$/, ''))
124
+ Ctor = await import(modPath).then((m) => m.default || m)
125
+ } catch {
126
+ // Fallback to trying to load the reporter from node_modules resolution.
127
+ Ctor = await import(name).then((m) => m.default || m)
128
+ }
129
+ }
118
130
  const reporter = Ctor.prototype && Object.getOwnPropertyDescriptor(Ctor.prototype, 'constructor') ? new Ctor() : Ctor
119
131
  let output = process.stdout
120
132
  if (dest) {
@@ -0,0 +1,3 @@
1
+ export function add (x, y) {
2
+ return x + y
3
+ }
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from '../lib/add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add', () => {
6
+ strictEqual(add(1, 2), 3)
7
+ })
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from '../../lib/add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add2', () => {
6
+ strictEqual(add(3, 2), 5)
7
+ })
@@ -0,0 +1,3 @@
1
+ export function add (x, y) {
2
+ return x + y
3
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ import { Transform } from 'node:stream'
4
+
5
+ const testReporter = new Transform({
6
+ writableObjectMode: true,
7
+ transform (event, encoding, callback) {
8
+ switch (event.type) {
9
+ case 'test:pass': {
10
+ return callback(null, `passed: ${event.data.file}\n`)
11
+ }
12
+
13
+ case 'test:fail': {
14
+ return callback(null, `failed: ${event.data.file}\n`)
15
+ }
16
+
17
+ default: {
18
+ callback(null, null)
19
+ }
20
+ }
21
+ }
22
+ })
23
+
24
+ export default testReporter
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from '../lib/add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add', () => {
6
+ strictEqual(add(1, 2), 3)
7
+ })
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from '../lib/add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add2', () => {
6
+ strictEqual(add(3, 2), 5)
7
+ })
package/lib/run.js CHANGED
@@ -3,7 +3,7 @@ import { glob } from 'glob'
3
3
  import { findUp } from 'find-up'
4
4
  import { createRequire } from 'node:module'
5
5
  import { join, dirname } from 'node:path'
6
- import { access, readFile } from 'node:fs/promises'
6
+ import { access } from 'node:fs/promises'
7
7
  import { execa } from 'execa'
8
8
 
9
9
  function deferred () {
@@ -17,7 +17,7 @@ function deferred () {
17
17
  }
18
18
 
19
19
  function enableSourceMapSupport (tsconfig) {
20
- if (!tsconfig?.compilerOptions?.sourceMap) {
20
+ if (!tsconfig?.options?.sourceMap) {
21
21
  return
22
22
  }
23
23
 
@@ -56,13 +56,21 @@ export default async function runWithTypeScript (config) {
56
56
  }
57
57
 
58
58
  if (tsconfigPath && config.typescript !== false) {
59
- const tsconfig = JSON.parse(await readFile(tsconfigPath))
60
59
  const _require = createRequire(tsconfigPath)
60
+ const { parseJsonConfigFileContent, readConfigFile, sys } = _require('typescript')
61
+
62
+ const configFile = readConfigFile(tsconfigPath, sys.readFile)
63
+ const tsconfig = parseJsonConfigFileContent(
64
+ configFile.config,
65
+ sys,
66
+ dirname(tsconfigPath)
67
+ )
68
+
61
69
  const typescriptPathCWD = _require.resolve('typescript')
62
70
  tscPath = join(typescriptPathCWD, '..', '..', 'bin', 'tsc')
63
- const outDir = tsconfig.compilerOptions.outDir
71
+ const outDir = tsconfig.options.outDir
64
72
  if (outDir) {
65
- prefix = join(dirname(tsconfigPath), outDir)
73
+ prefix = outDir
66
74
  }
67
75
 
68
76
  enableSourceMapSupport(tsconfig)
@@ -73,7 +81,7 @@ export default async function runWithTypeScript (config) {
73
81
 
74
82
  // Watch is handled aftterwards
75
83
  if (!config.watch) {
76
- if (Array.isArray(tsconfig.references) && tsconfig.references.length > 0) {
84
+ if (Array.isArray(tsconfig.projectReferences) && tsconfig.projectReferences.length > 0) {
77
85
  typescriptCliArgs.push('--build')
78
86
  }
79
87
  const start = Date.now()
@@ -123,8 +131,17 @@ export default async function runWithTypeScript (config) {
123
131
  p = deferred()
124
132
  let outDir = ''
125
133
  if (config['post-compile'] && tsconfigPath) {
126
- const tsconfig = JSON.parse(await readFile(tsconfigPath))
127
- outDir = tsconfig.compilerOptions.outDir
134
+ const _require = createRequire(tsconfigPath)
135
+ const { parseJsonConfigFileContent, readConfigFile, sys } = _require('typescript')
136
+
137
+ const configFile = readConfigFile(tsconfigPath, sys.readFile)
138
+ const tsconfig = parseJsonConfigFileContent(
139
+ configFile.config,
140
+ sys,
141
+ dirname(tsconfigPath)
142
+ )
143
+
144
+ outDir = tsconfig.options.outDir
128
145
 
129
146
  enableSourceMapSupport(tsconfig)
130
147
  }
@@ -158,6 +175,9 @@ export default async function runWithTypeScript (config) {
158
175
  })
159
176
  }
160
177
  })
178
+ // We must noop `.catch()`, otherwise `tscChild` will
179
+ // reject.
180
+ tscChild.catch(() => {})
161
181
  if (config.signal) {
162
182
  config.signal.addEventListener('abort', () => {
163
183
  tscChild.kill()
@@ -176,6 +196,16 @@ export default async function runWithTypeScript (config) {
176
196
  if (prefix) {
177
197
  files = files.map((file) => join(prefix, file.replace(/ts$/, 'js')))
178
198
  }
199
+ const expandedFiles = []
200
+ for (let i = 0; i < files.length; i += 1) {
201
+ if (files[i].includes('*') === false) {
202
+ expandedFiles.push(files[i])
203
+ continue
204
+ }
205
+ const parsed = await glob(files[i].replace(/^['"]/, '').replace(/['"]$/, ''), { ignore, cwd, windowsPathsNoEscape: true })
206
+ Array.prototype.push.apply(expandedFiles, parsed)
207
+ }
208
+ files = expandedFiles
179
209
  } else if (config.pattern) {
180
210
  let pattern = config.pattern
181
211
  if (prefix) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borp",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "node:test wrapper with TypeScript support",
6
6
  "main": "borp.js",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@reporters/github": "^1.5.4",
35
35
  "c8": "^10.0.0",
36
- "execa": "^8.0.1",
36
+ "execa": "^9.3.0",
37
37
  "find-up": "^7.0.0",
38
38
  "glob": "^10.3.10"
39
39
  }
package/test/cli.test.js CHANGED
@@ -74,6 +74,19 @@ test('reporter from node_modules', async () => {
74
74
  strictEqual(stdout.indexOf('tests 2') >= 0, true)
75
75
  })
76
76
 
77
+ test('reporter from relative path', async () => {
78
+ const cwd = join(import.meta.url, '..', 'fixtures', 'relative-reporter')
79
+ const { stdout } = await execa('node', [
80
+ borp,
81
+ '--reporter=./fixtures/relative-reporter/reporter.js'
82
+ ], {
83
+ cwd
84
+ })
85
+
86
+ strictEqual(/passed:.+add\.test\.js/.test(stdout), true)
87
+ strictEqual(/passed:.+add2\.test\.js/.test(stdout), true)
88
+ })
89
+
77
90
  test('gh reporter', async () => {
78
91
  const cwd = join(import.meta.url, '..', 'fixtures', 'js-esm')
79
92
  const { stdout } = await execa('node', [
@@ -89,6 +102,19 @@ test('gh reporter', async () => {
89
102
  strictEqual(stdout.indexOf('::notice') >= 0, true)
90
103
  })
91
104
 
105
+ test('interprets globs for files', async () => {
106
+ const cwd = join(import.meta.url, '..', 'fixtures', 'files-glob')
107
+ const { stdout } = await execa('node', [
108
+ borp,
109
+ '\'test1/*.test.js\'',
110
+ '\'test2/**/*.test.js\''
111
+ ], {
112
+ cwd
113
+ })
114
+
115
+ strictEqual(stdout.indexOf('tests 2') >= 0, true)
116
+ })
117
+
92
118
  test('Post compile script should be executed when --post-compile is sent with esm', async () => {
93
119
  const cwd = join(import.meta.url, '..', 'fixtures', 'ts-esm-post-compile')
94
120
  const { stdout } = await execa('node', [
@@ -6,7 +6,10 @@ import { mkdtemp, cp, writeFile, rm } from 'node:fs/promises'
6
6
  import path from 'node:path'
7
7
  import { once } from 'node:events'
8
8
 
9
- test('watch', async (t) => {
9
+ // These tests are currently broken on node v22
10
+ const skip = process.platform === 'darwin' && process.version.startsWith('v22')
11
+
12
+ test('watch', { skip }, async (t) => {
10
13
  const { strictEqual, completed, match } = tspl(t, { plan: 3 })
11
14
 
12
15
  const dir = path.resolve(await mkdtemp('.test-watch'))
@@ -24,15 +27,17 @@ test('watch', async (t) => {
24
27
 
25
28
  const config = {
26
29
  files: [],
27
- cwd: dir,
28
30
  signal: controller.signal,
31
+ cwd: dir,
29
32
  watch: true
30
33
  }
31
34
 
35
+ process._rawDebug('dir', dir)
32
36
  const stream = await runWithTypeScript(config)
33
37
 
34
38
  const fn = (test) => {
35
39
  if (test.type === 'test:fail') {
40
+ console.log('test', test)
36
41
  match(test.data.name, /add/)
37
42
  stream.removeListener('data', fn)
38
43
  }
@@ -58,7 +63,7 @@ test('add', () => {
58
63
  await completed
59
64
  })
60
65
 
61
- test('watch file syntax error', async (t) => {
66
+ test('watch file syntax error', { skip }, async (t) => {
62
67
  const { strictEqual, completed, match } = tspl(t, { plan: 3 })
63
68
 
64
69
  const dir = path.resolve(await mkdtemp('.test-watch'))
@@ -110,7 +115,7 @@ test('add', () => {
110
115
  await completed
111
116
  })
112
117
 
113
- test('watch with post compile hook should call the hook the right number of times', async (t) => {
118
+ test('watch with post compile hook should call the hook the right number of times', { skip }, async (t) => {
114
119
  const { completed, ok, match } = tspl(t, { plan: 2 })
115
120
 
116
121
  const dir = path.resolve(await mkdtemp('.test-watch-with-post-compile-hook'))