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.
- package/README.md +8 -8
- package/dist/agentsmd.js +3 -3
- package/dist/agentsmd.js.map +1 -1
- package/dist/aontu.d.ts +3 -3
- package/dist/aontu.js +4 -6
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.js +6 -12
- package/dist/aontumodel.js.map +1 -1
- package/dist/casing.d.ts +5 -0
- package/dist/casing.js +90 -0
- package/dist/casing.js.map +1 -0
- package/dist/cli.d.ts +16 -3
- package/dist/cli.js +653 -455
- package/dist/cli.js.map +1 -1
- package/dist/compat.d.ts +7 -0
- package/dist/compat.js +113 -0
- package/dist/compat.js.map +1 -0
- package/dist/helpdoc.js +5 -5
- package/dist/helpdoc.js.map +1 -1
- package/dist/hints.js +14 -5
- package/dist/hints.js.map +1 -1
- package/dist/lang.d.ts +1 -0
- package/dist/lang.js +8 -0
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/mcp.js +0 -36
- package/dist/mcp.js.map +1 -1
- package/dist/mod.d.ts +18 -3
- package/dist/mod.js +117 -46
- package/dist/mod.js.map +1 -1
- package/dist/pkg-net.d.ts +227 -0
- package/dist/pkg-net.js +1598 -0
- package/dist/pkg-net.js.map +1 -0
- package/dist/pkg-zip.d.ts +8 -0
- package/dist/pkg-zip.js +196 -0
- package/dist/pkg-zip.js.map +1 -0
- package/dist/pkg.d.ts +159 -0
- package/dist/pkg.js +767 -0
- package/dist/pkg.js.map +1 -0
- package/dist/profile.d.ts +9 -0
- package/dist/profile.js +28 -0
- package/dist/profile.js.map +1 -0
- package/dist/report-sarif.js +1 -1
- package/dist/report-sarif.js.map +1 -1
- package/dist/subsume.d.ts +3 -1
- package/dist/subsume.js +2 -0
- package/dist/subsume.js.map +1 -1
- package/dist/trace.d.ts +21 -0
- package/dist/trace.js +107 -0
- package/dist/trace.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.js +1 -6
- package/dist/unify.js.map +1 -1
- package/dist/val/CmpFuncVal.d.ts +2 -0
- package/dist/val/CmpFuncVal.js +47 -13
- package/dist/val/CmpFuncVal.js.map +1 -1
- package/dist/val/NomFuncVal.js +11 -13
- package/dist/val/NomFuncVal.js.map +1 -1
- package/dist/val/ReferFuncVal.js +3 -0
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +3 -3
- package/skill/error-codes.md +4 -4
- package/skill/examples.md +4 -4
- package/skill/grammar-card.md +1 -1
- package/skill/init/check.sh +2 -2
- package/skill/tasks.md +14 -12
- package/src/agentsmd.ts +3 -3
- package/src/aontu.ts +3 -8
- package/src/aontumodel.ts +6 -12
- package/src/casing.ts +95 -0
- package/src/cli.ts +758 -495
- package/src/compat.ts +148 -0
- package/src/helpdoc.ts +5 -5
- package/src/hints.ts +14 -7
- package/src/lang.ts +9 -1
- package/src/mcp.ts +0 -39
- package/src/mod.ts +142 -51
- package/src/pkg-net.ts +1945 -0
- package/src/pkg-zip.ts +228 -0
- package/src/pkg.ts +1018 -0
- package/src/profile.ts +42 -0
- package/src/report-sarif.ts +1 -1
- package/src/subsume.ts +2 -2
- package/src/trace.ts +157 -0
- package/src/unify.ts +1 -6
- package/src/val/CmpFuncVal.ts +66 -20
- package/src/val/NomFuncVal.ts +3 -5
- package/src/val/ReferFuncVal.ts +4 -0
- package/dist/lower.d.ts +0 -23
- package/dist/lower.js +0 -528
- package/dist/lower.js.map +0 -1
- package/dist/mod-tool.d.ts +0 -58
- package/dist/mod-tool.js +0 -403
- package/dist/mod-tool.js.map +0 -1
- package/dist/render.d.ts +0 -53
- package/dist/render.js +0 -432
- package/dist/render.js.map +0 -1
- package/src/lower.ts +0 -586
- package/src/mod-tool.ts +0 -561
- package/src/render.ts +0 -580
package/src/mod-tool.ts
DELETED
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync,
|
|
6
|
-
copyFileSync,
|
|
7
|
-
} from 'node:fs'
|
|
8
|
-
import { join as pathJoin, dirname as pathDirname } from 'node:path'
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
parseModuleRef, validateModulePath, moduleDir, lockJson,
|
|
12
|
-
} from './mod'
|
|
13
|
-
import { subsume } from './subsume'
|
|
14
|
-
import type { ModuleRef } from './mod'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// One entry of the lockfile, and of a tidy report.
|
|
18
|
-
export type ModLock = {
|
|
19
|
-
// The module path and major, as an import spells it.
|
|
20
|
-
mod: string
|
|
21
|
-
// The selected version.
|
|
22
|
-
v: string
|
|
23
|
-
canon: string
|
|
24
|
-
oci: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type ModTidyReport = {
|
|
28
|
-
verdict: 'ok' | 'missing' | 'error'
|
|
29
|
-
// The resolved closure, sorted by module.
|
|
30
|
-
lock: ModLock[]
|
|
31
|
-
missing: string[]
|
|
32
|
-
unevaluable: string[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type ModVerifyReport = {
|
|
36
|
-
verdict: 'ok' | 'mismatch' | 'unlocked' | 'missing'
|
|
37
|
-
verified: string[]
|
|
38
|
-
// What the lockfile pins against what the store now means, for each
|
|
39
|
-
// module that does not match, sorted by module.
|
|
40
|
-
mismatched: { mod: string, want: string, got: string }[]
|
|
41
|
-
// Dependencies the project declares that the lockfile does not name,
|
|
42
|
-
// sorted. A tidy is what fills them in.
|
|
43
|
-
unlocked: string[]
|
|
44
|
-
// Locked modules present in no store, sorted.
|
|
45
|
-
missing: string[]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type ModVendorReport = {
|
|
49
|
-
verdict: 'ok' | 'missing'
|
|
50
|
-
// The modules materialised into `aontu_meta/vendor/`, sorted.
|
|
51
|
-
vendored: string[]
|
|
52
|
-
// Locked modules present in no store, sorted.
|
|
53
|
-
missing: string[]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
export type ModToolEval = (src: string, path: string) =>
|
|
58
|
-
{
|
|
59
|
-
gen: any, hash: string, canon: string,
|
|
60
|
-
ok: boolean,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
export type ModToolOptions = {
|
|
65
|
-
// The content-addressed user cache. Empty means no cache is
|
|
66
|
-
// consulted, which is a store that misses rather than an error.
|
|
67
|
-
cache?: string
|
|
68
|
-
eval: ModToolEval
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// The `dep` block a module file declares: import string -> version.
|
|
73
|
-
function declaredDeps(file: string, options: ModToolOptions):
|
|
74
|
-
Record<string, string> {
|
|
75
|
-
if (!existsSync(file)) {
|
|
76
|
-
return {}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const gen: any = options.eval(readFileSync(file, 'utf8'), file).gen
|
|
80
|
-
const dep = gen?.dep
|
|
81
|
-
if (null == dep || 'object' !== typeof dep) {
|
|
82
|
-
return {}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const out: Record<string, string> = {}
|
|
86
|
-
for (const key of Object.keys(dep)) {
|
|
87
|
-
const v = dep[key]?.v
|
|
88
|
-
if ('string' === typeof v && '' !== v) {
|
|
89
|
-
out[key] = v
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return out
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
export function versionCompare(a: string, b: string): number {
|
|
97
|
-
const ap = a.split('.')
|
|
98
|
-
const bp = b.split('.')
|
|
99
|
-
for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
|
|
100
|
-
// A part the shorter version does not have is ZERO, so `1.2` and
|
|
101
|
-
// `1.2.0` are the same version -- which is what everyone means by
|
|
102
|
-
// them, and what a lockfile rewritten from either must agree on.
|
|
103
|
-
const x = ap[i] ?? '0'
|
|
104
|
-
const y = bp[i] ?? '0'
|
|
105
|
-
if (x === y) {
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
const xn = /^\d+$/.test(x)
|
|
109
|
-
const yn = /^\d+$/.test(y)
|
|
110
|
-
if (xn && yn) {
|
|
111
|
-
return +x < +y ? -1 : 1
|
|
112
|
-
}
|
|
113
|
-
if (xn !== yn) {
|
|
114
|
-
return xn ? -1 : 1
|
|
115
|
-
}
|
|
116
|
-
return x < y ? -1 : 1
|
|
117
|
-
}
|
|
118
|
-
return 0
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
function usableRef(mod: string): ModuleRef | undefined {
|
|
123
|
-
const ref = parseModuleRef(mod)
|
|
124
|
-
if (undefined === ref || undefined !== validateModulePath(ref.path)) {
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
return ref
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// The directory a module is in, in the local stores: the project's
|
|
132
|
-
// vendor tree first, then the cache under the hash the lockfile pins.
|
|
133
|
-
function storeDir(
|
|
134
|
-
root: string, ref: ModuleRef, hash: string, options: ModToolOptions,
|
|
135
|
-
): string | undefined {
|
|
136
|
-
const stores = [moduleDir(pathJoin(root, 'aontu_meta', 'vendor'), ref)]
|
|
137
|
-
if (null != options.cache && '' !== hash) {
|
|
138
|
-
stores.push(pathJoin(options.cache, hash))
|
|
139
|
-
}
|
|
140
|
-
return stores.find((d) => existsSync(pathJoin(d, 'mod.aon')))
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// The lockfile's entries, as written.
|
|
145
|
-
function readLock(root: string): Record<string, ModLock> {
|
|
146
|
-
const file = pathJoin(root, 'aontu_meta', 'mod-lock.aon')
|
|
147
|
-
if (!existsSync(file)) {
|
|
148
|
-
return {}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let lock: any
|
|
152
|
-
try {
|
|
153
|
-
lock = JSON.parse(lockJson(readFileSync(file, 'utf8')))
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return {}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const out: Record<string, ModLock> = {}
|
|
160
|
-
for (const mod of Object.keys(lock?.lock ?? {})) {
|
|
161
|
-
const e = lock.lock[mod]
|
|
162
|
-
out[mod] = {
|
|
163
|
-
mod,
|
|
164
|
-
v: 'string' === typeof e?.v ? e.v : '',
|
|
165
|
-
canon: 'string' === typeof e?.canon ? e.canon : '',
|
|
166
|
-
oci: 'string' === typeof e?.oci ? e.oci : '',
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return out
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
export function lockText(entries: ModLock[], options: ModToolOptions): string {
|
|
174
|
-
const parts = entries.map((e) =>
|
|
175
|
-
JSON.stringify(e.mod) + ':{' +
|
|
176
|
-
'"canon":' + JSON.stringify(e.canon) + ',' +
|
|
177
|
-
'"oci":' + JSON.stringify(e.oci) + ',' +
|
|
178
|
-
'"v":' + JSON.stringify(e.v) + '}')
|
|
179
|
-
return options.eval('{"lock":{' + parts.join(',') + '}}', 'mod-lock.aon').canon
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// `aontu mod tidy`: resolve the closure by MVS and rewrite the lockfile.
|
|
184
|
-
export function modTidy(root: string, options: ModToolOptions): ModTidyReport {
|
|
185
|
-
const previous = readLock(root)
|
|
186
|
-
const selected: Record<string, string> = {}
|
|
187
|
-
const missing: string[] = []
|
|
188
|
-
|
|
189
|
-
let frontier = declaredDeps(pathJoin(root, 'mod.aon'), options)
|
|
190
|
-
for (; 0 < Object.keys(frontier).length;) {
|
|
191
|
-
const next: Record<string, string> = {}
|
|
192
|
-
|
|
193
|
-
for (const mod of Object.keys(frontier)) {
|
|
194
|
-
const want = frontier[mod]
|
|
195
|
-
const have = selected[mod]
|
|
196
|
-
if (null != have && 0 <= versionCompare(have, want)) {
|
|
197
|
-
continue
|
|
198
|
-
}
|
|
199
|
-
selected[mod] = want
|
|
200
|
-
|
|
201
|
-
const ref = usableRef(mod)
|
|
202
|
-
if (undefined === ref) {
|
|
203
|
-
// A dependency key this tooling cannot act on names nothing
|
|
204
|
-
// this resolver can find, which is the same answer as a module
|
|
205
|
-
// that is not there (see usableRef).
|
|
206
|
-
missing.push(mod)
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const dir = storeDir(root, ref, previous[mod]?.canon ?? '', options)
|
|
211
|
-
if (undefined === dir) {
|
|
212
|
-
missing.push(mod)
|
|
213
|
-
continue
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const deps = declaredDeps(pathJoin(dir, 'mod.aon'), options)
|
|
217
|
-
for (const key of Object.keys(deps)) {
|
|
218
|
-
const bid = next[key]
|
|
219
|
-
if (null == bid || 0 > versionCompare(bid, deps[key])) {
|
|
220
|
-
next[key] = deps[key]
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
frontier = next
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const lock: ModLock[] = []
|
|
229
|
-
const unevaluable: string[] = []
|
|
230
|
-
for (const mod of Object.keys(selected).sort()) {
|
|
231
|
-
if (missing.includes(mod)) {
|
|
232
|
-
continue
|
|
233
|
-
}
|
|
234
|
-
const ref = usableRef(mod) as ModuleRef
|
|
235
|
-
const dir = storeDir(root, ref, previous[mod]?.canon ?? '', options) as string
|
|
236
|
-
const main = pathJoin(dir, mainOf(dir, options))
|
|
237
|
-
const got = existsSync(main) ?
|
|
238
|
-
options.eval(readFileSync(main, 'utf8'), main) : undefined
|
|
239
|
-
if (null != got && !got.ok) {
|
|
240
|
-
unevaluable.push(mod)
|
|
241
|
-
continue
|
|
242
|
-
}
|
|
243
|
-
lock.push({
|
|
244
|
-
mod,
|
|
245
|
-
v: selected[mod],
|
|
246
|
-
canon: null == got ? '' : got.hash,
|
|
247
|
-
// Carried over: the OCI digest is the registry's word about the
|
|
248
|
-
// bytes it served, and nothing local can hear it.
|
|
249
|
-
oci: previous[mod]?.oci ?? '',
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const uniqueMissing = [...new Set(missing)].sort()
|
|
254
|
-
const uniqueUnevaluable = [...new Set(unevaluable)].sort()
|
|
255
|
-
const held = 0 === uniqueMissing.length && 0 === uniqueUnevaluable.length
|
|
256
|
-
if (held) {
|
|
257
|
-
mkdirSync(pathJoin(root, 'aontu_meta'), { recursive: true })
|
|
258
|
-
writeFileSync(pathJoin(root, 'aontu_meta', 'mod-lock.aon'),
|
|
259
|
-
LOCK_HEADER + lockText(lock, options) + '\n')
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
verdict: held ? 'ok' :
|
|
264
|
-
0 < uniqueUnevaluable.length ? 'error' : 'missing',
|
|
265
|
-
lock,
|
|
266
|
-
missing: uniqueMissing,
|
|
267
|
-
unevaluable: uniqueUnevaluable,
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// The generated-file header. A lockfile is machine-written, and the
|
|
273
|
-
// file says so where an editor will see it.
|
|
274
|
-
const LOCK_HEADER =
|
|
275
|
-
'# mod-lock.aon (generated by `aontu mod tidy`; do not edit)\n'
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// The entry file a module declares, or the default. `dir` is always a
|
|
279
|
-
// STORE directory, and `storeDir` only answers one that holds a
|
|
280
|
-
// `mod.aon` -- so there is no missing-file arm to take here.
|
|
281
|
-
function mainOf(dir: string, options: ModToolOptions): string {
|
|
282
|
-
const file = pathJoin(dir, 'mod.aon')
|
|
283
|
-
const gen: any = options.eval(readFileSync(file, 'utf8'), file).gen
|
|
284
|
-
const main = gen?.mod?.main
|
|
285
|
-
return 'string' === typeof main && '' !== main ? main : 'main.aon'
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
export function modVerify(root: string, options: ModToolOptions):
|
|
290
|
-
ModVerifyReport {
|
|
291
|
-
const locked = readLock(root)
|
|
292
|
-
const verified: string[] = []
|
|
293
|
-
const mismatched: { mod: string, want: string, got: string }[] = []
|
|
294
|
-
const missing: string[] = []
|
|
295
|
-
|
|
296
|
-
const declared = declaredDeps(pathJoin(root, 'mod.aon'), options)
|
|
297
|
-
const unlocked = Object.keys(declared)
|
|
298
|
-
.filter((mod) => null == locked[mod]).sort()
|
|
299
|
-
|
|
300
|
-
for (const mod of Object.keys(locked).sort()) {
|
|
301
|
-
const ref = usableRef(mod)
|
|
302
|
-
if (undefined === ref) {
|
|
303
|
-
missing.push(mod)
|
|
304
|
-
continue
|
|
305
|
-
}
|
|
306
|
-
const dir = storeDir(root, ref, locked[mod].canon, options)
|
|
307
|
-
if (undefined === dir) {
|
|
308
|
-
missing.push(mod)
|
|
309
|
-
continue
|
|
310
|
-
}
|
|
311
|
-
const main = pathJoin(dir, mainOf(dir, options))
|
|
312
|
-
if (!existsSync(main)) {
|
|
313
|
-
missing.push(mod)
|
|
314
|
-
continue
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const got = options.eval(readFileSync(main, 'utf8'), main)
|
|
318
|
-
const want = locked[mod].canon
|
|
319
|
-
if (got.ok && want === got.hash) {
|
|
320
|
-
verified.push(mod)
|
|
321
|
-
continue
|
|
322
|
-
}
|
|
323
|
-
mismatched.push({ mod, want, got: got.ok ? got.hash : '' })
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
verdict: 0 < mismatched.length ? 'mismatch' :
|
|
328
|
-
0 < unlocked.length ? 'unlocked' :
|
|
329
|
-
0 < missing.length ? 'missing' : 'ok',
|
|
330
|
-
verified,
|
|
331
|
-
mismatched,
|
|
332
|
-
unlocked,
|
|
333
|
-
missing: missing.sort(),
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// `aontu mod vendor`: materialise the locked closure into `aontu_meta/vendor/`.
|
|
339
|
-
export function modVendor(root: string, options: ModToolOptions):
|
|
340
|
-
ModVendorReport {
|
|
341
|
-
const locked = readLock(root)
|
|
342
|
-
const vendored: string[] = []
|
|
343
|
-
const missing: string[] = []
|
|
344
|
-
|
|
345
|
-
const vendorRoot = pathJoin(root, 'aontu_meta', 'vendor')
|
|
346
|
-
|
|
347
|
-
for (const mod of Object.keys(locked).sort()) {
|
|
348
|
-
const ref = usableRef(mod)
|
|
349
|
-
if (undefined === ref) {
|
|
350
|
-
missing.push(mod)
|
|
351
|
-
continue
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const from = storeDir(root, ref, locked[mod].canon, options)
|
|
355
|
-
if (undefined === from) {
|
|
356
|
-
missing.push(mod)
|
|
357
|
-
continue
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const to = moduleDir(vendorRoot, ref)
|
|
361
|
-
if (from !== to) {
|
|
362
|
-
copyTree(from, to)
|
|
363
|
-
}
|
|
364
|
-
vendored.push(mod)
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return {
|
|
368
|
-
verdict: 0 === missing.length ? 'ok' : 'missing',
|
|
369
|
-
vendored,
|
|
370
|
-
missing: missing.sort(),
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// A whole module directory, copied. Modules are source trees — that is
|
|
376
|
-
// what an OCI layer holds — so this walks rather than reading one file.
|
|
377
|
-
function copyTree(from: string, to: string): void {
|
|
378
|
-
mkdirSync(to, { recursive: true })
|
|
379
|
-
for (const name of readdirSync(from).sort()) {
|
|
380
|
-
const src = pathJoin(from, name)
|
|
381
|
-
const dst = pathJoin(to, name)
|
|
382
|
-
if (statSync(src).isDirectory()) {
|
|
383
|
-
copyTree(src, dst)
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
mkdirSync(pathDirname(dst), { recursive: true })
|
|
387
|
-
copyFileSync(src, dst)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// The config media type the design fixes: an Aontu module is not an
|
|
394
|
-
// image, and the type is what tells a registry so.
|
|
395
|
-
export const MODULE_CONFIG_MEDIA_TYPE = 'application/vnd.aontu.module.v1+json'
|
|
396
|
-
|
|
397
|
-
export const MODULE_ANNOTATION_CANON = 'com.github.rjrodger.aontu.canon'
|
|
398
|
-
export const MODULE_ANNOTATION_MAJOR = 'com.github.rjrodger.aontu.major'
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
export type ModManifestReport = {
|
|
402
|
-
// `ok` the artifact may be minted; `breaking`, `undecided` or `error`
|
|
403
|
-
// the gate refused, and no publish should follow.
|
|
404
|
-
verdict: 'ok' | 'breaking' | 'undecided' | 'error'
|
|
405
|
-
// The module path with its major, as an import spells it.
|
|
406
|
-
mod: string
|
|
407
|
-
version: string
|
|
408
|
-
// The canon-hash of the module's entry file, evaluated standalone.
|
|
409
|
-
canon: string
|
|
410
|
-
config: string
|
|
411
|
-
files: string[]
|
|
412
|
-
annotations: Record<string, string>
|
|
413
|
-
// What the module does not declare, sorted. A manifest cannot be
|
|
414
|
-
// minted without them.
|
|
415
|
-
missing: string[]
|
|
416
|
-
// The gate's findings, when a prior version was named.
|
|
417
|
-
findings: any[]
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// What a module file says about ITSELF. Distinct from `declaredDeps`,
|
|
422
|
-
// which reads what it says about others.
|
|
423
|
-
type ModSelf = { path: string, version: string, main: string }
|
|
424
|
-
|
|
425
|
-
function modSelf(dir: string, options: ModToolOptions): ModSelf {
|
|
426
|
-
const file = pathJoin(dir, 'mod.aon')
|
|
427
|
-
if (!existsSync(file)) {
|
|
428
|
-
return { path: '', version: '', main: 'main.aon' }
|
|
429
|
-
}
|
|
430
|
-
const gen: any = options.eval(readFileSync(file, 'utf8'), file).gen
|
|
431
|
-
const mod = gen?.mod
|
|
432
|
-
const str = (k: string): string =>
|
|
433
|
-
'string' === typeof mod?.[k] ? mod[k] : ''
|
|
434
|
-
return {
|
|
435
|
-
path: str('path'),
|
|
436
|
-
version: str('version'),
|
|
437
|
-
main: '' === str('main') ? 'main.aon' : str('main'),
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
function majorOf(version: string): string {
|
|
443
|
-
const m = /^(\d+)/.exec(version)
|
|
444
|
-
return null == m ? '' : m[1]
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
function layerFiles(dir: string, prefix = ''): string[] {
|
|
449
|
-
const out: string[] = []
|
|
450
|
-
for (const name of readdirSync(dir).sort()) {
|
|
451
|
-
if ('aontu_meta' === name) {
|
|
452
|
-
continue
|
|
453
|
-
}
|
|
454
|
-
const full = pathJoin(dir, name)
|
|
455
|
-
const rel = '' === prefix ? name : prefix + '/' + name
|
|
456
|
-
if (statSync(full).isDirectory()) {
|
|
457
|
-
out.push(...layerFiles(full, rel))
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
out.push(rel)
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return out
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// `aontu mod manifest`: the OCI artifact description a publish would
|
|
468
|
-
// push, and the gate that decides whether it may be.
|
|
469
|
-
export function modManifest(
|
|
470
|
-
root: string, options: ModToolOptions, against?: string,
|
|
471
|
-
): ModManifestReport {
|
|
472
|
-
const self = modSelf(root, options)
|
|
473
|
-
const major = majorOf(self.version)
|
|
474
|
-
|
|
475
|
-
const missing: string[] = []
|
|
476
|
-
if ('' === self.path) {
|
|
477
|
-
missing.push('mod.path')
|
|
478
|
-
}
|
|
479
|
-
if ('' === major) {
|
|
480
|
-
missing.push('mod.version')
|
|
481
|
-
}
|
|
482
|
-
const main = pathJoin(root, self.main)
|
|
483
|
-
if (!existsSync(main)) {
|
|
484
|
-
missing.push(self.main)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const mod = '' === self.path || '' === major ?
|
|
488
|
-
'' : self.path + '@' + major
|
|
489
|
-
|
|
490
|
-
if (0 < missing.length) {
|
|
491
|
-
return {
|
|
492
|
-
verdict: 'error',
|
|
493
|
-
mod,
|
|
494
|
-
version: self.version,
|
|
495
|
-
canon: '',
|
|
496
|
-
config: MODULE_CONFIG_MEDIA_TYPE,
|
|
497
|
-
files: [],
|
|
498
|
-
annotations: {},
|
|
499
|
-
missing: missing.sort(),
|
|
500
|
-
findings: [],
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const newSrc = readFileSync(main, 'utf8')
|
|
505
|
-
const canon = options.eval(newSrc, main).hash
|
|
506
|
-
|
|
507
|
-
const report: ModManifestReport = {
|
|
508
|
-
verdict: 'ok',
|
|
509
|
-
mod,
|
|
510
|
-
version: self.version,
|
|
511
|
-
canon,
|
|
512
|
-
config: MODULE_CONFIG_MEDIA_TYPE,
|
|
513
|
-
files: layerFiles(root),
|
|
514
|
-
annotations: {
|
|
515
|
-
[MODULE_ANNOTATION_CANON]: canon,
|
|
516
|
-
[MODULE_ANNOTATION_MAJOR]: major,
|
|
517
|
-
'org.opencontainers.image.title': self.path,
|
|
518
|
-
'org.opencontainers.image.version': self.version,
|
|
519
|
-
},
|
|
520
|
-
missing: [],
|
|
521
|
-
findings: [],
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (null == against) {
|
|
525
|
-
return report
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// THE PUBLISH-TIME BREAKING GATE. The semantics of "breaking" belong
|
|
529
|
-
// wholly to G3 (ts/src/subsume.ts); this is the wiring, at the one
|
|
530
|
-
// place versions are minted.
|
|
531
|
-
const prior = modSelf(against, options)
|
|
532
|
-
const priorMain = pathJoin(against, prior.main)
|
|
533
|
-
if (!existsSync(priorMain)) {
|
|
534
|
-
report.verdict = 'error'
|
|
535
|
-
report.missing = [prior.main]
|
|
536
|
-
return report
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (majorOf(prior.version) !== major) {
|
|
540
|
-
return report
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const gate = subsume(newSrc, readFileSync(priorMain, 'utf8'), {
|
|
544
|
-
generalUrl: main,
|
|
545
|
-
specificUrl: priorMain,
|
|
546
|
-
generalPath: main,
|
|
547
|
-
specificPath: priorMain,
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
report.findings = gate.findings
|
|
551
|
-
report.verdict = MANIFEST_VERDICT[gate.verdict]
|
|
552
|
-
return report
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const MANIFEST_VERDICT: Record<string, ModManifestReport['verdict']> = {
|
|
557
|
-
subsumes: 'ok',
|
|
558
|
-
does_not_subsume: 'breaking',
|
|
559
|
-
undecided: 'undecided',
|
|
560
|
-
error: 'error',
|
|
561
|
-
}
|