import { mkdir, mkdtemp, readdir, readFile, readlink, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { expect, test } from 'bun:test'; import { createNodeWebComponentArtifactFileSystem } from '../../adapters/outbound/node/createNodeWebComponentArtifactFileSystem'; import { synchronizeWebComponentArtifactsAsync } from './synchronizeWebComponentArtifactsAsync'; test('syncs one shared provider graph, prunes removed components, and preserves output on failure', async () => { const sandbox = await mkdtemp(join(tmpdir(), 'zora-web-sync-')); const packageRoot = join(sandbox, 'owner'); const outputDirectory = join(sandbox, 'project', '.ankh', 'zora', 'web'); const entries = { 'runtime/ZoraProvider.js': "export { runtime } from '../chunks/shared.js';\n", 'runtime/ZoraProvider.d.ts': 'export declare const ZoraProvider: unknown;\n', 'components/select/Select.js': "export { select } from '../../chunks/shared.js';\n", 'components/select/Select.d.ts': 'export declare const Select: unknown;\n', 'components/button/Button.js': "export { button } from '../../chunks/shared.js';\n", 'components/button/Button.d.ts': 'export declare const Button: unknown;\n', 'chunks/shared.js': 'export const runtime = 1, select = 2, button = 3;\n', } as const; try { await Promise.all( Object.entries(entries).map(async ([file, source]) => { const path = join(packageRoot, 'web-dist', file); await mkdir(dirname(path), { recursive: true }); await writeFile(path, source); }), ); await writeFile( join(packageRoot, 'web-dist', 'manifest.json'), JSON.stringify({ schemaVersion: 2, runtime: { entry: 'runtime/ZoraProvider.js', files: ['runtime/ZoraProvider.js', 'runtime/ZoraProvider.d.ts', 'chunks/shared.js'], }, artifacts: [ { component: 'select', files: [ 'components/select/Select.js', 'components/select/Select.d.ts', 'chunks/shared.js', ], }, { component: 'button', files: [ 'components/button/Button.js', 'components/button/Button.d.ts', 'chunks/shared.js', ], }, ], }), ); const fileSystem = createNodeWebComponentArtifactFileSystem(); const base = { outputDirectory, packageRoot, packageVersion: '1.2.3' }; const first = await synchronizeWebComponentArtifactsAsync( { ...base, components: ['select', 'button'] }, fileSystem, ); const evidence = JSON.parse( await readFile(join(outputDirectory, 'materialization.json'), 'utf8'), ) as { readonly components: readonly string[]; readonly files: readonly string[] }; expect(evidence.components).toEqual(['button', 'select']); expect(evidence.files.filter((file) => file === 'chunks/shared.js')).toHaveLength(1); expect(first.files).toEqual(evidence.files); const firstGeneration = await readlink(outputDirectory); const second = await synchronizeWebComponentArtifactsAsync( { ...base, components: ['button'] }, fileSystem, ); expect(second.files).not.toContain('components/select/Select.js'); expect(await readlink(outputDirectory)).not.toBe(firstGeneration); expect(await readdir(join(outputDirectory, 'components'))).toEqual(['button']); expect(await readdir(dirname(outputDirectory))).toHaveLength(2); await synchronizeWebComponentArtifactsAsync( { ...base, packageVersion: '1.2.4', components: ['button'] }, fileSystem, ); expect(await readFile(join(outputDirectory, 'runtime/ZoraProvider.js'), 'utf8')).toContain( 'Generated by @ankhorage/zora v1.2.4', ); expect(await readFile(join(outputDirectory, 'components/button/Button.js'), 'utf8')).toContain( 'Generated by @ankhorage/zora v1.2.4', ); await rm(join(packageRoot, 'web-dist', 'components/button/Button.js')); const validGeneration = await readlink(outputDirectory); const failed = await synchronizeWebComponentArtifactsAsync( { ...base, components: ['button'] }, fileSystem, ).then( () => false, () => true, ); expect(failed).toBe(true); expect(await readlink(outputDirectory)).toBe(validGeneration); expect(await readFile(join(outputDirectory, 'components/button/Button.js'), 'utf8')).toContain( 'Generated by @ankhorage/zora v1.2.4', ); } finally { await rm(sandbox, { force: true, recursive: true }); } }); test('swaps Windows-style junction generations without leaving stale output', async () => { const sandbox = await mkdtemp(join(tmpdir(), 'zora-web-windows-swap-')); const outputDirectory = join(sandbox, 'web'); const fileSystem = createNodeWebComponentArtifactFileSystem('win32'); try { const first = await fileSystem.createStageDirectoryAsync(outputDirectory); await writeFile(join(first, 'version.txt'), 'first'); await fileSystem.commitStageDirectoryAsync(first, outputDirectory); const second = await fileSystem.createStageDirectoryAsync(outputDirectory); await writeFile(join(second, 'version.txt'), 'second'); await fileSystem.commitStageDirectoryAsync(second, outputDirectory); expect(await readFile(join(outputDirectory, 'version.txt'), 'utf8')).toBe('second'); expect(await readdir(sandbox)).toHaveLength(2); } finally { await rm(sandbox, { force: true, recursive: true }); } });