jorgex-stack 1.9.62 → 1.9.63
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/README.md +17 -30
- package/dist/browser-playwright.d.ts +2 -0
- package/dist/browser-playwright.js +955 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +9616 -1827
- package/dist/pi-ci-artifact.js +341 -35
- package/dist/quality-verifier.js +399 -29
- package/package.json +3 -2
- package/stack/mcp/servers.json +1 -1
- package/stack/system-prompt/browser-playwright.md +1 -1
- package/upstreams.json +8 -8
- package/dist/chunk-2CBCBOP7.js +0 -245
- package/dist/chunk-4USZMJ3I.js +0 -554
- package/dist/chunk-66WVKOU4.js +0 -693
- package/dist/chunk-B76ZJXXD.js +0 -989
- package/dist/chunk-BSHTDTJL.js +0 -781
- package/dist/chunk-KGL5VNVZ.js +0 -305
- package/dist/chunk-KKN4OYHD.js +0 -704
- package/dist/chunk-N5IC2JBJ.js +0 -467
- package/dist/chunk-NQZVJFV2.js +0 -33
- package/dist/claude-code-BBHLN5TD.js +0 -12
- package/dist/codex-ALGP2YBC.js +0 -12
- package/dist/official-engram-setup-4WIXM4EF.js +0 -34
- package/dist/opencode-VG2IZRRF.js +0 -32
- package/dist/pi-CKFSPGJU.js +0 -13
package/dist/pi-ci-artifact.js
CHANGED
|
@@ -1,61 +1,367 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
downloadVerifiedPiTarball,
|
|
4
|
-
resolveLatestPiRelease
|
|
5
|
-
} from "./chunk-KGL5VNVZ.js";
|
|
6
2
|
|
|
7
3
|
// src/lib/pi-ci-artifact.ts
|
|
4
|
+
import fs2 from "fs";
|
|
5
|
+
import path2 from "path";
|
|
6
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
7
|
+
import { pathToFileURL } from "url";
|
|
8
|
+
|
|
9
|
+
// src/lib/npm-provider.ts
|
|
8
10
|
import fs from "fs";
|
|
9
11
|
import path from "path";
|
|
10
|
-
import { randomBytes } from "crypto";
|
|
11
|
-
|
|
12
|
-
var
|
|
12
|
+
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
13
|
+
var REGISTRY_HOST = "registry.npmjs.org";
|
|
14
|
+
var ACCEPT = "application/vnd.npm.install-v1+json";
|
|
15
|
+
var MAX_METADATA_BYTES = 4 * 1024 * 1024;
|
|
16
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
17
|
+
var STABLE_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
18
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
19
|
+
function isStableSemverVersion(value) {
|
|
20
|
+
return typeof value === "string" && STABLE_SEMVER.test(value);
|
|
21
|
+
}
|
|
22
|
+
function isCanonicalSha512Integrity(integrity) {
|
|
23
|
+
if (typeof integrity !== "string" || !integrity.startsWith("sha512-")) return false;
|
|
24
|
+
const b64 = integrity.slice("sha512-".length);
|
|
25
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64)) return false;
|
|
26
|
+
let bytes;
|
|
27
|
+
try {
|
|
28
|
+
bytes = Buffer.from(b64, "base64");
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return bytes.length === 64 && bytes.toString("base64") === b64;
|
|
33
|
+
}
|
|
13
34
|
function fail(message) {
|
|
35
|
+
throw new Error(`npm-provider: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function registryUrl(packageName) {
|
|
41
|
+
return `https://${REGISTRY_HOST}/${packageName}`;
|
|
42
|
+
}
|
|
43
|
+
function canonicalTarballUrl(packageName, version) {
|
|
44
|
+
const shortName = packageName.includes("/") ? packageName.slice(packageName.lastIndexOf("/") + 1) : packageName;
|
|
45
|
+
return `https://${REGISTRY_HOST}/${packageName}/-/${shortName}-${version}.tgz`;
|
|
46
|
+
}
|
|
47
|
+
async function readBoundedText(response) {
|
|
48
|
+
if (response.body === null) fail("missing response body");
|
|
49
|
+
const reader = response.body.getReader();
|
|
50
|
+
const chunks = [];
|
|
51
|
+
let total = 0;
|
|
52
|
+
try {
|
|
53
|
+
for (; ; ) {
|
|
54
|
+
const { done, value } = await reader.read();
|
|
55
|
+
if (done) break;
|
|
56
|
+
total += value.byteLength;
|
|
57
|
+
if (total >= MAX_METADATA_BYTES) {
|
|
58
|
+
await reader.cancel().catch(() => {
|
|
59
|
+
});
|
|
60
|
+
fail("metadata exceeds 4 MiB");
|
|
61
|
+
}
|
|
62
|
+
chunks.push(value);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
reader.releaseLock();
|
|
66
|
+
}
|
|
67
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
68
|
+
}
|
|
69
|
+
function assertCanonicalIntegrity(integrity) {
|
|
70
|
+
if (!isCanonicalSha512Integrity(integrity)) fail("invalid integrity (expected canonical sha512 SRI)");
|
|
71
|
+
}
|
|
72
|
+
async function resolveLatestNpmPackageRelease(packageName, fetchImpl) {
|
|
73
|
+
if (typeof packageName !== "string" || !PACKAGE_NAME.test(packageName)) {
|
|
74
|
+
fail("invalid package name");
|
|
75
|
+
}
|
|
76
|
+
const url = registryUrl(packageName);
|
|
77
|
+
let response;
|
|
78
|
+
try {
|
|
79
|
+
response = await fetchImpl(url, {
|
|
80
|
+
headers: { Accept: ACCEPT },
|
|
81
|
+
redirect: "error",
|
|
82
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
fail(`registry fetch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
|
+
}
|
|
87
|
+
if (!response.ok) fail(`registry responded ${response.status}`);
|
|
88
|
+
if (response.url !== url) fail(`unexpected response URL ${response.url}`);
|
|
89
|
+
const lengthHeader = response.headers.get("content-length");
|
|
90
|
+
if (lengthHeader !== null) {
|
|
91
|
+
const trimmed = lengthHeader.trim();
|
|
92
|
+
if (!/^\d+$/.test(trimmed)) fail("invalid content-length");
|
|
93
|
+
if (Number(trimmed) > MAX_METADATA_BYTES) fail("metadata exceeds 4 MiB");
|
|
94
|
+
}
|
|
95
|
+
const text = await readBoundedText(response);
|
|
96
|
+
let data;
|
|
97
|
+
try {
|
|
98
|
+
data = JSON.parse(text);
|
|
99
|
+
} catch {
|
|
100
|
+
fail("malformed packument JSON");
|
|
101
|
+
}
|
|
102
|
+
if (!isRecord(data) || data["name"] !== packageName) fail("malformed packument name");
|
|
103
|
+
const tags = data["dist-tags"];
|
|
104
|
+
if (!isRecord(tags) || typeof tags["latest"] !== "string") fail("missing dist-tags.latest");
|
|
105
|
+
const latest = tags["latest"];
|
|
106
|
+
if (!isStableSemverVersion(latest)) fail(`unstable or malformed version ${latest}`);
|
|
107
|
+
const versions = data["versions"];
|
|
108
|
+
if (!isRecord(versions)) fail("missing versions map");
|
|
109
|
+
const entry = versions[latest];
|
|
110
|
+
if (!isRecord(entry)) fail(`missing versions[${latest}]`);
|
|
111
|
+
if (entry["name"] !== packageName || entry["version"] !== latest) {
|
|
112
|
+
fail("version mismatch in versions entry");
|
|
113
|
+
}
|
|
114
|
+
if (entry["deprecated"] !== void 0) fail(`version ${latest} is deprecated`);
|
|
115
|
+
const dist = entry["dist"];
|
|
116
|
+
if (!isRecord(dist)) fail("missing dist");
|
|
117
|
+
const { tarball, integrity } = dist;
|
|
118
|
+
if (typeof tarball !== "string" || typeof integrity !== "string") {
|
|
119
|
+
fail("missing dist.tarball/integrity");
|
|
120
|
+
}
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = new URL(tarball);
|
|
124
|
+
} catch {
|
|
125
|
+
fail("foreign tarball URL");
|
|
126
|
+
}
|
|
127
|
+
if (parsed.protocol !== "https:" || parsed.host !== REGISTRY_HOST || tarball !== canonicalTarballUrl(packageName, latest)) {
|
|
128
|
+
fail("foreign tarball URL");
|
|
129
|
+
}
|
|
130
|
+
assertCanonicalIntegrity(integrity);
|
|
131
|
+
return { version: latest, tarballUrl: tarball, integrity };
|
|
132
|
+
}
|
|
133
|
+
var MAX_TARBALL_BYTES = 128 * 1024 * 1024;
|
|
134
|
+
var TARBALL_FETCH_TIMEOUT_MS = 12e4;
|
|
135
|
+
function expectedSha512Digest(integrity) {
|
|
136
|
+
assertCanonicalIntegrity(integrity);
|
|
137
|
+
return Buffer.from(integrity.slice("sha512-".length), "base64");
|
|
138
|
+
}
|
|
139
|
+
function assertCanonicalReleaseInput(packageName, release) {
|
|
140
|
+
if (!isRecord(release)) fail("invalid release");
|
|
141
|
+
const { version, tarballUrl, integrity } = release;
|
|
142
|
+
if (!isStableSemverVersion(version)) fail("invalid release version");
|
|
143
|
+
if (typeof tarballUrl !== "string") fail("foreign tarball URL");
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = new URL(tarballUrl);
|
|
147
|
+
} catch {
|
|
148
|
+
fail("foreign tarball URL");
|
|
149
|
+
}
|
|
150
|
+
if (parsed.protocol !== "https:" || parsed.host !== REGISTRY_HOST || tarballUrl !== canonicalTarballUrl(packageName, version)) {
|
|
151
|
+
fail("foreign tarball URL");
|
|
152
|
+
}
|
|
153
|
+
return { tarballUrl, expectedSha512: expectedSha512Digest(integrity) };
|
|
154
|
+
}
|
|
155
|
+
function readExistingArtifact(file) {
|
|
156
|
+
const stat = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
157
|
+
if (stat === void 0) return null;
|
|
158
|
+
if (stat.isSymbolicLink() || !stat.isFile()) fail("destination is not a regular file");
|
|
159
|
+
let fd;
|
|
160
|
+
try {
|
|
161
|
+
fd = fs.openSync(file, "r");
|
|
162
|
+
} catch {
|
|
163
|
+
fail("cannot read existing destination");
|
|
164
|
+
}
|
|
165
|
+
const sha256 = createHash("sha256");
|
|
166
|
+
const sha512 = createHash("sha512");
|
|
167
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
168
|
+
let bytes = 0;
|
|
169
|
+
try {
|
|
170
|
+
for (; ; ) {
|
|
171
|
+
const read = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
172
|
+
if (read === 0) break;
|
|
173
|
+
bytes += read;
|
|
174
|
+
if (bytes >= MAX_TARBALL_BYTES) fail("existing destination exceeds tarball bound");
|
|
175
|
+
sha256.update(buffer.subarray(0, read));
|
|
176
|
+
sha512.update(buffer.subarray(0, read));
|
|
177
|
+
}
|
|
178
|
+
} finally {
|
|
179
|
+
try {
|
|
180
|
+
fs.closeSync(fd);
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { bytes, sha256: sha256.digest("hex"), sha512: sha512.digest("hex") };
|
|
185
|
+
}
|
|
186
|
+
async function downloadVerifiedNpmPackageTarball(packageName, release, destination, fetchImpl) {
|
|
187
|
+
if (typeof packageName !== "string" || !PACKAGE_NAME.test(packageName)) {
|
|
188
|
+
fail("invalid package name");
|
|
189
|
+
}
|
|
190
|
+
const { tarballUrl, expectedSha512 } = assertCanonicalReleaseInput(packageName, release);
|
|
191
|
+
if (typeof destination !== "string" || destination === "") fail("invalid destination");
|
|
192
|
+
const existing = readExistingArtifact(destination);
|
|
193
|
+
if (existing !== null) {
|
|
194
|
+
const existing512 = Buffer.from(existing.sha512, "hex");
|
|
195
|
+
if (existing512.length === expectedSha512.length && timingSafeEqual(existing512, expectedSha512)) {
|
|
196
|
+
return { path: destination, bytes: existing.bytes, sha256: existing.sha256, sha512: existing.sha512 };
|
|
197
|
+
}
|
|
198
|
+
fail("destination bytes do not match requested integrity");
|
|
199
|
+
}
|
|
200
|
+
let response;
|
|
201
|
+
try {
|
|
202
|
+
response = await fetchImpl(tarballUrl, {
|
|
203
|
+
headers: { accept: "application/octet-stream" },
|
|
204
|
+
redirect: "error",
|
|
205
|
+
signal: AbortSignal.timeout(TARBALL_FETCH_TIMEOUT_MS)
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
fail(`tarball fetch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
}
|
|
210
|
+
if (!response.ok) fail(`tarball responded ${response.status}`);
|
|
211
|
+
if (response.url !== tarballUrl) fail(`unexpected tarball response URL ${response.url}`);
|
|
212
|
+
const declared = response.headers.get("content-length");
|
|
213
|
+
if (declared !== null) {
|
|
214
|
+
const trimmed = declared.trim();
|
|
215
|
+
if (!/^\d+$/.test(trimmed)) fail("invalid tarball content-length");
|
|
216
|
+
if (Number(trimmed) > MAX_TARBALL_BYTES) fail("tarball exceeds 128 MiB");
|
|
217
|
+
}
|
|
218
|
+
if (response.body === null) fail("missing tarball body");
|
|
219
|
+
const tempPath = path.join(
|
|
220
|
+
path.dirname(destination),
|
|
221
|
+
`npm-tarball-partial-${process.pid}-${randomBytes(8).toString("hex")}`
|
|
222
|
+
);
|
|
223
|
+
let fd;
|
|
224
|
+
try {
|
|
225
|
+
fd = fs.openSync(tempPath, "wx", 384);
|
|
226
|
+
} catch {
|
|
227
|
+
fail("cannot stage tarball temp");
|
|
228
|
+
}
|
|
229
|
+
let fdClosed = false;
|
|
230
|
+
const closeFd = () => {
|
|
231
|
+
if (fdClosed) return;
|
|
232
|
+
fdClosed = true;
|
|
233
|
+
try {
|
|
234
|
+
fs.closeSync(fd);
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const discardStaged = () => {
|
|
239
|
+
closeFd();
|
|
240
|
+
try {
|
|
241
|
+
fs.rmSync(tempPath, { force: true });
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
try {
|
|
246
|
+
const reader = response.body.getReader();
|
|
247
|
+
const sha256 = createHash("sha256");
|
|
248
|
+
const sha512 = createHash("sha512");
|
|
249
|
+
let bytes = 0;
|
|
250
|
+
try {
|
|
251
|
+
for (; ; ) {
|
|
252
|
+
const { done, value } = await reader.read();
|
|
253
|
+
if (done) break;
|
|
254
|
+
bytes += value.byteLength;
|
|
255
|
+
if (bytes + value.byteLength >= MAX_TARBALL_BYTES) {
|
|
256
|
+
await reader.cancel().catch(() => {
|
|
257
|
+
});
|
|
258
|
+
fail("tarball exceeds 128 MiB");
|
|
259
|
+
}
|
|
260
|
+
sha256.update(value);
|
|
261
|
+
sha512.update(value);
|
|
262
|
+
let offset = 0;
|
|
263
|
+
while (offset < value.byteLength) {
|
|
264
|
+
offset += fs.writeSync(fd, value, offset, value.byteLength - offset);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
reader.releaseLock();
|
|
269
|
+
}
|
|
270
|
+
closeFd();
|
|
271
|
+
const actual512 = sha512.digest();
|
|
272
|
+
if (!timingSafeEqual(actual512, expectedSha512)) fail("tarball integrity mismatch");
|
|
273
|
+
try {
|
|
274
|
+
fs.linkSync(tempPath, destination);
|
|
275
|
+
} catch {
|
|
276
|
+
fail("tarball publish raced with existing destination");
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
fs.rmSync(tempPath, { force: true });
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
return { path: destination, bytes, sha256: sha256.digest("hex"), sha512: actual512.toString("hex") };
|
|
283
|
+
} catch (error) {
|
|
284
|
+
discardStaged();
|
|
285
|
+
if (error instanceof Error && error.message.startsWith("npm-provider: ")) throw error;
|
|
286
|
+
fail(`tarball download failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/lib/pi-release-resolver.ts
|
|
291
|
+
var MAX_METADATA_BYTES2 = 4 * 1024 * 1024;
|
|
292
|
+
async function resolveLatestPiRelease(fetchImpl) {
|
|
293
|
+
try {
|
|
294
|
+
return await resolveLatestNpmPackageRelease("jorgex-pi", fetchImpl);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof Error && error.message.startsWith("pi-release-resolver: ")) throw error;
|
|
297
|
+
if (error instanceof Error) {
|
|
298
|
+
const detail = error.message.startsWith("npm-provider: ") ? error.message.slice("npm-provider: ".length) : error.message;
|
|
299
|
+
throw new Error(`pi-release-resolver: ${detail}`);
|
|
300
|
+
}
|
|
301
|
+
throw new Error(`pi-release-resolver: ${String(error)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function downloadVerifiedPiTarball(release, destination, fetchImpl) {
|
|
305
|
+
try {
|
|
306
|
+
return await downloadVerifiedNpmPackageTarball("jorgex-pi", release, destination, fetchImpl);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (error instanceof Error && error.message.startsWith("pi-release-resolver: ")) throw error;
|
|
309
|
+
if (error instanceof Error) {
|
|
310
|
+
const detail = error.message.startsWith("npm-provider: ") ? error.message.slice("npm-provider: ".length) : error.message;
|
|
311
|
+
throw new Error(`pi-release-resolver: ${detail}`);
|
|
312
|
+
}
|
|
313
|
+
throw new Error(`pi-release-resolver: ${String(error)}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/lib/pi-ci-artifact.ts
|
|
318
|
+
var MAX_CANDIDATE_BYTES = 64 * 1024;
|
|
319
|
+
function fail2(message) {
|
|
14
320
|
throw new Error(`pi-ci-artifact: ${message}`);
|
|
15
321
|
}
|
|
16
322
|
function lstatOrNull(p) {
|
|
17
323
|
try {
|
|
18
|
-
return
|
|
324
|
+
return fs2.lstatSync(p);
|
|
19
325
|
} catch {
|
|
20
326
|
return null;
|
|
21
327
|
}
|
|
22
328
|
}
|
|
23
329
|
function assertAbsolute(label, value) {
|
|
24
|
-
if (typeof value !== "string" || value === "" || !
|
|
25
|
-
|
|
330
|
+
if (typeof value !== "string" || value === "" || !path2.isAbsolute(value)) {
|
|
331
|
+
fail2(`${label} must be a non-empty absolute path`);
|
|
26
332
|
}
|
|
27
|
-
return
|
|
333
|
+
return path2.resolve(value);
|
|
28
334
|
}
|
|
29
335
|
function assertParentRealDir(resolvedFile, label) {
|
|
30
|
-
const dir =
|
|
336
|
+
const dir = path2.dirname(resolvedFile);
|
|
31
337
|
const st = lstatOrNull(dir);
|
|
32
338
|
if (st === null || !st.isDirectory() || st.isSymbolicLink()) {
|
|
33
|
-
|
|
339
|
+
fail2(`${label} parent must be a real directory: ${dir}`);
|
|
34
340
|
}
|
|
35
341
|
return dir;
|
|
36
342
|
}
|
|
37
343
|
async function prepareObservedPiCiArtifact(input) {
|
|
38
344
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
39
|
-
|
|
345
|
+
fail2("input must be an object");
|
|
40
346
|
}
|
|
41
347
|
const { tarballPath, candidatePath, fetchImpl } = input;
|
|
42
348
|
if (typeof fetchImpl !== "function") {
|
|
43
|
-
|
|
349
|
+
fail2("fetchImpl must be a function");
|
|
44
350
|
}
|
|
45
351
|
const tarballResolved = assertAbsolute("tarballPath", tarballPath);
|
|
46
352
|
const candidateResolved = assertAbsolute("candidatePath", candidatePath);
|
|
47
353
|
if (tarballResolved === candidateResolved) {
|
|
48
|
-
|
|
354
|
+
fail2("tarballPath and candidatePath must differ");
|
|
49
355
|
}
|
|
50
356
|
const candidateDir = assertParentRealDir(candidateResolved, "candidatePath");
|
|
51
357
|
assertParentRealDir(tarballResolved, "tarballPath");
|
|
52
358
|
const candidateExisting = lstatOrNull(candidateResolved);
|
|
53
359
|
if (candidateExisting !== null) {
|
|
54
|
-
|
|
360
|
+
fail2(`candidate already exists: ${candidateResolved}`);
|
|
55
361
|
}
|
|
56
362
|
const tarballExisting = lstatOrNull(tarballResolved);
|
|
57
363
|
if (tarballExisting !== null && tarballExisting.isSymbolicLink()) {
|
|
58
|
-
|
|
364
|
+
fail2(`tarball path must not be a symlink: ${tarballResolved}`);
|
|
59
365
|
}
|
|
60
366
|
let release;
|
|
61
367
|
try {
|
|
@@ -64,7 +370,7 @@ async function prepareObservedPiCiArtifact(input) {
|
|
|
64
370
|
if (error instanceof Error && /^(pi-ci-artifact|pi-release-resolver): /.test(error.message)) {
|
|
65
371
|
throw error;
|
|
66
372
|
}
|
|
67
|
-
|
|
373
|
+
fail2(`provider release resolution failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
374
|
}
|
|
69
375
|
let artifact;
|
|
70
376
|
try {
|
|
@@ -77,7 +383,7 @@ async function prepareObservedPiCiArtifact(input) {
|
|
|
77
383
|
if (error instanceof Error && /^(pi-ci-artifact|pi-release-resolver): /.test(error.message)) {
|
|
78
384
|
throw error;
|
|
79
385
|
}
|
|
80
|
-
|
|
386
|
+
fail2(`verified tarball acquisition failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
387
|
}
|
|
82
388
|
const payload = {
|
|
83
389
|
version: release.version,
|
|
@@ -90,59 +396,59 @@ async function prepareObservedPiCiArtifact(input) {
|
|
|
90
396
|
const serialized = `${JSON.stringify(payload)}
|
|
91
397
|
`;
|
|
92
398
|
if (Buffer.byteLength(serialized, "utf8") >= MAX_CANDIDATE_BYTES) {
|
|
93
|
-
|
|
399
|
+
fail2("candidate JSON exceeds 64 KiB");
|
|
94
400
|
}
|
|
95
|
-
const tempPath =
|
|
401
|
+
const tempPath = path2.join(
|
|
96
402
|
candidateDir,
|
|
97
|
-
`pi-ci-candidate-partial-${process.pid}-${
|
|
403
|
+
`pi-ci-candidate-partial-${process.pid}-${randomBytes2(8).toString("hex")}.json`
|
|
98
404
|
);
|
|
99
405
|
let fd;
|
|
100
406
|
try {
|
|
101
|
-
fd =
|
|
407
|
+
fd = fs2.openSync(tempPath, "wx", 384);
|
|
102
408
|
} catch {
|
|
103
|
-
|
|
409
|
+
fail2(`cannot stage candidate temp: ${tempPath}`);
|
|
104
410
|
}
|
|
105
411
|
let fdClosed = false;
|
|
106
412
|
const closeFd = () => {
|
|
107
413
|
if (fdClosed) return;
|
|
108
414
|
fdClosed = true;
|
|
109
415
|
try {
|
|
110
|
-
|
|
416
|
+
fs2.closeSync(fd);
|
|
111
417
|
} catch {
|
|
112
418
|
}
|
|
113
419
|
};
|
|
114
420
|
const discardStaged = () => {
|
|
115
421
|
closeFd();
|
|
116
422
|
try {
|
|
117
|
-
|
|
423
|
+
fs2.rmSync(tempPath, { force: true });
|
|
118
424
|
} catch {
|
|
119
425
|
}
|
|
120
426
|
};
|
|
121
427
|
try {
|
|
122
428
|
try {
|
|
123
|
-
|
|
429
|
+
fs2.writeSync(fd, serialized, null, "utf8");
|
|
124
430
|
} catch {
|
|
125
|
-
|
|
431
|
+
fail2("cannot write candidate temp");
|
|
126
432
|
}
|
|
127
433
|
closeFd();
|
|
128
434
|
try {
|
|
129
|
-
|
|
435
|
+
fs2.chmodSync(tempPath, 384);
|
|
130
436
|
} catch {
|
|
131
|
-
|
|
437
|
+
fail2("cannot secure candidate temp");
|
|
132
438
|
}
|
|
133
439
|
try {
|
|
134
|
-
|
|
440
|
+
fs2.linkSync(tempPath, candidateResolved);
|
|
135
441
|
} catch {
|
|
136
|
-
|
|
442
|
+
fail2(`candidate already exists: ${candidateResolved}`);
|
|
137
443
|
}
|
|
138
444
|
try {
|
|
139
|
-
|
|
445
|
+
fs2.rmSync(tempPath, { force: true });
|
|
140
446
|
} catch {
|
|
141
447
|
}
|
|
142
448
|
} catch (error) {
|
|
143
449
|
discardStaged();
|
|
144
450
|
if (error instanceof Error && error.message.startsWith("pi-ci-artifact: ")) throw error;
|
|
145
|
-
|
|
451
|
+
fail2(`candidate publish failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
146
452
|
}
|
|
147
453
|
return payload;
|
|
148
454
|
}
|