diff --git a/.gitignore b/.gitignore index 7cd988e..ec655f0 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,6 @@ dist .tsimp .test-watch* coverage-* + +# Jetbrains ide config +.idea diff --git a/README.md b/README.md index e36deff..560d8e5 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Note the use of `incremental: true`, which speed up compilation massively. * `--pattern` or `-p`, run tests matching the given glob pattern * `--reporter` or `-r`, set up a reporter, use a colon to set a file destination. Default: `spec`. * `--no-typescript` or `-T`, disable automatic TypeScript compilation if `tsconfig.json` is found. +* `--post-compile` or `-P`, the path to a file that will be executed after each typescript compilation. ## Reporters diff --git a/borp.js b/borp.js index a01fc66..5a91a54 100755 --- a/borp.js +++ b/borp.js @@ -33,6 +33,7 @@ const args = parseArgs({ 'expose-gc': { type: 'boolean' }, help: { type: 'boolean', short: 'h' }, 'no-typescript': { type: 'boolean', short: 'T' }, + 'post-compile': { type: 'string', short: 'P' }, reporter: { type: 'string', short: 'r', diff --git a/fixtures/ts-cjs-post-compile/package.json b/fixtures/ts-cjs-post-compile/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/fixtures/ts-cjs-post-compile/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/fixtures/ts-cjs-post-compile/postCompile.ts b/fixtures/ts-cjs-post-compile/postCompile.ts new file mode 100644 index 0000000..1a72cec --- /dev/null +++ b/fixtures/ts-cjs-post-compile/postCompile.ts @@ -0,0 +1 @@ +console.log('Doing stuff') \ No newline at end of file diff --git a/fixtures/ts-cjs-post-compile/src/add.ts b/fixtures/ts-cjs-post-compile/src/add.ts new file mode 100644 index 0000000..95c44a7 --- /dev/null +++ b/fixtures/ts-cjs-post-compile/src/add.ts @@ -0,0 +1,4 @@ + +export function add (x: number, y: number): number { + return x + y +} diff --git a/fixtures/ts-cjs-post-compile/test/add.test.ts b/fixtures/ts-cjs-post-compile/test/add.test.ts new file mode 100644 index 0000000..2453771 --- /dev/null +++ b/fixtures/ts-cjs-post-compile/test/add.test.ts @@ -0,0 +1,7 @@ +import { test } from 'node:test' +import { add } from '../src/add.js' +import { strictEqual } from 'node:assert' + +test('add', () => { + strictEqual(add(1, 2), 3) +}) diff --git a/fixtures/ts-cjs-post-compile/test/add2.test.ts b/fixtures/ts-cjs-post-compile/test/add2.test.ts new file mode 100644 index 0000000..f0f049c --- /dev/null +++ b/fixtures/ts-cjs-post-compile/test/add2.test.ts @@ -0,0 +1,7 @@ +import { test } from 'node:test' +import { add } from '../src/add.js' +import { strictEqual } from 'node:assert' + +test('add2', () => { + strictEqual(add(3, 2), 5) +}) diff --git a/fixtures/ts-cjs-post-compile/tsconfig.json b/fixtures/ts-cjs-post-compile/tsconfig.json new file mode 100644 index 0000000..52f28e3 --- /dev/null +++ b/fixtures/ts-cjs-post-compile/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "outDir": "dist", + "sourceMap": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "resolveJsonModule": true, + "removeComments": true, + "newLine": "lf", + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "lib": [ + "ESNext" + ], + "incremental": true + } +} diff --git a/fixtures/ts-esm-post-compile/postCompile.ts b/fixtures/ts-esm-post-compile/postCompile.ts new file mode 100644 index 0000000..648e558 --- /dev/null +++ b/fixtures/ts-esm-post-compile/postCompile.ts @@ -0,0 +1,2 @@ + +console.log('Doing stuff') \ No newline at end of file diff --git a/fixtures/ts-esm-post-compile/src/add.ts b/fixtures/ts-esm-post-compile/src/add.ts new file mode 100644 index 0000000..95c44a7 --- /dev/null +++ b/fixtures/ts-esm-post-compile/src/add.ts @@ -0,0 +1,4 @@ + +export function add (x: number, y: number): number { + return x + y +} diff --git a/fixtures/ts-esm-post-compile/test/add.test.ts b/fixtures/ts-esm-post-compile/test/add.test.ts new file mode 100644 index 0000000..2453771 --- /dev/null +++ b/fixtures/ts-esm-post-compile/test/add.test.ts @@ -0,0 +1,7 @@ +import { test } from 'node:test' +import { add } from '../src/add.js' +import { strictEqual } from 'node:assert' + +test('add', () => { + strictEqual(add(1, 2), 3) +}) diff --git a/fixtures/ts-esm-post-compile/test/add2.test.ts b/fixtures/ts-esm-post-compile/test/add2.test.ts new file mode 100644 index 0000000..f0f049c --- /dev/null +++ b/fixtures/ts-esm-post-compile/test/add2.test.ts @@ -0,0 +1,7 @@ +import { test } from 'node:test' +import { add } from '../src/add.js' +import { strictEqual } from 'node:assert' + +test('add2', () => { + strictEqual(add(3, 2), 5) +}) diff --git a/fixtures/ts-esm-post-compile/tsconfig.json b/fixtures/ts-esm-post-compile/tsconfig.json new file mode 100644 index 0000000..52f28e3 --- /dev/null +++ b/fixtures/ts-esm-post-compile/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "outDir": "dist", + "sourceMap": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "resolveJsonModule": true, + "removeComments": true, + "newLine": "lf", + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "lib": [ + "ESNext" + ], + "incremental": true + } +} diff --git a/lib/run.js b/lib/run.js index 90cd231..c57d609 100644 --- a/lib/run.js +++ b/lib/run.js @@ -29,11 +29,35 @@ export default async function runWithTypeScript (config) { let tscPath const typescriptCliArgs = [] + let postCompileFn = async () => {} + + if (config['post-compile']) { + postCompileFn = async (outDir) => { + const postCompileFile = join(outDir ?? '', config['post-compile']).replace(/\.ts$/, '.js') + const postCompileStart = Date.now() + const { stdout } = await execa('node', [postCompileFile], { cwd: dirname(tsconfigPath) }) + pushable.push({ + type: 'test:diagnostic', + data: { + nesting: 0, + message: `Post compile hook complete (${Date.now() - postCompileStart}ms)`, + details: stdout, + typescriptCliArgs + } + }) + } + } + if (tsconfigPath && config.typescript !== false) { const tsconfig = JSON.parse(await readFile(tsconfigPath)) const _require = createRequire(tsconfigPath) const typescriptPathCWD = _require.resolve('typescript') tscPath = join(typescriptPathCWD, '..', '..', 'bin', 'tsc') + const outDir = tsconfig.compilerOptions.outDir + if (outDir) { + prefix = join(dirname(tsconfigPath), outDir) + } + if (tscPath) { // This will throw if we cannot find the `tsc` binary await access(tscPath) @@ -54,12 +78,10 @@ export default async function runWithTypeScript (config) { typescriptCliArgs } }) + + await postCompileFn(outDir) } } - const outDir = tsconfig.compilerOptions.outDir - if (outDir) { - prefix = join(dirname(tsconfigPath), outDir) - } } // TODO remove those and create a new object @@ -90,10 +112,15 @@ export default async function runWithTypeScript (config) { if (config.watch) { typescriptCliArgs.push('--watch') p = deferred() + let outDir = '' + if (config['post-compile'] && tsconfigPath) { + const tsconfig = JSON.parse(await readFile(tsconfigPath)) + outDir = tsconfig.compilerOptions.outDir + } let start = Date.now() tscChild = execa('node', [tscPath, ...typescriptCliArgs], { cwd }) tscChild.stdout.setEncoding('utf8') - tscChild.stdout.on('data', (data) => { + tscChild.stdout.on('data', async (data) => { if (data.includes('File change detected')) { start = Date.now() } else if (data.includes('Watching for file changes')) { @@ -106,6 +133,8 @@ export default async function runWithTypeScript (config) { } }) + await postCompileFn(outDir) + p.resolve() } if (data.includes('error TS')) { diff --git a/test/cli.test.js b/test/cli.test.js index 828277b..d264748 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -88,3 +88,26 @@ test('gh reporter', async () => { strictEqual(stdout.indexOf('::notice') >= 0, true) }) + +test('Post compile script should be executed when --post-compile is sent with esm', async () => { + const cwd = join(import.meta.url, '..', 'fixtures', 'ts-esm-post-compile') + const { stdout } = await execa('node', [ + borp, + '--post-compile=postCompile.ts' + ], { + cwd + }) + + strictEqual(stdout.indexOf('Post compile hook complete') >= 0, true, 'Post compile message should be found in stdout') +}) + +test('Post compile script should be executed when --post-compile is sent with cjs', async () => { + const { stdout } = await execa('node', [ + borp, + '--post-compile=postCompile.ts' + ], { + cwd: join(import.meta.url, '..', 'fixtures', 'ts-cjs-post-compile') + }) + + strictEqual(stdout.indexOf('Post compile hook complete') >= 0, true, 'Post compile message should be found in stdout') +}) diff --git a/test/watch.test.js b/test/watch.test.js index 4059b48..b10b896 100644 --- a/test/watch.test.js +++ b/test/watch.test.js @@ -109,3 +109,64 @@ test('add', () => { await completed }) + +test('watch with post compile hook should call the hook the right number of times', async (t) => { + const { strictEqual, completed, ok } = tspl(t, { plan: 2 }) + + const dir = path.resolve(await mkdtemp('.test-watch-with-post-compile-hook')) + await cp(join(import.meta.url, '..', 'fixtures', 'ts-esm-post-compile'), dir, { + recursive: true + }) + + const controller = new AbortController() + t.after(async () => { + controller.abort() + try { + await rm(dir, { recursive: true, retryDelay: 100, maxRetries: 10 }) + } catch {} + }) + + const config = { + 'post-compile': 'postCompile.ts', + files: [], + cwd: dir, + signal: controller.signal, + watch: true + } + + const stream = await runWithTypeScript(config) + + const fn = (test) => { + if (test.type === 'test:fail') { + strictEqual(test.data.name, 'add') + stream.removeListener('data', fn) + } + } + stream.on('data', fn) + + let postCompileEventCount = 0 + const diagnosticListenerFn = (test) => { + if (test.type === 'test:diagnostic' && test.data.message.includes('Post compile hook complete')) { + if (++postCompileEventCount === 2) { + ok(true, 'Post compile hook ran twice') + stream.removeListener('data', diagnosticListenerFn) + } + } + } + + stream.on('data', diagnosticListenerFn) + + const toWrite = ` +import { test } from 'node:test' +import { add } from '../src/add.js' +import { strictEqual } from 'node:assert' + +test('add', () => { + strictEqual(add(1, 2), 4) +}) +` + const file = path.join(dir, 'test', 'add.test.ts') + await writeFile(file, toWrite) + + await completed +})