@aid-framework/cli 0.0.0 → 0.1.48
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/LICENSE +201 -0
- package/README.md +225 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/args.d.ts +37 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +74 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/eval.d.ts +44 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +257 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/gen.d.ts +42 -0
- package/dist/commands/gen.d.ts.map +1 -0
- package/dist/commands/gen.js +138 -0
- package/dist/commands/gen.js.map +1 -0
- package/dist/commands/ingest.d.ts +44 -0
- package/dist/commands/ingest.d.ts.map +1 -0
- package/dist/commands/ingest.js +238 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +38 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +203 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/validate.d.ts +22 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +53 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/context.d.ts +37 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +36 -0
- package/dist/context.js.map +1 -0
- package/dist/diagnostics.d.ts +42 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +47 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/exit.d.ts +16 -0
- package/dist/exit.d.ts.map +1 -0
- package/dist/exit.js +15 -0
- package/dist/exit.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +25 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +155 -0
- package/dist/main.js.map +1 -0
- package/dist/output.d.ts +48 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +50 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +21 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/pipeline.d.ts +74 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +113 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/python.d.ts +30 -0
- package/dist/python.d.ts.map +1 -0
- package/dist/python.js +57 -0
- package/dist/python.js.map +1 -0
- package/dist/targets.d.ts +75 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +136 -0
- package/dist/targets.js.map +1 -0
- package/dist/usage.d.ts +20 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +49 -0
- package/dist/usage.js.map +1 -0
- package/package.json +49 -14
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid ingest` -- run the generated ingest entry point for a retriever's corpus.
|
|
3
|
+
*
|
|
4
|
+
* **What this shells out to, and why.** Indexing is a batch job, not a request, so the
|
|
5
|
+
* generator emits one runnable module per declared retriever at
|
|
6
|
+
* `app/generated/retrieval/ingest/<id>.py`, with a `main()` that chunks, embeds and writes
|
|
7
|
+
* the corpus. That module is the interface: it already knows the chunking and the store the
|
|
8
|
+
* spec declared, and re-deriving either here would be a second implementation of the
|
|
9
|
+
* runtime's rules. So the command runs `python -m app.generated.retrieval.ingest.<id>` from
|
|
10
|
+
* the project root, which is also what a person would type by hand.
|
|
11
|
+
*
|
|
12
|
+
* **The flag shape is a usage error, not a project verdict.** `--retriever` and `--all` are
|
|
13
|
+
* the two ways to say what to ingest; passing both, or neither, is a mistake in the
|
|
14
|
+
* invocation and exits 2 before the spec is even read. An id the spec does not declare is
|
|
15
|
+
* the same kind of mistake, and its message names the ids that do exist -- a typo should
|
|
16
|
+
* not require opening the spec.
|
|
17
|
+
*
|
|
18
|
+
* **A missing module is a failure, not a usage error.** `--retriever handbook` in a project
|
|
19
|
+
* that has never been generated is a well-formed command that cannot do its job; it exits 1
|
|
20
|
+
* and points at `aid gen`. Exit 2 would tell a script that it passed the wrong arguments,
|
|
21
|
+
* which is false and hides the one action that fixes it.
|
|
22
|
+
*
|
|
23
|
+
* **`--dry-run` is forwarded, not implemented here.** The generated module already grows
|
|
24
|
+
* its own `--dry-run`, which applies the chunker and reports the counts a real run would
|
|
25
|
+
* write without opening a store or calling an embedder. Answering the same question in
|
|
26
|
+
* the CLI would mean re-chunking the corpus, and a second dry run that drifted from the
|
|
27
|
+
* one the module performs is worse than no dry run at all.
|
|
28
|
+
*
|
|
29
|
+
* **Idempotence is the runtime's, not a rescan this command performs.** A chunk id is a
|
|
30
|
+
* content hash, and the store upserts on it, so a second run over unchanged documents
|
|
31
|
+
* replaces rather than appends. The CLI adds no diffing of its own: it would have to
|
|
32
|
+
* reimplement the chunker to know what changed.
|
|
33
|
+
*/
|
|
34
|
+
import { existsSync } from 'node:fs';
|
|
35
|
+
import { sortDiagnostics } from '@aid-framework/spec';
|
|
36
|
+
import { CLI_DIAGNOSTIC_CODES, cliError, cliWarning } from '../diagnostics.js';
|
|
37
|
+
import { EXIT_FAILURE, EXIT_OK, EXIT_USAGE } from '../exit.js';
|
|
38
|
+
import { outcome } from '../output.js';
|
|
39
|
+
import { absolutePath } from '../paths.js';
|
|
40
|
+
import { lowerSpec, readSpec } from '../pipeline.js';
|
|
41
|
+
import { discoverInterpreter, INTERPRETERS, interpreterCommand, invoke } from '../python.js';
|
|
42
|
+
import { resolveTarget, unknownTargetDiagnostic } from '../targets.js';
|
|
43
|
+
/** `Declared retrievers: a, b.`, or the truth when there are none. */
|
|
44
|
+
function declaredHint(ids) {
|
|
45
|
+
return ids.length > 0
|
|
46
|
+
? `Declared retrievers: ${ids.join(', ')}.`
|
|
47
|
+
: 'The spec declares no retrievers.';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The human rendering. One retriever prints the module's own report; several get a heading
|
|
51
|
+
* each, because two unlabelled "wrote N chunks" lines cannot be told apart.
|
|
52
|
+
*/
|
|
53
|
+
function render(results, stdout) {
|
|
54
|
+
const blocks = results.map((result, index) => {
|
|
55
|
+
const body = stdout[index]?.trimEnd() ?? '';
|
|
56
|
+
if (results.length === 1)
|
|
57
|
+
return body;
|
|
58
|
+
return [`==> ${result.retriever}`, body].filter((line) => line !== '').join('\n');
|
|
59
|
+
});
|
|
60
|
+
return blocks.filter((block) => block !== '').join('\n');
|
|
61
|
+
}
|
|
62
|
+
/** The `--retriever`/`--all` shape, which is decided without reading the project. */
|
|
63
|
+
function checkFlagShape(options) {
|
|
64
|
+
if (options.retriever !== undefined && options.all) {
|
|
65
|
+
return cliError({
|
|
66
|
+
code: CLI_DIAGNOSTIC_CODES.retrieverConflict,
|
|
67
|
+
message: '`--retriever` and `--all` say two different things; pass one of them.',
|
|
68
|
+
hint: 'Use `--retriever <id>` for one retriever, or `--all` for every retriever the spec declares.',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (options.retriever === undefined && !options.all) {
|
|
72
|
+
return cliError({
|
|
73
|
+
code: CLI_DIAGNOSTIC_CODES.retrieverRequired,
|
|
74
|
+
message: '`aid ingest` needs `--retriever <id>` or `--all`.',
|
|
75
|
+
hint: 'Use `--all` to ingest every retriever the spec declares.',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
export function ingest(context, options) {
|
|
81
|
+
const command = 'ingest';
|
|
82
|
+
const target = resolveTarget(options.target);
|
|
83
|
+
if (target === undefined) {
|
|
84
|
+
return outcome({
|
|
85
|
+
command,
|
|
86
|
+
exitCode: EXIT_USAGE,
|
|
87
|
+
diagnostics: [unknownTargetDiagnostic(options.target ?? '')],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// The flag shape is judged before the project is touched: an invocation that asks two
|
|
91
|
+
// contradictory things is wrong whether or not the spec happens to be readable, and
|
|
92
|
+
// reporting a spec problem instead would bury the actual mistake.
|
|
93
|
+
const malformed = checkFlagShape(options);
|
|
94
|
+
if (malformed !== undefined) {
|
|
95
|
+
return outcome({ command, exitCode: EXIT_USAGE, diagnostics: [malformed] });
|
|
96
|
+
}
|
|
97
|
+
const specPath = options.spec ?? target.specPath;
|
|
98
|
+
const fields = { target: target.name, spec: specPath };
|
|
99
|
+
const read = readSpec(context, specPath);
|
|
100
|
+
if (!read.ok) {
|
|
101
|
+
return outcome({ command, exitCode: EXIT_FAILURE, fields, diagnostics: read.diagnostics });
|
|
102
|
+
}
|
|
103
|
+
const lowered = lowerSpec(read.text, specPath);
|
|
104
|
+
if (!lowered.ok) {
|
|
105
|
+
return outcome({ command, exitCode: EXIT_FAILURE, fields, diagnostics: lowered.diagnostics });
|
|
106
|
+
}
|
|
107
|
+
const declared = lowered.ir.retrievers.map((entry) => entry.id);
|
|
108
|
+
if (options.retriever !== undefined && !declared.includes(options.retriever)) {
|
|
109
|
+
return outcome({
|
|
110
|
+
command,
|
|
111
|
+
exitCode: EXIT_USAGE,
|
|
112
|
+
fields: { ...fields, retrievers: declared },
|
|
113
|
+
diagnostics: [
|
|
114
|
+
cliError({
|
|
115
|
+
code: CLI_DIAGNOSTIC_CODES.retrieverNotFound,
|
|
116
|
+
message: `No retriever named "${options.retriever}" in ${specPath}.`,
|
|
117
|
+
path: [specPath],
|
|
118
|
+
hint: declaredHint(declared),
|
|
119
|
+
}),
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// `--all` over a project that declares none is a well-formed request with nothing to do.
|
|
124
|
+
// It is not an error: a generated app with no retrieval is a legitimate app, and making
|
|
125
|
+
// its CI fail would force a `--retriever` that does not exist.
|
|
126
|
+
const selected = options.all || options.retriever === undefined ? declared : [options.retriever];
|
|
127
|
+
const scoped = { ...fields, retrievers: selected };
|
|
128
|
+
if (selected.length === 0) {
|
|
129
|
+
return outcome({
|
|
130
|
+
command,
|
|
131
|
+
exitCode: EXIT_OK,
|
|
132
|
+
fields: { ...scoped, results: [] },
|
|
133
|
+
diagnostics: sortDiagnostics(lowered.diagnostics),
|
|
134
|
+
text: 'No retrievers are declared; nothing to ingest.',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// A declared retriever whose module is absent is a project that has not been generated,
|
|
138
|
+
// not a misuse of the command. Checked before discovery so the message is about the
|
|
139
|
+
// missing artefact rather than about the interpreter that would have run it.
|
|
140
|
+
const missing = selected.filter((id) => !existsSync(absolutePath(context.cwd, target.retrievalIngestModuleFile(id))));
|
|
141
|
+
if (missing.length > 0) {
|
|
142
|
+
return outcome({
|
|
143
|
+
command,
|
|
144
|
+
exitCode: EXIT_FAILURE,
|
|
145
|
+
fields: scoped,
|
|
146
|
+
diagnostics: [
|
|
147
|
+
cliError({
|
|
148
|
+
code: CLI_DIAGNOSTIC_CODES.ingestModulesMissing,
|
|
149
|
+
message: `No generated ingest module for ${missing.map((id) => `"${id}"`).join(', ')}.`,
|
|
150
|
+
path: missing.map((id) => target.retrievalIngestModuleFile(id)),
|
|
151
|
+
hint: 'Run `aid gen`: the ingest entry points are the modules the generator emits.',
|
|
152
|
+
}),
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (target.runner !== 'python') {
|
|
157
|
+
return outcome({
|
|
158
|
+
command,
|
|
159
|
+
// Exit 2, not 1: this command has no runner for this target, so nothing was
|
|
160
|
+
// ingested and the corpus was not judged.
|
|
161
|
+
exitCode: EXIT_USAGE,
|
|
162
|
+
fields: scoped,
|
|
163
|
+
diagnostics: [
|
|
164
|
+
cliError({
|
|
165
|
+
code: CLI_DIAGNOSTIC_CODES.runnerUnsupported,
|
|
166
|
+
message: `\`aid ingest\` runs generated Python modules, and ${target.name} emits TypeScript.`,
|
|
167
|
+
hint: 'The emitted ingest module exports `ingest()`; run it with Node.',
|
|
168
|
+
}),
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const interpreter = discoverInterpreter(context);
|
|
173
|
+
if (interpreter === undefined) {
|
|
174
|
+
return outcome({
|
|
175
|
+
command,
|
|
176
|
+
// Exit 2, not 1: nothing about the project was judged. Ingest has no verdict when the
|
|
177
|
+
// thing that would run it cannot start, and `1` would read as "the corpus was wrong".
|
|
178
|
+
exitCode: EXIT_USAGE,
|
|
179
|
+
fields: scoped,
|
|
180
|
+
diagnostics: [
|
|
181
|
+
cliError({
|
|
182
|
+
code: CLI_DIAGNOSTIC_CODES.pythonMissing,
|
|
183
|
+
message: `No interpreter could import aid_runtime (tried ${INTERPRETERS.join(', ')}).`,
|
|
184
|
+
hint: 'Install the runtime into the interpreter you run with, or run the generated ingest modules yourself.',
|
|
185
|
+
}),
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const base = interpreterCommand(interpreter);
|
|
190
|
+
const results = [];
|
|
191
|
+
const stdout = [];
|
|
192
|
+
const spoken = [];
|
|
193
|
+
const diagnostics = [...lowered.diagnostics];
|
|
194
|
+
for (const id of selected) {
|
|
195
|
+
const module = target.retrievalIngestModule(id);
|
|
196
|
+
// Appended after the module name so it lands as an argument to the module, which owns
|
|
197
|
+
// what a dry run means -- see the header note.
|
|
198
|
+
const run = invoke(context, [...base, '-m', module, ...(options.dryRun ? ['--dry-run'] : [])]);
|
|
199
|
+
if (run === undefined) {
|
|
200
|
+
return outcome({
|
|
201
|
+
command,
|
|
202
|
+
exitCode: EXIT_USAGE,
|
|
203
|
+
fields: { ...scoped, interpreter },
|
|
204
|
+
diagnostics: [
|
|
205
|
+
cliError({
|
|
206
|
+
code: CLI_DIAGNOSTIC_CODES.runnerFailed,
|
|
207
|
+
message: `Could not start ${interpreter} to run "${module}".`,
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
results.push({ retriever: id, module, status: run.status ?? EXIT_FAILURE });
|
|
213
|
+
stdout.push(run.stdout);
|
|
214
|
+
// The module reports what it could not ingest on stderr. It is surfaced rather than
|
|
215
|
+
// swallowed, and on the CLI's stderr even under `--json`, because that is where the
|
|
216
|
+
// process that wrote it wrote it.
|
|
217
|
+
if (run.stderr.trim() !== '') {
|
|
218
|
+
spoken.push(run.stderr.trimEnd());
|
|
219
|
+
diagnostics.push(cliWarning({
|
|
220
|
+
code: CLI_DIAGNOSTIC_CODES.runnerWarned,
|
|
221
|
+
message: `"${id}" wrote to stderr.`,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// A non-zero exit from a generated module is that module's verdict, so it propagates.
|
|
226
|
+
// Every selected retriever still runs: one corpus that could not be built should not
|
|
227
|
+
// hide the state of the others.
|
|
228
|
+
const failed = results.some((result) => result.status !== 0);
|
|
229
|
+
return outcome({
|
|
230
|
+
command,
|
|
231
|
+
exitCode: failed ? EXIT_FAILURE : EXIT_OK,
|
|
232
|
+
fields: { ...scoped, interpreter, results },
|
|
233
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
234
|
+
text: render(results, stdout),
|
|
235
|
+
stderr: spoken.join('\n'),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/commands/ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid init` — scaffold a project: a starter spec and a README.
|
|
3
|
+
*
|
|
4
|
+
* It does not generate. `aid gen` generates, and `--gen` is the convenience for the one
|
|
5
|
+
* thing a person types next; keeping the two apart is what makes "scaffold" and "compile"
|
|
6
|
+
* separately testable and separately re-runnable.
|
|
7
|
+
*
|
|
8
|
+
* Two deliberate refusals, because scaffolding is the command most likely to be pointed at
|
|
9
|
+
* a directory that already has something in it:
|
|
10
|
+
*
|
|
11
|
+
* - An existing spec is not overwritten without `--force`. That file is the project's
|
|
12
|
+
* source of truth; replacing it silently is the worst thing this command could do.
|
|
13
|
+
* - An existing README is left exactly as it is, and reported as skipped -- under `--force`
|
|
14
|
+
* too. A README is the file a directory is most likely to already have, its contents are
|
|
15
|
+
* the author's, and `--force` here is usually aimed at the spec.
|
|
16
|
+
*
|
|
17
|
+
* `--from <spec>` imports a spec rather than copying one: the imported text has to lower to
|
|
18
|
+
* an IR and pass the gates before anything is written, so `aid init --from` cannot produce a
|
|
19
|
+
* project that `aid validate` immediately rejects.
|
|
20
|
+
*/
|
|
21
|
+
import type { CliContext } from '../context.js';
|
|
22
|
+
import type { CommandOutcome } from '../output.js';
|
|
23
|
+
export interface InitOptions {
|
|
24
|
+
readonly dir: string | undefined;
|
|
25
|
+
readonly from: string | undefined;
|
|
26
|
+
readonly target: string | undefined;
|
|
27
|
+
readonly force: boolean;
|
|
28
|
+
readonly gen: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A `project.name` from a directory name. The spec's pattern is `^[a-z][a-z0-9-]{0,63}$`,
|
|
32
|
+
* and a directory called `My App` or `2024-prototype` is not a reason to fail `aid init`
|
|
33
|
+
* with a schema error about a field the caller never wrote.
|
|
34
|
+
*/
|
|
35
|
+
export declare function projectNameFor(directory: string): string;
|
|
36
|
+
export declare function starterSpec(target: string, projectName: string): string;
|
|
37
|
+
export declare function init(context: CliContext, options: InitOptions): CommandOutcome;
|
|
38
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQnD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AA0BD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAYvE;AA+CD,wBAAgB,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,GAAG,cAAc,CA8F9E"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid init` — scaffold a project: a starter spec and a README.
|
|
3
|
+
*
|
|
4
|
+
* It does not generate. `aid gen` generates, and `--gen` is the convenience for the one
|
|
5
|
+
* thing a person types next; keeping the two apart is what makes "scaffold" and "compile"
|
|
6
|
+
* separately testable and separately re-runnable.
|
|
7
|
+
*
|
|
8
|
+
* Two deliberate refusals, because scaffolding is the command most likely to be pointed at
|
|
9
|
+
* a directory that already has something in it:
|
|
10
|
+
*
|
|
11
|
+
* - An existing spec is not overwritten without `--force`. That file is the project's
|
|
12
|
+
* source of truth; replacing it silently is the worst thing this command could do.
|
|
13
|
+
* - An existing README is left exactly as it is, and reported as skipped -- under `--force`
|
|
14
|
+
* too. A README is the file a directory is most likely to already have, its contents are
|
|
15
|
+
* the author's, and `--force` here is usually aimed at the spec.
|
|
16
|
+
*
|
|
17
|
+
* `--from <spec>` imports a spec rather than copying one: the imported text has to lower to
|
|
18
|
+
* an IR and pass the gates before anything is written, so `aid init --from` cannot produce a
|
|
19
|
+
* project that `aid validate` immediately rejects.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { basename, join, resolve } from 'node:path';
|
|
23
|
+
import { CLI_DIAGNOSTIC_CODES, cliError } from '../diagnostics.js';
|
|
24
|
+
import { EXIT_FAILURE, EXIT_OK, EXIT_USAGE } from '../exit.js';
|
|
25
|
+
import { outcome } from '../output.js';
|
|
26
|
+
import { absolutePath, parentPath } from '../paths.js';
|
|
27
|
+
import { lowerSpec, readSpec } from '../pipeline.js';
|
|
28
|
+
import { resolveTarget, unknownTargetDiagnostic } from '../targets.js';
|
|
29
|
+
import { generate } from './gen.js';
|
|
30
|
+
/**
|
|
31
|
+
* Lower-case a directory name, keep only `[a-z0-9]`, and collapse every run of anything
|
|
32
|
+
* else into a single `-`.
|
|
33
|
+
*
|
|
34
|
+
* A loop rather than a regular expression on purpose: trimming with `/^-+|-+$/g` after
|
|
35
|
+
* collapsing is quadratic on a name that is mostly separators, and the directory name is
|
|
36
|
+
* input this command does not control. Emitting the separator only between two kept
|
|
37
|
+
* characters means the result can never begin or end with one, so no trim pass is needed.
|
|
38
|
+
*/
|
|
39
|
+
function slugify(name) {
|
|
40
|
+
let slug = '';
|
|
41
|
+
let separatorPending = false;
|
|
42
|
+
for (const character of name.toLowerCase()) {
|
|
43
|
+
if (!((character >= 'a' && character <= 'z') || (character >= '0' && character <= '9'))) {
|
|
44
|
+
separatorPending = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (separatorPending && slug !== '')
|
|
48
|
+
slug += '-';
|
|
49
|
+
separatorPending = false;
|
|
50
|
+
slug += character;
|
|
51
|
+
}
|
|
52
|
+
return slug;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A `project.name` from a directory name. The spec's pattern is `^[a-z][a-z0-9-]{0,63}$`,
|
|
56
|
+
* and a directory called `My App` or `2024-prototype` is not a reason to fail `aid init`
|
|
57
|
+
* with a schema error about a field the caller never wrote.
|
|
58
|
+
*/
|
|
59
|
+
export function projectNameFor(directory) {
|
|
60
|
+
const slug = slugify(basename(directory)).slice(0, 64);
|
|
61
|
+
if (slug === '')
|
|
62
|
+
return 'app';
|
|
63
|
+
const first = slug.charAt(0);
|
|
64
|
+
return first >= 'a' && first <= 'z' ? slug : `app-${slug}`.slice(0, 64);
|
|
65
|
+
}
|
|
66
|
+
export function starterSpec(target, projectName) {
|
|
67
|
+
return [
|
|
68
|
+
'specVersion: "0.1"',
|
|
69
|
+
'',
|
|
70
|
+
'# This file is the source of truth: `aid gen` compiles it into app/generated/,',
|
|
71
|
+
'# `aid validate` checks it, and `aid eval` gates the behaviour it declares.',
|
|
72
|
+
'project:',
|
|
73
|
+
` name: ${projectName}`,
|
|
74
|
+
' targets:',
|
|
75
|
+
` - ${target}`,
|
|
76
|
+
'',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
function starterReadme(projectName, target) {
|
|
80
|
+
return [
|
|
81
|
+
`# ${projectName}`,
|
|
82
|
+
'',
|
|
83
|
+
`An AID Framework project targeting \`${target.name}\`. \`${target.specPath}\` is the source`,
|
|
84
|
+
'of truth: the types, prompts, tools, pipelines and evals the application is made of.',
|
|
85
|
+
'',
|
|
86
|
+
'```bash',
|
|
87
|
+
'aid validate # parse the spec, reduce it to the IR, run every build-time gate',
|
|
88
|
+
'aid gen # compile the spec and reconcile the tree with it',
|
|
89
|
+
'aid gen --dry-run # report what would change, and write nothing',
|
|
90
|
+
'aid eval # run the eval gate',
|
|
91
|
+
'```',
|
|
92
|
+
'',
|
|
93
|
+
`\`${target.generatedDir}/\` is owned by the generator: \`aid gen\` writes it, and refuses to`,
|
|
94
|
+
'overwrite it once you have edited it, keeping the rejected bytes beside the file.',
|
|
95
|
+
`\`${target.businessDir}/\` is yours: \`aid gen\` creates a file there once and never touches it`,
|
|
96
|
+
'again.',
|
|
97
|
+
'',
|
|
98
|
+
].join('\n');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a file, reporting whether this call is the one that created it.
|
|
102
|
+
*
|
|
103
|
+
* The exclusive flag makes the create itself the existence check, so a file that appears
|
|
104
|
+
* between a probe and a write cannot be clobbered -- a check-then-act refusal is not a
|
|
105
|
+
* refusal. Returns `false` when the path already exists, and rethrows anything else.
|
|
106
|
+
*/
|
|
107
|
+
function createExclusively(path, data) {
|
|
108
|
+
try {
|
|
109
|
+
writeFileSync(path, data, { encoding: 'utf8', flag: 'wx' });
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (isAlreadyExists(error))
|
|
114
|
+
return false;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function isAlreadyExists(error) {
|
|
119
|
+
return (typeof error === 'object' && error !== null && error.code === 'EEXIST');
|
|
120
|
+
}
|
|
121
|
+
export function init(context, options) {
|
|
122
|
+
const command = 'init';
|
|
123
|
+
const target = resolveTarget(options.target);
|
|
124
|
+
if (target === undefined) {
|
|
125
|
+
return outcome({
|
|
126
|
+
command,
|
|
127
|
+
exitCode: EXIT_USAGE,
|
|
128
|
+
diagnostics: [unknownTargetDiagnostic(options.target ?? '')],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const root = options.dir === undefined ? context.cwd : resolve(context.cwd, options.dir);
|
|
132
|
+
const specPath = target.specPath;
|
|
133
|
+
const displayed = options.dir ?? '.';
|
|
134
|
+
const fields = { dir: displayed, target: target.name, spec: specPath };
|
|
135
|
+
const source = options.from === undefined
|
|
136
|
+
? { ok: true, text: starterSpec(target.name, projectNameFor(root)) }
|
|
137
|
+
: readSpec(context, options.from);
|
|
138
|
+
if (!source.ok) {
|
|
139
|
+
return outcome({ command, exitCode: EXIT_FAILURE, fields, diagnostics: source.diagnostics });
|
|
140
|
+
}
|
|
141
|
+
// Checked against the path the spec will actually live at, so the diagnostics carry the
|
|
142
|
+
// file the reader will open rather than the one it was imported from.
|
|
143
|
+
const lowered = lowerSpec(source.text, specPath);
|
|
144
|
+
if (!lowered.ok) {
|
|
145
|
+
return outcome({ command, exitCode: EXIT_FAILURE, fields, diagnostics: lowered.diagnostics });
|
|
146
|
+
}
|
|
147
|
+
const specAbsolute = absolutePath(root, specPath);
|
|
148
|
+
const written = [];
|
|
149
|
+
const skipped = [];
|
|
150
|
+
mkdirSync(absolutePath(root, parentPath(specPath)), { recursive: true });
|
|
151
|
+
// Created exclusively so that "does it already exist" and "write it" are one syscall. The
|
|
152
|
+
// refusal holds even if the file appears between a check and a write, and no separate
|
|
153
|
+
// `existsSync` has to stay true for the guarantee to mean anything. `--force` is the one
|
|
154
|
+
// path meant to replace the file, so it writes normally; nothing else reaches the write.
|
|
155
|
+
if (!createExclusively(specAbsolute, source.text)) {
|
|
156
|
+
if (!options.force) {
|
|
157
|
+
return outcome({
|
|
158
|
+
command,
|
|
159
|
+
exitCode: EXIT_FAILURE,
|
|
160
|
+
fields,
|
|
161
|
+
diagnostics: [
|
|
162
|
+
cliError({
|
|
163
|
+
code: CLI_DIAGNOSTIC_CODES.refusingToOverwrite,
|
|
164
|
+
message: `"${specPath}" already exists.`,
|
|
165
|
+
path: [specPath],
|
|
166
|
+
hint: 'Pass --force to overwrite it, or run `aid gen` to regenerate from the spec that is already there.',
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
writeFileSync(specAbsolute, source.text, 'utf8');
|
|
172
|
+
}
|
|
173
|
+
written.push(specPath);
|
|
174
|
+
// A README is the one file written here whose contents are the caller's, so it is
|
|
175
|
+
// created once and then never replaced -- not even under `--force`, which is passed
|
|
176
|
+
// routinely just to overwrite a spec. Replacing prose a person wrote as a side effect
|
|
177
|
+
// of resetting a spec is not what `--force` was asked for.
|
|
178
|
+
const readmeAbsolute = join(root, 'README.md');
|
|
179
|
+
if (createExclusively(readmeAbsolute, starterReadme(projectNameFor(root), target))) {
|
|
180
|
+
written.push('README.md');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
skipped.push('README.md');
|
|
184
|
+
}
|
|
185
|
+
// The generation runs against the scaffolded project root, not the caller's working
|
|
186
|
+
// directory: `aid init sub/project --gen` must generate into `sub/project`.
|
|
187
|
+
const generation = options.gen
|
|
188
|
+
? generate({ ...context, cwd: root }, { spec: specPath, target: target.name, dryRun: false, force: false })
|
|
189
|
+
: undefined;
|
|
190
|
+
const text = [
|
|
191
|
+
...written.map((path) => `created ${path}`),
|
|
192
|
+
...skipped.map((path) => `kept ${path} (already there; it is never replaced)`),
|
|
193
|
+
...(generation === undefined ? ['next: aid validate, then aid gen'] : [generation.text]),
|
|
194
|
+
].filter((line) => line !== '');
|
|
195
|
+
return outcome({
|
|
196
|
+
command,
|
|
197
|
+
exitCode: generation?.exitCode ?? EXIT_OK,
|
|
198
|
+
diagnostics: [...lowered.diagnostics, ...(generation?.diagnostics ?? [])],
|
|
199
|
+
text: text.join('\n'),
|
|
200
|
+
fields: { ...fields, written, skipped, generation: generation?.report ?? null },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpD,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAUpC;;;;;;;;GAQG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;YACxF,gBAAgB,GAAG,IAAI,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,gBAAgB,IAAI,IAAI,KAAK,EAAE;YAAE,IAAI,IAAI,GAAG,CAAC;QACjD,gBAAgB,GAAG,KAAK,CAAC;QACzB,IAAI,IAAI,SAAS,CAAC;IACpB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC7B,OAAO,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,WAAmB;IAC7D,OAAO;QACL,oBAAoB;QACpB,EAAE;QACF,gFAAgF;QAChF,6EAA6E;QAC7E,UAAU;QACV,WAAW,WAAW,EAAE;QACxB,YAAY;QACZ,SAAS,MAAM,EAAE;QACjB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,WAAmB,EAAE,MAAc;IACxD,OAAO;QACL,KAAK,WAAW,EAAE;QAClB,EAAE;QACF,wCAAwC,MAAM,CAAC,IAAI,SAAS,MAAM,CAAC,QAAQ,kBAAkB;QAC7F,sFAAsF;QACtF,EAAE;QACF,SAAS;QACT,sFAAsF;QACtF,uEAAuE;QACvE,mEAAmE;QACnE,yCAAyC;QACzC,KAAK;QACL,EAAE;QACF,KAAK,MAAM,CAAC,YAAY,sEAAsE;QAC9F,mFAAmF;QACnF,KAAK,MAAM,CAAC,WAAW,0EAA0E;QACjG,QAAQ;QACR,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,IAAY;IACnD,IAAI,CAAC;QACH,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,eAAe,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAC/F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAAmB,EAAE,OAAoB;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC;IACvB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC;YACb,OAAO;YACP,QAAQ,EAAE,UAAU;YACpB,WAAW,EAAE,CAAC,uBAAuB,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACzF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;IACrC,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAEvE,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,KAAK,SAAS;QACxB,CAAC,CAAC,EAAE,EAAE,EAAE,IAAa,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE;QAC7E,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,wFAAwF;IACxF,sEAAsE;IACtE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzE,0FAA0F;IAC1F,sFAAsF;IACtF,yFAAyF;IACzF,yFAAyF;IACzF,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,OAAO,OAAO,CAAC;gBACb,OAAO;gBACP,QAAQ,EAAE,YAAY;gBACtB,MAAM;gBACN,WAAW,EAAE;oBACX,QAAQ,CAAC;wBACP,IAAI,EAAE,oBAAoB,CAAC,mBAAmB;wBAC9C,OAAO,EAAE,IAAI,QAAQ,mBAAmB;wBACxC,IAAI,EAAE,CAAC,QAAQ,CAAC;wBAChB,IAAI,EAAE,mGAAmG;qBAC1G,CAAC;iBACH;aACF,CAAC,CAAC;QACL,CAAC;QACD,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEvB,kFAAkF;IAClF,oFAAoF;IACpF,sFAAsF;IACtF,2DAA2D;IAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC/C,IAAI,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IAED,oFAAoF;IACpF,4EAA4E;IAC5E,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG;QAC5B,CAAC,CAAC,QAAQ,CACN,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EACzB,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CACrE;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,IAAI,GAAG;QACX,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC;QAC3C,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,wCAAwC,CAAC;QAC9E,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;KACzF,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAEhC,OAAO,OAAO,CAAC;QACb,OAAO;QACP,QAAQ,EAAE,UAAU,EAAE,QAAQ,IAAI,OAAO;QACzC,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;QACzE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACrB,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,IAAI,IAAI,EAAE;KAChF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid validate` — parse the spec, reduce it to the IR, and run every build-time gate.
|
|
3
|
+
*
|
|
4
|
+
* This is the "is this spec buildable" question, asked without touching the tree and
|
|
5
|
+
* without asking the target to compile anything. It stops at the first stage that
|
|
6
|
+
* produced an error, because lowering a spec whose own schema did not validate reports a
|
|
7
|
+
* cascade of consequences of the first mistake and buries it. Warnings from the stages
|
|
8
|
+
* that did run are kept and printed: the eval-coverage gate warns by design, and a
|
|
9
|
+
* validation that swallowed warnings would make a deliberate signal invisible.
|
|
10
|
+
*
|
|
11
|
+
* `aid gen` asks the same question and then goes further, into the target's compiler and
|
|
12
|
+
* its plan. So a spec can pass `aid validate` and still fail `aid gen` -- that is the
|
|
13
|
+
* honest split, and it is why the two commands report their own stage rather than
|
|
14
|
+
* pretending to be one command with a flag.
|
|
15
|
+
*/
|
|
16
|
+
import type { CliContext } from '../context.js';
|
|
17
|
+
import type { CommandOutcome } from '../output.js';
|
|
18
|
+
export interface ValidateOptions {
|
|
19
|
+
readonly spec: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
export declare function validate(context: CliContext, options: ValidateOptions): CommandOutcome;
|
|
22
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKnD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAmCtF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aid validate` — parse the spec, reduce it to the IR, and run every build-time gate.
|
|
3
|
+
*
|
|
4
|
+
* This is the "is this spec buildable" question, asked without touching the tree and
|
|
5
|
+
* without asking the target to compile anything. It stops at the first stage that
|
|
6
|
+
* produced an error, because lowering a spec whose own schema did not validate reports a
|
|
7
|
+
* cascade of consequences of the first mistake and buries it. Warnings from the stages
|
|
8
|
+
* that did run are kept and printed: the eval-coverage gate warns by design, and a
|
|
9
|
+
* validation that swallowed warnings would make a deliberate signal invisible.
|
|
10
|
+
*
|
|
11
|
+
* `aid gen` asks the same question and then goes further, into the target's compiler and
|
|
12
|
+
* its plan. So a spec can pass `aid validate` and still fail `aid gen` -- that is the
|
|
13
|
+
* honest split, and it is why the two commands report their own stage rather than
|
|
14
|
+
* pretending to be one command with a flag.
|
|
15
|
+
*/
|
|
16
|
+
import { countBySeverity } from '@aid-framework/spec';
|
|
17
|
+
import { EXIT_FAILURE, EXIT_OK } from '../exit.js';
|
|
18
|
+
import { outcome } from '../output.js';
|
|
19
|
+
import { lowerSpec, readSpec } from '../pipeline.js';
|
|
20
|
+
import { DEFAULT_SPEC_PATH } from '../targets.js';
|
|
21
|
+
export function validate(context, options) {
|
|
22
|
+
const command = 'validate';
|
|
23
|
+
const specPath = options.spec ?? DEFAULT_SPEC_PATH;
|
|
24
|
+
const read = readSpec(context, specPath);
|
|
25
|
+
if (!read.ok) {
|
|
26
|
+
return outcome({
|
|
27
|
+
command,
|
|
28
|
+
exitCode: EXIT_FAILURE,
|
|
29
|
+
fields: { spec: specPath },
|
|
30
|
+
diagnostics: read.diagnostics,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const lowered = lowerSpec(read.text, specPath);
|
|
34
|
+
const counts = countBySeverity(lowered.diagnostics);
|
|
35
|
+
const fields = { spec: specPath, counts };
|
|
36
|
+
if (!lowered.ok) {
|
|
37
|
+
return outcome({
|
|
38
|
+
command,
|
|
39
|
+
exitCode: EXIT_FAILURE,
|
|
40
|
+
fields,
|
|
41
|
+
diagnostics: lowered.diagnostics,
|
|
42
|
+
text: `${specPath}: ${counts.error} error(s), ${counts.warning} warning(s)`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return outcome({
|
|
46
|
+
command,
|
|
47
|
+
exitCode: EXIT_OK,
|
|
48
|
+
fields,
|
|
49
|
+
diagnostics: lowered.diagnostics,
|
|
50
|
+
text: `${specPath}: ok (${counts.error} errors, ${counts.warning} warnings)`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAMlD,MAAM,UAAU,QAAQ,CAAC,OAAmB,EAAE,OAAwB;IACpE,MAAM,OAAO,GAAG,UAAU,CAAC;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,IAAI,iBAAiB,CAAC;IAEnD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,OAAO,CAAC;YACb,OAAO;YACP,QAAQ,EAAE,YAAY;YACtB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC1B,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAE1C,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,OAAO,CAAC;YACb,OAAO;YACP,QAAQ,EAAE,YAAY;YACtB,MAAM;YACN,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,IAAI,EAAE,GAAG,QAAQ,KAAK,MAAM,CAAC,KAAK,cAAc,MAAM,CAAC,OAAO,aAAa;SAC5E,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;QACb,OAAO;QACP,QAAQ,EAAE,OAAO;QACjB,MAAM;QACN,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,IAAI,EAAE,GAAG,QAAQ,SAAS,MAAM,CAAC,KAAK,YAAY,MAAM,CAAC,OAAO,YAAY;KAC7E,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the CLI touches outside of itself, behind one injectable object.
|
|
3
|
+
*
|
|
4
|
+
* A CLI is mostly side effects, and side effects are what makes a command hard to test.
|
|
5
|
+
* Naming them in one place keeps every command a pure function of its arguments and
|
|
6
|
+
* this context, so a test can run a whole command against a temporary directory and
|
|
7
|
+
* assert on what it printed, what it wrote, and what it exited with -- without a
|
|
8
|
+
* subprocess, and without a mock for the parts that are not I/O.
|
|
9
|
+
*/
|
|
10
|
+
export interface SpawnOptions {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
/** `inherit` streams the child's output straight through; `pipe` captures it. */
|
|
13
|
+
readonly stdio: 'inherit' | 'pipe';
|
|
14
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
15
|
+
/** Written to the child's stdin, then closed. Used for the baseline recipe. */
|
|
16
|
+
readonly stdin?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SpawnResult {
|
|
19
|
+
/** The exit status, or `null` when the child never ran or died by signal. */
|
|
20
|
+
readonly status: number | null;
|
|
21
|
+
readonly stdout: string;
|
|
22
|
+
readonly stderr: string;
|
|
23
|
+
/** Set when the child could not be executed at all, such as a missing binary. */
|
|
24
|
+
readonly error?: Error;
|
|
25
|
+
}
|
|
26
|
+
export type Spawn = (command: string, args: readonly string[], options: SpawnOptions) => SpawnResult;
|
|
27
|
+
export interface CliContext {
|
|
28
|
+
/** The project root: every repository-relative path resolves against this. */
|
|
29
|
+
readonly cwd: string;
|
|
30
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
31
|
+
readonly stdout: (text: string) => void;
|
|
32
|
+
readonly stderr: (text: string) => void;
|
|
33
|
+
readonly spawn: Spawn;
|
|
34
|
+
}
|
|
35
|
+
/** The real world: the process streams, the process environment, and real children. */
|
|
36
|
+
export declare function createContext(overrides?: Partial<CliContext>): CliContext;
|
|
37
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,KAAK,GAAG,CAClB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,YAAY,KAClB,WAAW,CAAC;AAEjB,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAkBD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAQ7E"}
|