@starci/cli 1.1.0 → 1.2.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/CHANGELOG.md +4 -0
- package/completions/_starci +99 -26
- package/completions/starci.bash +96 -23
- package/completions/starci.fish +105 -10
- package/completions/starci.ps1 +96 -23
- package/package.json +2 -2
- package/src/catalog.generated.mjs +1401 -288
- package/src/main.mjs +11 -5
- package/src/runtime-install.generated.mjs +1 -1
- package/src/runtime-target.mjs +41 -0
- package/src/validate-args.mjs +18 -5
package/src/main.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { retargetNotice, targetOf, treeTargetRefusal } from './runtime-target.mjs';
|
|
6
7
|
import { foreignRuntimeNotice, locateRuntime, ownRuntimeRoot, runtimeEntryOf } from './runtime-locate.mjs';
|
|
7
8
|
import { runtimeEnv } from './shim.mjs';
|
|
8
9
|
|
|
@@ -202,11 +203,16 @@ const hfsDispatch = async (split, group, args, { io, cwd, stdout, stderr }) => {
|
|
|
202
203
|
};
|
|
203
204
|
|
|
204
205
|
/** Spawn the located runtime for the resolved verb; its exit status is the command's. */
|
|
205
|
-
const runRuntime = (split, args, { io, cwd, env, home, stderr }) => {
|
|
206
|
+
const runRuntime = (split, args, { io, cwd, env, home, stderr, command }) => {
|
|
206
207
|
const skipped = [];
|
|
207
|
-
const
|
|
208
|
-
if (!
|
|
209
|
-
const
|
|
208
|
+
const record = (io.locateRuntime ?? locateRuntime)({ cwd, env, skipped, ...(home ? { home } : {}) });
|
|
209
|
+
if (!record) return noRuntime(stderr, split.group, skipped);
|
|
210
|
+
const own = (io.ownRuntimeRoot ?? ownRuntimeRoot)();
|
|
211
|
+
const { located, retargeted } = targetOf({ command, args, located: record, own });
|
|
212
|
+
if (retargeted) writeTo(stderr, retargetNotice(retargeted, own));
|
|
213
|
+
const refused = treeTargetRefusal({ command, args, located, own });
|
|
214
|
+
if (refused) return fail(stderr, refused.message, 2);
|
|
215
|
+
const notice = foreignRuntimeNotice(located, own);
|
|
210
216
|
if (notice) writeTo(stderr, notice);
|
|
211
217
|
const spawn = io.spawn ?? spawnSync;
|
|
212
218
|
let result;
|
|
@@ -268,5 +274,5 @@ export async function main(argv = process.argv.slice(2), io = {}) {
|
|
|
268
274
|
if (runtimeOut !== null) return runtimeOut;
|
|
269
275
|
const hfsOut = await hfsDispatch(split, group, args, { ...ctx, cwd });
|
|
270
276
|
if (hfsOut !== null) return hfsOut;
|
|
271
|
-
return runRuntime(split, args, { ...ctx, cwd });
|
|
277
|
+
return runRuntime(split, args, { ...ctx, cwd, command });
|
|
272
278
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/cli/gen-catalog.mjs — do not edit
|
|
2
|
-
export const RUNTIME_VERSION = "1.0.0-alpha.
|
|
2
|
+
export const RUNTIME_VERSION = "1.0.0-alpha.9";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
/** The code a verb that writes the runtime tree is refused with when it would write a tree other than the CLI's own. */
|
|
4
|
+
export const RUNTIME_TREE_FOREIGN = 'RUNTIME_TREE_TARGET_FOREIGN';
|
|
5
|
+
|
|
6
|
+
const sameRoot = (a, b) => (process.platform === 'win32' ? path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase() : path.resolve(a) === path.resolve(b));
|
|
7
|
+
|
|
8
|
+
/** Whether this invocation of `command` writes the runtime tree: its `writesRuntimeTree` is true (always), a flag name that makes it write, or `!flag` (writes unless that flag is given). */
|
|
9
|
+
function writesTree(command, args) {
|
|
10
|
+
const mark = command?.writesRuntimeTree;
|
|
11
|
+
if (mark === true) return true;
|
|
12
|
+
if (typeof mark !== 'string') return false;
|
|
13
|
+
return mark.startsWith('!') ? !args.includes(`--${mark.slice(1)}`) : args.includes(`--${mark}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The refusal of a verb that writes the runtime tree while the located runtime is not the tree that owns the running CLI and nothing chose it
|
|
18
|
+
* explicitly (STARCI_RUNTIME, or the verb's own --root). The per-user record outranks the checkout for reading, so a clone's bin would otherwise
|
|
19
|
+
* write the live tree. Null when the verb does not write the tree, the CLI is not in a runtime, or the target is explicit or the CLI's own. Pure.
|
|
20
|
+
*/
|
|
21
|
+
export function treeTargetRefusal({ command, args, located, own }) {
|
|
22
|
+
if (!located || !own || !writesTree(command, args)) return null;
|
|
23
|
+
if (located.source === 'STARCI_RUNTIME' || args.includes('--root') || sameRoot(located.root, own)) return null;
|
|
24
|
+
return { code: RUNTIME_TREE_FOREIGN, message: `${RUNTIME_TREE_FOREIGN}: this verb writes the runtime tree, and the located runtime ${located.root} (${located.source}) is not the tree ${own} that owns this CLI; set STARCI_RUNTIME=${own} (or pass --root) to say which tree to write` };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The explicit runtime choices that outrank a verb's side: STARCI_RUNTIME, or the verb's own --root. */
|
|
28
|
+
const chosenExplicitly = (located, args) => located.source === 'STARCI_RUNTIME' || args.includes('--root');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The runtime a verb runs: a tree-side verb (one whose result is about the runtime tree that owns the running bin: a check, a test selection, a receipt, a catalog
|
|
32
|
+
* regeneration) runs THIS checkout unless a tree was chosen explicitly, so its verdict is about the tree it ran in; a host-side verb runs the located runtime (the
|
|
33
|
+
* per-user record included). {located, retargeted}: `retargeted` carries the runtime the record pointed at when this checkout replaced it. Pure.
|
|
34
|
+
*/
|
|
35
|
+
export function targetOf({ command, args, located, own }) {
|
|
36
|
+
if (!located || !own || command?.runtimeSide !== 'tree' || chosenExplicitly(located, args) || sameRoot(located.root, own)) return { located, retargeted: null };
|
|
37
|
+
return { located: { root: own, source: 'own-checkout' }, retargeted: located };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The one stderr line saying a tree-side verb ran this checkout although the located runtime is another tree. */
|
|
41
|
+
export const retargetNotice = (retargeted, own) => `starci: this verb is about the runtime tree that owns this CLI, so it ran ${own}, not ${retargeted.root} (${retargeted.source}); set STARCI_RUNTIME to judge another tree\n`;
|
package/src/validate-args.mjs
CHANGED
|
@@ -31,15 +31,24 @@ const typedValue = (flag, raw) => {
|
|
|
31
31
|
return raw;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/** The values a list flag takes after its first one when the verb has no positional slot: every following token that is no option (`--knowledge a b c`), as the contracts write it. */
|
|
35
|
+
const listTail = (argv, index, flag, verb) => {
|
|
36
|
+
if (flag.type !== 'list' || (verb.positional ?? []).length > 0) return [];
|
|
37
|
+
const tail = [];
|
|
38
|
+
while (index + 2 + tail.length < argv.length && !argv[index + 2 + tail.length].startsWith('-')) tail.push(argv[index + 2 + tail.length]);
|
|
39
|
+
return tail;
|
|
40
|
+
};
|
|
41
|
+
|
|
34
42
|
/** One `--name[=value]` token: type-check it into values/global, or pass it through to localArgs. */
|
|
35
|
-
const applyOption = (argv, index, token, { known, values, global, localArgs }) => {
|
|
43
|
+
const applyOption = (argv, index, token, { known, internal, values, global, localArgs, verb }) => {
|
|
36
44
|
const name = optionName(token);
|
|
37
45
|
const flag = known.get(name);
|
|
38
|
-
if (!flag) throw new Error(`unknown option --${name}`);
|
|
46
|
+
if (!flag) throw new Error(internal.has(name) ? `unknown option --${name}: it is a call of the runtime itself, not a CLI option; ${internal.get(name)}` : `unknown option --${name}`);
|
|
39
47
|
const { value: raw, consumed } = optionValue(argv, index, flag, known);
|
|
40
48
|
const value = typedValue(flag, raw);
|
|
49
|
+
const tail = consumed ? listTail(argv, index, flag, verb) : [];
|
|
41
50
|
if (flag.type === 'list') {
|
|
42
|
-
values[name] = [...(values[name] ?? []), value];
|
|
51
|
+
values[name] = [...(values[name] ?? []), value, ...tail];
|
|
43
52
|
} else if (Object.hasOwn(values, name)) {
|
|
44
53
|
throw new Error(`--${name} may be given only once`);
|
|
45
54
|
} else {
|
|
@@ -49,8 +58,10 @@ const applyOption = (argv, index, token, { known, values, global, localArgs }) =
|
|
|
49
58
|
else {
|
|
50
59
|
localArgs.push(token);
|
|
51
60
|
if (consumed) localArgs.push(argv[index + consumed]);
|
|
61
|
+
// each further value of a list flag reaches the handler as a repeated option, the form every list-flag handler parses
|
|
62
|
+
for (const extra of tail) localArgs.push(token, extra);
|
|
52
63
|
}
|
|
53
|
-
return consumed;
|
|
64
|
+
return consumed + tail.length;
|
|
54
65
|
};
|
|
55
66
|
|
|
56
67
|
/** Every required local option must have been given. */
|
|
@@ -121,6 +132,8 @@ export function validateArgs(argv, verb, globalFlags = []) {
|
|
|
121
132
|
const globals = new Map(globalFlags.map((flag) => [flag.name, { ...flag, global: true }]));
|
|
122
133
|
const locals = new Map((verb.flags ?? []).map((flag) => [flag.name, flag]));
|
|
123
134
|
const known = new Map([...globals, ...locals]);
|
|
135
|
+
// Flags the runtime's own scripts take and the CLI refuses, each with the pointer a person is given instead.
|
|
136
|
+
const internal = new Map((verb.internalFlags ?? []).map((entry) => [entry.name, entry.pointer]));
|
|
124
137
|
const values = {};
|
|
125
138
|
const global = {};
|
|
126
139
|
const localArgs = [];
|
|
@@ -142,7 +155,7 @@ export function validateArgs(argv, verb, globalFlags = []) {
|
|
|
142
155
|
continue;
|
|
143
156
|
}
|
|
144
157
|
if (token.startsWith('--')) {
|
|
145
|
-
index += applyOption(argv, index, token, { known, values, global, localArgs });
|
|
158
|
+
index += applyOption(argv, index, token, { known, internal, values, global, localArgs, verb });
|
|
146
159
|
continue;
|
|
147
160
|
}
|
|
148
161
|
if (token.startsWith('-')) throw new Error(`unknown option ${token}`);
|