borp 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -21,7 +21,18 @@ Borp will autumatically run all tests files matching `*.test.{js|ts}`.
21
21
 
22
22
  ### Example project setup
23
23
 
24
- As an example, consider having a `src/add.ts` file
24
+ ```
25
+ .
26
+ ├── src
27
+ │   ├── lib
28
+ │   │   └── add.ts
29
+ │   └── test
30
+ │   └── add.test.ts
31
+ └── tsconfig.json
32
+
33
+ ```
34
+
35
+ As an example, consider having a `src/lib/add.ts` file
25
36
 
26
37
  ```typescript
27
38
  export function add (x: number, y: number): number {
@@ -29,11 +40,11 @@ export function add (x: number, y: number): number {
29
40
  }
30
41
  ```
31
42
 
32
- and a `test/add.test.ts` file:
43
+ and a `src/test/add.test.ts` file:
33
44
 
34
45
  ```typescript
35
46
  import { test } from 'node:test'
36
- import { add } from '../src/add.js'
47
+ import { add } from '../lib/add.js'
37
48
  import { strictEqual } from 'node:assert'
38
49
 
39
50
  test('add', () => {
@@ -41,7 +52,7 @@ test('add', () => {
41
52
  })
42
53
  ```
43
54
 
44
- and the following `tsconfig`:
55
+ and the following `tsconfig.json`:
45
56
 
