@open-agent-toolkit/cli 0.2.73 → 0.2.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +3 -56
- package/assets/docs/cli-utilities/tool-packs.md +4 -4
- package/assets/docs/contributing/code.md +2 -2
- package/assets/docs/contributing/index.md +0 -1
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/reference/troubleshooting.md +17 -40
- package/assets/docs/workflows/projects/artifacts.md +16 -34
- package/assets/docs/workflows/projects/lifecycle.md +20 -19
- package/assets/docs/workflows/skills/explainer-kit.md +180 -440
- package/assets/docs/workflows/skills/index.md +1 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +96 -171
- package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
- package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
- package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
- package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
- package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
- package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
- package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
- package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
- package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
- package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
- package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
- package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
- package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
- package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
- package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
- package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
- package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
- package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
- package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
- package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
- package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
- package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
- package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
- package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
- package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
- package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
- package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
- package/assets/skills/oat-project-complete/SKILL.md +168 -349
- package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
- package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
- package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
- package/assets/skills/oat-project-document/SKILL.md +1 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-lite/SKILL.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-plan/SKILL.md +5 -6
- package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
- package/assets/skills/oat-project-summary/SKILL.md +35 -25
- package/assets/skills/oat-wave-execute/SKILL.md +11 -36
- package/assets/skills/oat-wave-program/SKILL.md +13 -37
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +9 -113
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +32 -184
- package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
- package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
- package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
- package/dist/config/oat-config.d.ts +0 -11
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +0 -34
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +0 -8
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +2 -10
- package/dist/validation/project-state.d.ts.map +1 -1
- package/dist/validation/project-state.js +42 -3
- package/package.json +2 -2
- package/assets/docs/contributing/explainer-kit-verification.md +0 -133
- package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
- package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
- package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
- package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
- package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
- package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
- package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
- package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
- package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
- package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
- package/assets/skills/explainer-kit/references/contracts.md +0 -324
- package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
- package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
- package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
- package/assets/skills/explainer-kit/references/visual-review.md +0 -71
- package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
- package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
- package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
- package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
- package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
- package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
- package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
- package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
- package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
- package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
- package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
- package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
- package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
- package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
- package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
- package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
- package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
- package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
- package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
- package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
- package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
- package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
- package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
- package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
- package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
- package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
- package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
- package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
- package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
- package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
- package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
- package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
- package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
- package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
- package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
- package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
- package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
- package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
- package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
- package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
- package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
- package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
- package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
- package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
|
@@ -1,759 +0,0 @@
|
|
|
1
|
-
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
-
import { createHash, randomBytes as nodeRandomBytes } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
mkdtemp,
|
|
5
|
-
readFile,
|
|
6
|
-
realpath,
|
|
7
|
-
rename,
|
|
8
|
-
rm,
|
|
9
|
-
writeFile,
|
|
10
|
-
} from 'node:fs/promises';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
13
|
-
import { promisify } from 'node:util';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
catalogFromManifest,
|
|
17
|
-
initiativeCatalogPath,
|
|
18
|
-
resolvePublicVerificationPolicy,
|
|
19
|
-
serializeInitiativeCatalog,
|
|
20
|
-
validateInitiativeCatalog,
|
|
21
|
-
} from './catalog.mjs';
|
|
22
|
-
import { validateContract } from './contracts.mjs';
|
|
23
|
-
import { assertManifestPublishable } from './publication-policy.mjs';
|
|
24
|
-
import {
|
|
25
|
-
composePublicationTarget,
|
|
26
|
-
describeRootDivergence,
|
|
27
|
-
isPrivatePublicHost,
|
|
28
|
-
normalizePublishRoots,
|
|
29
|
-
privatePublicRootAllowed,
|
|
30
|
-
rootDivergenceWarningSuppressed,
|
|
31
|
-
} from './s3-roots.mjs';
|
|
32
|
-
|
|
33
|
-
export {
|
|
34
|
-
composePublicationTarget,
|
|
35
|
-
normalizePublishRoots,
|
|
36
|
-
} from './s3-roots.mjs';
|
|
37
|
-
|
|
38
|
-
const execFile = promisify(execFileCallback);
|
|
39
|
-
const SENTINEL_BODY = 'explainer-kit sentinel\n';
|
|
40
|
-
const TRANSIENT_PATTERN =
|
|
41
|
-
/(?:timeout|timed out|connection reset|temporar|throttl|slowdown|service unavailable|\b5\d\d\b)/i;
|
|
42
|
-
const AUTH_PATTERN =
|
|
43
|
-
/(?:credential|expired|sso session|access key|invalidclienttokenid|unrecognizedclient|token)/i;
|
|
44
|
-
const NOT_FOUND_PATTERN = /(?:\b404\b|not found|nosuchkey)/i;
|
|
45
|
-
|
|
46
|
-
export function createSentinelRelativePath(
|
|
47
|
-
runId,
|
|
48
|
-
randomBytes = nodeRandomBytes,
|
|
49
|
-
) {
|
|
50
|
-
const safeRunId =
|
|
51
|
-
String(runId)
|
|
52
|
-
.toLowerCase()
|
|
53
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
54
|
-
.replace(/^-|-$/g, '') || 'run';
|
|
55
|
-
const suffix = randomBytes(16).toString('hex');
|
|
56
|
-
if (!/^[a-f0-9]{32}$/.test(suffix)) {
|
|
57
|
-
throw publishError(
|
|
58
|
-
'E_PUBLISH_INPUT',
|
|
59
|
-
'Sentinel randomness must provide 128 bits.',
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
return `.explainer-kit-sentinel/${safeRunId}-${suffix}.txt`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function publishS3Static(request, dependencies = {}) {
|
|
66
|
-
if (dependencies.approved !== true) {
|
|
67
|
-
throw publishError(
|
|
68
|
-
'E_PUBLISH_APPROVAL',
|
|
69
|
-
'Publishing requires explicit human approval.',
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const requestValidation = validateContract('publish-request', request);
|
|
74
|
-
if (!requestValidation.valid) {
|
|
75
|
-
throw publishError(
|
|
76
|
-
'E_PUBLISH_INPUT',
|
|
77
|
-
`Invalid publish request: ${requestValidation.errors[0].message}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
const roots = normalizePublishRoots(request.s3Uri, request.publicBaseUrl);
|
|
81
|
-
const cwd = dependencies.cwd ?? process.cwd();
|
|
82
|
-
const siteRoot = resolve(cwd, request.siteRoot);
|
|
83
|
-
const manifestPath = resolve(cwd, request.manifestPath);
|
|
84
|
-
const manifest = await readJson(manifestPath, 'manifest');
|
|
85
|
-
const buildRecord =
|
|
86
|
-
manifest.outcome === 'incomplete'
|
|
87
|
-
? await readJson(
|
|
88
|
-
resolve(dirname(manifestPath), manifest.buildRecord?.path ?? ''),
|
|
89
|
-
'build record',
|
|
90
|
-
)
|
|
91
|
-
: undefined;
|
|
92
|
-
assertManifestPublishable(manifest, { buildRecord });
|
|
93
|
-
const manifestValidation = validateContract('manifest', manifest);
|
|
94
|
-
if (!manifestValidation.valid) {
|
|
95
|
-
throw publishError(
|
|
96
|
-
'E_PUBLISH_INPUT',
|
|
97
|
-
`Invalid manifest: ${manifestValidation.errors[0].message}`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
const artifacts = await collectArtifacts(manifest, siteRoot);
|
|
101
|
-
const command = dependencies.command ?? defaultCommand;
|
|
102
|
-
const httpGet = dependencies.httpGet ?? defaultHttpGet;
|
|
103
|
-
const randomBytes = dependencies.randomBytes ?? nodeRandomBytes;
|
|
104
|
-
const now = dependencies.now ?? (() => new Date().toISOString());
|
|
105
|
-
const sleep = dependencies.sleep ?? defaultSleep;
|
|
106
|
-
const awsOptions = {
|
|
107
|
-
region: request.awsRegion,
|
|
108
|
-
profile: request.awsProfile,
|
|
109
|
-
};
|
|
110
|
-
const sentinelRelativePath = createSentinelRelativePath(
|
|
111
|
-
manifest.runId,
|
|
112
|
-
randomBytes,
|
|
113
|
-
);
|
|
114
|
-
const sentinelTargetPath = composePublicationTarget(
|
|
115
|
-
sentinelRelativePath,
|
|
116
|
-
roots,
|
|
117
|
-
);
|
|
118
|
-
// Resolved before the catalog is built: the catalog is hashed and uploaded
|
|
119
|
-
// before any public verification runs, so it must carry the policy up front.
|
|
120
|
-
const publicAccess =
|
|
121
|
-
request.schemaVersion === 'explainer-kit.publish-request/v1'
|
|
122
|
-
? 'public'
|
|
123
|
-
: request.publicAccess;
|
|
124
|
-
const verificationPolicy = resolvePublicVerificationPolicy(publicAccess);
|
|
125
|
-
|
|
126
|
-
const divergence = describeRootDivergence(roots);
|
|
127
|
-
if (divergence && !rootDivergenceWarningSuppressed()) {
|
|
128
|
-
(dependencies.warn ?? ((message) => console.warn(message)))(
|
|
129
|
-
`explainer-kit: ${divergence}`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const catalog = catalogFromManifest(manifest, roots.publicBaseUrl, {
|
|
134
|
-
publicAccess,
|
|
135
|
-
});
|
|
136
|
-
const catalogValidation = validateInitiativeCatalog(
|
|
137
|
-
catalog,
|
|
138
|
-
manifest,
|
|
139
|
-
roots.publicBaseUrl,
|
|
140
|
-
{ publicAccess },
|
|
141
|
-
);
|
|
142
|
-
if (!catalogValidation.valid) {
|
|
143
|
-
throw publishError(
|
|
144
|
-
'E_PUBLISH_INPUT',
|
|
145
|
-
`Invalid initiative catalog: ${catalogValidation.errors[0].message}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
const catalogBody = Buffer.from(serializeInitiativeCatalog(catalog));
|
|
149
|
-
const catalogManifestPath = initiativeCatalogPath(manifest.slug);
|
|
150
|
-
const catalogPublishPath = catalogManifestPath.slice('site/'.length);
|
|
151
|
-
if (artifacts.some(({ publishPath }) => publishPath === catalogPublishPath)) {
|
|
152
|
-
throw publishError(
|
|
153
|
-
'E_PUBLISH_INPUT',
|
|
154
|
-
`Generated catalog path collides with a manifest artifact: ${catalogManifestPath}`,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
const sentinelDirectory = await mkdtemp(
|
|
158
|
-
join(tmpdir(), 'explainer-sentinel-'),
|
|
159
|
-
);
|
|
160
|
-
const sentinelBodyPath = join(sentinelDirectory, 'sentinel.txt');
|
|
161
|
-
const catalogBodyPath = join(sentinelDirectory, 'catalog.json');
|
|
162
|
-
const catalogArtifact = {
|
|
163
|
-
source: { kind: 'auxiliary', name: 'catalog' },
|
|
164
|
-
manifestPath: catalogManifestPath,
|
|
165
|
-
publishPath: catalogPublishPath,
|
|
166
|
-
filePath: catalogBodyPath,
|
|
167
|
-
hash: fileHash(catalogBody),
|
|
168
|
-
contentType: 'application/json',
|
|
169
|
-
cacheControl: 'public, max-age=300',
|
|
170
|
-
};
|
|
171
|
-
const publicationArtifacts = [...artifacts, catalogArtifact];
|
|
172
|
-
let sentinelUploaded = false;
|
|
173
|
-
let sentinelDeleted = false;
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await writeFile(sentinelBodyPath, SENTINEL_BODY, 'utf8');
|
|
177
|
-
await writeFile(catalogBodyPath, catalogBody);
|
|
178
|
-
await runAws(
|
|
179
|
-
command,
|
|
180
|
-
putObjectArgs({
|
|
181
|
-
roots,
|
|
182
|
-
relativePath: sentinelRelativePath,
|
|
183
|
-
bodyPath: sentinelBodyPath,
|
|
184
|
-
contentType: 'text/plain; charset=utf-8',
|
|
185
|
-
cacheControl: 'no-store',
|
|
186
|
-
hash: fileHash(Buffer.from(SENTINEL_BODY)),
|
|
187
|
-
awsOptions,
|
|
188
|
-
}),
|
|
189
|
-
{ sleep },
|
|
190
|
-
);
|
|
191
|
-
sentinelUploaded = true;
|
|
192
|
-
const sentinelMetadata = await readObjectMetadata(
|
|
193
|
-
command,
|
|
194
|
-
headObjectArgs(roots, sentinelRelativePath, awsOptions),
|
|
195
|
-
{ sleep },
|
|
196
|
-
);
|
|
197
|
-
const sentinelObjectVerification = await verifyObjectBytes({
|
|
198
|
-
command,
|
|
199
|
-
metadata: sentinelMetadata,
|
|
200
|
-
roots,
|
|
201
|
-
relativePath: sentinelRelativePath,
|
|
202
|
-
expectedHash: fileHash(Buffer.from(SENTINEL_BODY)),
|
|
203
|
-
downloadPath: join(sentinelDirectory, 'sentinel-download.txt'),
|
|
204
|
-
awsOptions,
|
|
205
|
-
sleep,
|
|
206
|
-
});
|
|
207
|
-
let sentinelPublicVerification = {
|
|
208
|
-
status: verificationPolicy.receiptSkipStatus,
|
|
209
|
-
};
|
|
210
|
-
if (verificationPolicy.verifyPublicly) {
|
|
211
|
-
const sentinelResponse = await httpGet(sentinelTargetPath.publicUrl);
|
|
212
|
-
if (
|
|
213
|
-
sentinelResponse.status < 200 ||
|
|
214
|
-
sentinelResponse.status > 299 ||
|
|
215
|
-
!responseBytes(sentinelResponse.body).equals(Buffer.from(SENTINEL_BODY))
|
|
216
|
-
) {
|
|
217
|
-
throw publishError(
|
|
218
|
-
'E_PUBLISH_ROOTS',
|
|
219
|
-
'Public root did not serve the uploaded sentinel.',
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
sentinelPublicVerification = {
|
|
223
|
-
status: 'verified',
|
|
224
|
-
httpStatus: sentinelResponse.status,
|
|
225
|
-
hash: fileHash(responseBytes(sentinelResponse.body)),
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
await runAws(
|
|
229
|
-
command,
|
|
230
|
-
deleteObjectArgs(roots, sentinelRelativePath, awsOptions),
|
|
231
|
-
{ sleep },
|
|
232
|
-
);
|
|
233
|
-
sentinelDeleted = true;
|
|
234
|
-
|
|
235
|
-
const receiptArtifacts = [];
|
|
236
|
-
for (const artifact of publicationArtifacts) {
|
|
237
|
-
const target = composePublicationTarget(artifact.publishPath, roots);
|
|
238
|
-
const metadata = await readExistingMetadata(
|
|
239
|
-
command,
|
|
240
|
-
headObjectArgs(roots, artifact.publishPath, awsOptions),
|
|
241
|
-
{ sleep },
|
|
242
|
-
);
|
|
243
|
-
if (!matchesPublishedArtifact(metadata, artifact)) {
|
|
244
|
-
await runAws(
|
|
245
|
-
command,
|
|
246
|
-
putObjectArgs({
|
|
247
|
-
roots,
|
|
248
|
-
relativePath: artifact.publishPath,
|
|
249
|
-
bodyPath: artifact.filePath,
|
|
250
|
-
contentType: artifact.contentType,
|
|
251
|
-
cacheControl: artifact.cacheControl,
|
|
252
|
-
hash: artifact.hash,
|
|
253
|
-
awsOptions,
|
|
254
|
-
}),
|
|
255
|
-
{ sleep },
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
const uploadedMetadata = await readObjectMetadata(
|
|
259
|
-
command,
|
|
260
|
-
headObjectArgs(roots, artifact.publishPath, awsOptions),
|
|
261
|
-
{ sleep },
|
|
262
|
-
);
|
|
263
|
-
assertMetadata(uploadedMetadata, artifact);
|
|
264
|
-
const objectVerification = await verifyObjectBytes({
|
|
265
|
-
command,
|
|
266
|
-
metadata: uploadedMetadata,
|
|
267
|
-
roots,
|
|
268
|
-
relativePath: artifact.publishPath,
|
|
269
|
-
expectedHash: artifact.hash,
|
|
270
|
-
downloadPath: join(
|
|
271
|
-
sentinelDirectory,
|
|
272
|
-
`object-${receiptArtifacts.length}.download`,
|
|
273
|
-
),
|
|
274
|
-
awsOptions,
|
|
275
|
-
sleep,
|
|
276
|
-
});
|
|
277
|
-
let publicVerification = {
|
|
278
|
-
status: verificationPolicy.receiptSkipStatus,
|
|
279
|
-
};
|
|
280
|
-
if (verificationPolicy.verifyPublicly) {
|
|
281
|
-
const response = await httpGet(target.publicUrl);
|
|
282
|
-
if (response.status < 200 || response.status > 299) {
|
|
283
|
-
throw publishError(
|
|
284
|
-
'E_PUBLISH_VERIFY',
|
|
285
|
-
`Public verification failed for ${artifact.manifestPath}.`,
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
const servedType = headerValue(response.headers, 'content-type');
|
|
289
|
-
if (!contentTypesMatch(servedType, artifact.contentType)) {
|
|
290
|
-
throw publishError(
|
|
291
|
-
'E_PUBLISH_VERIFY',
|
|
292
|
-
`Public content type mismatch for ${artifact.manifestPath}.`,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
const publicHash = fileHash(responseBytes(response.body));
|
|
296
|
-
if (publicHash !== artifact.hash) {
|
|
297
|
-
throw publishError(
|
|
298
|
-
'E_PUBLISH_VERIFY',
|
|
299
|
-
`Public bytes do not match the manifest for ${artifact.manifestPath}.`,
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
publicVerification = {
|
|
303
|
-
status: 'verified',
|
|
304
|
-
httpStatus: response.status,
|
|
305
|
-
hash: publicHash,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
receiptArtifacts.push({
|
|
309
|
-
source: artifact.source,
|
|
310
|
-
relativePath: artifact.manifestPath,
|
|
311
|
-
hash: artifact.hash,
|
|
312
|
-
s3Uri: target.s3Uri,
|
|
313
|
-
publicUrl: target.publicUrl,
|
|
314
|
-
contentType: artifact.contentType,
|
|
315
|
-
objectVerification,
|
|
316
|
-
publicVerification,
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const receipt = {
|
|
321
|
-
schemaVersion: 'explainer-kit.publish-receipt/v2',
|
|
322
|
-
provider: 's3-static',
|
|
323
|
-
publishedAt: now(),
|
|
324
|
-
publicAccess,
|
|
325
|
-
// Durable trace for the anti-SSRF opt-in. Without it, a run that
|
|
326
|
-
// published to an internal address with the control disabled is
|
|
327
|
-
// indistinguishable in retained evidence from one that did not, because
|
|
328
|
-
// the variable is read straight from the environment and enters no other
|
|
329
|
-
// record. Emitted only in that exceptional case, so ordinary receipts are
|
|
330
|
-
// byte-identical to before.
|
|
331
|
-
...(privatePublicRootAllowed() &&
|
|
332
|
-
isPrivatePublicHost(new URL(roots.publicBaseUrl).hostname) && {
|
|
333
|
-
publicRootPolicy: 'private-allowed',
|
|
334
|
-
}),
|
|
335
|
-
roots: { s3Uri: roots.s3Uri, publicBaseUrl: roots.publicBaseUrl },
|
|
336
|
-
sentinel: {
|
|
337
|
-
relativePath: sentinelRelativePath,
|
|
338
|
-
objectVerification: sentinelObjectVerification,
|
|
339
|
-
publicVerification: sentinelPublicVerification,
|
|
340
|
-
deleted: sentinelDeleted,
|
|
341
|
-
},
|
|
342
|
-
artifacts: receiptArtifacts,
|
|
343
|
-
};
|
|
344
|
-
const receiptValidation = validateContract('publish-receipt', receipt, {
|
|
345
|
-
manifest,
|
|
346
|
-
catalogArtifact: {
|
|
347
|
-
relativePath: catalogArtifact.manifestPath,
|
|
348
|
-
hash: catalogArtifact.hash,
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
if (!receiptValidation.valid) {
|
|
352
|
-
throw publishError(
|
|
353
|
-
'E_PUBLISH_VERIFY',
|
|
354
|
-
`Invalid publish receipt: ${receiptValidation.errors[0].message}`,
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
const receiptPath =
|
|
358
|
-
dependencies.receiptPath ??
|
|
359
|
-
join(dirname(manifestPath), 'publish-receipt.json');
|
|
360
|
-
await writeJsonAtomic(receiptPath, receipt);
|
|
361
|
-
return receipt;
|
|
362
|
-
} finally {
|
|
363
|
-
if (sentinelUploaded && !sentinelDeleted) {
|
|
364
|
-
try {
|
|
365
|
-
await runAws(
|
|
366
|
-
command,
|
|
367
|
-
deleteObjectArgs(roots, sentinelRelativePath, awsOptions),
|
|
368
|
-
{ sleep },
|
|
369
|
-
);
|
|
370
|
-
} catch {
|
|
371
|
-
// Preserve the original failure. The missing cleanup remains observable
|
|
372
|
-
// because no successful receipt is emitted.
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
await rm(sentinelDirectory, { recursive: true, force: true });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function collectArtifacts(manifest, siteRoot) {
|
|
380
|
-
const canonicalSiteRoot = await realpath(siteRoot);
|
|
381
|
-
const sitePrefix = `${basename(canonicalSiteRoot)}/`;
|
|
382
|
-
const seen = new Set();
|
|
383
|
-
const artifacts = [];
|
|
384
|
-
for (const entry of manifest.artifacts) {
|
|
385
|
-
if (entry.status !== 'built' || typeof entry.renderedPath !== 'string') {
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (!entry.renderedPath.startsWith(sitePrefix)) {
|
|
389
|
-
throw publishError(
|
|
390
|
-
'E_PUBLISH_INPUT',
|
|
391
|
-
`Rendered path is outside the site root: ${entry.renderedPath}`,
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
const publishPath = entry.renderedPath.slice(sitePrefix.length);
|
|
395
|
-
if (
|
|
396
|
-
!publishPath ||
|
|
397
|
-
publishPath.startsWith('/') ||
|
|
398
|
-
publishPath.includes('..')
|
|
399
|
-
) {
|
|
400
|
-
throw publishError(
|
|
401
|
-
'E_PUBLISH_INPUT',
|
|
402
|
-
`Unsafe publish path: ${entry.renderedPath}`,
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
if (seen.has(publishPath)) {
|
|
406
|
-
throw publishError(
|
|
407
|
-
'E_PUBLISH_INPUT',
|
|
408
|
-
`Duplicate publish path: ${publishPath}`,
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
seen.add(publishPath);
|
|
412
|
-
const filePath = await realpath(join(canonicalSiteRoot, publishPath));
|
|
413
|
-
if (
|
|
414
|
-
filePath !== canonicalSiteRoot &&
|
|
415
|
-
!filePath.startsWith(`${canonicalSiteRoot}${sep}`)
|
|
416
|
-
) {
|
|
417
|
-
throw publishError(
|
|
418
|
-
'E_PUBLISH_INPUT',
|
|
419
|
-
`Artifact escapes the site root: ${entry.renderedPath}`,
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
const bytes = await readFile(filePath);
|
|
423
|
-
const actualHash = fileHash(bytes);
|
|
424
|
-
if (actualHash !== entry.hash) {
|
|
425
|
-
throw publishError(
|
|
426
|
-
'E_PUBLISH_INPUT',
|
|
427
|
-
`Artifact hash mismatch: ${entry.renderedPath}`,
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
const contentType = contentTypeFor(publishPath, entry.mediaType);
|
|
431
|
-
artifacts.push({
|
|
432
|
-
source: { kind: 'manifest', artifactId: entry.id },
|
|
433
|
-
manifestPath: entry.renderedPath,
|
|
434
|
-
publishPath,
|
|
435
|
-
filePath,
|
|
436
|
-
hash: actualHash,
|
|
437
|
-
contentType,
|
|
438
|
-
cacheControl: 'public, max-age=300',
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
return artifacts;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function putObjectArgs({
|
|
445
|
-
roots,
|
|
446
|
-
relativePath,
|
|
447
|
-
bodyPath,
|
|
448
|
-
contentType,
|
|
449
|
-
cacheControl,
|
|
450
|
-
hash,
|
|
451
|
-
awsOptions,
|
|
452
|
-
}) {
|
|
453
|
-
return withAwsOptions(
|
|
454
|
-
[
|
|
455
|
-
's3api',
|
|
456
|
-
'put-object',
|
|
457
|
-
'--bucket',
|
|
458
|
-
roots.bucket,
|
|
459
|
-
'--key',
|
|
460
|
-
objectKey(roots, relativePath),
|
|
461
|
-
'--body',
|
|
462
|
-
bodyPath,
|
|
463
|
-
'--content-type',
|
|
464
|
-
contentType,
|
|
465
|
-
'--cache-control',
|
|
466
|
-
cacheControl,
|
|
467
|
-
'--metadata',
|
|
468
|
-
`explainer-sha256=${hash.slice('sha256:'.length)}`,
|
|
469
|
-
'--checksum-sha256',
|
|
470
|
-
Buffer.from(hash.slice('sha256:'.length), 'hex').toString('base64'),
|
|
471
|
-
'--no-cli-pager',
|
|
472
|
-
],
|
|
473
|
-
awsOptions,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function headObjectArgs(roots, relativePath, awsOptions) {
|
|
478
|
-
return withAwsOptions(
|
|
479
|
-
[
|
|
480
|
-
's3api',
|
|
481
|
-
'head-object',
|
|
482
|
-
'--bucket',
|
|
483
|
-
roots.bucket,
|
|
484
|
-
'--key',
|
|
485
|
-
objectKey(roots, relativePath),
|
|
486
|
-
'--checksum-mode',
|
|
487
|
-
'ENABLED',
|
|
488
|
-
'--output',
|
|
489
|
-
'json',
|
|
490
|
-
'--no-cli-pager',
|
|
491
|
-
],
|
|
492
|
-
awsOptions,
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function getObjectArgs(roots, relativePath, downloadPath, awsOptions) {
|
|
497
|
-
return withAwsOptions(
|
|
498
|
-
[
|
|
499
|
-
's3api',
|
|
500
|
-
'get-object',
|
|
501
|
-
'--bucket',
|
|
502
|
-
roots.bucket,
|
|
503
|
-
'--key',
|
|
504
|
-
objectKey(roots, relativePath),
|
|
505
|
-
'--checksum-mode',
|
|
506
|
-
'ENABLED',
|
|
507
|
-
downloadPath,
|
|
508
|
-
'--no-cli-pager',
|
|
509
|
-
],
|
|
510
|
-
awsOptions,
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function deleteObjectArgs(roots, relativePath, awsOptions) {
|
|
515
|
-
return withAwsOptions(
|
|
516
|
-
[
|
|
517
|
-
's3api',
|
|
518
|
-
'delete-object',
|
|
519
|
-
'--bucket',
|
|
520
|
-
roots.bucket,
|
|
521
|
-
'--key',
|
|
522
|
-
objectKey(roots, relativePath),
|
|
523
|
-
'--no-cli-pager',
|
|
524
|
-
],
|
|
525
|
-
awsOptions,
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function withAwsOptions(args, { region, profile }) {
|
|
530
|
-
const result = [...args, '--region', region];
|
|
531
|
-
if (profile) {
|
|
532
|
-
result.push('--profile', profile);
|
|
533
|
-
}
|
|
534
|
-
return result;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
async function runAws(
|
|
538
|
-
command,
|
|
539
|
-
args,
|
|
540
|
-
{ sleep = defaultSleep, attempts = 3, allowNotFound = false } = {},
|
|
541
|
-
) {
|
|
542
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
543
|
-
try {
|
|
544
|
-
return await command('aws', args);
|
|
545
|
-
} catch (caught) {
|
|
546
|
-
const diagnostic = `${caught?.message ?? ''}\n${caught?.stderr ?? ''}`;
|
|
547
|
-
if (AUTH_PATTERN.test(diagnostic)) {
|
|
548
|
-
throw publishError(
|
|
549
|
-
'E_PUBLISH_AUTH',
|
|
550
|
-
'AWS authentication failed; refresh the configured credential chain and retry.',
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
if (allowNotFound && NOT_FOUND_PATTERN.test(diagnostic)) {
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
if (!TRANSIENT_PATTERN.test(diagnostic) || attempt === attempts) {
|
|
557
|
-
throw publishError('E_PUBLISH_AWS', 'AWS object operation failed.');
|
|
558
|
-
}
|
|
559
|
-
await sleep(50 * 2 ** (attempt - 1));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async function readExistingMetadata(command, args, { sleep }) {
|
|
565
|
-
const result = await runAws(command, args, {
|
|
566
|
-
sleep,
|
|
567
|
-
allowNotFound: true,
|
|
568
|
-
});
|
|
569
|
-
if (result === null) return null;
|
|
570
|
-
try {
|
|
571
|
-
return JSON.parse(result.stdout || '{}');
|
|
572
|
-
} catch {
|
|
573
|
-
throw publishError(
|
|
574
|
-
'E_PUBLISH_VERIFY',
|
|
575
|
-
'AWS returned invalid object metadata.',
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
async function readObjectMetadata(command, args, { sleep }) {
|
|
581
|
-
const result = await runAws(command, args, { sleep });
|
|
582
|
-
return parseAwsJson(result.stdout);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function matchesPublishedArtifact(metadata, artifact) {
|
|
586
|
-
return (
|
|
587
|
-
metadata?.Metadata?.['explainer-sha256'] ===
|
|
588
|
-
artifact.hash.slice('sha256:'.length) &&
|
|
589
|
-
metadata.ContentType === artifact.contentType &&
|
|
590
|
-
metadata.CacheControl === artifact.cacheControl
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function assertMetadata(metadata, artifact) {
|
|
595
|
-
if (!matchesPublishedArtifact(metadata, artifact)) {
|
|
596
|
-
throw publishError(
|
|
597
|
-
'E_PUBLISH_VERIFY',
|
|
598
|
-
`Uploaded metadata mismatch for ${artifact.manifestPath}.`,
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async function verifyObjectBytes({
|
|
604
|
-
command,
|
|
605
|
-
metadata,
|
|
606
|
-
roots,
|
|
607
|
-
relativePath,
|
|
608
|
-
expectedHash,
|
|
609
|
-
downloadPath,
|
|
610
|
-
awsOptions,
|
|
611
|
-
sleep,
|
|
612
|
-
}) {
|
|
613
|
-
const expectedBase64 = Buffer.from(
|
|
614
|
-
expectedHash.slice('sha256:'.length),
|
|
615
|
-
'hex',
|
|
616
|
-
).toString('base64');
|
|
617
|
-
if (typeof metadata.ChecksumSHA256 === 'string') {
|
|
618
|
-
if (metadata.ChecksumSHA256 !== expectedBase64) {
|
|
619
|
-
throw publishError(
|
|
620
|
-
'E_PUBLISH_VERIFY',
|
|
621
|
-
`Authenticated object checksum mismatch for ${relativePath}.`,
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
return {
|
|
625
|
-
status: 'verified',
|
|
626
|
-
method: 'service-checksum',
|
|
627
|
-
hash: expectedHash,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
try {
|
|
632
|
-
await runAws(
|
|
633
|
-
command,
|
|
634
|
-
getObjectArgs(roots, relativePath, downloadPath, awsOptions),
|
|
635
|
-
{ sleep },
|
|
636
|
-
);
|
|
637
|
-
} catch {
|
|
638
|
-
throw publishError(
|
|
639
|
-
'E_PUBLISH_VERIFY',
|
|
640
|
-
`Authenticated object-byte verification is unavailable for ${relativePath}.`,
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
if (fileHash(await readFile(downloadPath)) !== expectedHash) {
|
|
644
|
-
throw publishError(
|
|
645
|
-
'E_PUBLISH_VERIFY',
|
|
646
|
-
`Authenticated object bytes do not match ${relativePath}.`,
|
|
647
|
-
);
|
|
648
|
-
}
|
|
649
|
-
return {
|
|
650
|
-
status: 'verified',
|
|
651
|
-
method: 'authenticated-download',
|
|
652
|
-
hash: expectedHash,
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function parseAwsJson(stdout) {
|
|
657
|
-
try {
|
|
658
|
-
return JSON.parse(stdout || '{}');
|
|
659
|
-
} catch {
|
|
660
|
-
throw publishError(
|
|
661
|
-
'E_PUBLISH_VERIFY',
|
|
662
|
-
'AWS returned invalid object metadata.',
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function contentTypeFor(path, declared) {
|
|
668
|
-
const extension = path.slice(path.lastIndexOf('.')).toLowerCase();
|
|
669
|
-
const known = {
|
|
670
|
-
'.css': 'text/css; charset=utf-8',
|
|
671
|
-
'.html': 'text/html; charset=utf-8',
|
|
672
|
-
'.js': 'text/javascript; charset=utf-8',
|
|
673
|
-
'.json': 'application/json',
|
|
674
|
-
'.svg': 'image/svg+xml',
|
|
675
|
-
'.txt': 'text/plain; charset=utf-8',
|
|
676
|
-
};
|
|
677
|
-
return known[extension] ?? declared ?? 'application/octet-stream';
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function contentTypesMatch(served, expected) {
|
|
681
|
-
if (!served) {
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
|
-
return (
|
|
685
|
-
served.toLowerCase().split(';')[0].trim() ===
|
|
686
|
-
expected.toLowerCase().split(';')[0].trim()
|
|
687
|
-
);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function objectKey(roots, relativePath) {
|
|
691
|
-
return roots.keyPrefix ? `${roots.keyPrefix}/${relativePath}` : relativePath;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
function fileHash(bytes) {
|
|
695
|
-
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function headerValue(headers, name) {
|
|
699
|
-
if (typeof headers?.get === 'function') {
|
|
700
|
-
return headers.get(name);
|
|
701
|
-
}
|
|
702
|
-
return headers?.[name] ?? headers?.[name.toLowerCase()];
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
async function readJson(path, label) {
|
|
706
|
-
try {
|
|
707
|
-
return JSON.parse(await readFile(path, 'utf8'));
|
|
708
|
-
} catch {
|
|
709
|
-
throw publishError('E_PUBLISH_INPUT', `Could not read ${label} JSON.`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
async function writeJsonAtomic(path, value) {
|
|
714
|
-
const temporary = `${path}.tmp-${process.pid}-${nodeRandomBytes(6).toString('hex')}`;
|
|
715
|
-
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
716
|
-
flag: 'wx',
|
|
717
|
-
});
|
|
718
|
-
await rename(temporary, path);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
async function defaultCommand(file, args) {
|
|
722
|
-
return execFile(file, args, { maxBuffer: 1024 * 1024 });
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
export async function defaultHttpGet(url, { fetchImpl = fetch } = {}) {
|
|
726
|
-
// A canonical public artifact URL is uploaded to a known key and should never
|
|
727
|
-
// legitimately redirect. Following redirects turned public verification into
|
|
728
|
-
// an outbound GET primitive that a third party could bounce inward, so a
|
|
729
|
-
// redirect is a verification failure rather than something to chase.
|
|
730
|
-
const response = await fetchImpl(url, {
|
|
731
|
-
redirect: 'error',
|
|
732
|
-
signal: AbortSignal.timeout(15_000),
|
|
733
|
-
});
|
|
734
|
-
return {
|
|
735
|
-
status: response.status,
|
|
736
|
-
headers: response.headers,
|
|
737
|
-
body: Buffer.from(await response.arrayBuffer()),
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
function responseBytes(body) {
|
|
742
|
-
if (Buffer.isBuffer(body)) return body;
|
|
743
|
-
if (body instanceof Uint8Array) {
|
|
744
|
-
return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
745
|
-
}
|
|
746
|
-
if (typeof body === 'string') return Buffer.from(body);
|
|
747
|
-
throw publishError(
|
|
748
|
-
'E_PUBLISH_VERIFY',
|
|
749
|
-
'Public verification returned an unreadable response body.',
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function defaultSleep(milliseconds) {
|
|
754
|
-
return new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds));
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function publishError(code, message) {
|
|
758
|
-
return Object.assign(new Error(message), { code });
|
|
759
|
-
}
|