Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
633 lines
20 KiB
JavaScript
633 lines
20 KiB
JavaScript
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { createHash } from 'node:crypto';
|
|
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import test from 'node:test';
|
|
import { CLI_RELEASE_ARTIFACT_LIMITS } from './release-cli-artifact-policy.mjs';
|
|
import {
|
|
assertRegistryNightlyPredecessor,
|
|
fetchRegistryRelease,
|
|
parseCliNightlyVersion,
|
|
parseCliReleaseVersion,
|
|
prepareNightlyRelease,
|
|
prepareSignatureAuditTree,
|
|
prepareStageRelease,
|
|
resolveRegistryNightlyPredecessor,
|
|
validateRegistryChannels,
|
|
validateSignatureAudit,
|
|
validateStageRun,
|
|
} from './release-cli-publication.mjs';
|
|
|
|
const SOURCE_SHA = 'a'.repeat(40);
|
|
const PUBLISHER_SHA = 'b'.repeat(40);
|
|
const WORKFLOW_PATH = '.github/workflows/npm-publication.yml';
|
|
const CURRENT_CLI_VERSION = JSON.parse(
|
|
readFileSync(resolve(import.meta.dirname, '../packages/cli/package.json'), 'utf8'),
|
|
).version;
|
|
const PRODUCT_TAG = 'v0.2.0';
|
|
const STAGE_RUN = {
|
|
id: 321,
|
|
run_attempt: 1,
|
|
path: WORKFLOW_PATH,
|
|
event: 'workflow_dispatch',
|
|
head_branch: 'main',
|
|
head_sha: PUBLISHER_SHA,
|
|
conclusion: 'success',
|
|
head_repository: { full_name: 'apache/maka' },
|
|
};
|
|
|
|
test('formal and Nightly versions map to their only public channels', () => {
|
|
assert.deepEqual(parseCliReleaseVersion('0.2.0'), {
|
|
version: '0.2.0',
|
|
distTag: 'latest',
|
|
tarball: 'maka-agent-0.2.0.tgz',
|
|
});
|
|
assert.deepEqual(parseCliNightlyVersion('0.2.0-dev.42.20260829', '0.2.0'), {
|
|
version: '0.2.0-dev.42.20260829',
|
|
distTag: 'nightly',
|
|
tarball: 'maka-agent-0.2.0-dev.42.20260829.tgz',
|
|
});
|
|
assert.throws(() => parseCliReleaseVersion('0.2.0-beta.1'), /must use a stable/u);
|
|
assert.throws(
|
|
() => parseCliNightlyVersion('0.2.0-beta.1', '0.2.0'),
|
|
/valid Product Nightly version/u,
|
|
);
|
|
for (const version of ['01.0.0', '0.1', '0.1.0+local', '0.1.0-beta..1', '../0.1.0']) {
|
|
assert.throws(() => parseCliReleaseVersion(version), /valid product release version/u);
|
|
}
|
|
});
|
|
|
|
test('formal finalization requires only the exact latest channel', () => {
|
|
assert.doesNotThrow(() =>
|
|
validateRegistryChannels({
|
|
releaseVersion: '0.2.0',
|
|
releaseDistTag: 'latest',
|
|
distTags: { latest: '0.2.0', nightly: '0.3.0-dev.42.20260829' },
|
|
}),
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
validateRegistryChannels({
|
|
releaseVersion: '0.2.0',
|
|
releaseDistTag: 'latest',
|
|
distTags: { latest: '0.1.0' },
|
|
}),
|
|
/does not point to 0\.2\.0/u,
|
|
);
|
|
});
|
|
|
|
test('Nightly preparation validates only the exact dev candidate', () => {
|
|
const fixture = createCandidate('0.2.0-dev.42.20260829', '0.2.0');
|
|
const prepared = prepareNightlyRelease({
|
|
repoRoot: fixture.root,
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
});
|
|
assert.equal(prepared.distTag, 'nightly');
|
|
assert.equal(prepared.tarballPath, fixture.tarballPath);
|
|
assert.equal(prepared.sha256, fixture.sha256);
|
|
});
|
|
|
|
test('stage records bind the checked candidate to one source workflow run', () => {
|
|
const fixture = createCandidate();
|
|
const prepared = prepareStageRelease({
|
|
repoRoot: fixture.root,
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
productTag: PRODUCT_TAG,
|
|
sourceSha: SOURCE_SHA,
|
|
publisherSha: PUBLISHER_SHA,
|
|
runId: '321',
|
|
runAttempt: '1',
|
|
repository: 'apache/maka',
|
|
workflowPath: WORKFLOW_PATH,
|
|
});
|
|
|
|
assert.equal(prepared.record.sha256, fixture.sha256);
|
|
assert.equal(prepared.record.schemaVersion, 4);
|
|
assert.equal(prepared.record.productTag, PRODUCT_TAG);
|
|
assert.equal(prepared.record.source.commit, SOURCE_SHA);
|
|
assert.equal(prepared.record.publisher.commit, PUBLISHER_SHA);
|
|
assert.equal(prepared.record.publisher.runId, '321');
|
|
assert.equal(prepared.record.publisher.runAttempt, '1');
|
|
assert.deepEqual(
|
|
JSON.parse(readFileSync(join(fixture.releaseDirectory, 'release.json'), 'utf8')),
|
|
prepared.record,
|
|
);
|
|
});
|
|
|
|
test('stage preparation rejects a product tag that does not match the version', () => {
|
|
const fixture = createCandidate();
|
|
assert.throws(
|
|
() =>
|
|
prepareStageRelease({
|
|
repoRoot: fixture.root,
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
productTag: 'v9.9.9',
|
|
sourceSha: SOURCE_SHA,
|
|
publisherSha: PUBLISHER_SHA,
|
|
runId: '321',
|
|
runAttempt: '1',
|
|
repository: 'apache/maka',
|
|
workflowPath: WORKFLOW_PATH,
|
|
}),
|
|
/Product tag .* does not match/u,
|
|
);
|
|
});
|
|
|
|
test('stage preparation rejects checksum drift', () => {
|
|
const fixture = createCandidate();
|
|
writeFileSync(`${fixture.tarballPath}.sha256`, `${'0'.repeat(64)} ${fixture.tarball}\n`);
|
|
assert.throws(
|
|
() =>
|
|
prepareStageRelease({
|
|
repoRoot: fixture.root,
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
productTag: PRODUCT_TAG,
|
|
sourceSha: SOURCE_SHA,
|
|
publisherSha: PUBLISHER_SHA,
|
|
runId: '321',
|
|
runAttempt: '1',
|
|
repository: 'apache/maka',
|
|
workflowPath: WORKFLOW_PATH,
|
|
}),
|
|
/checksum does not match/u,
|
|
);
|
|
});
|
|
|
|
test('finalization accepts only the exact successful main-branch stage run', () => {
|
|
const fixture = createPreparedCandidate();
|
|
|
|
assert.equal(
|
|
validateStageRun({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
run: STAGE_RUN,
|
|
}).source.commit,
|
|
SOURCE_SHA,
|
|
);
|
|
|
|
for (const drift of [
|
|
{ path: '.github/workflows/other.yml' },
|
|
{ event: 'pull_request' },
|
|
{ head_branch: 'other' },
|
|
{ conclusion: 'failure' },
|
|
{ head_sha: 'c'.repeat(40) },
|
|
{ run_attempt: 2 },
|
|
]) {
|
|
assert.throws(
|
|
() =>
|
|
validateStageRun({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
run: { ...STAGE_RUN, ...drift },
|
|
}),
|
|
/stage workflow run/u,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('finalization rejects a release record whose product tag does not match its version', () => {
|
|
const fixture = createPreparedCandidate();
|
|
const recordPath = join(fixture.releaseDirectory, 'release.json');
|
|
const record = JSON.parse(readFileSync(recordPath, 'utf8'));
|
|
writeFileSync(recordPath, `${JSON.stringify({ ...record, productTag: 'v9.9.9' }, null, 2)}\n`);
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateStageRun({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
run: STAGE_RUN,
|
|
}),
|
|
/productTag is inconsistent/u,
|
|
);
|
|
});
|
|
|
|
test('registry finalization requires the exact staged bytes and dist-tag', async () => {
|
|
const fixture = createPreparedCandidate();
|
|
const registryDirectory = mkdtempSync(join(tmpdir(), 'maka-cli-registry-release-'));
|
|
const fetchImpl = registryFetch({ fixture });
|
|
|
|
const result = await fetchRegistryRelease({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
registryDirectory,
|
|
fetchImpl,
|
|
});
|
|
|
|
assert.equal(result.sha256, fixture.sha256);
|
|
assert.deepEqual(readFileSync(result.tarballPath), fixture.bytes);
|
|
assert.deepEqual(
|
|
readFileSync(`${result.tarballPath}.files.json`),
|
|
readFileSync(`${fixture.tarballPath}.files.json`),
|
|
);
|
|
const registryRecord = JSON.parse(readFileSync(join(registryDirectory, 'release.json'), 'utf8'));
|
|
assert.equal(registryRecord.version, result.version);
|
|
|
|
await assert.rejects(
|
|
fetchRegistryRelease({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
registryDirectory: mkdtempSync(join(tmpdir(), 'maka-cli-registry-drift-')),
|
|
fetchImpl: registryFetch({ fixture, bytes: Buffer.from('different release') }),
|
|
}),
|
|
/Registry tarball does not match/u,
|
|
);
|
|
});
|
|
|
|
test('registry downloads stop reading as soon as the tarball exceeds its bound', async () => {
|
|
const fixture = createPreparedCandidate();
|
|
const fallback = registryFetch({ fixture });
|
|
const tarballUrl = `https://registry.npmjs.org/maka-agent/-/${fixture.tarball}`;
|
|
const chunkBytes = 1024 * 1024;
|
|
const offeredChunks = Math.floor(CLI_RELEASE_ARTIFACT_LIMITS.compressedBytes / chunkBytes) + 8;
|
|
let pulls = 0;
|
|
const fetchImpl = async (input, options) => {
|
|
if (String(input) !== tarballUrl) return fallback(input, options);
|
|
return new Response(
|
|
new ReadableStream({
|
|
pull(controller) {
|
|
pulls += 1;
|
|
if (pulls > offeredChunks) return controller.close();
|
|
controller.enqueue(new Uint8Array(chunkBytes));
|
|
},
|
|
}),
|
|
);
|
|
};
|
|
|
|
await assert.rejects(
|
|
fetchRegistryRelease({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
registryDirectory: mkdtempSync(join(tmpdir(), 'maka-cli-registry-oversized-')),
|
|
fetchImpl,
|
|
}),
|
|
/exceeds the reviewed compressed size limit/u,
|
|
);
|
|
assert.ok(pulls < offeredChunks, `expected an early bounded read, consumed ${pulls} chunks`);
|
|
});
|
|
|
|
test('release qualification binds the current Nightly tag to immutable registry bytes', async () => {
|
|
const fixture = createCandidate('0.2.0-dev.42.20260829', '0.2.0');
|
|
const predecessor = await resolveRegistryNightlyPredecessor({
|
|
fetchImpl: registryFetch({ fixture }),
|
|
});
|
|
|
|
assert.deepEqual(predecessor, {
|
|
version: fixture.version,
|
|
tarballUrl: `https://registry.npmjs.org/maka-agent/-/${fixture.tarball}`,
|
|
integrity: `sha512-${digest('sha512', fixture.bytes, 'base64')}`,
|
|
});
|
|
await assert.doesNotReject(
|
|
assertRegistryNightlyPredecessor({
|
|
expectedVersion: predecessor.version,
|
|
expectedTarballUrl: predecessor.tarballUrl,
|
|
expectedIntegrity: predecessor.integrity,
|
|
fetchImpl: registryFetch({ fixture }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('the release predecessor may come from the previous product version', async () => {
|
|
const fixture = createCandidate('0.1.0-dev.41.20260828', '0.1.0');
|
|
const predecessor = await resolveRegistryNightlyPredecessor({
|
|
fetchImpl: registryFetch({ fixture }),
|
|
});
|
|
assert.equal(predecessor.version, fixture.version);
|
|
});
|
|
|
|
test('a newer Nightly invalidates previously qualified predecessor evidence', async () => {
|
|
const previous = createCandidate('0.2.0-dev.42.20260829', '0.2.0');
|
|
const current = createCandidate('0.2.0-dev.43.20260830', '0.2.0');
|
|
|
|
await assert.rejects(
|
|
assertRegistryNightlyPredecessor({
|
|
expectedVersion: previous.version,
|
|
expectedTarballUrl: `https://registry.npmjs.org/maka-agent/-/${previous.tarball}`,
|
|
expectedIntegrity: `sha512-${digest('sha512', previous.bytes, 'base64')}`,
|
|
fetchImpl: registryFetch({ fixture: current }),
|
|
}),
|
|
/is no longer current; found 0\.2\.0-dev\.43\.20260830/u,
|
|
);
|
|
});
|
|
|
|
test('signature audit must contain Maka provenance for the finalized version', () => {
|
|
const fixture = createPreparedCandidate();
|
|
const verified = {
|
|
invalid: [],
|
|
missing: [],
|
|
verified: [
|
|
{
|
|
name: 'maka-agent',
|
|
version: fixture.version,
|
|
attestations: { provenance: { predicateType: 'https://slsa.dev/provenance/v1' } },
|
|
attestationBundles: [provenanceBundle()],
|
|
},
|
|
],
|
|
};
|
|
assert.doesNotThrow(() =>
|
|
validateSignatureAudit({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
audit: verified,
|
|
}),
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
validateSignatureAudit({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
audit: { ...verified, verified: [] },
|
|
}),
|
|
/verified provenance/u,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
validateSignatureAudit({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
audit: { ...verified, invalid: [{ name: 'dependency' }] },
|
|
}),
|
|
/invalid or missing signatures/u,
|
|
);
|
|
});
|
|
|
|
test('signature audit binds provenance to the exact main publisher workflow and run', () => {
|
|
const fixture = createPreparedCandidate();
|
|
const audit = (mutate) => ({
|
|
invalid: [],
|
|
missing: [],
|
|
verified: [
|
|
{
|
|
name: 'maka-agent',
|
|
version: fixture.version,
|
|
attestations: { provenance: { predicateType: 'https://slsa.dev/provenance/v1' } },
|
|
attestationBundles: [provenanceBundle(mutate)],
|
|
},
|
|
],
|
|
});
|
|
for (const mutate of [
|
|
(statement) => {
|
|
statement.predicate.buildDefinition.resolvedDependencies[0].digest.gitCommit = 'c'.repeat(40);
|
|
},
|
|
(statement) => {
|
|
statement.predicate.buildDefinition.externalParameters.workflow.ref = `refs/tags/${PRODUCT_TAG}`;
|
|
},
|
|
(statement) => {
|
|
statement.predicate.buildDefinition.externalParameters.workflow.path =
|
|
'.github/workflows/other.yml';
|
|
},
|
|
(statement) => {
|
|
statement.predicate.runDetails.metadata.invocationId =
|
|
'https://github.com/apache/maka/actions/runs/999/attempts/1';
|
|
},
|
|
(statement) => {
|
|
statement.predicate.buildDefinition.externalParameters.workflow.repository =
|
|
'https://github.com/other/repository';
|
|
},
|
|
]) {
|
|
assert.throws(
|
|
() =>
|
|
validateSignatureAudit({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
audit: audit(mutate),
|
|
}),
|
|
/provenance does not match/u,
|
|
);
|
|
}
|
|
const wrongPredicate = provenanceBundle();
|
|
wrongPredicate.predicateType = 'https://example.invalid/provenance';
|
|
assert.throws(
|
|
() =>
|
|
validateSignatureAudit({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
audit: {
|
|
invalid: [],
|
|
missing: [],
|
|
verified: [
|
|
{
|
|
name: 'maka-agent',
|
|
version: fixture.version,
|
|
attestations: { provenance: {} },
|
|
attestationBundles: [wrongPredicate],
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
/provenance does not match/u,
|
|
);
|
|
});
|
|
|
|
test('signature audit tree exposes only the top-level registry package', () => {
|
|
const fixture = createPreparedCandidate();
|
|
const auditDirectory = mkdtempSync(join(tmpdir(), 'maka-cli-signature-audit-'));
|
|
|
|
prepareSignatureAuditTree({
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
auditDirectory,
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(readFileSync(join(auditDirectory, 'package.json'), 'utf8')), {
|
|
name: 'maka-cli-signature-audit',
|
|
private: true,
|
|
dependencies: { 'maka-agent': fixture.version },
|
|
});
|
|
assert.deepEqual(
|
|
JSON.parse(readFileSync(join(auditDirectory, 'node_modules/maka-agent/package.json'), 'utf8')),
|
|
{ name: 'maka-agent', version: fixture.version },
|
|
);
|
|
});
|
|
|
|
test('prepare-stage CLI emits only consumed GitHub Actions outputs', () => {
|
|
const fixture = createCandidate(CURRENT_CLI_VERSION);
|
|
const output = join(fixture.root, 'github-output.txt');
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
resolve(import.meta.dirname, 'release-cli-publication.mjs'),
|
|
'prepare-stage',
|
|
fixture.releaseDirectory,
|
|
fixture.version,
|
|
`v${fixture.version}`,
|
|
SOURCE_SHA,
|
|
PUBLISHER_SHA,
|
|
'321',
|
|
'1',
|
|
'apache/maka',
|
|
WORKFLOW_PATH,
|
|
output,
|
|
],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.deepEqual(readFileSync(output, 'utf8').trim().split('\n'), [
|
|
`version=${fixture.version}`,
|
|
'dist_tag=latest',
|
|
`tarball=${fixture.tarballPath}`,
|
|
]);
|
|
});
|
|
|
|
test('validate-stage-run CLI accepts the canonical staged release identity', () => {
|
|
const fixture = createPreparedCandidate();
|
|
const runPath = join(fixture.root, 'stage-run.json');
|
|
const output = join(fixture.root, 'github-output.txt');
|
|
writeFileSync(
|
|
runPath,
|
|
JSON.stringify({
|
|
id: 321,
|
|
run_attempt: 1,
|
|
path: WORKFLOW_PATH,
|
|
event: 'workflow_dispatch',
|
|
head_branch: 'main',
|
|
head_sha: PUBLISHER_SHA,
|
|
conclusion: 'success',
|
|
head_repository: { full_name: 'apache/maka' },
|
|
}),
|
|
);
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
resolve(import.meta.dirname, 'release-cli-publication.mjs'),
|
|
'validate-stage-run',
|
|
fixture.releaseDirectory,
|
|
runPath,
|
|
fixture.version,
|
|
output,
|
|
],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.deepEqual(readFileSync(output, 'utf8').trim().split('\n'), [
|
|
`product_tag=${PRODUCT_TAG}`,
|
|
`source_commit=${SOURCE_SHA}`,
|
|
`version=${fixture.version}`,
|
|
]);
|
|
});
|
|
|
|
function createPreparedCandidate() {
|
|
const fixture = createCandidate();
|
|
prepareStageRelease({
|
|
repoRoot: fixture.root,
|
|
releaseDirectory: fixture.releaseDirectory,
|
|
expectedVersion: fixture.version,
|
|
productTag: `v${fixture.version}`,
|
|
sourceSha: SOURCE_SHA,
|
|
publisherSha: PUBLISHER_SHA,
|
|
runId: '321',
|
|
runAttempt: '1',
|
|
repository: 'apache/maka',
|
|
workflowPath: WORKFLOW_PATH,
|
|
});
|
|
return fixture;
|
|
}
|
|
|
|
function provenanceBundle(mutate = () => {}) {
|
|
const statement = {
|
|
_type: 'https://in-toto.io/Statement/v1',
|
|
predicateType: 'https://slsa.dev/provenance/v1',
|
|
predicate: {
|
|
buildDefinition: {
|
|
buildType: 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1',
|
|
externalParameters: {
|
|
workflow: {
|
|
repository: 'https://github.com/apache/maka',
|
|
ref: 'refs/heads/main',
|
|
path: WORKFLOW_PATH,
|
|
},
|
|
},
|
|
resolvedDependencies: [
|
|
{
|
|
uri: 'git+https://github.com/apache/maka@refs/heads/main',
|
|
digest: { gitCommit: PUBLISHER_SHA },
|
|
},
|
|
],
|
|
internalParameters: { github: { event_name: 'workflow_dispatch' } },
|
|
},
|
|
runDetails: {
|
|
builder: { id: 'https://github.com/actions/runner/github-hosted' },
|
|
metadata: {
|
|
invocationId: 'https://github.com/apache/maka/actions/runs/321/attempts/1',
|
|
},
|
|
},
|
|
},
|
|
};
|
|
mutate(statement);
|
|
return {
|
|
predicateType: 'https://slsa.dev/provenance/v1',
|
|
bundle: {
|
|
dsseEnvelope: {
|
|
payloadType: 'application/vnd.in-toto+json',
|
|
payload: Buffer.from(JSON.stringify(statement)).toString('base64'),
|
|
signatures: [{ keyid: '', sig: 'verified-by-npm' }],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function createCandidate(version = '0.2.0', sourceVersion = version) {
|
|
const root = mkdtempSync(join(tmpdir(), 'maka-cli-publication-'));
|
|
const releaseDirectory = join(root, 'packages/cli/release');
|
|
const tarball = `maka-agent-${version}.tgz`;
|
|
const tarballPath = join(releaseDirectory, tarball);
|
|
const bytes = Buffer.from('immutable cli tarball');
|
|
const sha256 = digest('sha256', bytes, 'hex');
|
|
mkdirSync(releaseDirectory, { recursive: true });
|
|
writeFileSync(join(root, 'package.json'), '{"packageManager":"npm@11.19.0"}\n');
|
|
writeFileSync(
|
|
join(root, 'packages/cli/package.json'),
|
|
`${JSON.stringify({ name: 'maka-agent', version: sourceVersion })}\n`,
|
|
);
|
|
writeFileSync(tarballPath, bytes);
|
|
writeFileSync(`${tarballPath}.sha256`, `${sha256} ${tarball}\n`);
|
|
writeFileSync(`${tarballPath}.files.json`, '[{"path":"dist/cli.js","size":1}]\n');
|
|
return { root, releaseDirectory, version, tarball, tarballPath, bytes, sha256 };
|
|
}
|
|
|
|
function registryFetch({ fixture, bytes = fixture.bytes }) {
|
|
const integrity = `sha512-${digest('sha512', bytes, 'base64')}`;
|
|
const shasum = digest('sha1', bytes, 'hex');
|
|
const tarballUrl = `https://registry.npmjs.org/maka-agent/-/${fixture.tarball}`;
|
|
return async (input, options = {}) => {
|
|
const url = String(input);
|
|
if (url === `https://registry.npmjs.org/maka-agent/${fixture.version}`) {
|
|
assert.equal(options.headers?.accept, 'application/json');
|
|
return Response.json({
|
|
name: 'maka-agent',
|
|
version: fixture.version,
|
|
dist: { tarball: tarballUrl, integrity, shasum },
|
|
});
|
|
}
|
|
if (url === 'https://registry.npmjs.org/maka-agent') {
|
|
assert.equal(options.headers?.accept, 'application/vnd.npm.install-v1+json');
|
|
return Response.json({ 'dist-tags': { latest: fixture.version, nightly: fixture.version } });
|
|
}
|
|
if (url === tarballUrl) return new Response(bytes);
|
|
return new Response('not found', { status: 404 });
|
|
};
|
|
}
|
|
|
|
function digest(algorithm, bytes, encoding) {
|
|
return createHash(algorithm).update(bytes).digest(encoding);
|
|
}
|