46
57
  ```json
47
58
  {
@@ -79,6 +90,8 @@ Note the use of `incremental: true`, which speed up compilation massively.
79
90
  * `--watch` or `-w`, re-run tests on changes
80
91
  * `--timeout` or `-t`, timeouts the tests after a given time; default is 30000 ms
81
92
  * `--coverage-exclude` or `-X`, a list of comma-separated patterns to exclude from the coverage report. All tests files are ignored by default.
93
+ * `--ignore` or `-i`, ignore a glob pattern, and not look for tests there
94
+ * `--pattern` or `-p`, run tests matching the given glob pattern
82
95
 
83
96
  ## License
84
97
 
package/borp.js CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { parseArgs } from 'node:util'
4
4
  import { tap, spec } from 'node:test/reporters'
5
- import { mkdtemp, rm } from 'node:fs/promises'
5
+ import { mkdtemp, rm, readFile } from 'node:fs/promises'
6
6
  import { finished } from 'node:stream/promises'
7
7
  import { join, relative } from 'node:path'
8
8
  import posix from 'node:path/posix'
9
9
  import runWithTypeScript from './lib/run.js'
10
10
  import { Report } from 'c8'
11
+ import os from 'node:os'
11
12
 
12
13
  let reporter
13
14
  /* c8 ignore next 4 */
@@ -24,14 +25,22 @@ const args = parseArgs({
24
25
  only: { type: 'boolean', short: 'o' },
25
26
  watch: { type: 'boolean', short: 'w' },
26
27
  pattern: { type: 'string', short: 'p' },
27
- concurrency: { type: 'string', short: 'c' },
28
+ concurrency: { type: 'string', short: 'c', default: os.availableParallelism() - 1 + '' },
28
29
  coverage: { type: 'boolean', short: 'C' },
29
30
  timeout: { type: 'string', short: 't', default: '30000' },
30
- 'coverage-exclude': { type: 'string', short: 'X' }
31
+ 'coverage-exclude': { type: 'string', short: 'X', multiple: true },
32
+ ignore: { type: 'string', short: 'i', multiple: true },
33
+ help: { type: 'boolean', short: 'h' }
31
34
  },
32
35
  allowPositionals: true
33
36
  })
34
37
 
38
+ /* c8 ignore next 4 */
39
+ if (args.values.help) {
40
+ console.log(await readFile(new URL('./README.md', import.meta.url), 'utf8'))
41
+ process.exit(0)
42
+ }
43
+
35
44
  if (args.values.concurrency) {
36
45
  args.values.concurrency = parseInt(args.values.concurrency)
37
46
  }
@@ -42,7 +51,7 @@ if (args.values.timeout) {
42
51
 
43
52
  let covDir
44
53
  if (args.values.coverage) {
45
- covDir = await mkdtemp(join(process.cwd(), 'coverage-'))
54
+ covDir = await mkdtemp(join(os.tmpdir(), 'coverage-'))
46
55
  process.env.NODE_V8_COVERAGE = covDir
47
56
  }
48
57
 
@@ -65,15 +74,12 @@ try {
65
74
  await finished(stream)
66
75
 
67
76
  if (covDir) {
68
- let exclude = (args.values['coverage-exclude'] || '').split(',').filter(Boolean)
77
+ let exclude = args.values['coverage-exclude']
69
78
 
70
- if (exclude.length === 0) {
71
- exclude = undefined
72
- } else if (config.prefix) {
79
+ if (exclude && config.prefix) {
73
80
  const localPrefix = relative(process.cwd(), config.prefix)
74
81
  exclude = exclude.map((file) => posix.join(localPrefix, file))
75
82
  }
76
- console.log('>> Excluding from coverage:', exclude)
77
83
  const report = Report({
78
84
  reporter: ['text'],
79
85
  tempDirectory: covDir,
@@ -87,6 +93,9 @@ try {
87
93
  console.error(err)
88
94
  } finally {
89
95
  if (covDir) {
90
- await rm(covDir, { recursive: true })
96
+ try {
97
+ await rm(covDir, { recursive: true, maxRetries: 10, retryDelay: 100 })
98
+ /* c8 ignore next 2 */
99
+ } catch {}
91
100
  }
92
101
  }
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from './add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add', () => {
6
+ strictEqual(add(1, 2), 3)
7
+ })
@@ -0,0 +1,4 @@
1
+
2
+ export function add (x: number, y: number): number {
3
+ return x + y
4
+ }
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test'
2
+ import { add } from './add.js'
3
+ import { strictEqual } from 'node:assert'
4
+
5
+ test('add2', () => {
6
+ strictEqual(add(3, 2), 5)
7
+ })
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "sourceMap": true,
6
+ "target": "ES2022",
7
+ "module": "NodeNext",
8
+ "moduleResolution": "NodeNext",
9
+ "esModuleInterop": true,
10
+ "strict": true,
11
+ "resolveJsonModule": true,
12
+ "removeComments": true,
13
+ "newLine": "lf",
14
+ "noUnusedLocals": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "isolatedModules": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "skipLibCheck": true,
19
+ "lib": [
20
+ "ESNext"
21
+ ],
22
+ "incremental": true
23
+ }
24
+ }
@@ -0,0 +1,4 @@
1
+
2
+ export function add (x: number, y: number): number {
3
+ return x + y
4
+ }
@@ -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,24 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "sourceMap": true,
6
+ "target": "ES2022",
7
+ "module": "NodeNext",
8
+ "moduleResolution": "NodeNext",
9
+ "esModuleInterop": true,
10
+ "strict": true,
11
+ "resolveJsonModule": true,
12
+ "removeComments": true,
13
+ "newLine": "lf",
14
+ "noUnusedLocals": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "isolatedModules": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "skipLibCheck": true,
19
+ "lib": [
20
+ "ESNext"
21
+ ],
22
+ "incremental": true
23
+ }
24
+ }
package/lib/run.js CHANGED
@@ -2,20 +2,10 @@ import { run } from 'node:test'
2
2
  import { glob } from 'glob'
3
3
  import { findUp } from 'find-up'
4
4
  import { createRequire } from 'node:module'
5
- import { resolve, join, dirname } from 'node:path'
5
+ import { join, dirname } from 'node:path'
6
6
  import { access, readFile } from 'node:fs/promises'
7
7
  import { execa } from 'execa'
8
8
 
9
- async function isFileAccessible (filename, directory) {
10
- try {
11
- const filePath = directory ? resolve(directory, filename) : filename
12
- await access(filePath)
13
- return true
14
- } catch (err) {
15
- return false
16
- }
17
- }
18
-
19
9
  function deferred () {
20
10
  let resolve
21
11
  let reject
@@ -28,7 +18,7 @@ function deferred () {
28
18
 
29
19
  export default async function runWithTypeScript (config) {
30
20
  const { cwd } = config
31
- const chunks = []
21
+ let pushable = []
32
22
  const tsconfigPath = await findUp('tsconfig.json', { cwd })
33
23
 
34
24
  let prefix = ''
@@ -39,22 +29,20 @@ export default async function runWithTypeScript (config) {
39
29
  const typescriptPathCWD = _require.resolve('typescript')
40
30
  tscPath = join(typescriptPathCWD, '..', '..', 'bin', 'tsc')
41
31
  if (tscPath) {
42
- const isAccessible = await isFileAccessible(tscPath)
43
- if (isAccessible) {
44
- // Watch is handled aftterwards
45
- if (!config.watch) {
46
- const start = Date.now()
47
- await execa('node', [tscPath], { cwd: dirname(tsconfigPath) })
48
- chunks.push({
49
- type: 'test:diagnostic',
50
- data: {
51
- nesting: 0,
52
- message: `TypeScript compilation complete (${Date.now() - start}ms)`
53
- }
54
- })
55
- }
56
- } else {
57
- throw new Error('Could not find tsc')
32
+ // This will throw if we cannot find the `tsc` binary
33
+ await access(tscPath)
34
+
35
+ // Watch is handled aftterwards
36
+ if (!config.watch) {
37
+ const start = Date.now()
38
+ await execa('node', [tscPath], { cwd: dirname(tsconfigPath) })
39
+ pushable.push({
40
+ type: 'test:diagnostic',
41
+ data: {
42
+ nesting: 0,
43
+ message: `TypeScript compilation complete (${Date.now() - start}ms)`
44
+ }
45
+ })
58
46
  }
59
47
  }
60
48
  const tsconfig = JSON.parse(await readFile(tsconfigPath))
@@ -65,9 +53,10 @@ export default async function runWithTypeScript (config) {
65
53
  }
66
54
  config.prefix = prefix
67
55
  config.setup = (test) => {
68
- for (const chunk of chunks) {
56
+ for (const chunk of pushable) {
69
57
  test.reporter.push(chunk)
70
58
  }
59
+ pushable = test.reporter
71
60
  }
72
61
 
73
62
  let tscChild
@@ -82,7 +71,7 @@ export default async function runWithTypeScript (config) {
82
71
  tscChild.stdout.setEncoding('utf8')
83
72
  tscChild.stdout.on('data', (data) => {
84
73
  if (data.includes('Watching for file changes')) {
85
- chunks.push({
74
+ pushable.push({
86
75
  type: 'test:diagnostic',
87
76
  data: {
88
77
  nesting: 0,
@@ -93,8 +82,7 @@ export default async function runWithTypeScript (config) {
93
82
  p.resolve()
94
83
  }
95
84
  if (data.includes('error TS')) {
96
- const toPush = stream || chunks
97
- toPush.push({
85
+ pushable.push({
98
86
  type: 'test:fail',
99
87
  data: {
100
88
  nesting: 0,
@@ -115,20 +103,23 @@ export default async function runWithTypeScript (config) {
115
103
  }
116
104
 
117
105
  let files = config.files || []
118
- const ignore = join('node_modules', '**')
106
+ const ignore = config.ignore || []
107
+ ignore.unshift('node_modules/**/*')
119
108
  if (files.length > 0) {
120
109
  if (prefix) {
121
110
  files = files.map((file) => join(prefix, file.replace(/ts$/, 'js')))
122
111
  }
123
112
  } else if (config.pattern) {
113
+ let pattern = config.pattern
124
114
  if (prefix) {
125
- config.pattern = join(prefix, config.pattern)
115
+ pattern = join(prefix, pattern)
116
+ pattern = pattern.replace(/ts$/, 'js')
126
117
  }
127
- files = await glob(config.pattern, { ignore, cwd, windowsPathsNoEscape: true })
118
+ files = await glob(pattern, { ignore, cwd, windowsPathsNoEscape: true })
128
119
  } else if (prefix) {
129
- files = await glob(join(prefix, join('test', '**', '*.test.{cjs,mjs,js}')), { ignore, cwd, windowsPathsNoEscape: true })
120
+ files = await glob(join(prefix, join('**', '*.test.{cjs,mjs,js}')), { ignore, cwd, windowsPathsNoEscape: true })
130
121
  } else {
131
- files = await glob(join('test', '**', '*.test.{cjs,mjs,js}'), { ignore, cwd, windowsPathsNoEscape: true })
122
+ files = await glob(join('**', '*.test.{cjs,mjs,js}'), { ignore, cwd, windowsPathsNoEscape: true })
132
123
  }
133
124
 
134
125
  config.files = files
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "borp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "node:test wrapper with TypeScript support",
6
6
  "main": "borp.js",
7
7
  "scripts": {
8
- "clean": "rm -rf fixtures/*/dist .test-* coverage-*",
8
+ "clean": "rm -rf fixtures/*/dist .test-*",
9
9
  "lint": "standard | snazzy",
10
- "unit": "node borp.js --concurrency=1 --coverage --coverage-exclude \"fixtures/**/*,test/**/*\"",
10
+ "unit": "node borp.js --ignore \"fixtures/**/*\" --coverage --coverage-exclude \"fixtures/**/*\" --coverage-exclude \"test/**/*\"",
11
11
  "test": "npm run clean ; npm run lint && npm run unit"
12
12
  },
13
13
  "keywords": [],
@@ -45,3 +45,123 @@ test('ts-cjs', async (t) => {
45
45
 
46
46
  await completed
47
47
  })
48
+
49
+ test('ts-esm with named failes', async (t) => {
50
+ const { strictEqual, completed, match } = tspl(t, { plan: 3 })
51
+ const config = {
52
+ files: ['test/add.test.ts'],
53
+ cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
54
+ }
55
+
56
+ const stream = await runWithTypeScript(config)
57
+
58
+ const names = new Set(['add'])
59
+
60
+ stream.once('data', (test) => {
61
+ strictEqual(test.type, 'test:diagnostic')
62
+ match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
63
+ })
64
+
65
+ stream.on('test:pass', (test) => {
66
+ strictEqual(names.has(test.name), true)
67
+ names.delete(test.name)
68
+ })
69
+
70
+ await completed
71
+ })
72
+
73
+ test('pattern', async (t) => {
74
+ const { strictEqual, completed, match } = tspl(t, { plan: 3 })
75
+ const config = {
76
+ files: [],
77
+ pattern: 'test/*2.test.ts',
78
+ cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
79
+ }
80
+
81
+ const stream = await runWithTypeScript(config)
82
+
83
+ const names = new Set(['add2'])
84
+
85
+ stream.once('data', (test) => {
86
+ strictEqual(test.type, 'test:diagnostic')
87
+ match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
88
+ })
89
+
90
+ stream.on('test:pass', (test) => {
91
+ strictEqual(names.has(test.name), true)
92
+ names.delete(test.name)
93
+ })
94
+
95
+ await completed
96
+ })
97
+
98
+ test('no files', async (t) => {
99
+ const { strictEqual, completed, match } = tspl(t, { plan: 4 })
100
+ const config = {
101
+ cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
102
+ }
103
+
104
+ const stream = await runWithTypeScript(config)
105
+
106
+ const names = new Set(['add', 'add2'])
107
+
108
+ stream.once('data', (test) => {
109
+ strictEqual(test.type, 'test:diagnostic')
110
+ match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
111
+ })
112
+
113
+ stream.on('test:pass', (test) => {
114
+ strictEqual(names.has(test.name), true)
115
+ names.delete(test.name)
116
+ })
117
+
118
+ await completed
119
+ })
120
+
121
+ test('src-to-dist', async (t) => {
122
+ const { strictEqual, completed, match } = tspl(t, { plan: 4 })
123
+ const config = {
124
+ files: [],
125
+ cwd: join(import.meta.url, '..', 'fixtures', 'src-to-dist')
126
+ }
127
+
128
+ const stream = await runWithTypeScript(config)
129
+
130
+ const names = new Set(['add', 'add2'])
131
+
132
+ stream.once('data', (test) => {
133
+ strictEqual(test.type, 'test:diagnostic')
134
+ match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
135
+ })
136
+
137
+ stream.on('test:pass', (test) => {
138
+ strictEqual(names.has(test.name), true)
139
+ names.delete(test.name)
140
+ })
141
+
142
+ await completed
143
+ })
144
+
145
+ test('only-src', async (t) => {
146
+ const { strictEqual, completed, match } = tspl(t, { plan: 4 })
147
+ const config = {
148
+ files: [],
149
+ cwd: join(import.meta.url, '..', 'fixtures', 'only-src')
150
+ }
151
+
152
+ const stream = await runWithTypeScript(config)
153
+
154
+ const names = new Set(['add', 'add2'])
155
+
156
+ stream.once('data', (test) => {
157
+ strictEqual(test.type, 'test:diagnostic')
158
+ match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
159
+ })
160
+
161
+ stream.on('test:pass', (test) => {
162
+ strictEqual(names.has(test.name), true)
163
+ names.delete(test.name)
164
+ })
165
+
166
+ await completed
167
+ })
@@ -17,7 +17,9 @@ test('watch', async (t) => {
17
17
  const controller = new AbortController()
18
18
  t.after(async () => {
19
19
  controller.abort()
20
- await rm(dir, { recursive: true })
20
+ try {
21
+ await rm(dir, { recursive: true, retryDelay: 100, maxRetries: 10 })
22
+ } catch {}
21
23
  })
22
24
 
23
25
  const config = {
@@ -67,7 +69,9 @@ test('watch file syntax error', async (t) => {
67
69
  const controller = new AbortController()
68
70
  t.after(async () => {
69
71
  controller.abort()
70
- await rm(dir, { recursive: true })
72
+ try {
73
+ await rm(dir, { recursive: true, retryDelay: 100, maxRetries: 10 })
74
+ } catch {}
71
75
  })
72
76
 
73
77
  const config = {