import { createHash } from 'node:crypto' import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { afterEach, expect, it } from 'vitest' import { annotateJavascriptParityFiles, compareJavascriptParityFiles } from './release-javascript-parity.mjs' const directories = [] afterEach(() => directories.splice(0).forEach((directory) => rmSync(directory, { recursive: true, force: true })) ) function inventory(entries) { const root = mkdtempSync(join(tmpdir(), 'orca-release-parity-')) directories.push(root) const files = Object.entries(entries).map(([path, content]) => { mkdirSync(dirname(join(root, path)), { recursive: true }) writeFileSync(join(root, path), content) return { path, sha256: createHash('sha256').update(content).digest('hex') } }) return annotateJavascriptParityFiles(root, files) } it('normalizes hashed JSON assets while rejecting content and reference changes', () => { const entries = (hash, content, reference = hash) => ({ [`renderer/assets/de-${hash}.json`]: content, 'renderer/assets/i18n.js': `fetch("./de-${reference}.json")`, 'renderer/.vite/manifest.json': JSON.stringify({ locale: { file: `assets/de-${hash}.json` } }), 'renderer/.orca-web-client-files.json': JSON.stringify([`assets/de-${hash}.json`]) }) const before = inventory(entries('AAAAAAAA', '{\r\n"hello":"Hallo"\r\n}\r\n')) const after = inventory(entries('BBBBBBBB', '{\n"hello":"Hallo"\n}\n')) expect(compareJavascriptParityFiles(before, after)).toEqual([]) expect( compareJavascriptParityFiles( before, inventory(entries('BBBBBBBB', '{\n"hello":"GeƤndert"\n}\n')) ) ).toEqual(['renderer/assets/de.json']) expect( compareJavascriptParityFiles( before, inventory(entries('BBBBBBBB', '{\n"hello":"Hallo"\n}\n', 'CCCCCCCC')) ) ).toEqual(['renderer/assets/i18n.js']) expect(compareJavascriptParityFiles(before, [])).toContain('renderer/assets/de.json') for (const reference of ['de.json', '@asset:de.json', '@asset:de.json@']) { const broken = entries('BBBBBBBB', '{\n"hello":"Hallo"\n}\n') broken['renderer/assets/i18n.js'] = `fetch("./${reference}")` expect(compareJavascriptParityFiles(before, inventory(broken))).toEqual([ 'renderer/assets/i18n.js' ]) } for (const [path, content] of [ ['renderer/.vite/manifest.json', '{"locale":{"file":"assets/\\u0040asset:de.json\\u0040"}}'], ['renderer/.orca-web-client-files.json', '["assets/\\u0040asset:de.json\\u0040"]'] ]) { const broken = entries('BBBBBBBB', '{\n"hello":"Hallo"\n}\n') broken[path] = content expect(compareJavascriptParityFiles(before, inventory(broken))).toEqual([path]) } for (const [path, content] of [ ['renderer/.vite/manifest.json', '{"locale":{"file":"assets/de\\u002dBBBBBBBB.json"}}'], ['renderer/.orca-web-client-files.json', '["assets/de\\u002dBBBBBBBB.json"]'] ]) { const escaped = entries('BBBBBBBB', '{\n"hello":"Hallo"\n}\n') escaped[path] = content expect(compareJavascriptParityFiles(before, inventory(escaped))).toEqual([]) } const suffixEntries = (reference) => ({ ...entries('AAAAAAAA', '{"hello":"Hallo"}'), 'renderer/assets/de.json-BBBBBBBB.json': '{}', 'renderer/assets/i18n.js': `fetch("./${reference}")` }) expect( compareJavascriptParityFiles( inventory(suffixEntries('de-AAAAAAAA.json.json')), inventory(suffixEntries('de.json-BBBBBBBB.json')) ) ).toEqual(['renderer/assets/i18n.js']) }) it('rejects code changes, missing files and incorrect asset references', () => { const before = inventory({ 'renderer/assets/index-AAAAAAAA.js': 'run()' }) const after = inventory({ 'renderer/assets/index-BBBBBBBB.js': 'other()' }) expect(compareJavascriptParityFiles(before, after)).toEqual(['renderer/assets/index.js']) expect(compareJavascriptParityFiles(before, [])).toEqual(['renderer/assets/index.js']) const reference = inventory({ 'renderer/assets/index-BBBBBBBB.js': 'run()', 'renderer/index.html': '' }) expect( compareJavascriptParityFiles( reference, inventory({ 'renderer/assets/index-DDDDDDDD.js': 'run()', 'renderer/index.html': '' }) ) ).toEqual(['renderer/index.html']) }) it('matches duplicate asset stems by content while rejecting a reference to the wrong module', () => { const before = inventory({ 'renderer/assets/App-AAAAAAAA.js': 'desktop()', 'renderer/assets/App-BBBBBBBB.js': 'web()', 'renderer/index.html': '' }) const after = inventory({ 'renderer/assets/App-CCCCCCCC.js': 'web()', 'renderer/assets/App-DDDDDDDD.js': 'desktop()', 'renderer/index.html': '' }) expect(compareJavascriptParityFiles(before, after)).toEqual([]) const wrong = inventory({ 'renderer/assets/App-CCCCCCCC.js': 'web()', 'renderer/assets/App-DDDDDDDD.js': 'desktop()', 'renderer/index.html': '' }) expect(compareJavascriptParityFiles(before, wrong)).toEqual(['renderer/index.html']) })