aontu 0.63.0 → 0.65.0

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.
Files changed (101) hide show
  1. package/README.md +8 -8
  2. package/dist/agentsmd.js +3 -3
  3. package/dist/agentsmd.js.map +1 -1
  4. package/dist/aontu.d.ts +3 -3
  5. package/dist/aontu.js +4 -6
  6. package/dist/aontu.js.map +1 -1
  7. package/dist/aontumodel.js +6 -12
  8. package/dist/aontumodel.js.map +1 -1
  9. package/dist/casing.d.ts +5 -0
  10. package/dist/casing.js +90 -0
  11. package/dist/casing.js.map +1 -0
  12. package/dist/cli.d.ts +16 -3
  13. package/dist/cli.js +653 -455
  14. package/dist/cli.js.map +1 -1
  15. package/dist/compat.d.ts +7 -0
  16. package/dist/compat.js +113 -0
  17. package/dist/compat.js.map +1 -0
  18. package/dist/helpdoc.js +5 -5
  19. package/dist/helpdoc.js.map +1 -1
  20. package/dist/hints.js +14 -5
  21. package/dist/hints.js.map +1 -1
  22. package/dist/lang.d.ts +1 -0
  23. package/dist/lang.js +8 -0
  24. package/dist/lang.js.map +1 -1
  25. package/dist/lsp.d.ts +1 -1
  26. package/dist/mcp.js +0 -36
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/mod.d.ts +18 -3
  29. package/dist/mod.js +117 -46
  30. package/dist/mod.js.map +1 -1
  31. package/dist/pkg-net.d.ts +227 -0
  32. package/dist/pkg-net.js +1598 -0
  33. package/dist/pkg-net.js.map +1 -0
  34. package/dist/pkg-zip.d.ts +8 -0
  35. package/dist/pkg-zip.js +196 -0
  36. package/dist/pkg-zip.js.map +1 -0
  37. package/dist/pkg.d.ts +159 -0
  38. package/dist/pkg.js +767 -0
  39. package/dist/pkg.js.map +1 -0
  40. package/dist/profile.d.ts +9 -0
  41. package/dist/profile.js +28 -0
  42. package/dist/profile.js.map +1 -0
  43. package/dist/report-sarif.js +1 -1
  44. package/dist/report-sarif.js.map +1 -1
  45. package/dist/subsume.d.ts +3 -1
  46. package/dist/subsume.js +2 -0
  47. package/dist/subsume.js.map +1 -1
  48. package/dist/trace.d.ts +21 -0
  49. package/dist/trace.js +107 -0
  50. package/dist/trace.js.map +1 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/dist/unify.js +1 -6
  53. package/dist/unify.js.map +1 -1
  54. package/dist/val/CmpFuncVal.d.ts +2 -0
  55. package/dist/val/CmpFuncVal.js +47 -13
  56. package/dist/val/CmpFuncVal.js.map +1 -1
  57. package/dist/val/NomFuncVal.js +11 -13
  58. package/dist/val/NomFuncVal.js.map +1 -1
  59. package/dist/val/ReferFuncVal.js +3 -0
  60. package/dist/val/ReferFuncVal.js.map +1 -1
  61. package/package.json +1 -1
  62. package/skill/SKILL.md +3 -3
  63. package/skill/error-codes.md +4 -4
  64. package/skill/examples.md +4 -4
  65. package/skill/grammar-card.md +1 -1
  66. package/skill/init/check.sh +2 -2
  67. package/skill/tasks.md +14 -12
  68. package/src/agentsmd.ts +3 -3
  69. package/src/aontu.ts +3 -8
  70. package/src/aontumodel.ts +6 -12
  71. package/src/casing.ts +95 -0
  72. package/src/cli.ts +758 -495
  73. package/src/compat.ts +148 -0
  74. package/src/helpdoc.ts +5 -5
  75. package/src/hints.ts +14 -7
  76. package/src/lang.ts +9 -1
  77. package/src/mcp.ts +0 -39
  78. package/src/mod.ts +142 -51
  79. package/src/pkg-net.ts +1945 -0
  80. package/src/pkg-zip.ts +228 -0
  81. package/src/pkg.ts +1018 -0
  82. package/src/profile.ts +42 -0
  83. package/src/report-sarif.ts +1 -1
  84. package/src/subsume.ts +2 -2
  85. package/src/trace.ts +157 -0
  86. package/src/unify.ts +1 -6
  87. package/src/val/CmpFuncVal.ts +66 -20
  88. package/src/val/NomFuncVal.ts +3 -5
  89. package/src/val/ReferFuncVal.ts +4 -0
  90. package/dist/lower.d.ts +0 -23
  91. package/dist/lower.js +0 -528
  92. package/dist/lower.js.map +0 -1
  93. package/dist/mod-tool.d.ts +0 -58
  94. package/dist/mod-tool.js +0 -403
  95. package/dist/mod-tool.js.map +0 -1
  96. package/dist/render.d.ts +0 -53
  97. package/dist/render.js +0 -432
  98. package/dist/render.js.map +0 -1
  99. package/src/lower.ts +0 -586
  100. package/src/mod-tool.ts +0 -561
  101. package/src/render.ts +0 -580
