refactor: 프론트엔드 리펙토링
This commit is contained in:
@@ -0,0 +1,541 @@
|
||||
import {
|
||||
createHash,
|
||||
generateKeyPairSync,
|
||||
sign,
|
||||
type KeyObject,
|
||||
} from "node:crypto";
|
||||
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { localEvidenceAssessmentArtifactSchema } from "../../scripts/contracts/release-artifacts.ts";
|
||||
import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts";
|
||||
import {
|
||||
providerEvidenceSignaturePayload,
|
||||
trustPolicySha256,
|
||||
} from "../../scripts/lib/provider-evidence.ts";
|
||||
import {
|
||||
LOCAL_EVIDENCE_ASSESSMENT_PATH,
|
||||
distSha256,
|
||||
type ReleaseCandidateManifest,
|
||||
} from "../../scripts/lib/release-candidate.ts";
|
||||
import { supplyChainDigest } from "../../scripts/lib/supply-chain.ts";
|
||||
|
||||
export const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
|
||||
|
||||
export const digest = (value: string): string =>
|
||||
createHash("sha256").update(value).digest("hex");
|
||||
export const digestBytes = (value: Buffer): string =>
|
||||
createHash("sha256").update(value).digest("hex");
|
||||
|
||||
export function passingAssessment(): any {
|
||||
return {
|
||||
schemaVersion: 1 as const,
|
||||
artifactType: "local-evidence-assessment" as const,
|
||||
generatedAt: "2026-08-02T00:00:00.000Z",
|
||||
status: "PASS" as const,
|
||||
verifier: {
|
||||
id: "clean-architecture-frontend-template/local-evidence-verifier",
|
||||
version: "1",
|
||||
sourceSha256: digest("verifier source"),
|
||||
},
|
||||
source: {
|
||||
revision: "a".repeat(40),
|
||||
sourceSetSha256: digest("source set"),
|
||||
},
|
||||
candidate: {
|
||||
distSha256: digest("dist"),
|
||||
lockfileSha256: digest("lockfile"),
|
||||
sbomSha256: digest("sbom"),
|
||||
},
|
||||
secretScan: {
|
||||
policySha256: digest("secret policy"),
|
||||
sarifSha256: digest("secret sarif"),
|
||||
scanInputSha256: digest("secret scan input"),
|
||||
},
|
||||
policyInputs: [
|
||||
{
|
||||
path: "config/security/dependency-policy.json",
|
||||
bytes: 3,
|
||||
sha256: digest("{}\n"),
|
||||
},
|
||||
],
|
||||
evidenceInputs: [
|
||||
{ path: "pnpm-lock.yaml", bytes: 9, sha256: digest("lockfile\n") },
|
||||
],
|
||||
checks: {
|
||||
release: "PASS" as const,
|
||||
supplyChain: "PASS" as const,
|
||||
dependencyPolicy: "PASS" as const,
|
||||
licensePolicy: "PASS" as const,
|
||||
vulnerabilityPolicy: "PASS" as const,
|
||||
secretScan: "PASS" as const,
|
||||
},
|
||||
failures: [] as string[],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function providerExpectedContext() {
|
||||
return {
|
||||
run: { id: "run-42", attempt: 1 },
|
||||
source: { revision: "b".repeat(40), sourceSetSha256: digest("provider source") },
|
||||
candidate: {
|
||||
archiveSha256: digest("archive"),
|
||||
bundleSha256: digest("bundle"),
|
||||
distSha256: digest("provider dist"),
|
||||
lockfileSha256: digest("provider lockfile"),
|
||||
},
|
||||
secretScanAttestation: {
|
||||
status: "PASS" as const,
|
||||
localEvidenceAssessmentSha256: digest("provider assessment"),
|
||||
sourceSetSha256: digest("provider source"),
|
||||
policySha256: digest("provider secret policy"),
|
||||
sarifSha256: digest("provider secret sarif"),
|
||||
scanInputSha256: digest("provider secret input"),
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function privatePromotionFiles() {
|
||||
return PROMOTED_FILE_NAMES.map((name) => {
|
||||
const bytes = Buffer.from(`${name}\n`);
|
||||
return { name, bytes, sha256: digestBytes(bytes) };
|
||||
});
|
||||
}
|
||||
|
||||
export function syntheticSignedPromotionBundle() {
|
||||
const vulnerabilityKeys = generateKeyPairSync("ed25519");
|
||||
const provenanceKeys = generateKeyPairSync("ed25519");
|
||||
const archiveBytes = Buffer.from("synthetic signed replay archive\n");
|
||||
const run = { id: "signed-run", attempt: 1 } as const;
|
||||
const source = {
|
||||
revision: "a".repeat(40),
|
||||
sourceSetSha256: digest("synthetic-source-set"),
|
||||
} as const;
|
||||
const candidate = {
|
||||
archiveSha256: digestBytes(archiveBytes),
|
||||
bundleSha256: digest("synthetic-bundle"),
|
||||
distSha256: digest("synthetic-dist"),
|
||||
lockfileSha256: digest("synthetic-lock"),
|
||||
} as const;
|
||||
const vulnerability = signedProviderV2(
|
||||
{
|
||||
schemaVersion: 2,
|
||||
evidenceType: "vulnerability-report",
|
||||
provider: "synthetic-vulnerability-provider",
|
||||
issuedAt: "2026-08-02T01:00:00.000Z",
|
||||
expiresAt: "2026-08-02T02:00:00.000Z",
|
||||
run: { ...run, invocationNonce: "1".repeat(64) },
|
||||
source,
|
||||
candidate,
|
||||
secretScanAttestation: {
|
||||
status: "PASS",
|
||||
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
|
||||
sourceSetSha256: source.sourceSetSha256,
|
||||
policySha256: digest("synthetic-policy"),
|
||||
sarifSha256: digest("synthetic-sarif"),
|
||||
scanInputSha256: digest("synthetic-scan-input"),
|
||||
},
|
||||
findings: [],
|
||||
},
|
||||
"synthetic-vulnerability",
|
||||
vulnerabilityKeys.publicKey,
|
||||
vulnerabilityKeys.privateKey,
|
||||
);
|
||||
const provenance = signedProviderV2(
|
||||
{
|
||||
schemaVersion: 2,
|
||||
evidenceType: "provenance-attestation",
|
||||
provider: "synthetic-provenance-provider",
|
||||
signer: "synthetic-signer",
|
||||
issuedAt: "2026-08-02T01:00:00.000Z",
|
||||
expiresAt: "2026-08-02T02:00:00.000Z",
|
||||
run: { ...run, invocationNonce: "2".repeat(64) },
|
||||
source,
|
||||
candidate,
|
||||
subject: { name: "dist", digest: { sha256: candidate.distSha256 } },
|
||||
},
|
||||
"synthetic-provenance",
|
||||
provenanceKeys.publicKey,
|
||||
provenanceKeys.privateKey,
|
||||
);
|
||||
const vulnerabilityBytes = Buffer.from(`${JSON.stringify(vulnerability)}\n`);
|
||||
const provenanceBytes = Buffer.from(`${JSON.stringify(provenance)}\n`);
|
||||
const vulnerabilityTrust = trust("synthetic-vulnerability", vulnerabilityKeys.publicKey);
|
||||
const provenanceTrust = trust("synthetic-provenance", provenanceKeys.publicKey);
|
||||
const providerEvidence = {
|
||||
vulnerabilityReportSha256: digestBytes(vulnerabilityBytes),
|
||||
provenanceAttestationSha256: digestBytes(provenanceBytes),
|
||||
vulnerabilityInvocationNonce: "1".repeat(64),
|
||||
provenanceInvocationNonce: "2".repeat(64),
|
||||
vulnerabilityKeyId: vulnerabilityTrust.keyId,
|
||||
vulnerabilityKeyFingerprint: vulnerabilityTrust.publicKeyFingerprint,
|
||||
provenanceKeyId: provenanceTrust.keyId,
|
||||
provenanceKeyFingerprint: provenanceTrust.publicKeyFingerprint,
|
||||
secretScanAttestation: vulnerability.secretScanAttestation,
|
||||
};
|
||||
const common = {
|
||||
schemaVersion: 3,
|
||||
verifiedAt: "2026-08-02T01:00:00.000Z",
|
||||
status: "PASS",
|
||||
verifier: {
|
||||
id: "clean-architecture-frontend-template/promotion-verifier",
|
||||
version: "3",
|
||||
},
|
||||
run,
|
||||
source,
|
||||
candidate,
|
||||
providerEvidence,
|
||||
trustPolicySha256: trustPolicySha256({ vulnerabilityTrust, provenanceTrust }),
|
||||
failures: [],
|
||||
};
|
||||
const providerBytes = Buffer.from(
|
||||
`${JSON.stringify({
|
||||
...common,
|
||||
artifactType: "provider-verification",
|
||||
vulnerabilityStatus: "PASS",
|
||||
provenanceAttestationStatus: "PASS",
|
||||
}, null, 2)}\n`,
|
||||
);
|
||||
const promotionBytes = Buffer.from(
|
||||
`${JSON.stringify({
|
||||
...common,
|
||||
artifactType: "promotion-verification",
|
||||
localEvidenceStatus: "PASS",
|
||||
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
|
||||
providerVerificationSha256: digestBytes(providerBytes),
|
||||
}, null, 2)}\n`,
|
||||
);
|
||||
return {
|
||||
files: {
|
||||
"release-candidate.tar.gz": archiveBytes,
|
||||
"vulnerability-report.json": vulnerabilityBytes,
|
||||
"provenance-attestation.json": provenanceBytes,
|
||||
"provider-verification.json": providerBytes,
|
||||
"promotion-verification.json": promotionBytes,
|
||||
},
|
||||
verification: {
|
||||
vulnerabilityTrust,
|
||||
provenanceTrust,
|
||||
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
||||
expected: {
|
||||
run,
|
||||
sourceRevision: source.revision,
|
||||
sourceSetSha256: source.sourceSetSha256,
|
||||
archiveSha256: candidate.archiveSha256,
|
||||
bundleSha256: candidate.bundleSha256,
|
||||
distSha256: candidate.distSha256,
|
||||
lockfileSha256: candidate.lockfileSha256,
|
||||
},
|
||||
},
|
||||
vulnerabilityPem: vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }),
|
||||
provenancePem: provenanceKeys.publicKey.export({ type: "spki", format: "pem" }),
|
||||
};
|
||||
}
|
||||
|
||||
export function fingerprint(publicKey: KeyObject): string {
|
||||
return `sha256:${createHash("sha256")
|
||||
.update(publicKey.export({ type: "spki", format: "der" }))
|
||||
.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function trust(keyId: string, publicKey: KeyObject) {
|
||||
return { keyId, publicKey, publicKeyFingerprint: fingerprint(publicKey) };
|
||||
}
|
||||
|
||||
export function signedProviderV2(
|
||||
unsigned: Record<string, unknown>,
|
||||
keyId: string,
|
||||
publicKey: KeyObject,
|
||||
privateKey: KeyObject,
|
||||
fingerprintOverride?: string,
|
||||
): Record<string, any> {
|
||||
const { signature: existingSignature, ...payload } = unsigned;
|
||||
const value = {
|
||||
...payload,
|
||||
signature: {
|
||||
algorithm: "Ed25519" as const,
|
||||
keyId,
|
||||
publicKeyFingerprint:
|
||||
fingerprintOverride ??
|
||||
(existingSignature && typeof existingSignature === "object" &&
|
||||
"publicKeyFingerprint" in existingSignature
|
||||
? String(existingSignature.publicKeyFingerprint)
|
||||
: fingerprint(publicKey)),
|
||||
value: "",
|
||||
},
|
||||
};
|
||||
value.signature.value = sign(
|
||||
null,
|
||||
providerEvidenceSignaturePayload(value),
|
||||
privateKey,
|
||||
).toString("base64");
|
||||
return value;
|
||||
}
|
||||
|
||||
export function providerUnsigned(
|
||||
kind: "vulnerability" | "provenance",
|
||||
expected: ReturnType<typeof providerExpectedContext>,
|
||||
): Record<string, any> {
|
||||
const common = {
|
||||
source: expected.source,
|
||||
candidate: expected.candidate,
|
||||
schemaVersion: 2,
|
||||
evidenceType:
|
||||
kind === "vulnerability"
|
||||
? "vulnerability-report"
|
||||
: "provenance-attestation",
|
||||
provider: `fixture-${kind}`,
|
||||
issuedAt: "2026-08-02T01:00:00.000Z",
|
||||
expiresAt: "2026-08-02T02:00:00.000Z",
|
||||
run: {
|
||||
...expected.run,
|
||||
invocationNonce: kind === "vulnerability" ? "1".repeat(64) : "2".repeat(64),
|
||||
},
|
||||
};
|
||||
return kind === "vulnerability"
|
||||
? {
|
||||
...common,
|
||||
secretScanAttestation: expected.secretScanAttestation,
|
||||
findings: [],
|
||||
}
|
||||
: {
|
||||
...common,
|
||||
signer: "fixture-workload",
|
||||
subject: {
|
||||
name: "dist",
|
||||
digest: { sha256: expected.candidate.distSha256 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createArchivedAssessmentFixture(): Promise<{
|
||||
root: string;
|
||||
manifest: ReleaseCandidateManifest;
|
||||
assessmentSha256: string;
|
||||
}> {
|
||||
const root = await mkdtemp(path.join(tmpdir(), "archived-assessment-"));
|
||||
const sourceRevision = "a".repeat(40);
|
||||
const sourceSetSha256 = digest("source set");
|
||||
const releaseManifestBytes = Buffer.from(
|
||||
`${JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
appVersion: "1.0.0",
|
||||
buildId: "build-1",
|
||||
commitSha: sourceRevision,
|
||||
configSchemaVersion: "1",
|
||||
apiContractVersion: "1",
|
||||
assetManifestHash: digest("vite manifest"),
|
||||
releaseId: "release-1",
|
||||
builtAt: "2026-08-02T00:00:00.000Z",
|
||||
routeChunks: { home: "assets/home.js" },
|
||||
})}\n`,
|
||||
);
|
||||
const distInputs = [
|
||||
{ path: "dist/app.js", bytes: Buffer.byteLength("app\n"), sha256: digest("app\n"), gzipBytes: 0 },
|
||||
{
|
||||
path: "dist/release-manifest.json",
|
||||
bytes: releaseManifestBytes.byteLength,
|
||||
sha256: digestBytes(releaseManifestBytes),
|
||||
gzipBytes: 0,
|
||||
},
|
||||
];
|
||||
const candidateDist = distSha256(distInputs);
|
||||
const sbomBytes = Buffer.from(
|
||||
`${JSON.stringify({
|
||||
bomFormat: "CycloneDX",
|
||||
specVersion: "1.6",
|
||||
serialNumber: "urn:uuid:00000000-0000-4000-8000-000000000001",
|
||||
version: 1,
|
||||
metadata: {
|
||||
component: { type: "application", name: "fixture", version: "1.0.0" },
|
||||
properties: [],
|
||||
},
|
||||
components: [],
|
||||
dependencies: [],
|
||||
})}\n`,
|
||||
);
|
||||
const sbomSha256 = digestBytes(sbomBytes);
|
||||
const lockfileBytes = Buffer.from("lockfile\n");
|
||||
const lockfileDigest = digestBytes(lockfileBytes);
|
||||
const buildManifest = {
|
||||
schemaVersion: 1,
|
||||
buildId: "build-1",
|
||||
commitSha: sourceRevision,
|
||||
releaseId: "release-1",
|
||||
moduleInventoryHash: digest("module inventory"),
|
||||
generatedAt: "2026-08-02T00:00:00.000Z",
|
||||
buildContext: {
|
||||
nodeVersion: "v24.0.0",
|
||||
packageManagerVersion: "11.0.0",
|
||||
runnerImage: "linux-x64",
|
||||
sourceDateEpoch: "1785638400",
|
||||
},
|
||||
outputs: {
|
||||
directory: "dist",
|
||||
viteManifest: "dist/.vite/manifest.json",
|
||||
moduleInventory: "artifacts/quality/vite-module-inventory.json",
|
||||
routeChunks: { home: "assets/home.js" },
|
||||
runtimeConfigSchema: "dist/runtime-config.schema.json",
|
||||
},
|
||||
};
|
||||
const provenance = {
|
||||
_type: "https://in-toto.io/Statement/v1",
|
||||
subject: [{ name: "dist", digest: { sha256: candidateDist } }],
|
||||
predicateType: "https://slsa.dev/provenance/v1",
|
||||
predicate: {
|
||||
buildDefinition: {
|
||||
buildType: "https://vite.dev/build/v1",
|
||||
externalParameters: {},
|
||||
internalParameters: {},
|
||||
resolvedDependencies: [
|
||||
{ uri: "pnpm-lock.yaml", digest: { sha256: lockfileDigest } },
|
||||
],
|
||||
},
|
||||
runDetails: {
|
||||
builder: { id: "fixture-builder" },
|
||||
metadata: { invocationId: "LOCAL_UNSIGNED" },
|
||||
},
|
||||
materials: { lockfileSha256: lockfileDigest, sourceSetSha256, sbomSha256 },
|
||||
},
|
||||
};
|
||||
const supplyVerification = {
|
||||
schemaVersion: 1,
|
||||
localStatus: "PASS",
|
||||
promotionStatus: "FAIL_UNVERIFIED",
|
||||
lockfileSha256: lockfileDigest,
|
||||
sourceSetSha256,
|
||||
distSha256: candidateDist,
|
||||
sbomSha256,
|
||||
dependencyDiff: { added: [], removed: [], changed: [], upgrades: [] },
|
||||
highRiskReview: [],
|
||||
vulnerabilityStatus: "FAIL_UNVERIFIED",
|
||||
provenanceAttestationStatus: "FAIL_UNVERIFIED",
|
||||
failures: [],
|
||||
};
|
||||
const members = new Map<string, Buffer>([
|
||||
["dist/app.js", Buffer.from("app\n")],
|
||||
["dist/release-manifest.json", releaseManifestBytes],
|
||||
["pnpm-lock.yaml", lockfileBytes],
|
||||
["artifacts/release/build-manifest.json", Buffer.from(`${JSON.stringify(buildManifest)}\n`)],
|
||||
["artifacts/release/provenance.json", Buffer.from(`${JSON.stringify(provenance)}\n`)],
|
||||
[
|
||||
"artifacts/security/supply-chain-verification.json",
|
||||
Buffer.from(`${JSON.stringify(supplyVerification)}\n`),
|
||||
],
|
||||
["artifacts/release/sbom.cdx.json", sbomBytes],
|
||||
]);
|
||||
const evidenceInputs = [...members.entries()]
|
||||
.map(([memberPath, bytes]) => ({
|
||||
path: memberPath,
|
||||
bytes: bytes.byteLength,
|
||||
sha256: digestBytes(bytes),
|
||||
}))
|
||||
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
||||
const policyPaths = [
|
||||
"config/security/dependency-baseline.approval.json",
|
||||
"config/security/dependency-baseline.json",
|
||||
"config/security/dependency-change-evidence.json",
|
||||
"config/security/dependency-policy.json",
|
||||
"config/security/secret-scan-policy.json",
|
||||
"config/security/vulnerability-exceptions.json",
|
||||
"config/security/vulnerability-policy.json",
|
||||
"schemas/artifacts/build-manifest.schema.json",
|
||||
"schemas/artifacts/dependency-inventory.schema.json",
|
||||
"schemas/artifacts/supply-chain-verification.schema.json",
|
||||
"scripts/contracts/release-artifacts.ts",
|
||||
"scripts/create-release-candidate.ts",
|
||||
"scripts/generate-supply-chain.ts",
|
||||
"scripts/lib/build-manifest-outputs.ts",
|
||||
"scripts/lib/json-schema.ts",
|
||||
"scripts/lib/local-policy-evidence.ts",
|
||||
"scripts/lib/local-release-evidence.ts",
|
||||
"scripts/lib/release-candidate.ts",
|
||||
"scripts/lib/release-input-evidence.ts",
|
||||
"scripts/lib/release-runtime-coherence.ts",
|
||||
"scripts/lib/repository-file-inventory.ts",
|
||||
"scripts/lib/secret-scan-evaluator.ts",
|
||||
"scripts/lib/secret-scan-policy.ts",
|
||||
"scripts/lib/secret-scan.ts",
|
||||
"scripts/lib/supply-chain.ts",
|
||||
"scripts/lib/validated-json-artifact.ts",
|
||||
"src/contracts/release-artifacts.ts",
|
||||
"src/features/installed-contract-contributions.ts",
|
||||
"src/features/installed-feature-contracts.ts",
|
||||
];
|
||||
const sbomRow = evidenceInputs.find(
|
||||
({ path: memberPath }) => memberPath === "artifacts/release/sbom.cdx.json",
|
||||
)!;
|
||||
const policyInputs = policyPaths.map((policyPath) => ({
|
||||
path: policyPath,
|
||||
bytes: 2,
|
||||
sha256: digest(`policy:${policyPath}`),
|
||||
}));
|
||||
const verifierPaths = new Set([
|
||||
"scripts/contracts/release-artifacts.ts",
|
||||
"scripts/create-release-candidate.ts",
|
||||
"scripts/generate-supply-chain.ts",
|
||||
"scripts/lib/build-manifest-outputs.ts",
|
||||
"scripts/lib/json-schema.ts",
|
||||
"scripts/lib/local-policy-evidence.ts",
|
||||
"scripts/lib/local-release-evidence.ts",
|
||||
"scripts/lib/release-candidate.ts",
|
||||
"scripts/lib/release-input-evidence.ts",
|
||||
"scripts/lib/release-runtime-coherence.ts",
|
||||
"scripts/lib/repository-file-inventory.ts",
|
||||
"scripts/lib/secret-scan-evaluator.ts",
|
||||
"scripts/lib/secret-scan-policy.ts",
|
||||
"scripts/lib/secret-scan.ts",
|
||||
"scripts/lib/supply-chain.ts",
|
||||
"scripts/lib/validated-json-artifact.ts",
|
||||
"src/contracts/release-artifacts.ts",
|
||||
"src/features/installed-contract-contributions.ts",
|
||||
"src/features/installed-feature-contracts.ts",
|
||||
]);
|
||||
const assessment = localEvidenceAssessmentArtifactSchema.parse({
|
||||
...passingAssessment(),
|
||||
verifier: {
|
||||
id: "clean-architecture-frontend-template/local-evidence-verifier",
|
||||
version: "1",
|
||||
sourceSha256: supplyChainDigest(
|
||||
policyInputs.filter(({ path: policyPath }) => verifierPaths.has(policyPath)),
|
||||
),
|
||||
},
|
||||
source: { revision: sourceRevision, sourceSetSha256 },
|
||||
candidate: {
|
||||
distSha256: candidateDist,
|
||||
lockfileSha256: evidenceInputs.find(({ path: memberPath }) => memberPath === "pnpm-lock.yaml")!
|
||||
.sha256,
|
||||
sbomSha256: sbomRow.sha256,
|
||||
},
|
||||
policyInputs,
|
||||
evidenceInputs,
|
||||
});
|
||||
const assessmentBytes = Buffer.from(`${JSON.stringify(assessment)}\n`);
|
||||
members.set(LOCAL_EVIDENCE_ASSESSMENT_PATH, assessmentBytes);
|
||||
for (const [memberPath, bytes] of members) {
|
||||
await mkdir(path.dirname(path.join(root, memberPath)), { recursive: true });
|
||||
await writeFile(path.join(root, memberPath), bytes);
|
||||
}
|
||||
const files = [...members.entries()]
|
||||
.map(([memberPath, bytes]) => ({
|
||||
path: memberPath,
|
||||
bytes: bytes.byteLength,
|
||||
sha256: digestBytes(bytes),
|
||||
}))
|
||||
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
||||
const manifest: ReleaseCandidateManifest = {
|
||||
schemaVersion: 1,
|
||||
distSha256: assessment.candidate.distSha256,
|
||||
lockfileSha256: assessment.candidate.lockfileSha256,
|
||||
bundleSha256: supplyChainDigest(files),
|
||||
files,
|
||||
};
|
||||
await mkdir(path.join(root, "artifacts/release"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(root, "artifacts/release/release-candidate.json"),
|
||||
`${JSON.stringify(manifest)}\n`,
|
||||
);
|
||||
return { root, manifest, assessmentSha256: digestBytes(assessmentBytes) };
|
||||
}
|
||||
Reference in New Issue
Block a user