jorgex-stack 1.9.62 → 1.9.64

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.
@@ -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
- import { pathToFileURL } from "url";
12
- var MAX_CANDIDATE_BYTES = 64 * 1024;
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 fs.lstatSync(p);
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 === "" || !path.isAbsolute(value)) {
25
- fail(`${label} must be a non-empty absolute path`);
330
+ if (typeof value !== "string" || value === "" || !path2.isAbsolute(value)) {
331
+ fail2(`${label} must be a non-empty absolute path`);
26
332
  }
27
- return path.resolve(value);
333
+ return path2.resolve(value);
28
334
  }
29
335
  function assertParentRealDir(resolvedFile, label) {
30
- const dir = path.dirname(resolvedFile);
336
+ const dir = path2.dirname(resolvedFile);
31
337
  const st = lstatOrNull(dir);
32
338
  if (st === null || !st.isDirectory() || st.isSymbolicLink()) {
33
- fail(`${label} parent must be a real directory: ${dir}`);
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
- fail("input must be an object");
345
+ fail2("input must be an object");
40
346
  }
41
347
  const { tarballPath, candidatePath, fetchImpl } = input;
42
348
  if (typeof fetchImpl !== "function") {
43
- fail("fetchImpl must be a function");
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
- fail("tarballPath and candidatePath must differ");
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
- fail(`candidate already exists: ${candidateResolved}`);
360
+ fail2(`candidate already exists: ${candidateResolved}`);
55
361
  }
56
362
  const tarballExisting = lstatOrNull(tarballResolved);
57
363
  if (tarballExisting !== null && tarballExisting.isSymbolicLink()) {
58
- fail(`tarball path must not be a symlink: ${tarballResolved}`);
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
- fail(`provider release resolution failed: ${error instanceof Error ? error.message : String(error)}`);
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
- fail(`verified tarball acquisition failed: ${error instanceof Error ? error.message : String(error)}`);
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
- fail("candidate JSON exceeds 64 KiB");
399
+ fail2("candidate JSON exceeds 64 KiB");
94
400
  }
95
- const tempPath = path.join(
401
+ const tempPath = path2.join(
96
402
  candidateDir,
97
- `pi-ci-candidate-partial-${process.pid}-${randomBytes(8).toString("hex")}.json`
403
+ `pi-ci-candidate-partial-${process.pid}-${randomBytes2(8).toString("hex")}.json`
98
404
  );
99
405
  let fd;
100
406
  try {
101
- fd = fs.openSync(tempPath, "wx", 384);
407
+ fd = fs2.openSync(tempPath, "wx", 384);
102
408
  } catch {
103
- fail(`cannot stage candidate temp: ${tempPath}`);
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
- fs.closeSync(fd);
416
+ fs2.closeSync(fd);
111
417
  } catch {
112
418
  }
113
419
  };
114
420
  const discardStaged = () => {
115
421
  closeFd();
116
422
  try {
117
- fs.rmSync(tempPath, { force: true });
423
+ fs2.rmSync(tempPath, { force: true });
118
424
  } catch {
119
425
  }
120
426
  };
121
427
  try {
122
428
  try {
123
- fs.writeSync(fd, serialized, null, "utf8");
429
+ fs2.writeSync(fd, serialized, null, "utf8");
124
430
  } catch {
125
- fail("cannot write candidate temp");
431
+ fail2("cannot write candidate temp");
126
432
  }
127
433
  closeFd();
128
434
  try {
129
- fs.chmodSync(tempPath, 384);
435
+ fs2.chmodSync(tempPath, 384);
130
436
  } catch {
131
- fail("cannot secure candidate temp");
437
+ fail2("cannot secure candidate temp");
132
438
  }
133
439
  try {
134
- fs.linkSync(tempPath, candidateResolved);
440
+ fs2.linkSync(tempPath, candidateResolved);
135
441
  } catch {
136
- fail(`candidate already exists: ${candidateResolved}`);
442
+ fail2(`candidate already exists: ${candidateResolved}`);
137
443
  }
138
444
  try {
139
- fs.rmSync(tempPath, { force: true });
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
- fail(`candidate publish failed: ${error instanceof Error ? error.message : String(error)}`);
451
+ fail2(`candidate publish failed: ${error instanceof Error ? error.message : String(error)}`);
146
452
  }
147
453
  return payload;
148
454
  }