feat(companion): emit release manifest metadata in bundles
This commit is contained in:
@@ -100,6 +100,7 @@ const {
|
||||
readmePath: `${input.outputDir}/README.md`,
|
||||
checksumsPath: `${input.outputDir}/CHECKSUMS.sha256`,
|
||||
signaturePath: input.signingKeyPem ? `${input.outputDir}/CHECKSUMS.sha256.sig` : undefined,
|
||||
releaseManifestPath: `${input.outputDir}/RELEASE_MANIFEST.json`,
|
||||
}));
|
||||
const writeCompanionShellTemplate = vi.fn(async (input: {
|
||||
outputDir: string;
|
||||
|
||||
@@ -398,6 +398,7 @@ export async function runCompanionSession(options: CompanionCommandOptions): Pro
|
||||
console.log(`- Launcher: ${result.launcherPath}`);
|
||||
console.log(`- README: ${result.readmePath}`);
|
||||
console.log(`- Checksums: ${result.checksumsPath}`);
|
||||
console.log(`- Release manifest: ${result.releaseManifestPath}`);
|
||||
if (result.signaturePath) {
|
||||
console.log(`- Signature: ${result.signaturePath}`);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ describe('writeCompanionReleaseBundle', () => {
|
||||
const launcherRaw = await readFile(result.launcherPath, 'utf8');
|
||||
const readmeRaw = await readFile(result.readmePath, 'utf8');
|
||||
const checksumsRaw = await readFile(result.checksumsPath, 'utf8');
|
||||
const bundleManifestRaw = await readFile(result.releaseManifestPath, 'utf8');
|
||||
const launcherStat = await stat(result.launcherPath);
|
||||
|
||||
expect(JSON.parse(manifestRaw)).toMatchObject({
|
||||
@@ -64,6 +65,13 @@ describe('writeCompanionReleaseBundle', () => {
|
||||
expect(checksumsRaw).toContain('companion.bootstrap.json');
|
||||
expect(checksumsRaw).toContain('run-companion.sh');
|
||||
expect(checksumsRaw).toContain('README.md');
|
||||
expect(JSON.parse(bundleManifestRaw)).toMatchObject({
|
||||
schemaVersion: 1,
|
||||
bundle: { nodeId: 'ios-node', platform: 'ios' },
|
||||
files: expect.arrayContaining([
|
||||
expect.objectContaining({ path: 'CHECKSUMS.sha256' }),
|
||||
]),
|
||||
});
|
||||
expect((launcherStat.mode & 0o111) !== 0).toBe(true);
|
||||
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
@@ -99,12 +107,20 @@ describe('writeCompanionReleaseBundle', () => {
|
||||
});
|
||||
|
||||
expect(result.signaturePath).toBe(`${outputDir}/CHECKSUMS.sha256.sig`);
|
||||
expect(result.releaseManifestPath).toBe(`${outputDir}/RELEASE_MANIFEST.json`);
|
||||
|
||||
const checksumsRaw = await readFile(result.checksumsPath, 'utf8');
|
||||
const signatureRaw = await readFile(result.signaturePath!, 'utf8');
|
||||
const bundleManifestRaw = await readFile(result.releaseManifestPath, 'utf8');
|
||||
const signatureLine = signatureRaw.split('\n').find((line) => line.startsWith('signature='));
|
||||
expect(signatureLine).toBeTruthy();
|
||||
expect(signatureRaw).toContain('key_id=test-key');
|
||||
expect(JSON.parse(bundleManifestRaw)).toMatchObject({
|
||||
signature: {
|
||||
path: 'CHECKSUMS.sha256.sig',
|
||||
keyId: 'test-key',
|
||||
},
|
||||
});
|
||||
|
||||
const signature = Buffer.from(String(signatureLine).replace('signature=', ''), 'base64');
|
||||
const verified = verify(
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface WriteCompanionReleaseBundleResult {
|
||||
readmePath: string;
|
||||
checksumsPath: string;
|
||||
signaturePath?: string;
|
||||
releaseManifestPath: string;
|
||||
}
|
||||
|
||||
function shSingleQuote(value: string): string {
|
||||
@@ -171,6 +172,7 @@ export async function writeCompanionReleaseBundle(
|
||||
const launcherPath = `${input.outputDir}/run-companion.sh`;
|
||||
const readmePath = `${input.outputDir}/README.md`;
|
||||
const checksumsPath = `${input.outputDir}/CHECKSUMS.sha256`;
|
||||
const releaseManifestPath = `${input.outputDir}/RELEASE_MANIFEST.json`;
|
||||
const manifestBody = `${JSON.stringify(input.manifest, null, 2)}\n`;
|
||||
const launcherBody = createLauncherScript(input.manifest);
|
||||
const readmeBody = createReadme(input.manifest);
|
||||
@@ -201,6 +203,29 @@ export async function writeCompanionReleaseBundle(
|
||||
].filter(Boolean).join('\n');
|
||||
await writeFile(signaturePath, `${signatureBody}\n`, 'utf8');
|
||||
}
|
||||
const releaseManifest = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: input.manifest.generatedAt,
|
||||
bundle: {
|
||||
nodeId: input.manifest.node.nodeId,
|
||||
platform: input.manifest.node.platform,
|
||||
role: input.manifest.node.role,
|
||||
gatewayUrl: input.manifest.gateway.url,
|
||||
},
|
||||
files: [
|
||||
{ path: 'companion.bootstrap.json', sha256: createHash('sha256').update(manifestBody, 'utf8').digest('hex') },
|
||||
{ path: 'run-companion.sh', sha256: createHash('sha256').update(launcherBody, 'utf8').digest('hex') },
|
||||
{ path: 'README.md', sha256: createHash('sha256').update(readmeBody, 'utf8').digest('hex') },
|
||||
{ path: 'CHECKSUMS.sha256', sha256: createHash('sha256').update(checksumsPayload, 'utf8').digest('hex') },
|
||||
],
|
||||
signature: signaturePath ? {
|
||||
path: 'CHECKSUMS.sha256.sig',
|
||||
algorithm: 'sha256',
|
||||
encoding: 'base64',
|
||||
keyId: input.signingKeyId ?? undefined,
|
||||
} : undefined,
|
||||
};
|
||||
await writeFile(releaseManifestPath, `${JSON.stringify(releaseManifest, null, 2)}\n`, 'utf8');
|
||||
|
||||
return {
|
||||
outputDir: input.outputDir,
|
||||
@@ -209,5 +234,6 @@ export async function writeCompanionReleaseBundle(
|
||||
readmePath,
|
||||
checksumsPath,
|
||||
signaturePath,
|
||||
releaseManifestPath,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user