72 lines
4.4 KiB
TypeScript
72 lines
4.4 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { createProjectConfig } from '../../shared/project-config';
|
|
|
|
const runElectronNodeMock = vi.hoisted(() => vi.fn());
|
|
vi.mock('@electron/services/publish-runtime', () => ({
|
|
resolvePublishRuntime: async () => ({ nodeExecutable: 'electron', npmCli: 'npm-cli.js', nodeVersion: '22.0.0', npmVersion: '11.6.2' }),
|
|
runElectronNode: runElectronNodeMock,
|
|
PublishRuntimeError: class extends Error {},
|
|
}));
|
|
import { prepareProjectRelease } from '@electron/services/project-release-builder';
|
|
import { startStaticReleaseServer, staticArtifactSnapshotFiles } from '@electron/services/static-release-server';
|
|
|
|
describe('project release builder', () => {
|
|
let root: string | null = null;
|
|
afterEach(async () => { if (root) await rm(root, { recursive: true, force: true }); root = null; vi.clearAllMocks(); });
|
|
|
|
it('builds from the exact source archive and returns the protocol-v1 canonical contract', async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'release-builder-test-'));
|
|
await mkdir(join(root, '.niancode'), { recursive: true });
|
|
await writeFile(join(root, '.niancode', 'project.json'), JSON.stringify(createProjectConfig(new Date().toISOString(), 'mini_game')));
|
|
await writeFile(join(root, 'package.json'), JSON.stringify({ name: 'demo', packageManager: 'npm@11.6.2', devDependencies: { vite: '7.3.1' } }));
|
|
await writeFile(join(root, 'package-lock.json'), JSON.stringify({ name: 'demo', lockfileVersion: 3, requires: true, packages: {} }));
|
|
await writeFile(join(root, 'index.html'), '<main>source</main>');
|
|
runElectronNodeMock.mockImplementation(async (input: { args: string[]; cwd: string }) => {
|
|
if (input.args.includes('ci')) {
|
|
await mkdir(join(input.cwd, 'node_modules', 'vite', 'bin'), { recursive: true });
|
|
await writeFile(join(input.cwd, 'node_modules', 'vite', 'package.json'), JSON.stringify({ version: '7.3.1' }));
|
|
} else if (input.args.includes('build')) {
|
|
const output = input.args[input.args.indexOf('--outDir') + 1];
|
|
await mkdir(join(output, 'assets'), { recursive: true });
|
|
await writeFile(join(output, 'index.html'), '<main>built</main>');
|
|
await writeFile(join(output, 'assets', '中.js'), 'ok');
|
|
}
|
|
return { stdout: '', stderr: '' };
|
|
});
|
|
const release = await prepareProjectRelease({ projectPath: root, clientVersion: '2.0.0' });
|
|
try {
|
|
expect(release.contract).toMatchObject({
|
|
schema_version: 1, entry_path: 'index.html', security_profile: 'works-square-static-sandbox-v1',
|
|
toolchain: { client: 'makelore', client_version: '2.0.0', node: '22.0.0', npm: '11.6.2', vite: '7.3.1' },
|
|
});
|
|
expect(release.contract.files.map((file) => file.path)).toEqual(['assets/中.js', 'index.html']);
|
|
const canonical = JSON.stringify(release.contract.files.map(({ path, sha256, size }) => ({ path, sha256, size })));
|
|
expect(release.contract.artifact_digest).toBe(createHash('sha256').update(canonical).digest('hex'));
|
|
expect(release.contract.source_digest).toBe(createHash('sha256').update(await readFile(release.sourceArchive.path)).digest('hex'));
|
|
expect(release.contract.built_archive_digest).toBe(createHash('sha256').update(release.builtArchive.bytes).digest('hex'));
|
|
expect(runElectronNodeMock.mock.calls[0][0].args).toEqual([
|
|
'npm-cli.js', 'ci', '--ignore-scripts', '--no-audit', '--no-fund',
|
|
'--userconfig', expect.stringMatching(/empty-npmrc$/), '--cache', expect.stringMatching(/npm-cache$/),
|
|
]);
|
|
await rm(release.distRoot, { recursive: true, force: true });
|
|
expect(staticArtifactSnapshotFiles(release.staticArtifact).map(({ path, bytes }) => ({
|
|
path,
|
|
size: bytes.length,
|
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
}))).toEqual(release.contract.files);
|
|
const server = await startStaticReleaseServer(release.staticArtifact);
|
|
try {
|
|
expect(await (await fetch(server.entryUrl)).text()).toBe('<main>built</main>');
|
|
expect(await (await fetch(new URL('assets/%E4%B8%AD.js', server.entryUrl))).text()).toBe('ok');
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
} finally { await release.dispose(); }
|
|
await expect(readFile(release.sourceArchive.path)).rejects.toThrow();
|
|
});
|
|
});
|