137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
collectDirectoryFiles,
|
|
createThirdPartyLicenseBundle,
|
|
} from '../scripts/release-archive.mjs';
|
|
|
|
interface PackageReleaseModule {
|
|
collectReleaseMetadataFiles(
|
|
projectRoot: string
|
|
): Promise<Record<string, Buffer>>;
|
|
}
|
|
|
|
describe('release archive', () => {
|
|
it('creates identical bytes in different process timezones', () => {
|
|
const hashes = ['UTC', 'Europe/Berlin', 'America/New_York'].map(
|
|
(timezone) => {
|
|
const script = `
|
|
import { createHash } from 'node:crypto';
|
|
import { createDeterministicZip } from './scripts/release-archive.mjs';
|
|
const archive = createDeterministicZip({
|
|
'z.txt': Buffer.from('last'),
|
|
'a.txt': Buffer.from('first'),
|
|
});
|
|
process.stdout.write(createHash('sha256').update(archive).digest('hex'));
|
|
`;
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
['--input-type=module', '--eval', script],
|
|
{
|
|
cwd: process.cwd(),
|
|
encoding: 'utf8',
|
|
env: { ...process.env, TZ: timezone },
|
|
}
|
|
);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
return result.stdout;
|
|
}
|
|
);
|
|
expect(new Set(hashes)).toHaveLength(1);
|
|
});
|
|
|
|
it('collects every installed runtime license text', async () => {
|
|
const bundle = await createThirdPartyLicenseBundle(process.cwd(), [
|
|
'@add-ideas/toolbox-contract',
|
|
'@add-ideas/toolbox-shell-react',
|
|
'react',
|
|
]);
|
|
const text = bundle.toString('utf8');
|
|
expect(text).toContain('@add-ideas/toolbox-contract@0.2.0');
|
|
expect(text).toContain('@add-ideas/toolbox-shell-react@0.2.0');
|
|
expect(text).toContain('react@');
|
|
expect(createHash('sha256').update(bundle).digest('hex')).toMatch(
|
|
/^[a-f0-9]{64}$/
|
|
);
|
|
});
|
|
|
|
it('packages parser provenance and every required license text', async () => {
|
|
const moduleUrl = pathToFileURL(
|
|
path.join(process.cwd(), 'scripts', 'package-release.mjs')
|
|
).href;
|
|
const { collectReleaseMetadataFiles } = (await import(
|
|
moduleUrl
|
|
)) as PackageReleaseModule;
|
|
const files = await collectReleaseMetadataFiles(process.cwd());
|
|
|
|
expect(Object.keys(files).sort()).toEqual(
|
|
expect.arrayContaining([
|
|
'LICENSE',
|
|
'LICENSES/AGPL-3.0.txt',
|
|
'LICENSES/compcol-MIT.txt',
|
|
'LICENSES/MPL-2.0.txt',
|
|
'LICENSES/THIRD-PARTY-LICENSES.txt',
|
|
'LICENSES/libmspack-LGPL-2.1.txt',
|
|
'LICENSES/lzxd-MIT.txt',
|
|
'LICENSES/rust-cab-MIT.txt',
|
|
'THIRD_PARTY_NOTICES.md',
|
|
'docs/FORMAT_SUPPORT.md',
|
|
'docs/PORTING.md',
|
|
'docs/REFERENCE_IMPLEMENTATIONS.md',
|
|
'docs/SECURITY.md',
|
|
'tests/fixtures/README.md',
|
|
])
|
|
);
|
|
expect(
|
|
createHash('sha256')
|
|
.update(files['LICENSES/rust-cab-MIT.txt']!)
|
|
.digest('hex')
|
|
).toBe('ebaac853b53fec0ed475796f533ce676912a4df9ad9d8a621fa76d5060591e10');
|
|
expect(
|
|
createHash('sha256').update(files['LICENSES/lzxd-MIT.txt']!).digest('hex')
|
|
).toBe('c7a1dcbf839b1dfb72f24e1de12ab55f789db2c61d894ca9aa8b5f595490d9ca');
|
|
expect(
|
|
createHash('sha256')
|
|
.update(files['LICENSES/compcol-MIT.txt']!)
|
|
.digest('hex')
|
|
).toBe('a7009ce74b33a2afcf9ab38007bf3c8f5020d9b816dc5fc7296bf17e6d7dc796');
|
|
expect(
|
|
createHash('sha256')
|
|
.update(files['LICENSES/libmspack-LGPL-2.1.txt']!)
|
|
.digest('hex')
|
|
).toBe('a190dc9c8043755d90f8b0a75fa66b9e42d4af4c980bf5ddc633f0124db3cee7');
|
|
expect(
|
|
files['LICENSES/THIRD-PARTY-LICENSES.txt']!.toString('utf8')
|
|
).toContain('fflate@0.8.3');
|
|
expect(files).not.toHaveProperty(
|
|
'tests/fixtures/joplin-test.onepkg.base64'
|
|
);
|
|
expect(files).not.toHaveProperty('tests/fixtures/onenote_desktop.one.b64');
|
|
});
|
|
|
|
it('rejects symbolic links in release input', async () => {
|
|
const temporaryDirectory = await mkdtemp(
|
|
path.join(os.tmpdir(), 'onenote-tools-release-')
|
|
);
|
|
try {
|
|
const outsideFile = path.join(temporaryDirectory, 'outside.txt');
|
|
const inputDirectory = path.join(temporaryDirectory, 'input');
|
|
await writeFile(outsideFile, 'private');
|
|
await mkdir(inputDirectory);
|
|
await symlink(outsideFile, path.join(inputDirectory, 'linked.txt'));
|
|
|
|
await expect(collectDirectoryFiles(inputDirectory)).rejects.toThrow(
|
|
'Unsupported filesystem entry in release input: linked.txt'
|
|
);
|
|
} finally {
|
|
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|