@@ -0,0 +1,1598 @@
1
+ "use strict";
2
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.relPathError = exports.PkgRefusal = exports.SIGNATURE_ENCODING = exports.ARCHIVE_MAX_FILE_BYTES = exports.ARCHIVE_MAX_FILES = exports.ARCHIVE_MAX_UNPACKED = exports.ARCHIVE_MAX_BYTES = exports.LIMITS = exports.CLOSURE_MAX = exports.COOLDOWN_HOURS = exports.PUBLISH_PATH = exports.DEFAULT_WRITE = exports.DEFAULT_BASE = void 0;
5
+ exports.isLoopback = isLoopback;
6
+ exports.baseAdmitted = baseAdmitted;
7
+ exports.repoConfig = repoConfig;
8
+ exports.patternMatches = patternMatches;
9
+ exports.trustEntryFor = trustEntryFor;
10
+ exports.isPrivateName = isPrivateName;
11
+ exports.pkgUrlPath = pkgUrlPath;
12
+ exports.objectPath = objectPath;
13
+ exports.timestamp = timestamp;
14
+ exports.keyIdOf = keyIdOf;
15
+ exports.keyIdFromPem = keyIdFromPem;
16
+ exports.keygen = keygen;
17
+ exports.signDigest = signDigest;
18
+ exports.smallOrderKey = smallOrderKey;
19
+ exports.verifyKeyProof = verifyKeyProof;
20
+ exports.packagePath = packagePath;
21
+ exports.manifestError = manifestError;
22
+ exports.acquire = acquire;
23
+ exports.pkgSync = pkgSync;
24
+ exports.editDeps = editDeps;
25
+ exports.parsePkgSpec = parsePkgSpec;
26
+ exports.pkgGet = pkgGet;
27
+ exports.pkgRemove = pkgRemove;
28
+ exports.pkgWhy = pkgWhy;
29
+ exports.writeLayout = writeLayout;
30
+ exports.dirHttp = dirHttp;
31
+ exports.publisherFromToken = publisherFromToken;
32
+ exports.pkgPublish = pkgPublish;
33
+ exports.pkgOutdated = pkgOutdated;
34
+ exports.objectShape = objectShape;
35
+ exports.objectMutable = objectMutable;
36
+ exports.serveObject = serveObject;
37
+ exports.startServe = startServe;
38
+ exports.servedUrl = servedUrl;
39
+ exports.splitListen = splitListen;
40
+ exports.readBounded = readBounded;
41
+ exports.defaultHttp = defaultHttp;
42
+ // THE CLIENT HALF OF THE PACKAGE REPOSITORY (aontu-lang/system, spec/):
43
+ // acquisition, sync, publication and the local registry, over one
44
+ // injected transport. Every decision is above the seam; the adapter
45
+ // below it is the platform's fetch and nothing else.
46
+ const node_crypto_1 = require("node:crypto");
47
+ const node_fs_1 = require("node:fs");
48
+ const node_http_1 = require("node:http");
49
+ const node_path_1 = require("node:path");
50
+ const mod_1 = require("./mod");
51
+ const pkg_1 = require("./pkg");
52
+ Object.defineProperty(exports, "relPathError", { enumerable: true, get: function () { return pkg_1.relPathError; } });
53
+ const pkg_zip_1 = require("./pkg-zip");
54
+ exports.DEFAULT_BASE = 'https://pkg.aontu.dev';
55
+ exports.DEFAULT_WRITE = 'https://publish.aontu.dev';
56
+ exports.PUBLISH_PATH = '/v1/publish';
57
+ exports.COOLDOWN_HOURS = 72;
58
+ exports.CLOSURE_MAX = 1024;
59
+ // The closure bounds, as a record so a test can lower them.
60
+ exports.LIMITS = { depth: mod_1.MODULE_MAX_DEPTH, closure: exports.CLOSURE_MAX };
61
+ exports.ARCHIVE_MAX_BYTES = pkg_1.ARCHIVE_LIMITS.bytes;
62
+ exports.ARCHIVE_MAX_UNPACKED = pkg_1.ARCHIVE_LIMITS.unpacked;
63
+ exports.ARCHIVE_MAX_FILES = pkg_1.ARCHIVE_LIMITS.files;
64
+ exports.ARCHIVE_MAX_FILE_BYTES = pkg_1.ARCHIVE_LIMITS.fileBytes;
65
+ exports.SIGNATURE_ENCODING = 'aontu-signature/v1';
66
+ const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
67
+ const CANON_RE = /^aon1-[A-Za-z0-9_-]{43}$/;
68
+ const KEY_ID_RE = /^ed25519:[A-Za-z0-9_-]{43}$/;
69
+ const SIG_RE = /^[A-Za-z0-9_-]{86}$/;
70
+ const PATTERN_RE = /^\*$|^[a-z0-9.-]+$|^[a-z0-9.-]+\/[A-Za-z0-9._/-]+$|^[a-z0-9.-]+\/\*$|^[a-z0-9.-]+\/[A-Za-z0-9._/-]+\/\*$/;
71
+ class PkgRefusal extends Error {
72
+ constructor(code, message, pkg) {
73
+ super(message);
74
+ this.code = code;
75
+ if (null != pkg) {
76
+ this.pkg = pkg;
77
+ }
78
+ }
79
+ }
80
+ exports.PkgRefusal = PkgRefusal;
81
+ function refuse(code, message, pkg) {
82
+ throw new PkgRefusal(code, message, pkg);
83
+ }
84
+ function isLoopback(url) {
85
+ let u;
86
+ try {
87
+ u = new URL(url);
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ return 'http:' === u.protocol &&
93
+ ('127.0.0.1' === u.hostname || 'localhost' === u.hostname || '[::1]' === u.hostname);
94
+ }
95
+ // A base is https, or http on a loopback host (ADR-039 part 10).
96
+ function baseAdmitted(url) {
97
+ let u;
98
+ try {
99
+ u = new URL(url);
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ return 'https:' === u.protocol || isLoopback(url);
105
+ }
106
+ // The project's trust configuration: `repo` in its package file, with
107
+ // the defaults where it is silent, and the command line over both.
108
+ function repoConfig(root, options, overrides = {}) {
109
+ const file = (0, node_path_1.join)(root, mod_1.PKG_FILE);
110
+ const gen = (0, node_fs_1.existsSync)(file) ?
111
+ options.eval((0, node_fs_1.readFileSync)(file, 'utf8'), file).gen : undefined;
112
+ const repo = gen?.repo ?? {};
113
+ const strs = (v) => Array.isArray(v) ? v.filter((s) => 'string' === typeof s) : [];
114
+ const trust = {
115
+ '*': { signer: 'forge', inclusion: 'required' },
116
+ };
117
+ const declared = repo.trust;
118
+ if (null != declared && 'object' === typeof declared) {
119
+ for (const pattern of Object.keys(declared)) {
120
+ const e = declared[pattern];
121
+ if (!PATTERN_RE.test(pattern) || null == e || 'object' !== typeof e) {
122
+ refuse('config_invalid', 'repo.trust: ' + pattern + ' is not a pattern');
123
+ }
124
+ const signer = 'string' === typeof e.signer ? e.signer : '';
125
+ if ('forge' !== signer && !KEY_ID_RE.test(signer)) {
126
+ refuse('config_invalid', 'repo.trust: ' + pattern + ' names no signer (forge, or ed25519:<key>)');
127
+ }
128
+ trust[pattern] = {
129
+ signer, inclusion: 'none' === e.inclusion ? 'none' : 'required',
130
+ };
131
+ }
132
+ }
133
+ const priv = strs(repo.private);
134
+ for (const p of priv) {
135
+ if (!PATTERN_RE.test(p)) {
136
+ refuse('config_invalid', 'repo.private: ' + p + ' is not a pattern');
137
+ }
138
+ }
139
+ // A package the project itself marks private is on the list by that
140
+ // fact alone (spec private.prefix_list.derived).
141
+ const self = (0, pkg_1.packageSelf)(root, options);
142
+ if ('' !== self.path && 'private' === self.publish && !priv.includes(self.path)) {
143
+ priv.push(self.path);
144
+ }
145
+ const base = overrides.base ?? (0 < strs(repo.base).length ? strs(repo.base) : [exports.DEFAULT_BASE]);
146
+ const write = overrides.write ?? ('string' === typeof repo.write ? repo.write : exports.DEFAULT_WRITE);
147
+ for (const b of [...base, write, ...strs(repo.private_base)]) {
148
+ if (!baseAdmitted(b)) {
149
+ refuse('base_not_https', 'repository base is not https: ' + b);
150
+ }
151
+ }
152
+ return { base, write, private: priv, privateBase: strs(repo.private_base), trust };
153
+ }
154
+ // A pattern is a path, a path with `/*` beneath it, or `*` alone.
155
+ function patternMatches(pattern, pkg) {
156
+ if ('*' === pattern) {
157
+ return true;
158
+ }
159
+ if (pattern.endsWith('/*')) {
160
+ const prefix = pattern.slice(0, -2);
161
+ return pkg === prefix || pkg.startsWith(prefix + '/');
162
+ }
163
+ return pattern === pkg;
164
+ }
165
+ // The most specific entry: an exact path, then the longest prefix,
166
+ // then the default.
167
+ function trustEntryFor(config, pkg) {
168
+ let best = '*';
169
+ for (const pattern of Object.keys(config.trust)) {
170
+ if (!patternMatches(pattern, pkg)) {
171
+ continue;
172
+ }
173
+ if (pattern === pkg || (best !== pkg && pattern.length > best.length)) {
174
+ best = pattern;
175
+ }
176
+ }
177
+ return config.trust[best];
178
+ }
179
+ function isPrivateName(config, pkg) {
180
+ return config.private.some((p) => patternMatches(p, pkg));
181
+ }
182
+ // The read-path layout (spec layout.objects).
183
+ function pkgUrlPath(pkg) {
184
+ return pkg.split('/').map(mod_1.escapeElem).join('/');
185
+ }
186
+ function objectPath(kind, pkg, version) {
187
+ const p = '/pkg/' + pkgUrlPath(pkg);
188
+ switch (kind) {
189
+ case 'list': return p + '/@v/list';
190
+ case 'latest': return p + '/@latest';
191
+ case 'archive': return p + '/@v/' + version + '.zip';
192
+ case 'manifest': return p + '/@v/' + version + '.manifest';
193
+ case 'signature': return p + '/@v/' + version + '.sig';
194
+ case 'sigstore': return p + '/@v/' + version + '.sigstore.json';
195
+ case 'advisory': return '/advisory/' + pkgUrlPath(pkg) + '.aon';
196
+ default: return '/tombstone/' + pkgUrlPath(pkg) + '/@v/' + version + '.aon';
197
+ }
198
+ }
199
+ function timestamp(d) {
200
+ return d.toISOString().replace(/\.\d{3}Z$/, 'Z');
201
+ }
202
+ const utf8 = (s) => new Uint8Array(Buffer.from(s, 'utf8'));
203
+ const text = (b) => Buffer.from(b).toString('utf8');
204
+ function parseDoc(b) {
205
+ try {
206
+ return JSON.parse((0, mod_1.lockJson)(text(b)));
207
+ }
208
+ catch {
209
+ return undefined;
210
+ }
211
+ }
212
+ // One canonical line, the form every stored object takes.
213
+ function canonLine(obj, options, name) {
214
+ return utf8(options.eval(JSON.stringify(obj), name).canon + '\n');
215
+ }
216
+ // THE KEY PROVIDER (ADR-024 part 4): an Ed25519 key signs the manifest's
217
+ // digest, domain-separated by the encoding name.
218
+ const SPKI_ED25519_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
219
+ function signedBytes(over) {
220
+ return Buffer.from(exports.SIGNATURE_ENCODING + '\n' + over + '\n', 'utf8');
221
+ }
222
+ function keyIdOf(publicKeyDer) {
223
+ const raw = Buffer.from(publicKeyDer).subarray(publicKeyDer.length - 32);
224
+ return 'ed25519:' + raw.toString('base64url');
225
+ }
226
+ // The signing key is an Ed25519 private key and nothing else: another
227
+ // kind signs, and every consumer refuses the proof, so it is refused
228
+ // here first.
229
+ function signingKey(pem) {
230
+ let priv;
231
+ try {
232
+ priv = (0, node_crypto_1.createPrivateKey)(pem);
233
+ }
234
+ catch {
235
+ refuse('key_invalid', 'the key file is not a PEM private key', '');
236
+ }
237
+ if ('ed25519' !== priv.asymmetricKeyType) {
238
+ refuse('key_invalid', 'the key is ' + priv.asymmetricKeyType + ', not ed25519', '');
239
+ }
240
+ return priv;
241
+ }
242
+ function keyIdFromPem(pem) {
243
+ const der = (0, node_crypto_1.createPublicKey)(signingKey(pem)).export({ format: 'der', type: 'spki' });
244
+ return keyIdOf(new Uint8Array(der));
245
+ }
246
+ // `aontu pkg keygen`: a new signing key, written once. The answer is
247
+ // the signer id a consumer names, or the reason nothing was written.
248
+ function keygen(file) {
249
+ if ((0, node_fs_1.existsSync)(file)) {
250
+ return { refused: file + ' exists; a key is written once' };
251
+ }
252
+ const pem = (0, node_crypto_1.generateKeyPairSync)('ed25519').privateKey
253
+ .export({ format: 'pem', type: 'pkcs8' });
254
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(file), { recursive: true });
255
+ (0, node_fs_1.writeFileSync)(file, pem, { mode: 0o600 });
256
+ return { signer: keyIdFromPem(pem) };
257
+ }
258
+ function signDigest(pem, over) {
259
+ const priv = signingKey(pem);
260
+ const sig = (0, node_crypto_1.sign)(null, signedBytes(over), priv);
261
+ return {
262
+ kind: 'key',
263
+ encoding: exports.SIGNATURE_ENCODING,
264
+ over,
265
+ signer: keyIdFromPem(pem),
266
+ signature: Buffer.from(sig).toString('base64url'),
267
+ };
268
+ }
269
+ // The small-order points of the curve, by y with the sign bit cleared:
270
+ // a signature under one verifies for any message. The last entry is p,
271
+ // and every encoding at or above it is not canonical.
272
+ const SMALL_ORDER_Y = new Set([
273
+ '0000000000000000000000000000000000000000000000000000000000000000',
274
+ '0100000000000000000000000000000000000000000000000000000000000000',
275
+ '26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05',
276
+ 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',
277
+ 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
278
+ ]);
279
+ function smallOrderKey(raw) {
280
+ const y = Buffer.from(raw);
281
+ y[31] &= 0x7f;
282
+ const hex = y.toString('hex');
283
+ return SMALL_ORDER_Y.has(hex) || (hex.endsWith('ff'.repeat(30) + '7f') && 0xed <= y[0]);
284
+ }
285
+ // Base64url that decodes and re-encodes to itself: Buffer drops
286
+ // trailing bits silently, so a signature has one spelling here.
287
+ function canonicalBase64url(text, length) {
288
+ const bytes = Buffer.from(text, 'base64url');
289
+ return length === bytes.length && bytes.toString('base64url') === text ? bytes : undefined;
290
+ }
291
+ function verifyKeyProof(proof, over, signer) {
292
+ if (null == proof || 'key' !== proof.kind || exports.SIGNATURE_ENCODING !== proof.encoding ||
293
+ 'string' !== typeof proof.signature || !KEY_ID_RE.test(proof.signer ?? '')) {
294
+ return 'the proof is not an aontu-signature/v1 key proof';
295
+ }
296
+ if (proof.over !== over) {
297
+ return 'the proof signs ' + proof.over + ', not this manifest';
298
+ }
299
+ if (proof.signer !== signer) {
300
+ return 'signed by ' + proof.signer + '; the trust entry accepts ' + signer;
301
+ }
302
+ const raw = canonicalBase64url(proof.signer.slice('ed25519:'.length), 32);
303
+ const sig = SIG_RE.test(proof.signature) ? canonicalBase64url(proof.signature, 64) : undefined;
304
+ if (undefined === raw || undefined === sig) {
305
+ return 'the proof carries a malformed key or signature';
306
+ }
307
+ if (smallOrderKey(raw)) {
308
+ return 'the signer is a key of small order';
309
+ }
310
+ const key = (0, node_crypto_1.createPublicKey)({
311
+ key: Buffer.concat([SPKI_ED25519_PREFIX, raw]), format: 'der', type: 'spki',
312
+ });
313
+ return (0, node_crypto_1.verify)(null, signedBytes(over), key, sig) ?
314
+ undefined : 'the signature does not verify';
315
+ }
316
+ // A package path: domain-shaped, the element rules, never an alias.
317
+ function packagePath(s) {
318
+ return 'string' === typeof s && !(0, mod_1.isAlias)(s) && (0, pkg_1.usableKey)(s);
319
+ }
320
+ // T.Manifest, checked field by field: a served object is input.
321
+ function manifestError(m) {
322
+ if (null == m || 'object' !== typeof m || pkg_1.MANIFEST_SCHEMA !== m.schema) {
323
+ return 'schema is not ' + pkg_1.MANIFEST_SCHEMA;
324
+ }
325
+ if (!packagePath(m.package)) {
326
+ return 'package is not a package path';
327
+ }
328
+ if ('string' !== typeof m.version || !pkg_1.VERSION_RE.test(m.version)) {
329
+ return 'version is not MAJOR.MINOR.PATCH';
330
+ }
331
+ if ('public' !== m.publish && 'private' !== m.publish) {
332
+ return 'publish is not public or private';
333
+ }
334
+ const a = m.archive;
335
+ if (null == a || 'zip' !== a.format || !DIGEST_RE.test(a.digest ?? '') ||
336
+ !Number.isInteger(a.size) || !Array.isArray(a.files) || 0 === a.files.length) {
337
+ return 'archive is not a zip with a digest, a size and files';
338
+ }
339
+ for (const f of a.files) {
340
+ if ('string' !== typeof f?.path || !DIGEST_RE.test(f.digest ?? '') ||
341
+ !Number.isInteger(f.size) || undefined !== (0, pkg_1.relPathError)(f.path)) {
342
+ return 'archive.files names a file without a path, a digest and a size';
343
+ }
344
+ }
345
+ if (!Array.isArray(m.modules) || 1 !== m.modules.length ||
346
+ m.modules[0]?.path !== m.package || 'string' !== typeof m.modules[0]?.main ||
347
+ !CANON_RE.test(m.modules[0]?.canon ?? '')) {
348
+ return 'modules is not the one module at the package path with an entry and a canon-hash';
349
+ }
350
+ if (undefined !== (0, pkg_1.relPathError)(m.modules[0].main) ||
351
+ !a.files.some((f) => f.path === m.modules[0].main)) {
352
+ return 'modules names an entry the archive does not hold';
353
+ }
354
+ if (null == m.deps || 'object' !== typeof m.deps || Array.isArray(m.deps)) {
355
+ return 'deps is not a map';
356
+ }
357
+ for (const k of Object.keys(m.deps)) {
358
+ const d = m.deps[k];
359
+ if ('string' !== typeof d?.v || !pkg_1.VERSION_RE.test(d.v) ||
360
+ (null != d.pkg && !packagePath(d.pkg))) {
361
+ return 'deps.' + k + ' is not a minimum version';
362
+ }
363
+ }
364
+ if ('string' !== typeof m.published) {
365
+ return 'published is not a timestamp';
366
+ }
367
+ if (null != m.moved && !packagePath(m.moved)) {
368
+ return 'moved is not a package path';
369
+ }
370
+ if (null != m.retract && (!Array.isArray(m.retract) ||
371
+ m.retract.some((v) => 'string' !== typeof v || !pkg_1.VERSION_RE.test(v)))) {
372
+ return 'retract is not a list of versions';
373
+ }
374
+ return undefined;
375
+ }
376
+ async function getObject(ctx, bases, path) {
377
+ let last = { status: 0, body: new Uint8Array(), base: '' };
378
+ for (const base of bases) {
379
+ const r = await ctx.http.get(base.replace(/\/$/, '') + path);
380
+ if (200 === r.status) {
381
+ return { ...r, base };
382
+ }
383
+ last = { ...r, base };
384
+ }
385
+ return last;
386
+ }
387
+ function basesFor(ctx, pkg) {
388
+ if (!isPrivateName(ctx.config, pkg)) {
389
+ return ctx.config.base;
390
+ }
391
+ if (0 === ctx.config.privateBase.length) {
392
+ refuse('private_name_public_path', pkg + ' is on the private list and repo.private_base names no repository', pkg);
393
+ }
394
+ return ctx.config.privateBase;
395
+ }
396
+ async function fetchList(ctx, bases, pkg) {
397
+ const r = await getObject(ctx, bases, objectPath('list', pkg));
398
+ if (200 !== r.status) {
399
+ refuse('fetch_failed', 'no version list for ' + pkg +
400
+ (0 === r.status ? ' (no repository answered)' : ' (' + r.status + ' from ' + r.base + ')'), pkg);
401
+ }
402
+ const doc = parseDoc(r.body);
403
+ if (doc?.package !== pkg || !Array.isArray(doc?.versions)) {
404
+ refuse('response_mismatch', 'the version list served does not name ' + pkg, pkg);
405
+ }
406
+ const versions = [];
407
+ for (const e of doc.versions) {
408
+ if ('string' !== typeof e?.version || !pkg_1.VERSION_RE.test(e.version) ||
409
+ 'string' !== typeof e?.seen) {
410
+ refuse('response_mismatch', 'the version list for ' + pkg + ' is malformed', pkg);
411
+ }
412
+ versions.push({ version: e.version, seen: e.seen });
413
+ }
414
+ versions.sort((a, b) => (0, pkg_1.versionCompare)(a.version, b.version));
415
+ // Every version the list offers is a version this client has seen:
416
+ // its absence later is a rollback whichever version was taken.
417
+ if ('' !== ctx.cache) {
418
+ const subject = trustEntryFor(ctx.config, pkg).signer;
419
+ for (const e of versions) {
420
+ recordSeen(ctx, pkg, e.version, subject);
421
+ }
422
+ }
423
+ return versions;
424
+ }
425
+ async function fetchAdvisory(ctx, bases, pkg) {
426
+ const r = await getObject(ctx, bases, objectPath('advisory', pkg));
427
+ const out = {};
428
+ if (200 !== r.status) {
429
+ return out;
430
+ }
431
+ const doc = parseDoc(r.body);
432
+ for (const e of Array.isArray(doc?.retracted) ? doc.retracted : []) {
433
+ if ('string' === typeof e?.version && 'string' === typeof e?.by) {
434
+ out[e.version] = e.by;
435
+ }
436
+ }
437
+ return out;
438
+ }
439
+ // The versions this client has seen for a package, from its own
440
+ // records: a version absent from the list now is a rollback.
441
+ function seenVersions(ctx, pkg) {
442
+ const dir = (0, mod_1.cacheSeenDir)(ctx.cache, pkg);
443
+ if (!(0, node_fs_1.existsSync)(dir)) {
444
+ return [];
445
+ }
446
+ return (0, node_fs_1.readdirSync)(dir).filter((f) => f.endsWith('.aon'))
447
+ .map((f) => f.slice(0, -'.aon'.length)).sort(pkg_zip_1.cmpBytes);
448
+ }
449
+ function recordSeen(ctx, pkg, version, subject) {
450
+ const dir = (0, mod_1.cacheSeenDir)(ctx.cache, pkg);
451
+ const file = (0, node_path_1.join)(dir, version + '.aon');
452
+ if ((0, node_fs_1.existsSync)(file)) {
453
+ return;
454
+ }
455
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
456
+ (0, node_fs_1.writeFileSync)(file, canonLine({ package: pkg, version, seen: timestamp(ctx.now()), subject }, ctx.options, 'seen.aon'));
457
+ }
458
+ // Selection (spec acquire step 6): the newest version outside the
459
+ // cooldown, timed from the repository's first-seen time (ADR-039 part
460
+ // 9), not retracted. A version named explicitly is taken as it is.
461
+ function selectVersion(ctx, pkg, list, advisory, asked, fallback) {
462
+ if (undefined !== asked) {
463
+ return asked;
464
+ }
465
+ const priv = isPrivateName(ctx.config, pkg);
466
+ const now = ctx.now().getTime();
467
+ let held;
468
+ for (let i = list.length - 1; 0 <= i; i--) {
469
+ const e = list[i];
470
+ if (null != advisory[e.version]) {
471
+ continue;
472
+ }
473
+ const seen = Date.parse(e.seen);
474
+ const until = seen + exports.COOLDOWN_HOURS * 3600 * 1000;
475
+ if (!priv && (Number.isNaN(seen) || now < until)) {
476
+ held = held ?? e;
477
+ continue;
478
+ }
479
+ if (null != held) {
480
+ ctx.events.push({
481
+ code: 'cooldown_pending',
482
+ message: pkg + ' ' + held.version + ' is inside the cooldown until ' +
483
+ timestamp(new Date(Date.parse(held.seen) + exports.COOLDOWN_HOURS * 3600 * 1000)) +
484
+ '; ' + e.version + ' was selected',
485
+ });
486
+ }
487
+ return e.version;
488
+ }
489
+ const why = null == held ? pkg + ' has no selectable version' :
490
+ pkg + ' ' + held.version + ' is inside the cooldown until ' +
491
+ timestamp(new Date(Date.parse(held.seen) + exports.COOLDOWN_HOURS * 3600 * 1000)) +
492
+ ' and no earlier version is selectable';
493
+ if (undefined !== fallback) {
494
+ ctx.events.push({ code: 'cooldown_pending', message: why });
495
+ return fallback;
496
+ }
497
+ refuse(null == held ? 'fetch_failed' : 'cooldown_pending', why, pkg);
498
+ }
499
+ async function fetchManifest(ctx, bases, pkg, version) {
500
+ const cached = (0, node_path_1.join)((0, mod_1.cacheDownloadDir)(ctx.cache, pkg), version + '.manifest');
501
+ let bytes;
502
+ if ((0, node_fs_1.existsSync)(cached)) {
503
+ bytes = new Uint8Array((0, node_fs_1.readFileSync)(cached));
504
+ }
505
+ else {
506
+ const r = await getObject(ctx, bases, objectPath('manifest', pkg, version));
507
+ if (200 !== r.status) {
508
+ const t = await getObject(ctx, bases, objectPath('tombstone', pkg, version));
509
+ if (200 === t.status) {
510
+ const doc = parseDoc(t.body);
511
+ refuse('tombstoned', pkg + ' ' + version + ' was withdrawn by the repository' +
512
+ ('string' === typeof doc?.reason ? ' (' + doc.reason + ')' : ''), pkg);
513
+ }
514
+ refuse('fetch_failed', 'no manifest for ' + pkg + ' ' + version, pkg);
515
+ }
516
+ bytes = r.body;
517
+ }
518
+ const manifest = parseDoc(bytes);
519
+ const bad = manifestError(manifest);
520
+ if (undefined !== bad) {
521
+ refuse('manifest_invalid', 'the manifest for ' + pkg + ' ' + version + ': ' + bad, pkg);
522
+ }
523
+ if (manifest.package !== pkg || manifest.version !== version) {
524
+ refuse('response_mismatch', 'the manifest served names ' + manifest.package + ' ' +
525
+ manifest.version + ', not ' + pkg + ' ' + version, pkg);
526
+ }
527
+ return { bytes, manifest };
528
+ }
529
+ async function fetchProof(ctx, bases, pkg, version, entry, manifestDigest) {
530
+ if ('forge' === entry.signer) {
531
+ refuse('proof_signer_untrusted', 'the trust entry for ' + pkg +
532
+ ' names the forge signer, and this build verifies key proofs only;' +
533
+ ' name a key under repo.trust', pkg);
534
+ }
535
+ const cached = (0, node_path_1.join)((0, mod_1.cacheDownloadDir)(ctx.cache, pkg), version + '.sig');
536
+ let bytes;
537
+ if ((0, node_fs_1.existsSync)(cached)) {
538
+ bytes = new Uint8Array((0, node_fs_1.readFileSync)(cached));
539
+ }
540
+ else {
541
+ const r = await getObject(ctx, bases, objectPath('signature', pkg, version));
542
+ if (200 !== r.status) {
543
+ refuse('proof_missing', 'no proof is served for ' + pkg + ' ' + version, pkg);
544
+ }
545
+ bytes = r.body;
546
+ }
547
+ const bad = verifyKeyProof(parseDoc(bytes), manifestDigest, entry.signer);
548
+ if (undefined !== bad) {
549
+ refuse(bad.startsWith('signed by') ? 'proof_signer_untrusted' : 'proof_invalid', 'the proof for ' + pkg + ' ' + version + ': ' + bad, pkg);
550
+ }
551
+ if ('required' === entry.inclusion) {
552
+ refuse('inclusion_missing', 'the trust entry for ' + pkg +
553
+ ' requires log inclusion, which a key proof does not carry in this build;' +
554
+ ' set inclusion: none for a key signer', pkg);
555
+ }
556
+ return bytes;
557
+ }
558
+ async function fetchArchive(ctx, bases, pkg, version, manifest) {
559
+ const cached = (0, node_path_1.join)((0, mod_1.cacheDownloadDir)(ctx.cache, pkg), version + '.zip');
560
+ let bytes;
561
+ if ((0, node_fs_1.existsSync)(cached)) {
562
+ bytes = new Uint8Array((0, node_fs_1.readFileSync)(cached));
563
+ }
564
+ else {
565
+ const r = await getObject(ctx, bases, objectPath('archive', pkg, version));
566
+ if (200 !== r.status) {
567
+ refuse('fetch_failed', 'no archive for ' + pkg + ' ' + version, pkg);
568
+ }
569
+ bytes = r.body;
570
+ }
571
+ if (pkg_1.ARCHIVE_LIMITS.bytes < bytes.length) {
572
+ refuse('archive_too_large', 'the archive for ' + pkg + ' ' + version +
573
+ ' is over the compressed cap', pkg);
574
+ }
575
+ const digest = (0, pkg_zip_1.sha256Hex)(bytes);
576
+ if (digest !== manifest.archive.digest) {
577
+ refuse('archive_digest_mismatch', 'the archive for ' + pkg + ' ' + version +
578
+ ' is ' + digest + ', not ' + manifest.archive.digest, pkg);
579
+ }
580
+ return bytes;
581
+ }
582
+ // Unpack (spec acquire step 11): the entry rules the write path applies,
583
+ // applied again here, because only this protects against a hostile
584
+ // mirror; then every file against the manifest.
585
+ function unpack(pkg, version, zip, manifest) {
586
+ let entries;
587
+ try {
588
+ entries = (0, pkg_zip_1.unzipCanonical)(zip);
589
+ }
590
+ catch (e) {
591
+ refuse('archive_not_canonical', 'the archive for ' + pkg + ' ' + version + ': ' +
592
+ e.message, pkg);
593
+ }
594
+ if (pkg_1.ARCHIVE_LIMITS.files < entries.length) {
595
+ refuse('archive_too_many_files', 'the archive for ' + pkg + ' ' + version +
596
+ ' is over the file-count cap', pkg);
597
+ }
598
+ let total = 0;
599
+ const listed = new Map(manifest.archive.files.map((f) => [f.path, f]));
600
+ for (const e of entries) {
601
+ const bad = (0, pkg_1.relPathError)(e.path);
602
+ if (undefined !== bad) {
603
+ refuse('archive_path_invalid', 'the archive for ' + pkg + ' ' + version + ': ' +
604
+ bad + ' (' + e.path + ')', pkg);
605
+ }
606
+ if (!(0, pkg_1.archiveAdmits)(e.path)) {
607
+ refuse('archive_entry_forbidden', 'the archive for ' + pkg + ' ' + version +
608
+ ' carries ' + e.path + ', which the allowlist does not admit', pkg);
609
+ }
610
+ total += e.data.length;
611
+ if (pkg_1.ARCHIVE_LIMITS.fileBytes < e.data.length || pkg_1.ARCHIVE_LIMITS.unpacked < total) {
612
+ refuse('archive_bomb', 'the archive for ' + pkg + ' ' + version +
613
+ ' unpacks past the size cap', pkg);
614
+ }
615
+ const f = listed.get(e.path);
616
+ if (undefined === f || f.digest !== (0, pkg_zip_1.sha256Hex)(e.data) || f.size !== e.data.length) {
617
+ refuse('file_manifest_mismatch', 'the archive for ' + pkg + ' ' + version +
618
+ ' holds ' + e.path + ', which the manifest does not list as served', pkg);
619
+ }
620
+ listed.delete(e.path);
621
+ }
622
+ if (0 < listed.size) {
623
+ refuse('file_manifest_mismatch', 'the archive for ' + pkg + ' ' + version +
624
+ ' lacks ' + [...listed.keys()].sort(pkg_zip_1.cmpBytes)[0] + ', which the manifest lists', pkg);
625
+ }
626
+ return entries;
627
+ }
628
+ function writeTree(dir, entries) {
629
+ for (const e of entries) {
630
+ const full = (0, node_path_1.join)(dir, ...e.path.split('/'));
631
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(full), { recursive: true });
632
+ (0, node_fs_1.writeFileSync)(full, e.data);
633
+ }
634
+ }
635
+ // ACQUIRE (spec ops.acquire): one package, its deps first, the pins
636
+ // checked in order -- proof, bytes, meaning -- then recorded, cached
637
+ // and returned for the lock.
638
+ async function acquire(ctx, pkg, asked, depth) {
639
+ if (exports.LIMITS.depth <= depth) {
640
+ refuse('module_depth', 'the closure under ' + pkg + ' nests past ' + exports.LIMITS.depth, pkg);
641
+ }
642
+ if (exports.LIMITS.closure <= ctx.count) {
643
+ refuse('closure_too_large', 'the closure exceeds ' + exports.LIMITS.closure + ' packages', pkg);
644
+ }
645
+ const bases = basesFor(ctx, pkg);
646
+ const entry = trustEntryFor(ctx.config, pkg);
647
+ const list = await fetchList(ctx, bases, pkg);
648
+ // A version seen before and gone from the list is a rollback, unless
649
+ // the repository says why: a tombstone stands where it was.
650
+ for (const v of seenVersions(ctx, pkg)) {
651
+ if (!list.some((e) => e.version === v) &&
652
+ 200 !== (await getObject(ctx, bases, objectPath('tombstone', pkg, v))).status) {
653
+ refuse('list_rollback', pkg + ' ' + v + ' was seen before and is absent from the list', pkg);
654
+ }
655
+ }
656
+ const advisory = await fetchAdvisory(ctx, bases, pkg);
657
+ const version = selectVersion(ctx, pkg, list, advisory, asked);
658
+ const newest = list[list.length - 1]?.version;
659
+ if (undefined !== asked && !list.some((e) => e.version === asked)) {
660
+ const t = await getObject(ctx, bases, objectPath('tombstone', pkg, asked));
661
+ if (200 === t.status) {
662
+ const doc = parseDoc(t.body);
663
+ refuse('tombstoned', pkg + ' ' + asked + ' was withdrawn by the repository' +
664
+ ('string' === typeof doc?.reason ? ' (' + doc.reason + ')' : ''), pkg);
665
+ }
666
+ refuse('fetch_failed', pkg + ' ' + asked + ' is not in the version list', pkg);
667
+ }
668
+ const id = pkg + '@' + version;
669
+ const had = ctx.acquired[id];
670
+ if (undefined !== had) {
671
+ return had;
672
+ }
673
+ ctx.count++;
674
+ // The newest version speaks for the name: a move declared there
675
+ // refuses every version, and nothing follows it.
676
+ if (undefined !== newest && newest !== version) {
677
+ const top = await fetchManifest(ctx, bases, pkg, newest);
678
+ if (null != top.manifest.moved) {
679
+ refuse('module_moved', pkg + ' moved to ' + top.manifest.moved +
680
+ '; import that instead, nothing follows a move', pkg);
681
+ }
682
+ }
683
+ const { bytes: manifestBytes, manifest } = await fetchManifest(ctx, bases, pkg, version);
684
+ if (null != manifest.moved) {
685
+ refuse('module_moved', pkg + ' moved to ' + manifest.moved +
686
+ '; import that instead, nothing follows a move', pkg);
687
+ }
688
+ const manifestDigest = (0, pkg_zip_1.sha256Hex)(manifestBytes);
689
+ const proofBytes = await fetchProof(ctx, bases, pkg, version, entry, manifestDigest);
690
+ const zip = await fetchArchive(ctx, bases, pkg, version, manifest);
691
+ const entries = unpack(pkg, version, zip, manifest);
692
+ // Deps first, at the minima the manifest declares: the module is
693
+ // evaluated in its publisher's context, which is what its canon pins.
694
+ const deps = manifest.deps;
695
+ const pins = [];
696
+ for (const key of Object.keys(deps).sort(pkg_zip_1.cmpBytes)) {
697
+ const target = deps[key].pkg ?? key;
698
+ if ((0, mod_1.isAlias)(key) && null == deps[key].pkg) {
699
+ refuse('manifest_invalid', 'the manifest for ' + pkg + ' ' + version +
700
+ ' declares ' + key + ' without the package it names', pkg);
701
+ }
702
+ const dep = await acquire(ctx, target, deps[key].v, depth + 1);
703
+ pins.push({
704
+ key, v: dep.version, canon: dep.canon, archive: dep.archive,
705
+ manifest: dep.manifestDigest, ...((0, mod_1.isAlias)(key) ? { pkg: target } : {}),
706
+ });
707
+ for (const e of dep.closure) {
708
+ if (!pins.some((p) => p.key === e.key)) {
709
+ pins.push(e);
710
+ }
711
+ }
712
+ }
713
+ pins.sort((a, b) => (0, pkg_zip_1.cmpBytes)(a.key, b.key));
714
+ const tmp = (0, node_path_1.join)(ctx.cache, 'tmp', (0, node_crypto_1.randomBytes)(8).toString('hex'));
715
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(tmp, mod_1.META_DIR), { recursive: true });
716
+ writeTree(tmp, entries);
717
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmp, mod_1.META_DIR, 'manifest.aon'), manifestBytes);
718
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmp, mod_1.META_DIR, 'proof.aon'), proofBytes);
719
+ const self = (0, pkg_1.packageSelf)(tmp, ctx.options);
720
+ const mod = manifest.modules[0];
721
+ if (self.path !== pkg || self.version !== version || self.main !== mod.main) {
722
+ (0, node_fs_1.rmSync)(tmp, { recursive: true, force: true });
723
+ refuse('manifest_invalid', 'the package file inside ' + pkg + ' ' + version +
724
+ ' disagrees with the manifest', pkg);
725
+ }
726
+ // The store tree keeps its own lock: the closure it was verified
727
+ // against, so it verifies again from the cache alone. The vendored
728
+ // copy loses it, and resolves against the consumer's lock instead.
729
+ if (0 < pins.length) {
730
+ (0, pkg_1.writeLock)(tmp, pins, ctx.options);
731
+ }
732
+ // The entry is in the archive: the manifest named it among the files
733
+ // and every listed file was unpacked.
734
+ const main = (0, node_path_1.join)(tmp, mod.main);
735
+ const got = ctx.options.eval((0, node_fs_1.readFileSync)(main, 'utf8'), main);
736
+ if (!got.ok || got.hash !== mod.canon) {
737
+ (0, node_fs_1.rmSync)(tmp, { recursive: true, force: true });
738
+ refuse('module_integrity', pkg + ' ' + version + ' means ' +
739
+ (!got.ok ? 'nothing (it does not evaluate)' : got.hash) +
740
+ ', and the manifest pins ' + mod.canon, pkg);
741
+ }
742
+ const dir = (0, mod_1.cacheStoreDir)(ctx.cache, got.hash, pkg);
743
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
744
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dir), { recursive: true });
745
+ (0, node_fs_1.renameSync)(tmp, dir);
746
+ const down = (0, mod_1.cacheDownloadDir)(ctx.cache, pkg);
747
+ (0, node_fs_1.mkdirSync)(down, { recursive: true });
748
+ for (const [name, data] of [
749
+ [version + '.zip', zip], [version + '.manifest', manifestBytes],
750
+ [version + '.sig', proofBytes]
751
+ ]) {
752
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(down, name))) {
753
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(down, name), data);
754
+ }
755
+ }
756
+ recordSeen(ctx, pkg, version, entry.signer);
757
+ const out = {
758
+ pkg, version, canon: got.hash, archive: (0, pkg_zip_1.sha256Hex)(zip), manifestDigest,
759
+ deps, dir, closure: pins,
760
+ };
761
+ ctx.acquired[id] = out;
762
+ ctx.fetched.push(pkg + ' ' + version);
763
+ return out;
764
+ }
765
+ function emptySync() {
766
+ return {
767
+ verdict: 'ok', fetched: [], lock: [], vendored: [], missing: [], unevaluable: [],
768
+ forbidden: [], mismatched: [], unlocked: [], changes: [], events: [],
769
+ };
770
+ }
771
+ function refused(report, e) {
772
+ if (!(e instanceof PkgRefusal)) {
773
+ throw e;
774
+ }
775
+ report.verdict = 'refused';
776
+ report.refusal = { code: e.code, message: e.message, ...(null == e.pkg ? {} : { pkg: e.pkg }) };
777
+ return report;
778
+ }
779
+ function makeCtx(root, options, http, args) {
780
+ return {
781
+ options, http, config: repoConfig(root, options, args),
782
+ cache: options.cache,
783
+ now: args.now ?? (() => new Date()),
784
+ events: [], fetched: [], acquired: {}, count: 0,
785
+ };
786
+ }
787
+ // Where the project already holds a package at a version: its vendor
788
+ // tree, when the tree's own file agrees, else the cache, when the
789
+ // download tree has that version's manifest.
790
+ function heldAt(root, key, pkg, version, ctx) {
791
+ const vendored = (0, mod_1.moduleDir)((0, node_path_1.join)(root, mod_1.META_DIR, mod_1.VENDOR_DIR), key);
792
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(vendored, mod_1.PKG_FILE))) {
793
+ const self = (0, pkg_1.packageSelf)(vendored, ctx.options);
794
+ if (('' === self.version || self.version === version) &&
795
+ ('' === self.path || self.path === pkg)) {
796
+ return vendored;
797
+ }
798
+ }
799
+ const manifestFile = (0, node_path_1.join)((0, mod_1.cacheDownloadDir)(ctx.cache, pkg), version + '.manifest');
800
+ if ((0, node_fs_1.existsSync)(manifestFile)) {
801
+ const m = parseDoc(new Uint8Array((0, node_fs_1.readFileSync)(manifestFile)));
802
+ const canon = m?.modules?.[0]?.canon;
803
+ if ('string' === typeof canon) {
804
+ const dir = (0, mod_1.cacheStoreDir)(ctx.cache, canon, pkg);
805
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, mod_1.PKG_FILE))) {
806
+ return dir;
807
+ }
808
+ }
809
+ }
810
+ return undefined;
811
+ }
812
+ // SYNC (spec ops.sync): resolve by MVS, fetch what is missing, vendor,
813
+ // lock, verify. Idempotent, and `--frozen` refuses to change the lock.
814
+ async function pkgSync(root, options, http, args = {}) {
815
+ const report = emptySync();
816
+ let ctx;
817
+ try {
818
+ ctx = makeCtx(root, options, http, args);
819
+ }
820
+ catch (e) {
821
+ return refused(report, e);
822
+ }
823
+ const previous = (0, pkg_1.readLock)(root);
824
+ const selected = {};
825
+ const dirs = {};
826
+ const missing = [];
827
+ const pending = new Set();
828
+ const bid = (deps) => {
829
+ for (const key of Object.keys(deps)) {
830
+ const have = selected[key];
831
+ if (null == have || 0 > (0, pkg_1.versionCompare)(have.v, deps[key].v)) {
832
+ selected[key] = { ...have, ...deps[key] };
833
+ pending.add(key);
834
+ }
835
+ }
836
+ };
837
+ const target = (key) => (0, pkg_1.targetOf)(key, selected[key], previous[key]);
838
+ // Held packages are read before anything is fetched, so a held
839
+ // dependant's bid raises a version before that version is requested.
840
+ try {
841
+ bid((0, pkg_1.declaredDeps)((0, node_path_1.join)(root, mod_1.PKG_FILE), options));
842
+ for (; 0 < pending.size;) {
843
+ const keys = [...pending].sort(pkg_zip_1.cmpBytes);
844
+ let key = keys.find((k) => !(0, pkg_1.usableKey)(k) || '' === target(k));
845
+ if (undefined !== key) {
846
+ missing.push(key);
847
+ pending.delete(key);
848
+ continue;
849
+ }
850
+ let dir;
851
+ for (const k of keys) {
852
+ dir = heldAt(root, k, target(k), selected[k].v, ctx);
853
+ if (undefined !== dir) {
854
+ key = k;
855
+ break;
856
+ }
857
+ }
858
+ if (undefined === key) {
859
+ key = keys.find((k) => true !== args.frozen || previous[k]?.v === selected[k].v);
860
+ if (undefined === key) {
861
+ for (const k of keys) {
862
+ report.changes.push(k + ': ' + (previous[k]?.v ?? 'unlocked') + ' -> ' + selected[k].v);
863
+ }
864
+ report.verdict = 'frozen';
865
+ return report;
866
+ }
867
+ dir = (await acquire(ctx, target(key), selected[key].v, 0)).dir;
868
+ }
869
+ pending.delete(key);
870
+ dirs[key] = dir;
871
+ bid((0, pkg_1.declaredDeps)((0, node_path_1.join)(dir, mod_1.PKG_FILE), options));
872
+ }
873
+ }
874
+ catch (e) {
875
+ report.events = ctx.events;
876
+ report.fetched = ctx.fetched.sort(pkg_zip_1.cmpBytes);
877
+ return refused(report, e);
878
+ }
879
+ report.events = ctx.events;
880
+ report.fetched = ctx.fetched.sort(pkg_zip_1.cmpBytes);
881
+ // Materialise: the closure into the vendor tree, and nothing else
882
+ // left there.
883
+ const vendorRoot = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.VENDOR_DIR);
884
+ for (const key of Object.keys(dirs).sort(pkg_zip_1.cmpBytes)) {
885
+ const to = (0, mod_1.moduleDir)(vendorRoot, key);
886
+ if (dirs[key] !== to) {
887
+ (0, node_fs_1.rmSync)(to, { recursive: true, force: true });
888
+ (0, pkg_1.vendorCopy)(dirs[key], to);
889
+ }
890
+ report.vendored.push(key);
891
+ }
892
+ // Pruning waits for the lock to be writable: a frozen sync that
893
+ // refuses leaves the locked build whole.
894
+ const prune = () => {
895
+ for (const key of Object.keys(previous)) {
896
+ if (null == selected[key] && (0, pkg_1.usableKey)(key)) {
897
+ (0, node_fs_1.rmSync)((0, mod_1.moduleDir)(vendorRoot, key), { recursive: true, force: true });
898
+ pruneEmpty((0, node_path_1.dirname)((0, mod_1.moduleDir)(vendorRoot, key)), vendorRoot);
899
+ }
900
+ }
901
+ };
902
+ const resolved = (0, pkg_1.pkgResolve)(root, options);
903
+ report.lock = resolved.lock;
904
+ report.missing = [...new Set([...missing, ...resolved.missing])].sort(pkg_zip_1.cmpBytes);
905
+ report.unevaluable = resolved.unevaluable;
906
+ report.forbidden = resolved.forbidden;
907
+ if ('ok' !== resolved.verdict || 0 < report.missing.length) {
908
+ report.verdict = 0 < report.unevaluable.length || 0 < report.forbidden.length ?
909
+ 'error' : 'missing';
910
+ return report;
911
+ }
912
+ const lockFile = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.LOCK_FILE);
913
+ const before = (0, node_fs_1.existsSync)(lockFile) ? (0, node_fs_1.readFileSync)(lockFile, 'utf8') : '';
914
+ const after = (0, pkg_1.lockText)(resolved.lock, options);
915
+ if (true === args.frozen && before.split('\n')[before.startsWith('#') ? 1 : 0] !== after) {
916
+ for (const e of resolved.lock) {
917
+ const p = previous[e.key];
918
+ if (null == p || p.canon !== e.canon || p.archive !== e.archive ||
919
+ p.v !== e.v || p.manifest !== e.manifest) {
920
+ report.changes.push(e.key + ': ' + (null == p ? 'unlocked' : 'repinned'));
921
+ }
922
+ }
923
+ for (const key of Object.keys(previous)) {
924
+ if (!resolved.lock.some((e) => e.key === key)) {
925
+ report.changes.push(key + ': dropped');
926
+ }
927
+ }
928
+ report.verdict = 'frozen';
929
+ return report;
930
+ }
931
+ prune();
932
+ (0, pkg_1.writeLock)(root, resolved.lock, options);
933
+ const verify = (0, pkg_1.pkgVerify)(root, options);
934
+ report.mismatched = verify.mismatched;
935
+ report.unlocked = verify.unlocked;
936
+ report.verdict = verify.verdict;
937
+ return report;
938
+ }
939
+ function pruneEmpty(dir, stop) {
940
+ for (; dir.startsWith(stop) && (0, node_fs_1.existsSync)(dir) && 0 === (0, node_fs_1.readdirSync)(dir).length; dir = (0, node_path_1.dirname)(dir)) {
941
+ (0, node_fs_1.rmSync)(dir, { recursive: true });
942
+ }
943
+ }
944
+ function editDeps(root, edit, options) {
945
+ const file = (0, node_path_1.join)(root, mod_1.PKG_FILE);
946
+ const before = (0, node_fs_1.existsSync)(file) ? (0, node_fs_1.readFileSync)(file, 'utf8') : '';
947
+ let after;
948
+ if ('add' === edit.op) {
949
+ after = before + ('' === before || before.endsWith('\n') ? '' : '\n') +
950
+ 'dep: ' + JSON.stringify(edit.key) + ': { v: ' + JSON.stringify(edit.v) + ' }\n';
951
+ }
952
+ else {
953
+ const lines = before.split('\n');
954
+ const at = lines.findIndex((l) => l.includes(JSON.stringify(edit.key)));
955
+ if (0 > at) {
956
+ return edit.key + ' is not on one line of ' + mod_1.PKG_FILE + '; edit it by hand';
957
+ }
958
+ const line = lines[at];
959
+ if ((line.match(/\{/g) ?? []).length !== (line.match(/\}/g) ?? []).length) {
960
+ return edit.key + ' spans several lines of ' + mod_1.PKG_FILE + '; edit it by hand';
961
+ }
962
+ if ('raise' === edit.op) {
963
+ const re = /\bv\s*:\s*"[^"]*"/;
964
+ if (!re.test(line)) {
965
+ return edit.key + ' declares its version on another line of ' + mod_1.PKG_FILE +
966
+ '; edit it by hand';
967
+ }
968
+ lines[at] = line.replace(re, 'v: ' + JSON.stringify(edit.v));
969
+ }
970
+ else {
971
+ lines.splice(at, 1);
972
+ }
973
+ after = lines.join('\n');
974
+ }
975
+ (0, node_fs_1.writeFileSync)(file, after);
976
+ const deps = (0, pkg_1.declaredDeps)(file, options);
977
+ const held = options.eval(after, file).ok &&
978
+ ('remove' === edit.op ? null == deps[edit.key] : deps[edit.key]?.v === edit.v);
979
+ if (!held) {
980
+ (0, node_fs_1.writeFileSync)(file, before);
981
+ return 'the edit to ' + mod_1.PKG_FILE + ' did not take; edit it by hand';
982
+ }
983
+ return undefined;
984
+ }
985
+ // A `<pkg>[@<version>]` argument.
986
+ function parsePkgSpec(spec) {
987
+ const at = spec.indexOf('@');
988
+ const pkg = 0 > at ? spec : spec.slice(0, at);
989
+ const version = 0 > at ? undefined : spec.slice(at + 1);
990
+ if (!packagePath(pkg)) {
991
+ return 'not a package path: ' + spec;
992
+ }
993
+ if (undefined !== version && !pkg_1.VERSION_RE.test(version)) {
994
+ return 'not a version: ' + version + ' (MAJOR.MINOR.PATCH)';
995
+ }
996
+ return { pkg, ...(undefined === version ? {} : { version }) };
997
+ }
998
+ // `aontu add` and `aontu get`: a dependency declared or raised, then a
999
+ // sync. `add` refuses what is already declared and names `get`.
1000
+ async function pkgGet(root, options, http, spec, args) {
1001
+ const parsed = parsePkgSpec(spec);
1002
+ if ('string' === typeof parsed) {
1003
+ return parsed;
1004
+ }
1005
+ const { pkg } = parsed;
1006
+ const declared = (0, pkg_1.declaredDeps)((0, node_path_1.join)(root, mod_1.PKG_FILE), options);
1007
+ const have = declared[pkg];
1008
+ if ('add' === args.mode && null != have) {
1009
+ return pkg + ' is already a dependency at ' + have.v + ' (aontu get raises it)';
1010
+ }
1011
+ let version = parsed.version;
1012
+ let ctx;
1013
+ try {
1014
+ ctx = makeCtx(root, options, http, args);
1015
+ if (undefined === version) {
1016
+ const bases = basesFor(ctx, pkg);
1017
+ const list = await fetchList(ctx, bases, pkg);
1018
+ version = selectVersion(ctx, pkg, list, await fetchAdvisory(ctx, bases, pkg), undefined);
1019
+ }
1020
+ }
1021
+ catch (e) {
1022
+ const report = { ...refused(emptySync(), e), change: 'none' };
1023
+ report.events = ctx?.events ?? [];
1024
+ return report;
1025
+ }
1026
+ let change;
1027
+ const before = snapshot(root);
1028
+ if (null == have) {
1029
+ const bad = editDeps(root, { op: 'add', key: pkg, v: version }, options);
1030
+ if (undefined !== bad) {
1031
+ return bad;
1032
+ }
1033
+ change = 'added ' + pkg + ' ' + version;
1034
+ }
1035
+ else if (0 <= (0, pkg_1.versionCompare)(have.v, version)) {
1036
+ change = pkg + ' is at ' + have.v + ' already';
1037
+ }
1038
+ else {
1039
+ const bad = editDeps(root, { op: 'raise', key: pkg, v: version }, options);
1040
+ if (undefined !== bad) {
1041
+ return bad;
1042
+ }
1043
+ change = 'raised ' + pkg + ' ' + have.v + ' -> ' + version;
1044
+ }
1045
+ const sync = await pkgSync(root, options, http, { ...args, frozen: false });
1046
+ sync.events = [...ctx.events, ...sync.events];
1047
+ return { ...sync, change: settled(root, before, sync, change) };
1048
+ }
1049
+ function snapshot(root) {
1050
+ const snap = { pkgFile: (0, node_fs_1.readFileSync)((0, node_path_1.join)(root, mod_1.PKG_FILE), 'utf8') };
1051
+ const lockFile = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.LOCK_FILE);
1052
+ if ((0, node_fs_1.existsSync)(lockFile)) {
1053
+ snap.lock = (0, node_fs_1.readFileSync)(lockFile, 'utf8');
1054
+ }
1055
+ const vendorRoot = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.VENDOR_DIR);
1056
+ if ((0, node_fs_1.existsSync)(vendorRoot)) {
1057
+ snap.vendor = (0, node_path_1.join)(root, mod_1.META_DIR, 'tmp', (0, node_crypto_1.randomBytes)(8).toString('hex'));
1058
+ (0, pkg_1.copyTree)(vendorRoot, snap.vendor);
1059
+ }
1060
+ return snap;
1061
+ }
1062
+ // A change the sync could not carry is taken back, lock and vendor
1063
+ // tree included: neither verb leaves the project half-changed.
1064
+ function settled(root, before, sync, change) {
1065
+ const tmp = (0, node_path_1.join)(root, mod_1.META_DIR, 'tmp');
1066
+ if ('ok' === sync.verdict) {
1067
+ (0, node_fs_1.rmSync)(tmp, { recursive: true, force: true });
1068
+ return change;
1069
+ }
1070
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, mod_1.PKG_FILE), before.pkgFile);
1071
+ const lockFile = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.LOCK_FILE);
1072
+ if (undefined === before.lock) {
1073
+ (0, node_fs_1.rmSync)(lockFile, { force: true });
1074
+ }
1075
+ else {
1076
+ (0, node_fs_1.writeFileSync)(lockFile, before.lock);
1077
+ }
1078
+ const vendorRoot = (0, node_path_1.join)(root, mod_1.META_DIR, mod_1.VENDOR_DIR);
1079
+ (0, node_fs_1.rmSync)(vendorRoot, { recursive: true, force: true });
1080
+ if (undefined !== before.vendor) {
1081
+ (0, node_fs_1.renameSync)(before.vendor, vendorRoot);
1082
+ }
1083
+ (0, node_fs_1.rmSync)(tmp, { recursive: true, force: true });
1084
+ return 'none (' + change + ' was taken back)';
1085
+ }
1086
+ // `aontu remove`: the pair of `add`.
1087
+ async function pkgRemove(root, options, http, pkg, args) {
1088
+ const declared = (0, pkg_1.declaredDeps)((0, node_path_1.join)(root, mod_1.PKG_FILE), options);
1089
+ if (null == declared[pkg]) {
1090
+ return pkg + ' is not a dependency of this project';
1091
+ }
1092
+ const before = snapshot(root);
1093
+ const bad = editDeps(root, { op: 'remove', key: pkg }, options);
1094
+ if (undefined !== bad) {
1095
+ return bad;
1096
+ }
1097
+ const sync = await pkgSync(root, options, http, { ...args, frozen: false });
1098
+ return { ...sync, change: settled(root, before, sync, 'removed ' + pkg) };
1099
+ }
1100
+ // `aontu why`: every chain of dependencies from the project to a
1101
+ // package, read from the lock and the package files in the store.
1102
+ function pkgWhy(root, options, pkg) {
1103
+ const locked = (0, pkg_1.readLock)(root);
1104
+ const self = (0, pkg_1.packageSelf)(root, options);
1105
+ const rootKey = '' === self.path ? '.' : self.path;
1106
+ const edges = {
1107
+ [rootKey]: Object.keys((0, pkg_1.declaredDeps)((0, node_path_1.join)(root, mod_1.PKG_FILE), options)).sort(pkg_zip_1.cmpBytes),
1108
+ };
1109
+ for (const key of Object.keys(locked)) {
1110
+ const entry = locked[key];
1111
+ const dir = (0, pkg_1.usableKey)(key) ?
1112
+ storeDirOf(root, key, entry, options) : undefined;
1113
+ edges[key] = undefined === dir ? [] :
1114
+ Object.keys((0, pkg_1.declaredDeps)((0, node_path_1.join)(dir, mod_1.PKG_FILE), options)).sort(pkg_zip_1.cmpBytes);
1115
+ }
1116
+ const paths = [];
1117
+ const walk = (key, trail) => {
1118
+ if (key === pkg || ((0, mod_1.isAlias)(key) && locked[key]?.pkg === pkg)) {
1119
+ paths.push([...trail, key]);
1120
+ return;
1121
+ }
1122
+ if (trail.includes(key)) {
1123
+ return;
1124
+ }
1125
+ for (const dep of edges[key] ?? []) {
1126
+ walk(dep, [...trail, key]);
1127
+ }
1128
+ };
1129
+ walk(rootKey, []);
1130
+ return { verdict: 0 < paths.length ? 'ok' : 'missing', pkg, paths };
1131
+ }
1132
+ function storeDirOf(root, key, entry, options) {
1133
+ const stores = [(0, mod_1.moduleDir)((0, node_path_1.join)(root, mod_1.META_DIR, mod_1.VENDOR_DIR), key)];
1134
+ if (null != options.cache && '' !== entry.canon) {
1135
+ stores.push((0, mod_1.cacheStoreDir)(options.cache, entry.canon, entry.pkg ?? key));
1136
+ }
1137
+ return stores.find((d) => (0, node_fs_1.existsSync)((0, node_path_1.join)(d, mod_1.PKG_FILE)));
1138
+ }
1139
+ function writeLayout(dir, w, options, now) {
1140
+ const pkg = w.manifest.package;
1141
+ const version = w.manifest.version;
1142
+ if (!packagePath(pkg) || !pkg_1.VERSION_RE.test(version)) {
1143
+ refuse('manifest_invalid', 'the manifest names ' + pkg + ' ' + version +
1144
+ ', not a package path at a version', pkg);
1145
+ }
1146
+ const at = (0, node_path_1.join)(dir, 'pkg', ...pkgUrlPath(pkg).split('/'), '@v');
1147
+ (0, node_fs_1.mkdirSync)(at, { recursive: true });
1148
+ const existing = (0, node_fs_1.readdirSync)(at)
1149
+ .filter((f) => f.endsWith('.manifest')).map((f) => f.slice(0, -'.manifest'.length));
1150
+ if (existing.includes(version)) {
1151
+ refuse('version_exists', pkg + ' ' + version + ' was published before and is never reusable', pkg);
1152
+ }
1153
+ const manifests = existing.map((v) => parseDoc(new Uint8Array((0, node_fs_1.readFileSync)((0, node_path_1.join)(at, v + '.manifest')))));
1154
+ for (const m of manifests) {
1155
+ if (null != m?.moved) {
1156
+ refuse('path_moved', pkg + ' is frozen by a moved declaration (now ' + m.moved + ')', pkg);
1157
+ }
1158
+ }
1159
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(at, version + '.zip'), w.archive);
1160
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(at, version + '.manifest'), w.manifestBytes);
1161
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(at, version + '.sig'), w.proofBytes);
1162
+ const listFile = (0, node_path_1.join)(at, 'list');
1163
+ const old = (0, node_fs_1.existsSync)(listFile) ? parseDoc(new Uint8Array((0, node_fs_1.readFileSync)(listFile))) : undefined;
1164
+ const versions = Array.isArray(old?.versions) ? old.versions : [];
1165
+ versions.push({ version, seen: timestamp(now) });
1166
+ versions.sort((a, b) => (0, pkg_1.versionCompare)(a.version, b.version));
1167
+ (0, node_fs_1.writeFileSync)(listFile, canonLine({ package: pkg, versions }, options, 'list'));
1168
+ const top = versions[versions.length - 1];
1169
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)((0, node_path_1.dirname)(at), '@latest'), canonLine({ package: pkg, version: top.version, seen: top.seen }, options, 'latest'));
1170
+ const retracted = [];
1171
+ for (const m of [...manifests, w.manifest]) {
1172
+ for (const v of m?.retract ?? []) {
1173
+ retracted.push({ version: v, by: m.version });
1174
+ }
1175
+ }
1176
+ retracted.sort((a, b) => (0, pkg_1.versionCompare)(a.version, b.version));
1177
+ const advisory = (0, node_path_1.join)(dir, 'advisory', ...pkgUrlPath(pkg).split('/'));
1178
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(advisory + '.aon'), { recursive: true });
1179
+ (0, node_fs_1.writeFileSync)(advisory + '.aon', canonLine({ package: pkg, retracted }, options, 'advisory'));
1180
+ }
1181
+ // A directory as a repository: the layout above, read by path.
1182
+ function dirHttp(dir) {
1183
+ return {
1184
+ get: async (url) => {
1185
+ const p = new URL(url).pathname;
1186
+ const file = (0, node_path_1.join)(dir, ...p.split('/').filter((e) => '' !== e));
1187
+ if (p.split('/').includes('..') || !(0, node_fs_1.existsSync)(file) || !(0, node_fs_1.statSync)(file).isFile()) {
1188
+ return { status: 404, body: new Uint8Array() };
1189
+ }
1190
+ return { status: 200, body: new Uint8Array((0, node_fs_1.readFileSync)(file)) };
1191
+ },
1192
+ post: async () => ({ status: 405, body: utf8('a directory takes no publish') }),
1193
+ };
1194
+ }
1195
+ // What a forge token says about its bearer, read for the manifest's
1196
+ // publisher block. The write path verifies it; the client copies it.
1197
+ function publisherFromToken(token) {
1198
+ const parts = token.trim().split('.');
1199
+ if (3 !== parts.length) {
1200
+ return undefined;
1201
+ }
1202
+ let claims;
1203
+ try {
1204
+ claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
1205
+ }
1206
+ catch {
1207
+ return undefined;
1208
+ }
1209
+ const iss = claims?.iss ?? '';
1210
+ if ('https://token.actions.githubusercontent.com' === iss) {
1211
+ return {
1212
+ host: 'github.com',
1213
+ namespace: claims.repository,
1214
+ subject: {
1215
+ host: 'github.com', owner_id: String(claims.repository_owner_id),
1216
+ repository_id: String(claims.repository_id),
1217
+ },
1218
+ trigger: 'release' === claims.event_name ? 'release' : 'push',
1219
+ runner: 'self-hosted' === claims.runner_environment ? 'self_hosted' : 'hosted',
1220
+ ...(null == claims.workflow_ref ? {} : { workflow: claims.workflow_ref }),
1221
+ };
1222
+ }
1223
+ if ('https://gitlab.com' === iss) {
1224
+ return {
1225
+ host: 'gitlab.com',
1226
+ namespace: claims.project_path,
1227
+ subject: {
1228
+ host: 'gitlab.com', owner_id: String(claims.namespace_id),
1229
+ repository_id: String(claims.project_id),
1230
+ },
1231
+ trigger: 'push',
1232
+ runner: 'self-hosted' === claims.runner_environment ? 'self_hosted' : 'hosted',
1233
+ ...(null == claims.ci_config_ref_uri ? {} : { workflow: claims.ci_config_ref_uri }),
1234
+ };
1235
+ }
1236
+ return undefined;
1237
+ }
1238
+ // PUBLISH (spec ops.publish, the client's side): the manifest and its
1239
+ // gate, then the signed objects sent, or written into a directory. A
1240
+ // dry run without `--yes`.
1241
+ async function pkgPublish(root, options, http, args) {
1242
+ const now = args.now ?? (() => new Date());
1243
+ const local = (0, pkg_1.pkgManifest)(root, options, args.against);
1244
+ const report = {
1245
+ verdict: 'dry-run', missing: local.missing, forbidden: local.forbidden,
1246
+ findings: local.findings,
1247
+ ...(null == args.against ? {} : { against: args.against }),
1248
+ };
1249
+ if ('ok' !== local.verdict) {
1250
+ report.verdict = local.verdict;
1251
+ return report;
1252
+ }
1253
+ const m = local.manifest;
1254
+ report.manifest = m;
1255
+ let config;
1256
+ try {
1257
+ config = repoConfig(root, options, args);
1258
+ }
1259
+ catch (e) {
1260
+ return refusedPublish(report, e);
1261
+ }
1262
+ const target = args.to ?? config.write;
1263
+ report[null == args.to ? 'write' : 'to'] = target;
1264
+ // The repository's own predecessor, when one can be read: the highest
1265
+ // existing version is what compatibility is decided against.
1266
+ const reader = null == args.to ? http : dirHttp(args.to);
1267
+ const ctx = {
1268
+ options, http: reader, config: { ...config, private: [] },
1269
+ cache: options.cache ?? '', now, events: [], fetched: [], acquired: {}, count: 0,
1270
+ };
1271
+ const bases = null == args.to ? config.base : ['http://127.0.0.1'];
1272
+ try {
1273
+ const listed = await reader.get(bases[0].replace(/\/$/, '') + objectPath('list', m.package));
1274
+ if (200 === listed.status && null == args.against) {
1275
+ const list = await fetchList(ctx, bases, m.package);
1276
+ if (list.some((e) => e.version === m.version)) {
1277
+ refuse('version_exists', m.package + ' ' + m.version +
1278
+ ' was published before and is never reusable', m.package);
1279
+ }
1280
+ const newest = list[list.length - 1];
1281
+ if (undefined !== newest) {
1282
+ const prior = await fetchManifest(ctx, bases, m.package, newest.version);
1283
+ if (null != prior.manifest.moved) {
1284
+ refuse('path_moved', m.package + ' is frozen by a moved declaration (now ' +
1285
+ prior.manifest.moved + ')', m.package);
1286
+ }
1287
+ const zip = await fetchArchive(ctx, bases, m.package, newest.version, prior.manifest);
1288
+ const entries = unpack(m.package, newest.version, zip, prior.manifest);
1289
+ // Under the project's own meta directory, so the predecessor's
1290
+ // imports resolve against the vendor tree the publisher synced.
1291
+ const tmp = (0, node_path_1.join)(root, mod_1.META_DIR, 'tmp', (0, node_crypto_1.randomBytes)(8).toString('hex'));
1292
+ (0, node_fs_1.mkdirSync)(tmp, { recursive: true });
1293
+ writeTree(tmp, entries);
1294
+ const gate = (0, pkg_1.pkgManifest)(root, options, tmp);
1295
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(root, mod_1.META_DIR, 'tmp'), { recursive: true, force: true });
1296
+ report.against = m.package + ' ' + newest.version;
1297
+ report.findings = gate.findings;
1298
+ if ('ok' !== gate.verdict) {
1299
+ report.verdict = gate.verdict;
1300
+ return report;
1301
+ }
1302
+ }
1303
+ }
1304
+ }
1305
+ catch (e) {
1306
+ return refusedPublish(report, e);
1307
+ }
1308
+ if (null == args.to && 'public' !== m.publish && !isLoopback(target)) {
1309
+ return refusedPublish(report, new PkgRefusal('not_public', m.package + ' does not declare publish: public; nothing is uploaded', m.package));
1310
+ }
1311
+ const full = { ...m, published: timestamp(now()) };
1312
+ if (null != args.token) {
1313
+ const publisher = publisherFromToken((0, node_fs_1.readFileSync)(args.token, 'utf8'));
1314
+ if (undefined !== publisher) {
1315
+ full.publisher = publisher;
1316
+ }
1317
+ }
1318
+ const manifestBytes = utf8((0, pkg_1.manifestText)(full, options) + '\n');
1319
+ report.digest = (0, pkg_zip_1.sha256Hex)(manifestBytes);
1320
+ if (null != args.key) {
1321
+ try {
1322
+ report.signer = keyIdFromPem((0, node_fs_1.readFileSync)(args.key, 'utf8'));
1323
+ }
1324
+ catch (e) {
1325
+ return refusedPublish(report, e);
1326
+ }
1327
+ }
1328
+ if (true !== args.yes) {
1329
+ return report;
1330
+ }
1331
+ const proof = signDigest((0, node_fs_1.readFileSync)(args.key, 'utf8'), report.digest);
1332
+ const proofBytes = canonLine(proof, options, 'proof.aon');
1333
+ const archive = (0, pkg_1.archiveOf)(root);
1334
+ try {
1335
+ if (null != args.to) {
1336
+ writeLayout(args.to, { manifest: full, manifestBytes, proofBytes, archive: archive.zip }, options, now());
1337
+ }
1338
+ else {
1339
+ const r = await http.post(target.replace(/\/$/, '') + exports.PUBLISH_PATH, { manifest: manifestBytes, proof: proofBytes, archive: archive.zip }, null == args.token ? '' : (0, node_fs_1.readFileSync)(args.token, 'utf8').trim());
1340
+ if (200 !== r.status && 201 !== r.status) {
1341
+ const doc = parseDoc(r.body);
1342
+ refuse('string' === typeof doc?.code ? doc.code : 'fetch_failed', 'string' === typeof doc?.message ? doc.message :
1343
+ 'the write path answered ' + r.status, m.package);
1344
+ }
1345
+ }
1346
+ }
1347
+ catch (e) {
1348
+ return refusedPublish(report, e);
1349
+ }
1350
+ report.verdict = 'sent';
1351
+ return report;
1352
+ }
1353
+ function refusedPublish(report, e) {
1354
+ if (!(e instanceof PkgRefusal)) {
1355
+ throw e;
1356
+ }
1357
+ report.verdict = 'refused';
1358
+ report.refusal = { code: e.code, message: e.message, ...(null == e.pkg ? {} : { pkg: e.pkg }) };
1359
+ return report;
1360
+ }
1361
+ // `aontu pkg outdated`: for every locked package, the newest selectable
1362
+ // version, and what a resolution taking it would move with it.
1363
+ async function pkgOutdated(root, options, http, args = {}) {
1364
+ const report = { verdict: 'current', locked: [], events: [] };
1365
+ let ctx;
1366
+ try {
1367
+ ctx = makeCtx(root, options, http, args);
1368
+ }
1369
+ catch (e) {
1370
+ return refusedOutdated(report, e);
1371
+ }
1372
+ const locked = (0, pkg_1.readLock)(root);
1373
+ try {
1374
+ for (const key of Object.keys(locked).sort(pkg_zip_1.cmpBytes)) {
1375
+ const entry = locked[key];
1376
+ const pkg = entry.pkg ?? key;
1377
+ if (!(0, pkg_1.usableKey)(key) || '' === pkg) {
1378
+ continue;
1379
+ }
1380
+ const bases = basesFor(ctx, pkg);
1381
+ const list = await fetchList(ctx, bases, pkg);
1382
+ const advisory = await fetchAdvisory(ctx, bases, pkg);
1383
+ const newest = selectVersion(ctx, pkg, list, advisory, undefined, entry.v);
1384
+ const out = { key, v: entry.v, newest, moves: [] };
1385
+ if (null != advisory[entry.v]) {
1386
+ out.retracted = advisory[entry.v];
1387
+ }
1388
+ if (0 < (0, pkg_1.versionCompare)(newest, entry.v)) {
1389
+ out.moves = await movesWith(ctx, locked, key, pkg, newest);
1390
+ report.verdict = 'outdated';
1391
+ }
1392
+ else if (null != out.retracted) {
1393
+ report.verdict = 'outdated';
1394
+ }
1395
+ report.locked.push(out);
1396
+ }
1397
+ }
1398
+ catch (e) {
1399
+ report.events = ctx.events;
1400
+ return refusedOutdated(report, e);
1401
+ }
1402
+ report.events = ctx.events;
1403
+ return report;
1404
+ }
1405
+ // What a resolution taking `newest` for one key would move with it:
1406
+ // minimum version selection over the repository's manifests, from the
1407
+ // upgraded declaration down to the closure, against the lock.
1408
+ async function movesWith(ctx, locked, key, pkg, newest) {
1409
+ const selected = {};
1410
+ const targets = {};
1411
+ for (const k of Object.keys(locked)) {
1412
+ selected[k] = locked[k].v;
1413
+ if (null != locked[k].pkg) {
1414
+ targets[k] = locked[k].pkg;
1415
+ }
1416
+ }
1417
+ selected[key] = newest;
1418
+ targets[key] = pkg;
1419
+ let frontier = [key];
1420
+ for (let depth = 0; 0 < frontier.length; depth++) {
1421
+ if (exports.LIMITS.depth <= depth) {
1422
+ refuse('module_depth', 'the closure under ' + pkg + ' nests past ' + exports.LIMITS.depth, pkg);
1423
+ }
1424
+ if (exports.LIMITS.closure < Object.keys(selected).length) {
1425
+ refuse('closure_too_large', 'the closure exceeds ' + exports.LIMITS.closure + ' packages', pkg);
1426
+ }
1427
+ const next = [];
1428
+ for (const k of frontier) {
1429
+ const target = targets[k] ?? k;
1430
+ if (!(0, pkg_1.usableKey)(k) || ((0, mod_1.isAlias)(k) && null == targets[k])) {
1431
+ continue;
1432
+ }
1433
+ const top = await fetchManifest(ctx, basesFor(ctx, target), target, selected[k]);
1434
+ const deps = top.manifest.deps;
1435
+ for (const dk of Object.keys(deps).sort(pkg_zip_1.cmpBytes)) {
1436
+ if (null != deps[dk].pkg) {
1437
+ targets[dk] = deps[dk].pkg;
1438
+ }
1439
+ if (null == selected[dk] || 0 > (0, pkg_1.versionCompare)(selected[dk], deps[dk].v)) {
1440
+ selected[dk] = deps[dk].v;
1441
+ next.push(dk);
1442
+ }
1443
+ }
1444
+ }
1445
+ frontier = next;
1446
+ }
1447
+ return Object.keys(selected).sort(pkg_zip_1.cmpBytes)
1448
+ .filter((k) => k !== key && selected[k] !== locked[k]?.v)
1449
+ .map((k) => k + ' ' + (null == locked[k] ? 'unlocked' : locked[k].v) + ' -> ' + selected[k]);
1450
+ }
1451
+ function refusedOutdated(report, e) {
1452
+ if (!(e instanceof PkgRefusal)) {
1453
+ throw e;
1454
+ }
1455
+ report.verdict = 'refused';
1456
+ report.refusal = { code: e.code, message: e.message, ...(null == e.pkg ? {} : { pkg: e.pkg }) };
1457
+ return report;
1458
+ }
1459
+ // THE LOCAL REGISTRY AND PROXY (`aontu pkg serve`): the directory
1460
+ // layout served verbatim; with upstreams, fetched on a miss and kept.
1461
+ const OBJECT_RE = /^\/(pkg\/[A-Za-z0-9!._/-]+\/@v\/(list|(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\.(zip|manifest|sig|sigstore\.json))|pkg\/[A-Za-z0-9!._/-]+\/@latest|advisory\/[A-Za-z0-9!._/-]+\.aon|tombstone\/(feed\.aon|[A-Za-z0-9!._/-]+\/@v\/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\.aon))$/;
1462
+ function objectShape(p) {
1463
+ return OBJECT_RE.test(p) && !p.split('/').some((e) => '' === e && p.indexOf('//') >= 0) &&
1464
+ !p.split('/').some((e) => '.' === e || '..' === e);
1465
+ }
1466
+ function objectMutable(p) {
1467
+ return p.endsWith('/list') || p.endsWith('/@latest') || p.startsWith('/advisory/') ||
1468
+ p.startsWith('/tombstone/');
1469
+ }
1470
+ function contentType(p) {
1471
+ return p.endsWith('.zip') ? 'application/zip' :
1472
+ p.endsWith('.json') ? 'application/json' : 'text/plain; charset=utf-8';
1473
+ }
1474
+ // One request: the shape gate, the directory, then the upstreams.
1475
+ async function serveObject(opts, p) {
1476
+ if (!objectShape(p)) {
1477
+ return { status: 404, body: utf8('not an object path\n') };
1478
+ }
1479
+ const file = (0, node_path_1.join)(opts.dir, ...p.split('/').filter((e) => '' !== e));
1480
+ const have = (0, node_fs_1.existsSync)(file) && (0, node_fs_1.statSync)(file).isFile();
1481
+ const mutable = objectMutable(p);
1482
+ if (have && !mutable) {
1483
+ return { status: 200, body: new Uint8Array((0, node_fs_1.readFileSync)(file)) };
1484
+ }
1485
+ for (const up of opts.upstream) {
1486
+ const r = await opts.http.get(up.replace(/\/$/, '') + p);
1487
+ if (200 === r.status) {
1488
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(file), { recursive: true });
1489
+ (0, node_fs_1.writeFileSync)(file, r.body);
1490
+ return { status: 200, body: r.body };
1491
+ }
1492
+ }
1493
+ if (have) {
1494
+ return { status: 200, body: new Uint8Array((0, node_fs_1.readFileSync)(file)), stale: 0 < opts.upstream.length };
1495
+ }
1496
+ return { status: 404, body: utf8('no such object\n') };
1497
+ }
1498
+ function startServe(opts) {
1499
+ const handler = (req, res) => {
1500
+ if ('GET' !== req.method && 'HEAD' !== req.method) {
1501
+ res.writeHead(405, { 'content-type': 'text/plain; charset=utf-8' });
1502
+ res.end('the read path is GET\n');
1503
+ return;
1504
+ }
1505
+ const p = new URL(req.url, 'http://localhost').pathname;
1506
+ serveObject(opts, p).then((r) => {
1507
+ const headers = {
1508
+ 'content-type': 200 === r.status ? contentType(p) : 'text/plain; charset=utf-8',
1509
+ 'cache-control': objectMutable(p) ? 'max-age=60' : 'max-age=31536000, immutable',
1510
+ };
1511
+ if (true === r.stale) {
1512
+ headers['x-aontu-stale'] = 'no upstream answered; served from the cache';
1513
+ }
1514
+ res.writeHead(r.status, headers);
1515
+ res.end('HEAD' === req.method ? undefined : Buffer.from(r.body));
1516
+ });
1517
+ };
1518
+ const server = (0, node_http_1.createServer)(handler);
1519
+ const [host, port] = splitListen(opts.listen);
1520
+ return new Promise((resolveServed, reject) => {
1521
+ server.once('error', reject);
1522
+ server.listen(port, host, () => {
1523
+ const addr = server.address();
1524
+ resolveServed({
1525
+ url: servedUrl(addr.address, addr.port),
1526
+ server,
1527
+ close: () => new Promise((done) => server.close(() => done())),
1528
+ });
1529
+ });
1530
+ });
1531
+ }
1532
+ // An IPv6 address is bracketed in a URL.
1533
+ function servedUrl(address, port) {
1534
+ return 'http://' + (address.includes(':') ? '[' + address + ']' : address) + ':' + port;
1535
+ }
1536
+ function splitListen(listen) {
1537
+ const bracketed = /^\[([^\]]*)\](?::(.*))?$/.exec(listen);
1538
+ if (null != bracketed) {
1539
+ const port = Number(bracketed[2]);
1540
+ return [bracketed[1], undefined !== bracketed[2] && Number.isInteger(port) ? port : 8017];
1541
+ }
1542
+ const at = listen.lastIndexOf(':');
1543
+ if (0 > at) {
1544
+ return [listen, 8017];
1545
+ }
1546
+ const port = Number(listen.slice(at + 1));
1547
+ return [listen.slice(0, at), Number.isInteger(port) ? port : 8017];
1548
+ }
1549
+ // A body read no further than the archive cap: what comes back past
1550
+ // it is over the cap by construction, and every reader refuses it.
1551
+ async function readBounded(r, max) {
1552
+ const chunks = [];
1553
+ let total = 0;
1554
+ const reader = r.body?.getReader();
1555
+ for (; undefined !== reader && total <= max;) {
1556
+ const { done, value } = await reader.read();
1557
+ if (done) {
1558
+ break;
1559
+ }
1560
+ chunks.push(value);
1561
+ total += value.length;
1562
+ }
1563
+ if (undefined !== reader && total > max) {
1564
+ await reader.cancel();
1565
+ }
1566
+ return new Uint8Array(Buffer.concat(chunks));
1567
+ }
1568
+ // THE ADAPTER: the platform's fetch, and the multipart a publish sends.
1569
+ function defaultHttp() {
1570
+ return {
1571
+ get: async (url) => {
1572
+ try {
1573
+ const r = await fetch(url, { redirect: 'manual' });
1574
+ return { status: r.status, body: await readBounded(r, pkg_1.ARCHIVE_LIMITS.bytes) };
1575
+ }
1576
+ catch {
1577
+ return { status: 0, body: new Uint8Array() };
1578
+ }
1579
+ },
1580
+ post: async (url, parts, token) => {
1581
+ const form = new FormData();
1582
+ form.set('manifest', new Blob([Buffer.from(parts.manifest)], { type: 'text/plain' }), 'manifest.aon');
1583
+ form.set('proof', new Blob([Buffer.from(parts.proof)], { type: 'text/plain' }), 'proof.aon');
1584
+ form.set('archive', new Blob([Buffer.from(parts.archive)], { type: 'application/zip' }), 'archive.zip');
1585
+ try {
1586
+ const r = await fetch(url, {
1587
+ method: 'POST', body: form,
1588
+ headers: '' === token ? {} : { authorization: 'Bearer ' + token },
1589
+ });
1590
+ return { status: r.status, body: new Uint8Array(await r.arrayBuffer()) };
1591
+ }
1592
+ catch {
1593
+ return { status: 0, body: new Uint8Array() };
1594
+ }
1595
+ },
1596
+ };
1597
+ }
1598
+ //# sourceMappingURL=pkg-net.js.map