aontu 0.64.0 → 0.66.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 +2 -2
- package/dist/agentsmd.js +3 -3
- package/dist/agentsmd.js.map +1 -1
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.d.ts +16 -2
- package/dist/cli.js +741 -137
- 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 +11 -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/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/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/tsconfig.tsbuildinfo +1 -1
- package/grammar/aontu.gbnf +8 -18
- package/grammar/aontu.lark +6 -14
- package/grammar/aontu.tmLanguage.json +4 -6
- package/package.json +3 -2
- 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 +17 -12
- package/src/agentsmd.ts +3 -3
- package/src/aontu.ts +1 -1
- package/src/cli.ts +851 -151
- package/src/compat.ts +148 -0
- package/src/helpdoc.ts +5 -5
- package/src/hints.ts +11 -7
- package/src/lang.ts +9 -1
- 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/report-sarif.ts +1 -1
- package/src/subsume.ts +2 -2
- package/dist/mod-tool.d.ts +0 -58
- package/dist/mod-tool.js +0 -418
- package/dist/mod-tool.js.map +0 -1
- package/src/mod-tool.ts +0 -578
package/src/cli.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
import { evalFailure } from './query'
|
|
6
6
|
// __importStar downlevel helper, whose branches no supported Node takes.
|
|
7
7
|
import {
|
|
8
|
-
existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync,
|
|
8
|
+
existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync,
|
|
9
|
+
writeFileSync,
|
|
9
10
|
} from 'node:fs'
|
|
11
|
+
import type { Dirent } from 'node:fs'
|
|
10
12
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
11
13
|
import { tmpdir } from 'node:os'
|
|
12
14
|
import { createInterface } from 'node:readline'
|
|
@@ -29,11 +31,23 @@ import { sarifReport } from './report-sarif'
|
|
|
29
31
|
import { main as lspMain } from './lsp-server'
|
|
30
32
|
import { main as mcpMain } from './mcp-server'
|
|
31
33
|
import { jsonSchema } from './jsonschema'
|
|
32
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
pkgTidy, pkgVerify, pkgVendor, pkgManifest, pkgRefreeze, pkgTree,
|
|
36
|
+
versionCompare,
|
|
37
|
+
} from './pkg'
|
|
38
|
+
import {
|
|
39
|
+
pkgSync, pkgGet, pkgRemove, pkgWhy, pkgPublish, pkgOutdated, startServe,
|
|
40
|
+
defaultHttp, keygen, PUBLISH_PATH,
|
|
41
|
+
} from './pkg-net'
|
|
42
|
+
import type {
|
|
43
|
+
PkgSyncReport, PkgChangeReport, PkgWhyReport, PkgPublishReport,
|
|
44
|
+
PkgOutdatedReport, Served, PkgHttp,
|
|
45
|
+
} from './pkg-net'
|
|
33
46
|
import type {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
PkgTidyReport, PkgVerifyReport, PkgVendorReport, PkgManifestReport,
|
|
48
|
+
PkgRefreezeReport, PkgTreeReport, PkgToolOptions,
|
|
49
|
+
} from './pkg'
|
|
50
|
+
import { modCacheDir, PKG_FILE, LOCK_FILE, META_DIR } from './mod'
|
|
37
51
|
import { VET_MAX_ERRORS } from './vet'
|
|
38
52
|
import type {
|
|
39
53
|
VetReport, VetFinding, VetVerdict, VetCoverage,
|
|
@@ -85,11 +99,22 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
85
99
|
[--profile <file>] <file>
|
|
86
100
|
aontu trace [--at <path>] [--format json] [--marker <token>]
|
|
87
101
|
[--profile <file>] <file>
|
|
102
|
+
aontu render [--check] [--at <path>] [--format json]
|
|
103
|
+
[--marker <token>] [--profile <file>]
|
|
104
|
+
<file|folder> <path>
|
|
88
105
|
aontu hash [options] <file>
|
|
89
|
-
aontu
|
|
90
|
-
aontu
|
|
91
|
-
aontu
|
|
92
|
-
aontu
|
|
106
|
+
aontu sync [--frozen] [options] [dir]
|
|
107
|
+
aontu add <pkg>[@<version>] [options] [dir]
|
|
108
|
+
aontu get <pkg>[@<version>] [options] [dir]
|
|
109
|
+
aontu remove <pkg> [options] [dir]
|
|
110
|
+
aontu why <pkg> [options] [dir]
|
|
111
|
+
aontu publish [--yes] [--to <dir>] [options] [dir]
|
|
112
|
+
aontu pkg tidy|verify|vendor|manifest|refreeze|tree|outdated|serve
|
|
113
|
+
[options] [dir]
|
|
114
|
+
aontu pkg keygen <file>
|
|
115
|
+
aontu model get <path> [options] <file>
|
|
116
|
+
aontu model why <path> [options] <file>
|
|
117
|
+
aontu model set <path>=<value>... --entry <file> --overlay <file>
|
|
93
118
|
aontu allow --role <role> [--at <path>] <roles-file> <path>...
|
|
94
119
|
aontu agentsmd [--write <AGENTS.md>] [--depth <n>] <file>
|
|
95
120
|
aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
|
|
@@ -158,18 +183,57 @@ Options:
|
|
|
158
183
|
named format keeps its meaning, and .js stays
|
|
159
184
|
refused. Every verb takes it
|
|
160
185
|
|
|
161
|
-
|
|
186
|
+
Package verbs (a module is imported; a package is published):
|
|
187
|
+
sync Make the project correct: resolve by minimum version
|
|
188
|
+
selection, fetch what is missing, write aontu_meta/pkg-lock.aon,
|
|
189
|
+
vendor, verify. --frozen refuses to change the lockfile
|
|
190
|
+
add Take on a dependency the project does not have, then sync;
|
|
191
|
+
refuses one it has and names get
|
|
192
|
+
get Add a dependency or raise its minimum, then sync. Without a
|
|
193
|
+
version, the newest version outside the cooldown
|
|
194
|
+
remove Drop a dependency, then sync
|
|
195
|
+
why Why is this package in the closure: every path of
|
|
196
|
+
requirements that reaches it
|
|
197
|
+
publish Publish this package. A dry run without --yes; --to <dir>
|
|
198
|
+
writes the repository layout into a directory instead
|
|
199
|
+
|
|
200
|
+
Package options:
|
|
162
201
|
--format <f> text (default) or json
|
|
163
|
-
--
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
--frozen sync: fail if the lockfile would change (the CI mode)
|
|
203
|
+
--yes publish: send it (the default is a dry run)
|
|
204
|
+
--to <dir> publish: write the read-path layout into a directory
|
|
205
|
+
--key <file> publish: the Ed25519 private key (PKCS#8 PEM) that
|
|
206
|
+
signs the manifest, for the key provider
|
|
207
|
+
--token <file> publish: the forge's OIDC token, read from a file
|
|
208
|
+
--base <url> the repository to read from (repeatable; overrides
|
|
209
|
+
pkg.aon repo.base)
|
|
210
|
+
--write <url> publish: the write path (overrides pkg.aon repo.write)
|
|
211
|
+
--against <dir> manifest: a prior version's tree, to gate on
|
|
212
|
+
--upstream <u> serve: fetch on miss from this repository (repeatable)
|
|
213
|
+
--listen <a> serve: the address to listen on (default 127.0.0.1:8017)
|
|
214
|
+
|
|
215
|
+
pkg subcommands (the rest of the package operations):
|
|
216
|
+
tidy Resolve the closure by minimum version selection and
|
|
217
|
+
rewrite aontu_meta/pkg-lock.aon in canonical form
|
|
218
|
+
verify Check every locked package still is and still means what
|
|
219
|
+
the lockfile pins, bytes before meaning, and change nothing
|
|
220
|
+
(the CI gate; tidy rewrites)
|
|
170
221
|
vendor Materialise the locked closure into aontu_meta/vendor/
|
|
171
|
-
manifest Print the
|
|
172
|
-
|
|
222
|
+
manifest Print the manifest a publish would send, gated on the
|
|
223
|
+
compatibility check against --against
|
|
224
|
+
refreeze Recompute every canon pin and nothing else, after a
|
|
225
|
+
canonical-form change in the engine
|
|
226
|
+
tree The locked closure as a graph
|
|
227
|
+
outdated What could move, and what would move with it
|
|
228
|
+
serve Serve a repository directory; with --upstream, a caching
|
|
229
|
+
proxy in front of another repository
|
|
230
|
+
keygen Write a new Ed25519 signing key (PKCS#8 PEM) to a file,
|
|
231
|
+
once, and print the signer id a consumer names
|
|
232
|
+
|
|
233
|
+
model subcommands (one document, interrogated or edited):
|
|
234
|
+
get What the document says at a path
|
|
235
|
+
why Every contribution to the value at a path
|
|
236
|
+
set Append a path-flattened conjunct to an overlay
|
|
173
237
|
|
|
174
238
|
Vet options:
|
|
175
239
|
--at <path> Validate against this path of the schema ($.a.b)
|
|
@@ -248,21 +312,21 @@ Hash options:
|
|
|
248
312
|
Hash exit codes: 0 hashed, 2 usage, 4 the document does not stand up
|
|
249
313
|
on its own.
|
|
250
314
|
|
|
251
|
-
|
|
315
|
+
Model get options:
|
|
252
316
|
-c, --canon Canonical-form fragment (default: generated JSON)
|
|
253
317
|
--keys Keys at the node, one per line
|
|
254
318
|
--types Shape view: concrete leaves lifted to their kinds
|
|
255
319
|
--depth <n> Structure to depth n; deeper nodes render as top
|
|
256
320
|
--format <f> text (default) or json
|
|
257
321
|
|
|
258
|
-
|
|
259
|
-
document does not stand up on its own.
|
|
322
|
+
Model get exit codes: 0 rendered, 1 the path names nothing, 2 usage, 4
|
|
323
|
+
the document does not stand up on its own.
|
|
260
324
|
|
|
261
|
-
|
|
325
|
+
Model why options:
|
|
262
326
|
--format <f> text (default) or json
|
|
263
327
|
|
|
264
|
-
|
|
265
|
-
2 usage, 4 the document does not stand up on its own.
|
|
328
|
+
Model why exit codes mirror get's: 0 explained, 1 the path names
|
|
329
|
+
nothing, 2 usage, 4 the document does not stand up on its own.
|
|
266
330
|
|
|
267
331
|
View kinds: doc, lattice, tree, matrix, graph, layer, sets, layers,
|
|
268
332
|
ladder, poset (the poset takes several files). The figure goes to stdout, the loss
|
|
@@ -352,7 +416,32 @@ every other line is a line of output.
|
|
|
352
416
|
|
|
353
417
|
Template exit codes: 0 written, 1 --check drift, 2 usage or I/O.
|
|
354
418
|
|
|
355
|
-
|
|
419
|
+
Render options:
|
|
420
|
+
--check Compare what the generator writes with what <path>
|
|
421
|
+
holds, write nothing, and exit 1 on drift
|
|
422
|
+
--at <path> Where the component tree lives in the document
|
|
423
|
+
(default $.out)
|
|
424
|
+
--format json Print the files written, or the drift, as JSON
|
|
425
|
+
--marker <t> The file is a generator, and this is its marker
|
|
426
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
427
|
+
--profile <f> A profile file, whose template.ext names the
|
|
428
|
+
extensions it marks and template.marker the marker
|
|
429
|
+
|
|
430
|
+
The render verb writes the component tree a generator answers. The
|
|
431
|
+
tree is handed to jostraca, the generator runtime, which writes the
|
|
432
|
+
files: a tree that is one file is written to <path> itself, unless
|
|
433
|
+
<path> is a directory, and any other tree is written below <path>.
|
|
434
|
+
With --check nothing is written and <path> is compared with what the
|
|
435
|
+
generator writes, one "kind: file" line per difference.
|
|
436
|
+
A folder as the generator is a set: every regular file directly in
|
|
437
|
+
it, dotfiles aside, in name order, and their trees are written below
|
|
438
|
+
<path> as one run, so a path two of them claim is refused. A file with
|
|
439
|
+
no marker line in it is refused by name.
|
|
440
|
+
|
|
441
|
+
Render exit codes: 0 written or clean, 1 --check drift, 2 usage or
|
|
442
|
+
I/O, 4 the document does not stand up, or --at names nothing.
|
|
443
|
+
|
|
444
|
+
Model set options:
|
|
356
445
|
--entry <file> The document the change is checked against
|
|
357
446
|
--overlay <file> The file the change is appended to (created if
|
|
358
447
|
absent; not written when the change does not hold)
|
|
@@ -368,7 +457,7 @@ Set options:
|
|
|
368
457
|
--format <f> text (default) or json
|
|
369
458
|
|
|
370
459
|
Set exit codes are vet's verdict classes: 0 valid, 1 invalid (the
|
|
371
|
-
change contradicts a pinned value -- aontu why locates it, and
|
|
460
|
+
change contradicts a pinned value -- aontu model why locates it, and
|
|
372
461
|
--in-place rewrites it), 2 usage, 3 incomplete, 4 the entry does not
|
|
373
462
|
stand up on its own.
|
|
374
463
|
|
|
@@ -601,7 +690,7 @@ function trustOpts(trust: TrustArg, entryRoot: string): any {
|
|
|
601
690
|
}
|
|
602
691
|
|
|
603
692
|
|
|
604
|
-
function takeTrust(argv: string[]):
|
|
693
|
+
function takeTrust(argv: string[], io: Io = PROCESS_IO):
|
|
605
694
|
{ argv: string[], trust: TrustArg } | undefined {
|
|
606
695
|
const rest: string[] = []
|
|
607
696
|
let trust: TrustArg = { kind: 'system-warn', textExt: [] }
|
|
@@ -611,7 +700,7 @@ function takeTrust(argv: string[]):
|
|
|
611
700
|
if ('--trust' === arg) {
|
|
612
701
|
const parsed = null == argv[i + 1] ? undefined : parseTrustArg(argv[++i])
|
|
613
702
|
if (null == parsed) {
|
|
614
|
-
|
|
703
|
+
io.err(
|
|
615
704
|
'aontu: --trust needs system, none, or root[:dir]\n')
|
|
616
705
|
return undefined
|
|
617
706
|
}
|
|
@@ -620,7 +709,7 @@ function takeTrust(argv: string[]):
|
|
|
620
709
|
else if ('--include-root' === arg) {
|
|
621
710
|
const dir = argv[++i]
|
|
622
711
|
if (null == dir) {
|
|
623
|
-
|
|
712
|
+
io.err('aontu: --include-root needs a directory\n')
|
|
624
713
|
return undefined
|
|
625
714
|
}
|
|
626
715
|
trust = { kind: 'root', dir, textExt }
|
|
@@ -628,7 +717,7 @@ function takeTrust(argv: string[]):
|
|
|
628
717
|
else if ('--text-ext' === arg) {
|
|
629
718
|
const list = null == argv[i + 1] ? undefined : parseTextExt(argv[++i])
|
|
630
719
|
if (null == list) {
|
|
631
|
-
|
|
720
|
+
io.err(
|
|
632
721
|
'aontu: --text-ext needs extensions, without dots' +
|
|
633
722
|
' (--text-ext md,sql)\n')
|
|
634
723
|
return undefined
|
|
@@ -644,7 +733,7 @@ function takeTrust(argv: string[]):
|
|
|
644
733
|
|
|
645
734
|
|
|
646
735
|
// `md,sql` or `.md,.sql` -- the dot is accepted and dropped, because a
|
|
647
|
-
// reader who has just written `@"notes.txt"` reaches for one. An empty
|
|
736
|
+
// reader who has just written `@"./notes.txt"` reaches for one. An empty
|
|
648
737
|
// element, or anything that is not an extension, is a usage error
|
|
649
738
|
// rather than a silently ignored word: a flag that quietly does
|
|
650
739
|
// nothing is how a document ends up refused with no reason visible.
|
|
@@ -2048,110 +2137,186 @@ const VIEW_USAGE_CODES = [
|
|
|
2048
2137
|
'view_document_shape', 'view_style_profile', 'view_style_unknown',
|
|
2049
2138
|
]
|
|
2050
2139
|
|
|
2051
|
-
const
|
|
2140
|
+
const PKG_HELP =
|
|
2141
|
+
'aontu pkg tidy|verify|vendor|manifest|refreeze|tree|outdated|serve [dir] | keygen <file> (try --help)'
|
|
2052
2142
|
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2143
|
+
const PKG_SUBS = [
|
|
2144
|
+
'tidy', 'verify', 'vendor', 'manifest', 'refreeze', 'tree', 'outdated',
|
|
2145
|
+
'serve', 'keygen',
|
|
2146
|
+
]
|
|
2147
|
+
|
|
2148
|
+
// The options every package verb parses, so `sync`, `get`, `publish`
|
|
2149
|
+
// and the `pkg` subcommands read one table.
|
|
2150
|
+
type PkgArgs = {
|
|
2151
|
+
rest: string[]
|
|
2152
|
+
format: SubsumeFormat
|
|
2153
|
+
against?: string
|
|
2154
|
+
frozen: boolean
|
|
2155
|
+
yes: boolean
|
|
2156
|
+
to?: string
|
|
2157
|
+
key?: string
|
|
2158
|
+
token?: string
|
|
2159
|
+
base: string[]
|
|
2160
|
+
write?: string
|
|
2161
|
+
upstream: string[]
|
|
2162
|
+
listen?: string
|
|
2163
|
+
}
|
|
2063
2164
|
|
|
2165
|
+
function parsePkgArgs(argv: string[], verb: string, io: Io): PkgArgs | undefined {
|
|
2166
|
+
const out: PkgArgs = {
|
|
2167
|
+
rest: [], format: 'text', frozen: false, yes: false, base: [], upstream: [],
|
|
2168
|
+
}
|
|
2169
|
+
const value = (name: string, i: number): string | undefined => {
|
|
2170
|
+
const v = argv[i]
|
|
2171
|
+
if (null == v) {
|
|
2172
|
+
io.err(`aontu: ${name} needs a value\n`)
|
|
2173
|
+
}
|
|
2174
|
+
return v
|
|
2175
|
+
}
|
|
2064
2176
|
for (let i = 0; i < argv.length; i++) {
|
|
2065
2177
|
const arg = argv[i]
|
|
2066
2178
|
if ('-h' === arg || '--help' === arg) {
|
|
2067
|
-
|
|
2068
|
-
return
|
|
2179
|
+
io.out(HELP)
|
|
2180
|
+
return undefined
|
|
2069
2181
|
}
|
|
2070
2182
|
if ('--format' === arg) {
|
|
2071
2183
|
const f = argv[++i]
|
|
2072
2184
|
if ('text' !== f && 'json' !== f) {
|
|
2073
|
-
|
|
2074
|
-
return
|
|
2185
|
+
io.err('aontu: --format needs text or json\n')
|
|
2186
|
+
return undefined
|
|
2075
2187
|
}
|
|
2076
|
-
format = f
|
|
2188
|
+
out.format = f
|
|
2077
2189
|
}
|
|
2078
|
-
else if ('--
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2190
|
+
else if ('--frozen' === arg) {
|
|
2191
|
+
out.frozen = true
|
|
2192
|
+
}
|
|
2193
|
+
else if ('--yes' === arg) {
|
|
2194
|
+
out.yes = true
|
|
2195
|
+
}
|
|
2196
|
+
else if ('--against' === arg || '--to' === arg || '--key' === arg ||
|
|
2197
|
+
'--token' === arg || '--write' === arg || '--listen' === arg) {
|
|
2198
|
+
const v = value(arg, ++i)
|
|
2199
|
+
if (null == v) {
|
|
2200
|
+
return undefined
|
|
2083
2201
|
}
|
|
2084
|
-
against
|
|
2202
|
+
out['--against' === arg ? 'against' : '--to' === arg ? 'to' :
|
|
2203
|
+
'--key' === arg ? 'key' : '--token' === arg ? 'token' :
|
|
2204
|
+
'--write' === arg ? 'write' : 'listen'] = v
|
|
2205
|
+
}
|
|
2206
|
+
else if ('--base' === arg || '--upstream' === arg) {
|
|
2207
|
+
const v = value(arg, ++i)
|
|
2208
|
+
if (null == v) {
|
|
2209
|
+
return undefined
|
|
2210
|
+
}
|
|
2211
|
+
out['--base' === arg ? 'base' : 'upstream'].push(v)
|
|
2085
2212
|
}
|
|
2086
2213
|
else if (arg.startsWith('-')) {
|
|
2087
|
-
|
|
2088
|
-
return
|
|
2214
|
+
io.err(`aontu: unknown ${verb} option ${arg} (try --help)\n`)
|
|
2215
|
+
return undefined
|
|
2089
2216
|
}
|
|
2090
2217
|
else {
|
|
2091
|
-
rest.push(arg)
|
|
2218
|
+
out.rest.push(arg)
|
|
2092
2219
|
}
|
|
2093
2220
|
}
|
|
2221
|
+
return out
|
|
2222
|
+
}
|
|
2094
2223
|
|
|
2095
|
-
const sub = rest[0]
|
|
2096
|
-
const dir = rest[1] ?? '.'
|
|
2097
2224
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2225
|
+
// A verb that finds the older layout names the current one, once, and
|
|
2226
|
+
// reads nothing from it.
|
|
2227
|
+
function nameOldLayout(dir: string, io: Io): void {
|
|
2228
|
+
const old = ['aon_vendor', 'mod-lock.aon', 'mod.aon',
|
|
2229
|
+
join(META_DIR, 'mod-lock.aon')].filter((f) => existsSync(join(dir, f)))
|
|
2230
|
+
if (0 < old.length) {
|
|
2231
|
+
io.err(
|
|
2232
|
+
'aontu: ' + old.join(', ') + ' belong to an older layout: the package ' +
|
|
2233
|
+
'file is ' + PKG_FILE + ', the lockfile ' + join(META_DIR, LOCK_FILE) +
|
|
2234
|
+
' and the vendor tree ' + join(META_DIR, 'vendor') + '; rename ' +
|
|
2235
|
+
PKG_FILE + '\'s `mod` block to `pkg`, then run aontu sync\n')
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
|
|
2240
|
+
function runPkg(argv: string[], servers: Servers): number | Promise<number> {
|
|
2241
|
+
const io = servers.io ?? PROCESS_IO
|
|
2242
|
+
const trusted = takeTrust(argv, io)
|
|
2243
|
+
if (null == trusted) {
|
|
2103
2244
|
return 2
|
|
2104
2245
|
}
|
|
2246
|
+
const args = parsePkgArgs(trusted.argv, 'pkg', io)
|
|
2247
|
+
if (null == args) {
|
|
2248
|
+
return '-h' === trusted.argv[0] || trusted.argv.includes('--help') ? 0 : 2
|
|
2249
|
+
}
|
|
2250
|
+
const trust = trusted.trust
|
|
2251
|
+
const sub = args.rest[0]
|
|
2252
|
+
const dir = args.rest[1] ?? '.'
|
|
2105
2253
|
|
|
2106
|
-
if (!
|
|
2107
|
-
|
|
2108
|
-
`aontu:
|
|
2254
|
+
if (!PKG_SUBS.includes(sub) || 2 < args.rest.length) {
|
|
2255
|
+
io.err(
|
|
2256
|
+
`aontu: pkg needs one of ${PKG_SUBS.join(', ')}\n${PKG_HELP}\n`)
|
|
2109
2257
|
return 2
|
|
2110
2258
|
}
|
|
2111
2259
|
|
|
2112
|
-
if (
|
|
2113
|
-
|
|
2114
|
-
'aontu:
|
|
2115
|
-
|
|
2260
|
+
if ('keygen' === sub) {
|
|
2261
|
+
if (2 !== args.rest.length) {
|
|
2262
|
+
io.err('aontu: pkg keygen needs the file to write\naontu pkg keygen <file>\n')
|
|
2263
|
+
return 2
|
|
2264
|
+
}
|
|
2265
|
+
const made = keygen(args.rest[1])
|
|
2266
|
+
if (null != made.refused) {
|
|
2267
|
+
io.err('aontu: ' + made.refused + '\n')
|
|
2268
|
+
return 2
|
|
2269
|
+
}
|
|
2270
|
+
io.out('signer: ' + made.signer + '\n')
|
|
2271
|
+
return 0
|
|
2116
2272
|
}
|
|
2117
2273
|
|
|
2118
|
-
|
|
2274
|
+
nameOldLayout(dir, io)
|
|
2275
|
+
|
|
2276
|
+
// `--against` gates a manifest and means nothing to the others;
|
|
2119
2277
|
// accepting it there would say it had been honoured.
|
|
2120
|
-
if (null != against && 'manifest' !== sub) {
|
|
2121
|
-
|
|
2278
|
+
if (null != args.against && 'manifest' !== sub) {
|
|
2279
|
+
io.err('aontu: --against is a manifest option\n')
|
|
2122
2280
|
return 2
|
|
2123
2281
|
}
|
|
2124
2282
|
|
|
2125
|
-
const
|
|
2283
|
+
const opts = pkgToolOptions(trust, resolve(dir))
|
|
2126
2284
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
'vendor' === sub ? modVendor(dir, modopts) :
|
|
2131
|
-
modManifest(dir, modopts, against)
|
|
2285
|
+
if ('outdated' === sub || 'serve' === sub) {
|
|
2286
|
+
return runPkgNet(sub, dir, args, opts, servers, io)
|
|
2287
|
+
}
|
|
2132
2288
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2289
|
+
const report =
|
|
2290
|
+
'tidy' === sub ? pkgTidy(dir, opts) :
|
|
2291
|
+
'verify' === sub ? pkgVerify(dir, opts) :
|
|
2292
|
+
'vendor' === sub ? pkgVendor(dir, opts) :
|
|
2293
|
+
'refreeze' === sub ? pkgRefreeze(dir, opts) :
|
|
2294
|
+
'tree' === sub ? pkgTree(dir, opts) :
|
|
2295
|
+
pkgManifest(dir, opts, args.against)
|
|
2296
|
+
|
|
2297
|
+
io.out(('json' === args.format ?
|
|
2298
|
+
exactJSON({ aontu: { version: version(), verb: 'pkg ' + sub }, ...report },
|
|
2135
2299
|
2) :
|
|
2136
|
-
|
|
2300
|
+
pkgText(sub, report)) + '\n')
|
|
2137
2301
|
|
|
2138
|
-
return
|
|
2302
|
+
return PKG_EXIT[report.verdict]
|
|
2139
2303
|
}
|
|
2140
2304
|
|
|
2141
2305
|
|
|
2142
|
-
const MOD_SUBS = ['tidy', 'verify', 'vendor', 'manifest']
|
|
2143
|
-
|
|
2144
2306
|
// The verdict classes: `ok` 0, a refused gate 1, an open question 3, a
|
|
2145
2307
|
// document that does not stand up 4 -- `subsume`'s classes, because a
|
|
2146
2308
|
// manifest gate IS a subsumption check and a caller reading exit codes
|
|
2147
2309
|
// should not have to learn a second table.
|
|
2148
|
-
type
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2310
|
+
type PkgVerdict =
|
|
2311
|
+
PkgTidyReport['verdict'] |
|
|
2312
|
+
PkgVerifyReport['verdict'] |
|
|
2313
|
+
PkgVendorReport['verdict'] |
|
|
2314
|
+
PkgManifestReport['verdict'] |
|
|
2315
|
+
PkgRefreezeReport['verdict'] |
|
|
2316
|
+
PkgTreeReport['verdict'] |
|
|
2317
|
+
'frozen' | 'refused' | 'sent' | 'dry-run' | 'outdated' | 'current'
|
|
2318
|
+
|
|
2319
|
+
const PKG_EXIT: Record<PkgVerdict, number> = {
|
|
2155
2320
|
ok: 0,
|
|
2156
2321
|
missing: 1,
|
|
2157
2322
|
mismatch: 1,
|
|
@@ -2159,6 +2324,12 @@ const MOD_EXIT: Record<ModVerdict, number> = {
|
|
|
2159
2324
|
// nothing to check, which is a refusal and not a pass.
|
|
2160
2325
|
unlocked: 1,
|
|
2161
2326
|
breaking: 1,
|
|
2327
|
+
frozen: 1,
|
|
2328
|
+
refused: 1,
|
|
2329
|
+
outdated: 1,
|
|
2330
|
+
sent: 0,
|
|
2331
|
+
'dry-run': 0,
|
|
2332
|
+
current: 0,
|
|
2162
2333
|
undecided: 3,
|
|
2163
2334
|
error: 4,
|
|
2164
2335
|
}
|
|
@@ -2167,7 +2338,7 @@ const MOD_EXIT: Record<ModVerdict, number> = {
|
|
|
2167
2338
|
// The tooling's evaluator: the same standalone evaluation the module
|
|
2168
2339
|
// resolver verifies with (ts/src/mod.ts), and for the same reason —
|
|
2169
2340
|
// only the engine can say what a module MEANS.
|
|
2170
|
-
function
|
|
2341
|
+
function pkgToolOptions(trust: TrustArg, entryRoot: string): PkgToolOptions {
|
|
2171
2342
|
const opts = verbOpts(trust, entryRoot)
|
|
2172
2343
|
// The user cache lives outside any confinement root, so a confined
|
|
2173
2344
|
// run reads the vendor tree only -- as the evaluator's own module
|
|
@@ -2184,7 +2355,7 @@ function modToolOptions(trust: TrustArg, entryRoot: string) {
|
|
|
2184
2355
|
hash: canonHash(val),
|
|
2185
2356
|
canon: val.canon,
|
|
2186
2357
|
// The same question `aontu hash` asks before it will answer:
|
|
2187
|
-
// did this document stand up ON ITS OWN? See
|
|
2358
|
+
// did this document stand up ON ITS OWN? See PkgToolEval.
|
|
2188
2359
|
ok: 0 === ctx.err.length && true !== val.isNil,
|
|
2189
2360
|
}
|
|
2190
2361
|
},
|
|
@@ -2192,19 +2363,54 @@ function modToolOptions(trust: TrustArg, entryRoot: string) {
|
|
|
2192
2363
|
}
|
|
2193
2364
|
|
|
2194
2365
|
|
|
2195
|
-
function
|
|
2366
|
+
function pkgLockLines(entries: any[]): string[] {
|
|
2367
|
+
return entries.map((e) => e.key + ' ' + e.v + ' ' + e.canon)
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// The renderers verify, tidy and sync share, as the Go port's do.
|
|
2371
|
+
function pkgMismatchLines(mismatched: any[]): string[] {
|
|
2372
|
+
return mismatched.map((m) => 'canon' === m.pin ?
|
|
2373
|
+
m.key + ': pinned ' + m.want + ' but the store means ' +
|
|
2374
|
+
('' === m.got ? 'nothing (it does not evaluate)' : m.got) :
|
|
2375
|
+
m.key + ': pinned ' + m.pin + ' ' + m.want + ' but the store holds ' + m.got)
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// NOT a fetch: the package may well be sitting in the store. What is
|
|
2379
|
+
// absent is the PIN, and only a sync writes one.
|
|
2380
|
+
function pkgUnlockedLines(unlocked: string[]): string[] {
|
|
2381
|
+
return unlocked.map((key) => key + ': not in the lockfile (run: aontu sync)')
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
function pkgForbiddenLines(forbidden: string[]): string[] {
|
|
2385
|
+
return forbidden.map((f) => f + ': not admitted in a package')
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
|
|
2389
|
+
function pkgText(sub: string, report: any): string {
|
|
2196
2390
|
const lines = ['verdict: ' + report.verdict]
|
|
2197
2391
|
|
|
2198
2392
|
if ('manifest' === sub) {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
lines.push('
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2393
|
+
const m = report.manifest
|
|
2394
|
+
if (null != m) {
|
|
2395
|
+
lines.push(m.package + ' ' + m.version + ' ' + m.publish)
|
|
2396
|
+
lines.push('archive: ' + m.archive.digest + ' (' + m.archive.files.length +
|
|
2397
|
+
' files, ' + m.archive.size + ' bytes)')
|
|
2398
|
+
for (const mod of m.modules) {
|
|
2399
|
+
lines.push('module: ' + mod.path + ' ' + mod.main + ' ' + mod.canon)
|
|
2400
|
+
}
|
|
2401
|
+
for (const key of Object.keys(m.deps).sort(cmpCodePoint)) {
|
|
2402
|
+
lines.push('dep: ' + key + ' ' + m.deps[key].v +
|
|
2403
|
+
(null == m.deps[key].pkg ? '' : ' (' + m.deps[key].pkg + ')'))
|
|
2404
|
+
}
|
|
2405
|
+
for (const v of m.retract ?? []) {
|
|
2406
|
+
lines.push('retract: ' + v)
|
|
2407
|
+
}
|
|
2408
|
+
if (null != m.moved) {
|
|
2409
|
+
lines.push('moved: ' + m.moved)
|
|
2410
|
+
}
|
|
2411
|
+
for (const f of m.archive.files) {
|
|
2412
|
+
lines.push('file: ' + f.path + ' ' + f.digest + ' ' + f.size)
|
|
2413
|
+
}
|
|
2208
2414
|
}
|
|
2209
2415
|
for (const f of report.findings) {
|
|
2210
2416
|
lines.push(f.path + ': ' + f.message)
|
|
@@ -2212,48 +2418,313 @@ function modText(sub: string, report: any): string {
|
|
|
2212
2418
|
for (const miss of report.missing) {
|
|
2213
2419
|
lines.push(miss + ': missing')
|
|
2214
2420
|
}
|
|
2421
|
+
lines.push(...pkgForbiddenLines(report.forbidden))
|
|
2215
2422
|
return lines.join('\n')
|
|
2216
2423
|
}
|
|
2217
2424
|
|
|
2218
2425
|
if ('verify' === sub) {
|
|
2219
|
-
for (const
|
|
2220
|
-
lines.push(
|
|
2426
|
+
for (const key of report.verified) {
|
|
2427
|
+
lines.push(key + ': verified')
|
|
2221
2428
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2429
|
+
lines.push(...pkgMismatchLines(report.mismatched), ...pkgUnlockedLines(report.unlocked))
|
|
2430
|
+
for (const miss of report.missing) {
|
|
2431
|
+
lines.push(miss + ': not fetched (run: aontu sync)')
|
|
2225
2432
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2433
|
+
return lines.join('\n')
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
if ('refreeze' === sub) {
|
|
2437
|
+
for (const r of report.repinned) {
|
|
2438
|
+
lines.push(r.key + ': ' + r.from + ' -> ' + r.to)
|
|
2439
|
+
}
|
|
2440
|
+
for (const key of report.unchanged) {
|
|
2441
|
+
lines.push(key + ': unchanged')
|
|
2442
|
+
}
|
|
2443
|
+
for (const bad of report.unevaluable) {
|
|
2444
|
+
lines.push(bad + ': does not evaluate on its own; nothing to pin')
|
|
2230
2445
|
}
|
|
2231
2446
|
for (const miss of report.missing) {
|
|
2232
|
-
lines.push(miss + ': not fetched (run: aontu
|
|
2447
|
+
lines.push(miss + ': not fetched (run: aontu sync)')
|
|
2233
2448
|
}
|
|
2234
2449
|
return lines.join('\n')
|
|
2235
2450
|
}
|
|
2236
2451
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2452
|
+
if ('tree' === sub) {
|
|
2453
|
+
const byKey = new Map(report.nodes.map((n: any) => [n.key, n]))
|
|
2454
|
+
const seen = new Set<string>()
|
|
2455
|
+
const walk = (key: string, depth: number): void => {
|
|
2456
|
+
const node: any = byKey.get(key)
|
|
2457
|
+
const again = seen.has(key)
|
|
2458
|
+
seen.add(key)
|
|
2459
|
+
lines.push(' '.repeat(depth) + key +
|
|
2460
|
+
(null == node || '' === node.v ? '' : ' ' + node.v) +
|
|
2461
|
+
(again ? ' (above)' : null == node ? ' (not locked)' : ''))
|
|
2462
|
+
if (again || null == node) {
|
|
2463
|
+
return
|
|
2464
|
+
}
|
|
2465
|
+
for (const dep of node.deps) {
|
|
2466
|
+
walk(dep, depth + 1)
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
walk(report.root, 0)
|
|
2470
|
+
for (const miss of report.missing) {
|
|
2471
|
+
lines.push(miss + ': not fetched (run: aontu sync)')
|
|
2472
|
+
}
|
|
2473
|
+
return lines.join('\n')
|
|
2241
2474
|
}
|
|
2242
|
-
|
|
2475
|
+
|
|
2476
|
+
const done: any[] = 'tidy' === sub ? pkgLockLines(report.lock) : report.vendored
|
|
2477
|
+
lines.push(...done)
|
|
2478
|
+
// A package that is PRESENT but does not stand up. Named separately
|
|
2243
2479
|
// from a missing one because the repair is different: a fetch cannot
|
|
2244
|
-
// help, the
|
|
2480
|
+
// help, the package itself has to be fixed (or its own dependencies
|
|
2245
2481
|
// vendored beside it). Before the missing tail, as the Go port's
|
|
2246
2482
|
// shared renderer orders them.
|
|
2247
2483
|
for (const bad of report.unevaluable ?? []) {
|
|
2248
2484
|
lines.push(bad + ': does not evaluate on its own; nothing to pin')
|
|
2249
2485
|
}
|
|
2486
|
+
lines.push(...pkgForbiddenLines(report.forbidden ?? []))
|
|
2250
2487
|
for (const miss of report.missing) {
|
|
2251
|
-
lines.push(miss + ': not fetched (run: aontu
|
|
2488
|
+
lines.push(miss + ': not fetched (run: aontu sync)')
|
|
2252
2489
|
}
|
|
2253
2490
|
return lines.join('\n')
|
|
2254
2491
|
}
|
|
2255
2492
|
|
|
2256
2493
|
|
|
2494
|
+
// The two `pkg` subcommands that reach a repository.
|
|
2495
|
+
async function runPkgNet(sub: string, dir: string, args: PkgArgs,
|
|
2496
|
+
opts: PkgToolOptions, servers: Servers, io: Io): Promise<number> {
|
|
2497
|
+
if ('serve' === sub) {
|
|
2498
|
+
const served = await startServe({
|
|
2499
|
+
dir: resolve(dir), upstream: args.upstream,
|
|
2500
|
+
listen: args.listen ?? '127.0.0.1:8017', http: servers.http(),
|
|
2501
|
+
})
|
|
2502
|
+
io.out('serving ' + resolve(dir) + ' at ' + served.url + '\n' +
|
|
2503
|
+
args.upstream.map((u) => 'upstream: ' + u + '\n').join(''))
|
|
2504
|
+
await servers.serve(served)
|
|
2505
|
+
await served.close()
|
|
2506
|
+
return 0
|
|
2507
|
+
}
|
|
2508
|
+
if (null == opts.cache) {
|
|
2509
|
+
io.err(NO_CACHE)
|
|
2510
|
+
return 2
|
|
2511
|
+
}
|
|
2512
|
+
const report = await pkgOutdated(dir, opts, servers.http(), { base: netBase(args) })
|
|
2513
|
+
io.out(pkgReportText('pkg outdated', args.format, report) + '\n')
|
|
2514
|
+
return PKG_EXIT[report.verdict]
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
const NO_CACHE = 'aontu: this verb reads and writes the user cache, which a ' +
|
|
2518
|
+
'confined run (--trust root) does not reach and this host does not name ' +
|
|
2519
|
+
'(set XDG_CACHE_HOME or HOME)\n'
|
|
2520
|
+
|
|
2521
|
+
function netBase(args: PkgArgs): string[] | undefined {
|
|
2522
|
+
return 0 === args.base.length ? undefined : args.base
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
const PACKAGE_VERBS = ['sync', 'add', 'get', 'remove', 'why', 'publish']
|
|
2526
|
+
|
|
2527
|
+
const PACKAGE_HELP: Record<string, string> = {
|
|
2528
|
+
sync: 'aontu sync [--frozen] [dir] (try --help)',
|
|
2529
|
+
add: 'aontu add <pkg> [dir] (try --help)',
|
|
2530
|
+
get: 'aontu get <pkg>[@<version>] [dir] (try --help)',
|
|
2531
|
+
remove: 'aontu remove <pkg> [dir] (try --help)',
|
|
2532
|
+
why: 'aontu why <pkg> [dir] (try --help)',
|
|
2533
|
+
publish: 'aontu publish [--yes] [--to <dir>] [--key <file>] [dir] (try --help)',
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// The top-level package verbs (ADR-039 part 5). Every one but `why`
|
|
2537
|
+
// reaches a repository, so every one runs behind the seam.
|
|
2538
|
+
async function runPackageVerb(verb: string, argv: string[], servers: Servers):
|
|
2539
|
+
Promise<number> {
|
|
2540
|
+
const io = servers.io ?? PROCESS_IO
|
|
2541
|
+
const trusted = takeTrust(argv, io)
|
|
2542
|
+
if (null == trusted) {
|
|
2543
|
+
return 2
|
|
2544
|
+
}
|
|
2545
|
+
const args = parsePkgArgs(trusted.argv, verb, io)
|
|
2546
|
+
if (null == args) {
|
|
2547
|
+
return '-h' === trusted.argv[0] || trusted.argv.includes('--help') ? 0 : 2
|
|
2548
|
+
}
|
|
2549
|
+
const wantsPkg = 'sync' !== verb && 'publish' !== verb
|
|
2550
|
+
const dir = args.rest[wantsPkg ? 1 : 0] ?? '.'
|
|
2551
|
+
if (args.rest.length > (wantsPkg ? 2 : 1) || (wantsPkg && 0 === args.rest.length)) {
|
|
2552
|
+
io.err('aontu: ' + verb + (wantsPkg ? ' needs a package' : ' takes a directory') +
|
|
2553
|
+
'\n' + PACKAGE_HELP[verb] + '\n')
|
|
2554
|
+
return 2
|
|
2555
|
+
}
|
|
2556
|
+
nameOldLayout(dir, io)
|
|
2557
|
+
const opts = pkgToolOptions(trusted.trust, resolve(dir))
|
|
2558
|
+
const http = servers.http()
|
|
2559
|
+
|
|
2560
|
+
if ('why' === verb) {
|
|
2561
|
+
const report = pkgWhy(dir, opts, args.rest[0])
|
|
2562
|
+
io.out(pkgReportText('why', args.format, report) + '\n')
|
|
2563
|
+
return PKG_EXIT[report.verdict]
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
if ('publish' === verb) {
|
|
2567
|
+
for (const f of [args.key, args.token]) {
|
|
2568
|
+
if (null != f && !existsSync(f)) {
|
|
2569
|
+
io.err('aontu: cannot read ' + f + '\n')
|
|
2570
|
+
return 2
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
if (true === args.yes && null == args.key) {
|
|
2574
|
+
io.err('aontu: publish --yes needs --key <file>, the Ed25519 key that signs\n')
|
|
2575
|
+
return 2
|
|
2576
|
+
}
|
|
2577
|
+
const report = await pkgPublish(dir, opts, http, {
|
|
2578
|
+
yes: args.yes, to: null == args.to ? undefined : resolve(args.to), key: args.key,
|
|
2579
|
+
token: args.token, against: args.against, base: netBase(args), write: args.write,
|
|
2580
|
+
})
|
|
2581
|
+
io.out(pkgReportText('publish', args.format, report) + '\n')
|
|
2582
|
+
return PKG_EXIT[report.verdict]
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
if (null == opts.cache) {
|
|
2586
|
+
io.err(NO_CACHE)
|
|
2587
|
+
return 2
|
|
2588
|
+
}
|
|
2589
|
+
const net = { base: netBase(args) }
|
|
2590
|
+
const report: PkgSyncReport | PkgChangeReport | string =
|
|
2591
|
+
'sync' === verb ? await pkgSync(dir, opts, http, { ...net, frozen: args.frozen }) :
|
|
2592
|
+
'remove' === verb ? await pkgRemove(dir, opts, http, args.rest[0], net) :
|
|
2593
|
+
await pkgGet(dir, opts, http, args.rest[0], { ...net, mode: verb as 'add' | 'get' })
|
|
2594
|
+
if ('string' === typeof report) {
|
|
2595
|
+
io.err('aontu: ' + report + '\n')
|
|
2596
|
+
return 2
|
|
2597
|
+
}
|
|
2598
|
+
io.out(pkgReportText(verb, args.format, report) + '\n')
|
|
2599
|
+
return PKG_EXIT[report.verdict]
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
|
|
2603
|
+
function pkgReportText(verb: string, fmt: SubsumeFormat, report: any): string {
|
|
2604
|
+
return 'json' === fmt ?
|
|
2605
|
+
exactJSON({ aontu: { version: version(), verb }, ...report }, 2) :
|
|
2606
|
+
pkgNetText(verb, report)
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
|
|
2610
|
+
function pkgNetText(verb: string, report: any): string {
|
|
2611
|
+
const lines = ['verdict: ' + report.verdict]
|
|
2612
|
+
const tail = (): void => {
|
|
2613
|
+
for (const e of report.events ?? []) {
|
|
2614
|
+
lines.push(e.code + ': ' + e.message)
|
|
2615
|
+
}
|
|
2616
|
+
if (null != report.refusal) {
|
|
2617
|
+
lines.push('refused: ' + report.refusal.code + ': ' + report.refusal.message)
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
if ('why' === verb) {
|
|
2622
|
+
const why = report as PkgWhyReport
|
|
2623
|
+
for (const p of why.paths) {
|
|
2624
|
+
lines.push(p.join(' -> '))
|
|
2625
|
+
}
|
|
2626
|
+
if ('missing' === why.verdict) {
|
|
2627
|
+
lines.push(why.pkg + ': not in the closure')
|
|
2628
|
+
}
|
|
2629
|
+
return lines.join('\n')
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
if ('publish' === verb) {
|
|
2633
|
+
const pub = report as PkgPublishReport
|
|
2634
|
+
lines.push(...pkgText('manifest', pub).split('\n').slice(1))
|
|
2635
|
+
if (null != pub.digest) {
|
|
2636
|
+
lines.push('digest: ' + pub.digest)
|
|
2637
|
+
}
|
|
2638
|
+
if (null != pub.signer) {
|
|
2639
|
+
lines.push('signer: ' + pub.signer)
|
|
2640
|
+
}
|
|
2641
|
+
if (null != pub.against) {
|
|
2642
|
+
lines.push('against: ' + pub.against)
|
|
2643
|
+
}
|
|
2644
|
+
if (null != pub.to) {
|
|
2645
|
+
lines.push('to: ' + pub.to)
|
|
2646
|
+
}
|
|
2647
|
+
if (null != pub.write) {
|
|
2648
|
+
lines.push('write: ' + pub.write + PUBLISH_PATH)
|
|
2649
|
+
}
|
|
2650
|
+
if ('dry-run' === pub.verdict) {
|
|
2651
|
+
lines.push('dry run: nothing sent (add --yes)')
|
|
2652
|
+
}
|
|
2653
|
+
if ('sent' === pub.verdict) {
|
|
2654
|
+
lines.push('sent')
|
|
2655
|
+
}
|
|
2656
|
+
tail()
|
|
2657
|
+
return lines.join('\n')
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
if ('pkg outdated' === verb) {
|
|
2661
|
+
const out = report as PkgOutdatedReport
|
|
2662
|
+
for (const e of out.locked) {
|
|
2663
|
+
if (0 < versionCompare(e.newest, e.v)) {
|
|
2664
|
+
lines.push(e.key + ' ' + e.v + ' -> ' + e.newest)
|
|
2665
|
+
for (const m of e.moves) {
|
|
2666
|
+
lines.push(' ' + m)
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
else {
|
|
2670
|
+
lines.push(e.key + ' ' + e.v + ': current')
|
|
2671
|
+
}
|
|
2672
|
+
if (null != e.retracted) {
|
|
2673
|
+
lines.push(e.key + ' ' + e.v + ': retracted by ' + e.retracted)
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
tail()
|
|
2677
|
+
return lines.join('\n')
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
const sync = report as PkgChangeReport
|
|
2681
|
+
if (null != sync.change) {
|
|
2682
|
+
lines.push('change: ' + sync.change)
|
|
2683
|
+
}
|
|
2684
|
+
for (const f of sync.fetched) {
|
|
2685
|
+
lines.push('fetched: ' + f)
|
|
2686
|
+
}
|
|
2687
|
+
lines.push(...pkgLockLines(sync.lock))
|
|
2688
|
+
for (const bad of sync.unevaluable) {
|
|
2689
|
+
lines.push(bad + ': does not evaluate on its own; nothing to pin')
|
|
2690
|
+
}
|
|
2691
|
+
lines.push(...pkgForbiddenLines(sync.forbidden), ...pkgMismatchLines(sync.mismatched),
|
|
2692
|
+
...pkgUnlockedLines(sync.unlocked))
|
|
2693
|
+
for (const miss of sync.missing) {
|
|
2694
|
+
lines.push(miss + ': not fetched (run: aontu sync)')
|
|
2695
|
+
}
|
|
2696
|
+
for (const c of sync.changes) {
|
|
2697
|
+
lines.push('lockfile would change: ' + c)
|
|
2698
|
+
}
|
|
2699
|
+
tail()
|
|
2700
|
+
return lines.join('\n')
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
|
|
2704
|
+
const MODEL_HELP = 'aontu model get|why|set ... (try --help)'
|
|
2705
|
+
|
|
2706
|
+
// One document, interrogated or edited (ADR-039 part 5).
|
|
2707
|
+
function runModel(argv: string[]): number {
|
|
2708
|
+
const sub = argv[0]
|
|
2709
|
+
if ('-h' === sub || '--help' === sub) {
|
|
2710
|
+
process.stdout.write(HELP)
|
|
2711
|
+
return 0
|
|
2712
|
+
}
|
|
2713
|
+
if ('get' === sub) {
|
|
2714
|
+
return runGet(argv.slice(1))
|
|
2715
|
+
}
|
|
2716
|
+
if ('why' === sub) {
|
|
2717
|
+
return runWhy(argv.slice(1))
|
|
2718
|
+
}
|
|
2719
|
+
if ('set' === sub) {
|
|
2720
|
+
return runSet(argv.slice(1))
|
|
2721
|
+
}
|
|
2722
|
+
process.stderr.write(`aontu: model needs get, why or set\n${MODEL_HELP}\n`)
|
|
2723
|
+
return 2
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
|
|
2727
|
+
|
|
2257
2728
|
function vacuous(what: string, why: string): void {
|
|
2258
2729
|
process.stderr.write(`aontu: ${what}: ${why}\n`)
|
|
2259
2730
|
}
|
|
@@ -2439,6 +2910,211 @@ function runTrace(argv: string[]): number {
|
|
|
2439
2910
|
}
|
|
2440
2911
|
|
|
2441
2912
|
|
|
2913
|
+
// ---------------------------------------------------------------------
|
|
2914
|
+
// The writer (ADR-040). A generator answers a component tree; this verb
|
|
2915
|
+
// hands it to jostraca, which writes the files below a path or holds
|
|
2916
|
+
// them to it.
|
|
2917
|
+
|
|
2918
|
+
const RENDER_HELP =
|
|
2919
|
+
'aontu render [--check] [--at <path>] [--format json] ' +
|
|
2920
|
+
'[--marker <token>] [--profile <file>] <file|folder> <path> (try --help)'
|
|
2921
|
+
|
|
2922
|
+
|
|
2923
|
+
// Loaded at the call, so every other verb starts without it.
|
|
2924
|
+
function generatorRuntime(): any {
|
|
2925
|
+
return require('jostraca')
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
|
|
2929
|
+
function isDirectory(path: string): boolean {
|
|
2930
|
+
return true === statSync(path, { throwIfNoEntry: false })?.isDirectory()
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
|
|
2934
|
+
async function runRender(argv: string[]): Promise<number> {
|
|
2935
|
+
const trusted = takeTrust(argv)
|
|
2936
|
+
if (null == trusted) {
|
|
2937
|
+
return 2
|
|
2938
|
+
}
|
|
2939
|
+
argv = trusted.argv
|
|
2940
|
+
const trust = trusted.trust
|
|
2941
|
+
const rest: string[] = []
|
|
2942
|
+
const profileFiles: string[] = []
|
|
2943
|
+
let format: SubsumeFormat = 'text'
|
|
2944
|
+
let at: string | undefined = undefined
|
|
2945
|
+
let marker: string | undefined = undefined
|
|
2946
|
+
let check = false
|
|
2947
|
+
|
|
2948
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2949
|
+
const arg = argv[i]
|
|
2950
|
+
if ('-h' === arg || '--help' === arg) {
|
|
2951
|
+
process.stdout.write(HELP)
|
|
2952
|
+
return 0
|
|
2953
|
+
}
|
|
2954
|
+
if ('--check' === arg) {
|
|
2955
|
+
check = true
|
|
2956
|
+
}
|
|
2957
|
+
else if ('--format' === arg) {
|
|
2958
|
+
const f = argv[++i]
|
|
2959
|
+
if ('text' !== f && 'json' !== f) {
|
|
2960
|
+
process.stderr.write('aontu: --format needs text or json\n')
|
|
2961
|
+
return 2
|
|
2962
|
+
}
|
|
2963
|
+
format = f
|
|
2964
|
+
}
|
|
2965
|
+
else if ('--at' === arg) {
|
|
2966
|
+
at = argv[++i]
|
|
2967
|
+
if (null == at || '' === at) {
|
|
2968
|
+
process.stderr.write('aontu: --at needs a path\n')
|
|
2969
|
+
return 2
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
else if ('--marker' === arg) {
|
|
2973
|
+
marker = argv[++i]
|
|
2974
|
+
if (null == marker || '' === marker) {
|
|
2975
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
2976
|
+
return 2
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
else if ('--profile' === arg) {
|
|
2980
|
+
const pf = argv[++i]
|
|
2981
|
+
if (null == pf || '' === pf) {
|
|
2982
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
2983
|
+
return 2
|
|
2984
|
+
}
|
|
2985
|
+
profileFiles.push(pf)
|
|
2986
|
+
}
|
|
2987
|
+
else if (arg.startsWith('-')) {
|
|
2988
|
+
process.stderr.write(`aontu: unknown render option ${arg} (try --help)\n`)
|
|
2989
|
+
return 2
|
|
2990
|
+
}
|
|
2991
|
+
else {
|
|
2992
|
+
rest.push(arg)
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
if (2 !== rest.length) {
|
|
2997
|
+
process.stderr.write(`aontu: render needs a file and a path\n${RENDER_HELP}\n`)
|
|
2998
|
+
return 2
|
|
2999
|
+
}
|
|
3000
|
+
const [file, dest] = rest
|
|
3001
|
+
|
|
3002
|
+
const declared = loadProfiles(profileFiles, trust)
|
|
3003
|
+
if ('number' === typeof declared) {
|
|
3004
|
+
return declared
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// A FOLDER IS A SET OF GENERATORS: every regular file directly in it,
|
|
3008
|
+
// dotfiles aside, in code-point order, written as one tree.
|
|
3009
|
+
let entries: Dirent[] | undefined
|
|
3010
|
+
try {
|
|
3011
|
+
entries = readdirSync(file, { withFileTypes: true })
|
|
3012
|
+
}
|
|
3013
|
+
catch (err: any) {
|
|
3014
|
+
entries = undefined
|
|
3015
|
+
}
|
|
3016
|
+
const set = undefined !== entries
|
|
3017
|
+
const files = undefined === entries ? [file] :
|
|
3018
|
+
entries.filter((e) => e.isFile() && !e.name.startsWith('.'))
|
|
3019
|
+
.map((e) => e.name).sort(cmpCodePoint).map((n) => join(file, n))
|
|
3020
|
+
if (0 === files.length) {
|
|
3021
|
+
process.stderr.write(`aontu: ${file} holds no generator\n`)
|
|
3022
|
+
return 2
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
const trees: any[] = []
|
|
3026
|
+
for (const f of files) {
|
|
3027
|
+
let src: string
|
|
3028
|
+
try {
|
|
3029
|
+
src = readFileSync(f, 'utf8')
|
|
3030
|
+
}
|
|
3031
|
+
catch (err: any) {
|
|
3032
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3033
|
+
return 2
|
|
3034
|
+
}
|
|
3035
|
+
if (!/[.](aon|aontu)$/.test(f)) {
|
|
3036
|
+
const mark = marker ?? markerFromProfiles(declared, f) ?? markerFor(f)
|
|
3037
|
+
if (!templateOutputs(src, mark).some((out) => !out)) {
|
|
3038
|
+
process.stderr.write(`aontu: ${f} carries no ${mark} marker line, ` +
|
|
3039
|
+
'so there is no aontu in it to render\n')
|
|
3040
|
+
return 2
|
|
3041
|
+
}
|
|
3042
|
+
src = desugarTemplate(src, mark)
|
|
3043
|
+
}
|
|
3044
|
+
const report = get(src, at ?? '$.out', {
|
|
3045
|
+
view: 'json', path: f, ...verbOpts(trust, entryRootOf(f)),
|
|
3046
|
+
})
|
|
3047
|
+
if (!report.ok) {
|
|
3048
|
+
process.stderr.write(report.findings.map(renderFinding).join('\n') + '\n')
|
|
3049
|
+
return 4
|
|
3050
|
+
}
|
|
3051
|
+
const tree = JSON.parse(report.out)
|
|
3052
|
+
// A `File` without a name is refused: the runtime ports disagree
|
|
3053
|
+
// about it.
|
|
3054
|
+
if ('File' === tree?.cmp && 'string' !== typeof tree.props?.name) {
|
|
3055
|
+
process.stderr.write(`aontu: ${f}: the file at ${at ?? '$.out'} has no name\n`)
|
|
3056
|
+
return 4
|
|
3057
|
+
}
|
|
3058
|
+
trees.push(tree)
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// ONE FILE GOES TO THE PATH ITSELF, unless the path is a directory; a
|
|
3062
|
+
// set is written below the path whatever its trees are.
|
|
3063
|
+
let folder = dest
|
|
3064
|
+
const tree: any = set ? trees.flatMap((t) => Array.isArray(t) ? t : [t]) : trees[0]
|
|
3065
|
+
if (!set && 'File' === tree?.cmp && !isDirectory(dest)) {
|
|
3066
|
+
tree.props.name = basename(dest)
|
|
3067
|
+
folder = dirname(dest)
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
const { cmpTree, Jostraca } = generatorRuntime()
|
|
3071
|
+
let root: any
|
|
3072
|
+
try {
|
|
3073
|
+
root = cmpTree(tree, { raw: true })
|
|
3074
|
+
}
|
|
3075
|
+
catch (err: any) {
|
|
3076
|
+
process.stderr.write(`aontu: ${file}: ${err.message}\n`)
|
|
3077
|
+
return 4
|
|
3078
|
+
}
|
|
3079
|
+
const runtime = Jostraca()
|
|
3080
|
+
|
|
3081
|
+
try {
|
|
3082
|
+
if (check) {
|
|
3083
|
+
const res = await runtime.check({ folder }, root)
|
|
3084
|
+
const drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
|
|
3085
|
+
if ('json' === format) {
|
|
3086
|
+
process.stdout.write(exactJSON({
|
|
3087
|
+
aontu: { version: version(), verb: 'render' },
|
|
3088
|
+
verdict: 0 === drift.length ? 'ok' : 'drift',
|
|
3089
|
+
checked: res.checked,
|
|
3090
|
+
drift,
|
|
3091
|
+
}, 2) + '\n')
|
|
3092
|
+
}
|
|
3093
|
+
else {
|
|
3094
|
+
for (const d of drift) {
|
|
3095
|
+
process.stdout.write(`${d.kind}: ${d.path}\n`)
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return 0 === drift.length ? 0 : 1
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
const res = await runtime.generate({ folder }, root)
|
|
3102
|
+
if ('json' === format) {
|
|
3103
|
+
process.stdout.write(exactJSON({
|
|
3104
|
+
aontu: { version: version(), verb: 'render' },
|
|
3105
|
+
verdict: 'ok',
|
|
3106
|
+
files: res.files,
|
|
3107
|
+
}, 2) + '\n')
|
|
3108
|
+
}
|
|
3109
|
+
return 0
|
|
3110
|
+
}
|
|
3111
|
+
catch (err: any) {
|
|
3112
|
+
process.stderr.write(`aontu: ${err.message}\n`)
|
|
3113
|
+
return 2
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
|
|
2442
3118
|
function runReaches(argv: string[]): number {
|
|
2443
3119
|
const trusted = takeTrust(argv)
|
|
2444
3120
|
if (null == trusted) {
|
|
@@ -3272,7 +3948,7 @@ function runHash(argv: string[]): number {
|
|
|
3272
3948
|
}
|
|
3273
3949
|
|
|
3274
3950
|
|
|
3275
|
-
const GET_HELP = 'aontu get <path> <file> (try --help)'
|
|
3951
|
+
const GET_HELP = 'aontu model get <path> <file> (try --help)'
|
|
3276
3952
|
|
|
3277
3953
|
function runGet(argv: string[]): number {
|
|
3278
3954
|
const trusted = takeTrust(argv)
|
|
@@ -3318,7 +3994,7 @@ function runGet(argv: string[]): number {
|
|
|
3318
3994
|
format = f
|
|
3319
3995
|
}
|
|
3320
3996
|
else if (arg.startsWith('-')) {
|
|
3321
|
-
process.stderr.write(`aontu: unknown get option ${arg} (try --help)\n`)
|
|
3997
|
+
process.stderr.write(`aontu: unknown model get option ${arg} (try --help)\n`)
|
|
3322
3998
|
return 2
|
|
3323
3999
|
}
|
|
3324
4000
|
else {
|
|
@@ -3327,7 +4003,7 @@ function runGet(argv: string[]): number {
|
|
|
3327
4003
|
}
|
|
3328
4004
|
|
|
3329
4005
|
if (2 !== rest.length) {
|
|
3330
|
-
process.stderr.write(`aontu: get needs a path and one file\n${GET_HELP}\n`)
|
|
4006
|
+
process.stderr.write(`aontu: model get needs a path and one file\n${GET_HELP}\n`)
|
|
3331
4007
|
return 2
|
|
3332
4008
|
}
|
|
3333
4009
|
const [path, file] = rest
|
|
@@ -3355,7 +4031,7 @@ function runGet(argv: string[]): number {
|
|
|
3355
4031
|
})
|
|
3356
4032
|
if ('json' === format) {
|
|
3357
4033
|
process.stdout.write(exactJSON({
|
|
3358
|
-
aontu: { version: version(), verb: 'get' },
|
|
4034
|
+
aontu: { version: version(), verb: 'model get' },
|
|
3359
4035
|
findings: report.findings,
|
|
3360
4036
|
ok: report.ok,
|
|
3361
4037
|
out: report.out,
|
|
@@ -3384,7 +4060,7 @@ function runGet(argv: string[]): number {
|
|
|
3384
4060
|
// The positive twin of the vet report: errors explain what failed to
|
|
3385
4061
|
// unify, this explains what did.
|
|
3386
4062
|
|
|
3387
|
-
const WHY_HELP = 'aontu why <path> <file> (try --help)'
|
|
4063
|
+
const WHY_HELP = 'aontu model why <path> <file> (try --help)'
|
|
3388
4064
|
|
|
3389
4065
|
function runWhy(argv: string[]): number {
|
|
3390
4066
|
const trusted = takeTrust(argv)
|
|
@@ -3411,7 +4087,7 @@ function runWhy(argv: string[]): number {
|
|
|
3411
4087
|
format = f
|
|
3412
4088
|
}
|
|
3413
4089
|
else if (arg.startsWith('-')) {
|
|
3414
|
-
process.stderr.write(`aontu: unknown why option ${arg} (try --help)\n`)
|
|
4090
|
+
process.stderr.write(`aontu: unknown model why option ${arg} (try --help)\n`)
|
|
3415
4091
|
return 2
|
|
3416
4092
|
}
|
|
3417
4093
|
else {
|
|
@@ -3420,7 +4096,7 @@ function runWhy(argv: string[]): number {
|
|
|
3420
4096
|
}
|
|
3421
4097
|
|
|
3422
4098
|
if (2 !== rest.length) {
|
|
3423
|
-
process.stderr.write(`aontu: why needs a path and one file\n${WHY_HELP}\n`)
|
|
4099
|
+
process.stderr.write(`aontu: model why needs a path and one file\n${WHY_HELP}\n`)
|
|
3424
4100
|
return 2
|
|
3425
4101
|
}
|
|
3426
4102
|
const [path, file] = rest
|
|
@@ -3439,7 +4115,7 @@ function runWhy(argv: string[]): number {
|
|
|
3439
4115
|
})
|
|
3440
4116
|
if ('json' === format) {
|
|
3441
4117
|
process.stdout.write(exactJSON({
|
|
3442
|
-
aontu: { version: version(), verb: 'why' },
|
|
4118
|
+
aontu: { version: version(), verb: 'model why' },
|
|
3443
4119
|
findings: report.findings,
|
|
3444
4120
|
ok: report.ok,
|
|
3445
4121
|
...(null == report.record ? {} : { record: report.record }),
|
|
@@ -3477,7 +4153,7 @@ function renderWhyText(record: WhyRecord): string {
|
|
|
3477
4153
|
|
|
3478
4154
|
|
|
3479
4155
|
const SET_HELP =
|
|
3480
|
-
'aontu set <path>=<value> --entry <file> --overlay <file> (try --help)'
|
|
4156
|
+
'aontu model set <path>=<value> --entry <file> --overlay <file> (try --help)'
|
|
3481
4157
|
|
|
3482
4158
|
function runSet(argv: string[]): number {
|
|
3483
4159
|
const trusted = takeTrust(argv)
|
|
@@ -3520,7 +4196,7 @@ function runSet(argv: string[]): number {
|
|
|
3520
4196
|
format = f
|
|
3521
4197
|
}
|
|
3522
4198
|
else if (arg.startsWith('-')) {
|
|
3523
|
-
process.stderr.write(`aontu: unknown set option ${arg} (try --help)\n`)
|
|
4199
|
+
process.stderr.write(`aontu: unknown model set option ${arg} (try --help)\n`)
|
|
3524
4200
|
return 2
|
|
3525
4201
|
}
|
|
3526
4202
|
else {
|
|
@@ -3530,7 +4206,7 @@ function runSet(argv: string[]): number {
|
|
|
3530
4206
|
|
|
3531
4207
|
if (0 === assignments.length || null == entry || null == overlayFile) {
|
|
3532
4208
|
process.stderr.write(
|
|
3533
|
-
`aontu: set needs assignments, --entry and --overlay\n${SET_HELP}\n`)
|
|
4209
|
+
`aontu: model set needs assignments, --entry and --overlay\n${SET_HELP}\n`)
|
|
3534
4210
|
return 2
|
|
3535
4211
|
}
|
|
3536
4212
|
|
|
@@ -3582,7 +4258,7 @@ function runSet(argv: string[]): number {
|
|
|
3582
4258
|
|
|
3583
4259
|
if ('json' === format) {
|
|
3584
4260
|
process.stdout.write(exactJSON({
|
|
3585
|
-
aontu: { version: version(), verb: 'set' },
|
|
4261
|
+
aontu: { version: version(), verb: 'model set' },
|
|
3586
4262
|
appended: report.appended,
|
|
3587
4263
|
findings: report.findings,
|
|
3588
4264
|
overlay: report.overlay,
|
|
@@ -4082,17 +4758,38 @@ function fmtOne(
|
|
|
4082
4758
|
}
|
|
4083
4759
|
|
|
4084
4760
|
|
|
4761
|
+
// Where a verb writes. The package verbs are asynchronous, so a test
|
|
4762
|
+
// cannot capture the process streams around them; it hands these in.
|
|
4763
|
+
type Io = { out: (s: string) => void, err: (s: string) => void }
|
|
4764
|
+
|
|
4765
|
+
const PROCESS_IO: Io = {
|
|
4766
|
+
out: (s) => void process.stdout.write(s),
|
|
4767
|
+
err: (s) => void process.stderr.write(s),
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4085
4770
|
type Servers = {
|
|
4086
4771
|
lsp: () => void
|
|
4087
4772
|
mcp: (argv: string[]) => void
|
|
4773
|
+
// `pkg serve` runs until this settles: the process's SIGINT, or a
|
|
4774
|
+
// test's say-so.
|
|
4775
|
+
serve: (served: Served) => Promise<void>
|
|
4776
|
+
http: () => PkgHttp
|
|
4777
|
+
io?: Io
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// `pkg serve` runs until the process is interrupted.
|
|
4781
|
+
function serveUntilInterrupted(): Promise<void> {
|
|
4782
|
+
return new Promise((done) => process.once('SIGINT', () => done()))
|
|
4088
4783
|
}
|
|
4089
4784
|
|
|
4090
4785
|
// Excluded: the real pair takes the process stdio, so ts/test/cli.test.ts
|
|
4091
4786
|
// drives each server through a child process instead.
|
|
4092
|
-
/* node:coverage ignore next
|
|
4787
|
+
/* node:coverage ignore next 6 */
|
|
4093
4788
|
const SERVERS: Servers = {
|
|
4094
4789
|
lsp: () => void lspMain(),
|
|
4095
4790
|
mcp: (argv) => void mcpMain(undefined, undefined, undefined, undefined, argv),
|
|
4791
|
+
serve: serveUntilInterrupted,
|
|
4792
|
+
http: defaultHttp,
|
|
4096
4793
|
}
|
|
4097
4794
|
|
|
4098
4795
|
// undefined: the server took the process; a number: an answer the CLI
|
|
@@ -4416,10 +5113,10 @@ function runInit(argv: string[]): number {
|
|
|
4416
5113
|
|
|
4417
5114
|
|
|
4418
5115
|
const KNOWN_VERBS = [
|
|
4419
|
-
'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
|
|
4420
|
-
'help', 'init', 'jsonschema', 'lsp', 'mcp', '
|
|
4421
|
-
'relations', '
|
|
4422
|
-
'vet', 'view', 'why',
|
|
5116
|
+
'add', 'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
|
|
5117
|
+
'help', 'init', 'jsonschema', 'lsp', 'mcp', 'model', 'pkg', 'publish',
|
|
5118
|
+
'reaches', 'relations', 'remove', 'render', 'subsume', 'sync', 'template',
|
|
5119
|
+
'trace', 'trim', 'vet', 'view', 'why',
|
|
4423
5120
|
]
|
|
4424
5121
|
|
|
4425
5122
|
|
|
@@ -4514,20 +5211,22 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
4514
5211
|
return servers.mcp(argv.slice(3))
|
|
4515
5212
|
}
|
|
4516
5213
|
|
|
4517
|
-
if ('set' === argv[2]) {
|
|
4518
|
-
return finish(runSet(argv.slice(3)))
|
|
4519
|
-
}
|
|
4520
5214
|
|
|
4521
5215
|
if ('allow' === argv[2]) {
|
|
4522
5216
|
return finish(runAllow(argv.slice(3)))
|
|
4523
5217
|
}
|
|
4524
5218
|
|
|
4525
|
-
if ('
|
|
4526
|
-
return finish(
|
|
5219
|
+
if ('model' === argv[2]) {
|
|
5220
|
+
return finish(runModel(argv.slice(3)))
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
if ('pkg' === argv[2]) {
|
|
5224
|
+
const r = runPkg(argv.slice(3), servers)
|
|
5225
|
+
return 'number' === typeof r ? finish(r) : void r.then(finish)
|
|
4527
5226
|
}
|
|
4528
5227
|
|
|
4529
|
-
if (
|
|
4530
|
-
return
|
|
5228
|
+
if (PACKAGE_VERBS.includes(argv[2])) {
|
|
5229
|
+
return void runPackageVerb(argv[2], argv.slice(3), servers).then(finish)
|
|
4531
5230
|
}
|
|
4532
5231
|
|
|
4533
5232
|
if ('hash' === argv[2]) {
|
|
@@ -4548,10 +5247,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
4548
5247
|
return finish(runInit(argv.slice(3)))
|
|
4549
5248
|
}
|
|
4550
5249
|
|
|
4551
|
-
if ('mod' === argv[2]) {
|
|
4552
|
-
return finish(runMod(argv.slice(3)))
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
5250
|
if ('relations' === argv[2]) {
|
|
4556
5251
|
return finish(runRelations(argv.slice(3)))
|
|
4557
5252
|
}
|
|
@@ -4569,6 +5264,10 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
4569
5264
|
return finish(runTrace(argv.slice(3)))
|
|
4570
5265
|
}
|
|
4571
5266
|
|
|
5267
|
+
if ('render' === argv[2]) {
|
|
5268
|
+
return void runRender(argv.slice(3)).then(finish)
|
|
5269
|
+
}
|
|
5270
|
+
|
|
4572
5271
|
if ('reaches' === argv[2]) {
|
|
4573
5272
|
return finish(runReaches(argv.slice(3)))
|
|
4574
5273
|
}
|
|
@@ -4686,7 +5385,8 @@ export {
|
|
|
4686
5385
|
runJsonSchema,
|
|
4687
5386
|
runTemplate,
|
|
4688
5387
|
runTrace,
|
|
4689
|
-
|
|
5388
|
+
runRender,
|
|
5389
|
+
runPkg, runModel, runPackageVerb, pkgToolOptions, serveUntilInterrupted,
|
|
4690
5390
|
runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
|
|
4691
5391
|
looksLikeVerb,
|
|
4692
5392
|
KNOWN_VERBS,
|