UNPKG

cmpl

Version:

no deps, no operations, no vowels - just a tiny compiler

769 lines (732 loc) 19.1 kB
/// <reference types="types-node-test" /> import test from 'node:test'; import assert from 'node:assert'; import { relative, join } from 'node:path'; import { cmpl, cntntHsh, wtch, WatchFs, WatchEvent } from './index'; // (async () => { // for await (const mnfst of wtch({ // entry: __dirname, // processors: [ // { // rename: cntntHsh(1), // outDir: __dirname + '/../dist/test', // }, // { // rename: cntntHsh(2), // outDir: __dirname + '/../dist/test', // }, // ], // })) { // console.log(mnfst); // } // })().catch((err) => { // console.log({ err }); // process.exit(1); // }); test('cmpl', async (t) => { await t.test('copies over a single file', async () => { const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}' }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: join(__dirname, 'test.json'), processors: [ { outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'test.json' }, ); fs.done(); }); await t.test('copies single file in directory', async () => { const fs = createFs( [ { method: 'stat', path: '', isDir: true }, { method: 'readdir', path: '', contents: ['test.json'] }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}' }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: __dirname, processors: [ { outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'test.json' }, ); fs.done(); }); await t.test('copies files in nested directories', async () => { const fs = createFs( [ { method: 'stat', path: '', isDir: true }, { method: 'readdir', path: '', contents: ['test.json', 'deep'] }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'stat', path: 'deep', isDir: true }, { method: 'readdir', path: 'deep', contents: ['test2.json'] }, { method: 'stat', path: 'deep/test2.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}' }, { method: 'readFile', path: 'deep/test2.json', contents: '{"hü":"hott"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist/deep' }, { method: 'writeFile', path: 'dist/deep/test2.json', contents: '{"hü":"hott"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: __dirname, processors: [ { outDir: join(__dirname, 'dist'), }, ], fs, }), { 'deep/test2.json': 'deep/test2.json', 'test.json': 'test.json', }, ); fs.done(); }); await t.test('it ignores nested folders', async () => { const fs = createFs( [ { method: 'stat', path: '', isDir: true }, { method: 'readdir', path: '', contents: ['test.json', 'deep'] }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'stat', path: 'deep', isDir: true }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}' }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: __dirname, processors: [ { recursive: false, outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'test.json', }, ); fs.done(); }); await t.test('it ignores specific files', async () => { const fs = createFs( [ { method: 'stat', path: '', isDir: true }, { method: 'readdir', path: '', contents: ['test.json', 'test2.json', 'deep'], }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'stat', path: 'test2.json', isDir: false }, { method: 'stat', path: 'deep', isDir: true }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}' }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, ], __dirname, ); let i = 0; assert.deepEqual( await cmpl({ entry: __dirname, processors: [ { include: (name, isDir) => { switch (i++) { case 0: assert.equal(name, 'test.json'); assert.equal(isDir, false); break; case 1: assert.equal(name, 'test2.json'); assert.equal(isDir, false); break; case 2: assert.equal(name, 'deep'); assert.equal(isDir, true); break; } return name === 'test.json'; }, outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'test.json', }, ); fs.done(); }); await t.test('it transforms contents', async () => { const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: 'rofl', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: 'leeeeel', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: join(__dirname, 'test.json'), processors: [ { transform: (content, file) => { assert.equal(file, 'test.json'); assert.equal(content.toString(), 'rofl'); return Buffer.from('leeeeel'); }, outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'test.json', }, ); fs.done(); }); await t.test('it applies content hashes', async () => { const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test-DF67FD3A.json', contents: '{"hi":"ho"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: join(__dirname, 'test.json'), processors: [ { outDir: join(__dirname, 'dist'), rename: cntntHsh(8), }, ], fs, }), { 'test.json': 'test-DF67FD3A.json', }, ); fs.done(); }); await t.test('it creates custom file names', async () => { const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/q3p45giunrfd.f35r', contents: '{"hi":"ho"}', }, ], __dirname, ); assert.deepEqual( await cmpl({ entry: join(__dirname, 'test.json'), processors: [ { rename: (name, content) => { assert.equal(name, 'test.json'); assert.equal(content.toString(), '{"hi":"ho"}'); return 'q3p45giunrfd.f35r'; }, outDir: join(__dirname, 'dist'), }, ], fs, }), { 'test.json': 'q3p45giunrfd.f35r', }, ); fs.done(); }); await t.test('watches single file', async () => { const watchEvents: Deferred<WatchEvent>[] = [defer()]; const watcher = createWatcher(watchEvents.map(({ promise }) => promise)); const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test-DF.json', contents: '{"hi":"ho"}', }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'watch', path: 'test.json', options: { recursive: false, signal: undefined }, watcher, }, { method: 'readFile', path: 'test.json', contents: '{"ho":"hi"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test-9E.json', contents: '{"ho":"hi"}', }, ], __dirname, ); const wtchr = wtch({ processors: [ { outDir: join(__dirname, 'dist'), rename: cntntHsh(2), }, ], entry: join(__dirname, 'test.json'), fs, }); let i = 0; for await (const manifest of wtchr) { switch (i++) { case 0: assert.deepEqual(manifest, { 'test.json': 'test-DF.json', }); watchEvents[0].resolve({ eventType: 'change', filename: 'test.json', }); break; case 1: assert.deepEqual(manifest, { 'test.json': 'test-9E.json', }); break; default: throw new Error('NEIN'); } } fs.done(); }); await t.test('ignores newly added files', async () => { const watchEvents: Deferred<WatchEvent>[] = [defer()]; const watcher = createWatcher(watchEvents.map(({ promise }) => promise)); const fs = createFs( [ { method: 'stat', path: '', isDir: true }, { method: 'readdir', path: '', contents: ['test.json'] }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, { method: 'stat', path: '', isDir: true }, { method: 'watch', path: '', options: { recursive: true, signal: undefined }, watcher, }, ], __dirname, ); const wtchr = wtch({ entry: __dirname, processors: [ { include: (name) => name === 'test.json', outDir: join(__dirname, 'dist'), }, ], fs, }); let i = 0; for await (const manifest of wtchr) { switch (i++) { case 0: assert.deepEqual(manifest, { 'test.json': 'test.json', }); watchEvents[0].resolve({ eventType: 'change', filename: 'test2.json', }); break; default: throw new Error('NEIN'); } } fs.done(); }); await t.test('continues watching despite error', async () => { const watchEvents: Deferred<WatchEvent>[] = [defer(), defer(), defer()]; const watcher = createWatcher(watchEvents.map(({ promise }) => promise)); const myErr1 = new Error('Err1'); const myErr2 = new Error('Err2'); const fs = createFs( [ { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: myErr1, }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'watch', path: 'test.json', options: { recursive: false, signal: undefined }, watcher, }, { method: 'stat', path: 'test.json', isDir: false }, { method: 'readFile', path: 'test.json', contents: '{"hi":"ho"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"hi":"ho"}', }, { method: 'readFile', path: 'test.json', contents: myErr2, }, { method: 'readFile', path: 'test.json', contents: '{"le":"la"}', }, { method: 'mkdir', path: 'dist' }, { method: 'writeFile', path: 'dist/test.json', contents: '{"le":"la"}', }, ], __dirname, ); let i = 0; const wtchr = wtch({ entry: join(__dirname, 'test.json'), onError: (err) => { switch (i++) { case 0: assert.equal(err, myErr1); watchEvents[0].resolve({ eventType: 'change', filename: 'test.json', }); break; case 1: assert.equal(err, myErr2); watchEvents[2].resolve({ eventType: 'change', filename: 'test.json', }); break; default: throw err; } }, processors: [ { outDir: join(__dirname, 'dist'), }, ], fs, }); let j = 0; for await (const manifest of wtchr) { switch (j++) { case 0: assert.deepEqual(manifest, { 'test.json': 'test.json', }); watchEvents[1].resolve({ eventType: 'change', filename: 'test.json', }); break; case 1: assert.deepEqual(manifest, { 'test.json': 'test.json', }); break; default: throw new Error('NEIN'); } } fs.done(); }); }); interface ExpectedReaddir { method: 'readdir'; path: string; contents: string[] | Error; } interface ExpectedStat { method: 'stat'; path: string; isDir: boolean | Error; } interface ExpectedMkdir { method: 'mkdir'; path: string; error?: Error; } interface ExpectedReadFile { method: 'readFile'; path: string; contents: Buffer | string | Error; } interface ExpectedWriteFile { method: 'writeFile'; path: string; contents: Buffer | string | Error; } interface ExpectedWatch { method: 'watch'; path: string; options: { recursive?: boolean; signal?: AbortSignal }; watcher: AsyncIterable<WatchEvent> | Error; } type ExpectedCall = | ExpectedReaddir | ExpectedStat | ExpectedMkdir | ExpectedReadFile | ExpectedWriteFile | ExpectedWatch; type Deferred<T> = { promise: Promise<T>; resolve: (val: T) => void; reject: (error: unknown) => void; }; function defer<T>(): Deferred<T> { let resolve: (val: T) => void; let reject: (error: unknown) => void; let promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; } function createWatcher(events: Promise<WatchEvent | Error>[]) { return { async *[Symbol.asyncIterator]() { for (const event of events) { const next = await event; if (next instanceof Error) { throw next; } else { yield next; } } }, }; } function createFs( expectedCalls: ExpectedCall[], baseDir: string, ): WatchFs & { done: () => void } { let i = 0; const getNextCall = <M extends ExpectedCall>( method: M['method'], args: any, ): M => { // console.log(`${i++} --- fs.${method}(${JSON.stringify(args)})`); const next = expectedCalls.shift(); assert.ok(next !== undefined, `Unexpected call to fs.${method}`); const { method: exMeth, ...exArgs } = next; assert.ok( exMeth === method, `Expected fs.${exMeth}(${JSON.stringify( exArgs, )}) call - got fs.${method}(${JSON.stringify(args)})`, ); return next as any; }; return { readdir: async (dir) => { const { path, contents } = getNextCall<ExpectedReaddir>( 'readdir', relative(baseDir, dir), ); assert.equal(relative(baseDir, dir), path); if (contents instanceof Error) { throw contents; } return contents; }, stat: async (p) => { const { path, isDir } = getNextCall<ExpectedStat>( 'stat', relative(baseDir, p), ); assert.equal(relative(baseDir, p), path); if (isDir instanceof Error) { throw isDir; } return { isDirectory: () => isDir, }; }, mkdir: async (p) => { const { path, error } = getNextCall<ExpectedMkdir>( 'mkdir', relative(baseDir, p), ); assert.equal(relative(baseDir, p), path); if (error) { throw error; } return undefined; }, readFile: async (p) => { const { path, contents } = getNextCall<ExpectedReadFile>( 'readFile', relative(baseDir, p), ); assert.equal(relative(baseDir, p), path); if (contents instanceof Error) { throw contents; } return contents instanceof Buffer ? contents : Buffer.from(contents); }, writeFile: async (p, c) => { const { path, contents } = getNextCall<ExpectedWriteFile>('writeFile', [ relative(baseDir, p), c, ]); assert.equal(relative(baseDir, p), path); if (contents instanceof Error) { throw contents; } assert.equal(c.toString(), contents.toString()); }, watch: (p, o) => { const { path, options, watcher } = getNextCall<ExpectedWatch>('watch', [ relative(baseDir, p), o, ]); assert.equal(relative(baseDir, p), path); assert.deepEqual(options, o); if (watcher instanceof Error) { throw watcher; } return watcher; }, done() { assert.ok( expectedCalls.length === 0, `Got ${expectedCalls.length} trailing expectations on fs`, ); }, }; }