@cplieger/deadset-ts 8.0.0-dev.12 → 8.0.0-dev.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +2 -0
  2. package/THIRD_PARTY_NOTICES.md +27 -0
  3. package/dist/src/build-scripts.js +6 -13
  4. package/dist/src/commands/read.js +156 -0
  5. package/dist/src/commands/shell.js +75 -0
  6. package/dist/src/commands/tools.js +460 -0
  7. package/dist/src/configuration-files.js +11 -25
  8. package/dist/src/conformance-record.js +1 -1
  9. package/dist/src/dependencies.js +67 -115
  10. package/dist/src/load/synthesize.js +12 -5
  11. package/dist/src/manifest.js +27 -14
  12. package/dist/src/roots.js +18 -11
  13. package/dist/src/text/named-files.js +24 -3
  14. package/dist/src/text/yaml.js +464 -173
  15. package/dist/src/vendor/unbash/ansi-c.js +132 -0
  16. package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
  17. package/dist/src/vendor/unbash/arithmetic.js +591 -0
  18. package/dist/src/vendor/unbash/assignment.js +131 -0
  19. package/dist/src/vendor/unbash/chars.js +48 -0
  20. package/dist/src/vendor/unbash/command.js +41 -0
  21. package/dist/src/vendor/unbash/expansion.js +80 -0
  22. package/dist/src/vendor/unbash/glob.js +268 -0
  23. package/dist/src/vendor/unbash/heredoc.js +27 -0
  24. package/dist/src/vendor/unbash/internal-types.js +1 -0
  25. package/dist/src/vendor/unbash/lexer.js +4274 -0
  26. package/dist/src/vendor/unbash/limits.js +3 -0
  27. package/dist/src/vendor/unbash/parser.js +1366 -0
  28. package/dist/src/vendor/unbash/parts.js +57 -0
  29. package/dist/src/vendor/unbash/substitution-context.js +8 -0
  30. package/dist/src/vendor/unbash/traversal.js +212 -0
  31. package/dist/src/vendor/unbash/types.js +1 -0
  32. package/dist/src/vendor/unbash/word-value.js +61 -0
  33. package/dist/src/vendor/unbash/word.js +40 -0
  34. package/dist/src/workflow-steps.js +111 -133
  35. package/jsr.json +2 -1
  36. package/package.json +4 -2
  37. package/src/build-scripts.ts +6 -13
  38. package/src/commands/read.ts +230 -0
  39. package/src/commands/shell.ts +126 -0
  40. package/src/commands/tools.ts +618 -0
  41. package/src/configuration-files.ts +10 -26
  42. package/src/conformance-record.ts +1 -1
  43. package/src/dependencies.ts +99 -123
  44. package/src/load/synthesize.ts +18 -5
  45. package/src/manifest.ts +43 -25
  46. package/src/roots.ts +20 -13
  47. package/src/text/named-files.ts +26 -3
  48. package/src/text/yaml.ts +503 -179
  49. package/src/vendor/unbash/LICENSE +15 -0
  50. package/src/vendor/unbash/VENDORED.md +16 -0
  51. package/src/vendor/unbash/ansi-c.ts +132 -0
  52. package/src/vendor/unbash/arithmetic-parts.ts +53 -0
  53. package/src/vendor/unbash/arithmetic.ts +641 -0
  54. package/src/vendor/unbash/assignment.ts +163 -0
  55. package/src/vendor/unbash/chars.ts +48 -0
  56. package/src/vendor/unbash/command.ts +47 -0
  57. package/src/vendor/unbash/expansion.ts +144 -0
  58. package/src/vendor/unbash/glob.ts +284 -0
  59. package/src/vendor/unbash/heredoc.ts +34 -0
  60. package/src/vendor/unbash/internal-types.ts +94 -0
  61. package/src/vendor/unbash/lexer.ts +4352 -0
  62. package/src/vendor/unbash/limits.ts +3 -0
  63. package/src/vendor/unbash/parser.ts +1612 -0
  64. package/src/vendor/unbash/parts.ts +96 -0
  65. package/src/vendor/unbash/substitution-context.ts +20 -0
  66. package/src/vendor/unbash/traversal.ts +182 -0
  67. package/src/vendor/unbash/types.ts +660 -0
  68. package/src/vendor/unbash/word-value.ts +64 -0
  69. package/src/vendor/unbash/word.ts +50 -0
  70. package/src/workflow-steps.ts +153 -154
  71. package/dist/src/text/shell.js +0 -159
  72. package/src/text/named-files.inputs.json +0 -1522
  73. package/src/text/shell.ts +0 -161
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Layer 2 of the command reader: one row per tool, its reader of a simple command's words after
3
+ * its program word and the options it names its configuration file by, each as the tool
4
+ * documents them, and one reader per shell builtin that moves the shell's directory. A reader
5
+ * never guesses: an option it does not handle, or a dynamic word where it needs a value, leaves
6
+ * the command unread.
7
+ */
8
+ import { resolvePath } from "../paths.js";
9
+ /** The options `words` start with, or `undefined` where one is unhandled or a needed word is dynamic. */
10
+ function optionsOf(words, options) {
11
+ const values = new Map();
12
+ const keep = (name, value) => {
13
+ values.set(name, [...(values.get(name) ?? []), value]);
14
+ };
15
+ for (let at = 0; at < words.length; at += 1) {
16
+ const word = words[at]?.value;
17
+ if (word === undefined) {
18
+ return undefined;
19
+ }
20
+ if (word === "--") {
21
+ return { values, at: at + 1, ended: true };
22
+ }
23
+ if (!word.startsWith("-") || options.flags.has(word)) {
24
+ if (!word.startsWith("-")) {
25
+ return { values, at, ended: false };
26
+ }
27
+ continue;
28
+ }
29
+ const equals = word.startsWith("--") ? word.indexOf("=") : -1;
30
+ const name = equals < 0 ? word : word.slice(0, equals);
31
+ if (options.valued.has(name)) {
32
+ const value = equals < 0 ? words[(at += 1)]?.value : word.slice(equals + 1);
33
+ if (value === undefined) {
34
+ return undefined;
35
+ }
36
+ keep(name, value);
37
+ }
38
+ else if (options.attached === true &&
39
+ options.valued.has(word.slice(0, 2)) &&
40
+ !word.startsWith("--")) {
41
+ keep(word.slice(0, 2), word.slice(2));
42
+ }
43
+ else {
44
+ return undefined;
45
+ }
46
+ }
47
+ return { values, at: words.length, ended: false };
48
+ }
49
+ /** A specifier that starts with a URL scheme, `file:` and `node:` among them. */
50
+ const SCHEME = /^[a-z][a-z\d+.-]*:/iu;
51
+ /** A specifier written as a path: `.`, `..`, or one starting `./`, `../` or `/`. */
52
+ const RELATIVE = /^(?:\.{1,2}(?:\/|$)|\/)/u;
53
+ /**
54
+ * The path a URL's path text names, percent-decoded as Node's `fileURLToPath` does, with a
55
+ * backslash read as the URL parser reads it, or `undefined` where it does not decode or
56
+ * encodes a separator: https://nodejs.org/api/url.html#urlfileurltopathurl-options
57
+ */
58
+ function urlPath(text) {
59
+ const path = text.replace(/[?#].*$/su, "").replaceAll("\\", "/");
60
+ if (/%2f|%5c/iu.test(path)) {
61
+ return undefined;
62
+ }
63
+ try {
64
+ return decodeURIComponent(path);
65
+ }
66
+ catch {
67
+ return undefined;
68
+ }
69
+ }
70
+ /**
71
+ * Where one preload is, read in `dir`: the absolute path of a file, a package's name, or
72
+ * `undefined` where it names neither. `require` reads a path as written; an ECMAScript
73
+ * specifier is a URL, so a relative one or a `file:` one is percent-decoded and any other
74
+ * scheme loads no file: https://nodejs.org/api/esm.html#urls
75
+ */
76
+ export function preloadTarget(preload, dir) {
77
+ const { specifier, resolution } = preload;
78
+ if (resolution === "require") {
79
+ return RELATIVE.test(specifier)
80
+ ? { path: resolvePath(dir, specifier) }
81
+ : SCHEME.test(specifier)
82
+ ? undefined
83
+ : { package: specifier };
84
+ }
85
+ if (!RELATIVE.test(specifier) && !SCHEME.test(specifier)) {
86
+ return { package: specifier };
87
+ }
88
+ const local = /^file:(?:\/\/(?=\/))?/iu.exec(specifier);
89
+ const rest = specifier.slice(local?.[0].length ?? 0);
90
+ // `file://host/…` names a file on another host.
91
+ if ((SCHEME.test(specifier) && local === null) || rest.startsWith("//")) {
92
+ return undefined;
93
+ }
94
+ const path = urlPath(rest);
95
+ return path === undefined ? undefined : { path: resolvePath(dir, path) };
96
+ }
97
+ /** A runtime: its preload options, then its entry, and every later word handed to the program. */
98
+ function runtime(preloads, flags, runsNode, subcommand) {
99
+ const options = { valued: new Set(preloads.keys()), flags: new Set(flags) };
100
+ return (words) => {
101
+ const from = subcommand !== undefined && words[0]?.value === subcommand ? 1 : 0;
102
+ const read = optionsOf(words.slice(from), options);
103
+ if (read === undefined) {
104
+ return undefined;
105
+ }
106
+ const entry = words[from + read.at];
107
+ const loads = [...preloads].flatMap(([name, resolution]) => (read.values.get(name) ?? []).map((specifier) => ({ specifier, resolution })));
108
+ if ((entry !== undefined && entry.value === undefined) ||
109
+ loads.some((one) => preloadTarget(one, "/") === undefined)) {
110
+ return undefined;
111
+ }
112
+ return {
113
+ kind: "run",
114
+ entry: entry?.value,
115
+ preloads: loads,
116
+ runtime: runsNode && entry !== undefined ? "node" : undefined,
117
+ };
118
+ };
119
+ }
120
+ /**
121
+ * `node [options] [entry] [arguments]`, https://nodejs.org/api/cli.html: the options that load
122
+ * a module before the entry, `-r` as `require` does and the rest as an ECMAScript module.
123
+ * `-e`, `-p` and `-` run text that is no file, so they are unhandled.
124
+ */
125
+ const node = runtime(new Map([
126
+ ["-r", "require"],
127
+ ["--require", "require"],
128
+ ["--import", "import"],
129
+ ["--loader", "import"],
130
+ ["--experimental-loader", "import"],
131
+ ]), [], true);
132
+ /** `tsx [watch] [options] <file> [arguments]`, https://tsx.is/usage, https://tsx.is/node-enhancement */
133
+ const tsx = runtime(new Map([
134
+ ["-r", "require"],
135
+ ["--require", "require"],
136
+ ["--import", "import"],
137
+ ]), [], false, "watch");
138
+ /** `ts-node [options] [script] [arguments]`, https://typestrong.org/ts-node/docs/options; `--esm` picks no file. */
139
+ const tsNode = runtime(new Map([
140
+ ["-r", "require"],
141
+ ["--require", "require"],
142
+ ]), ["--esm"], false);
143
+ /** Hands on the command `words` from `at`, after the assignments `assigns` accepts. */
144
+ function afterAssignments(words, at, assigns) {
145
+ let end = at;
146
+ while (end < words.length && words[end]?.value?.includes("=") === true) {
147
+ if (!assigns(words[end]?.value ?? "")) {
148
+ return undefined;
149
+ }
150
+ end += 1;
151
+ }
152
+ const program = words[end];
153
+ if (program !== undefined && (program.value === undefined || program.value.startsWith("-"))) {
154
+ return undefined;
155
+ }
156
+ return program === undefined
157
+ ? { kind: "run", entry: undefined, preloads: [], runtime: undefined }
158
+ : { kind: "command", words: words.slice(end) };
159
+ }
160
+ /**
161
+ * `env [option]... [name=value]... [command [argument]...]`,
162
+ * https://www.gnu.org/software/coreutils/manual/html_node/env-invocation.html: the options that
163
+ * change only the environment, `-i`, `-u` and `-v` in each spelling. GNU env takes any word
164
+ * holding an `=` after its first character as an assignment, and fails before running anything
165
+ * on a `-u` name that is empty or holds an `=`.
166
+ */
167
+ const env = (words) => {
168
+ const read = optionsOf(words, {
169
+ valued: new Set(["-u", "--unset"]),
170
+ flags: new Set(["-i", "--ignore-environment", "-", "-v", "--debug"]),
171
+ attached: true,
172
+ });
173
+ const unset = [...(read?.values.get("-u") ?? []), ...(read?.values.get("--unset") ?? [])];
174
+ return read === undefined || unset.some((name) => name === "" || name.includes("="))
175
+ ? undefined
176
+ : afterAssignments(words, read.at, (value) => !value.startsWith("="));
177
+ };
178
+ /** `cross-env [name=value]... command [argument]...`, https://github.com/kentcdodds/cross-env#usage */
179
+ const crossEnv = (words) => {
180
+ const reading = afterAssignments(words, 0, (value) => /^\w+=/u.test(value));
181
+ return reading?.kind === "command" ? reading : undefined;
182
+ };
183
+ /** The command a package runner hands on: its first word a package's or a command's name. */
184
+ function runnerCommand(words, packaged) {
185
+ const [first, ...rest] = words;
186
+ if (first?.value === undefined || first.value.startsWith("-")) {
187
+ return undefined;
188
+ }
189
+ // A package specifier names the package's command; a version picks no other command.
190
+ const name = packaged ? first.value.replace(/(?<=.)@[^/@]*$/u, "") : first.value;
191
+ return { kind: "command", words: [{ value: name, text: first.text }, ...rest] };
192
+ }
193
+ /**
194
+ * npx and npm exec, https://docs.npmjs.com/cli/v11/commands/npx and
195
+ * https://docs.npmjs.com/cli/v11/commands/npm-exec: `--package` (and npx's `-p`), `--call`,
196
+ * `--yes` and `--no`, before the first positional word, which is the package whose command
197
+ * runs unless a `--package` names one. npm exec reads options after it too, unless a `--` ends them.
198
+ */
199
+ function packageExec(shortPackage, laterOptions) {
200
+ const valued = new Set(["--package", "-c", "--call", ...(shortPackage ? ["-p"] : [])]);
201
+ return (words) => {
202
+ const read = optionsOf(words, { valued, flags: new Set(["-y", "--yes", "--no"]) });
203
+ if (read === undefined) {
204
+ return undefined;
205
+ }
206
+ const operands = words.slice(read.at);
207
+ if (laterOptions &&
208
+ !read.ended &&
209
+ operands.some((word) => word.value?.startsWith("-") !== false)) {
210
+ return undefined;
211
+ }
212
+ const call = [...(read.values.get("-c") ?? []), ...(read.values.get("--call") ?? [])];
213
+ if (call.length > 0) {
214
+ return call.length === 1 && operands.length === 0 && call[0] !== undefined
215
+ ? { kind: "line", text: call[0] }
216
+ : undefined;
217
+ }
218
+ const packages = [...(read.values.get("--package") ?? []), ...(read.values.get("-p") ?? [])];
219
+ return runnerCommand(operands, packages.length === 0);
220
+ };
221
+ }
222
+ const npx = packageExec(true, false);
223
+ const npmExec = packageExec(false, true);
224
+ /**
225
+ * `npm run <script> [-- <arguments>]`, https://docs.npmjs.com/cli/v11/commands/npm-run-script:
226
+ * it runs another script of the same manifest, which is read on its own.
227
+ */
228
+ const npmRun = (words) => {
229
+ const [script, ...rest] = words;
230
+ const ended = rest.findIndex((word) => word.value === "--");
231
+ const before = ended < 0 ? rest : rest.slice(0, ended);
232
+ if (script?.value === undefined ||
233
+ script.value.startsWith("-") ||
234
+ before.some((word) => word.value?.startsWith("-") !== false)) {
235
+ return undefined;
236
+ }
237
+ return { kind: "run", entry: undefined, preloads: [], runtime: undefined };
238
+ };
239
+ /** A tool whose second word picks the reader of the rest. */
240
+ function subcommands(readers) {
241
+ return ([first, ...rest]) => first?.value === undefined ? undefined : readers.get(first.value)?.(rest);
242
+ }
243
+ /** `npm exec`, `npm x` and `npm run` with `run-script`. */
244
+ const npm = subcommands(new Map([
245
+ ["exec", npmExec],
246
+ ["x", npmExec],
247
+ ["run", npmRun],
248
+ ["run-script", npmRun],
249
+ ]));
250
+ /**
251
+ * `pnpm exec <command>`, https://pnpm.io/cli/exec, which hands on every later word, and
252
+ * `pnpm dlx [--package <name>]... <command>`, https://pnpm.io/cli/dlx.
253
+ */
254
+ const pnpm = subcommands(new Map([
255
+ ["exec", (words) => runnerCommand(words, false)],
256
+ [
257
+ "dlx",
258
+ (words) => {
259
+ const read = optionsOf(words, { valued: new Set(["--package"]), flags: new Set() });
260
+ return read === undefined
261
+ ? undefined
262
+ : runnerCommand(words.slice(read.at), !read.values.has("--package"));
263
+ },
264
+ ],
265
+ ]));
266
+ /**
267
+ * `yarn node`, https://yarnpkg.com/cli/node, and `yarn dlx [-p <name>]... <command>`,
268
+ * https://yarnpkg.com/cli/dlx, whose command runs in the current directory.
269
+ */
270
+ const yarn = subcommands(new Map([
271
+ ["node", (words) => ({ kind: "command", words: [{ value: "node", text: "node" }, ...words] })],
272
+ [
273
+ "dlx",
274
+ (words) => {
275
+ const read = optionsOf(words, {
276
+ valued: new Set(["-p", "--package"]),
277
+ flags: new Set(["-q", "--quiet"]),
278
+ });
279
+ return read === undefined
280
+ ? undefined
281
+ : runnerCommand(words.slice(read.at), !read.values.has("-p") && !read.values.has("--package"));
282
+ },
283
+ ],
284
+ ]));
285
+ /** `bunx [-p <name>] <command>[@version] [arguments]`, https://bun.com/docs/pm/bunx */
286
+ const bunx = (words) => {
287
+ const read = optionsOf(words, { valued: new Set(["-p", "--package"]), flags: new Set() });
288
+ return read === undefined ? undefined : runnerCommand(words.slice(read.at), true);
289
+ };
290
+ /** The row of a tool whose reader is `read`. */
291
+ function reading(read) {
292
+ return { read };
293
+ }
294
+ /** The row of a tool that names its configuration file by `options` anywhere before a `--`. */
295
+ function configuredBy(...options) {
296
+ return { configurations: { valued: new Set(options) } };
297
+ }
298
+ /** The row of each tool, by its program word: its name, or a path to it in `node_modules/.bin`. */
299
+ const TOOLS = new Map([
300
+ ["node", reading(node)],
301
+ ["tsx", reading(tsx)],
302
+ ["ts-node", reading(tsNode)],
303
+ ["env", reading(env)],
304
+ ["cross-env", reading(crossEnv)],
305
+ [
306
+ "npx",
307
+ {
308
+ read: npx,
309
+ // `-c` takes a command line, which is read as a configuration file's name as well, so
310
+ // every configuration `fixtures/commands/observed.json` records stays named.
311
+ configurations: {
312
+ valued: new Set(["-c"]),
313
+ beforeOperand: new Set(["--package", "-p", "--call"]),
314
+ },
315
+ },
316
+ ],
317
+ ["npm", reading(npm)],
318
+ ["pnpm", reading(pnpm)],
319
+ ["yarn", reading(yarn)],
320
+ ["bunx", reading(bunx)],
321
+ // https://eslint.org/docs/latest/use/command-line-interface
322
+ ["eslint", configuredBy("-c", "--config")],
323
+ // https://prettier.io/docs/cli, whose `-c` is `--check`.
324
+ ["prettier", configuredBy("--config")],
325
+ // https://stylelint.io/user-guide/cli
326
+ ["stylelint", configuredBy("-c", "--config")],
327
+ // https://jestjs.io/docs/cli
328
+ ["jest", configuredBy("-c", "--config")],
329
+ // https://vitest.dev/guide/cli
330
+ ["vitest", configuredBy("-c", "--config")],
331
+ // https://vite.dev/guide/cli
332
+ ["vite", configuredBy("-c", "--config")],
333
+ // https://webpack.js.org/api/cli/
334
+ ["webpack", configuredBy("-c", "--config")],
335
+ // https://mochajs.org/#command-line-usage, whose `-c` is `--color`.
336
+ ["mocha", configuredBy("--config")],
337
+ ]);
338
+ /** The row of a simple command's program word, by its name or a path to it in `node_modules/.bin`. */
339
+ function rowOf(program) {
340
+ const name = program?.value === undefined
341
+ ? undefined
342
+ : /^(?:.*\/)?node_modules\/\.bin\/([^/]+)$|^([^/]+)$/u.exec(program.value);
343
+ const tool = name?.[1] ?? name?.[2];
344
+ return tool === undefined ? undefined : TOOLS.get(tool);
345
+ }
346
+ /**
347
+ * What the reader of one simple command's program reads it as, or `undefined` where none
348
+ * understands it. A program word that is any other path runs that file, whatever its name.
349
+ */
350
+ export function readTool(words) {
351
+ const [program, ...rest] = words;
352
+ return rowOf(program)?.read?.(rest);
353
+ }
354
+ /**
355
+ * The configuration files one simple command names, as written: each static value of an option
356
+ * its program's row names a configuration file by, and none for a program with no such row.
357
+ */
358
+ export function configurationsOf(words) {
359
+ const [program, ...rest] = words;
360
+ const options = rowOf(program)?.configurations;
361
+ const found = [];
362
+ for (let at = 0; options !== undefined && at < rest.length; at += 1) {
363
+ const word = rest[at]?.value;
364
+ if (word === "--" || (word === undefined && options.beforeOperand !== undefined)) {
365
+ break;
366
+ }
367
+ const equals = word?.startsWith("--") === true ? word.indexOf("=") : -1;
368
+ const name = equals < 0 ? word : word?.slice(0, equals);
369
+ if (name !== undefined && options.valued.has(name)) {
370
+ const value = equals < 0 ? rest[(at += 1)]?.value : word?.slice(equals + 1);
371
+ found.push(...(value === undefined || value === "" ? [] : [value]));
372
+ }
373
+ else if (options.beforeOperand !== undefined && name !== undefined) {
374
+ if (!name.startsWith("-")) {
375
+ break;
376
+ }
377
+ at += options.beforeOperand.has(name) && equals < 0 ? 1 : 0;
378
+ }
379
+ }
380
+ return found;
381
+ }
382
+ /** Either status, in a directory the analysis does not know. */
383
+ const ELSEWHERE = [
384
+ { dir: undefined, ok: true },
385
+ { dir: undefined, ok: false },
386
+ ];
387
+ const elsewhere = () => ELSEWHERE;
388
+ /** The directory a builtin's one operand states, where it is static and starts with no character of `options`. */
389
+ function statedDirectory(operands, options) {
390
+ const [dir] = operands;
391
+ return operands.length === 1 && dir?.value !== undefined && !options.includes(dir.value.charAt(0))
392
+ ? dir.value
393
+ : undefined;
394
+ }
395
+ /** `cd` to a stated directory is taken to enter it, as `roots.13` states. */
396
+ const cd = (operands, dir) => {
397
+ const to = statedDirectory(operands, "-");
398
+ return to === undefined
399
+ ? ELSEWHERE
400
+ : [{ dir: dir === undefined ? undefined : resolvePath(dir, to), ok: true }];
401
+ };
402
+ /** `pushd` enters a stated directory, or fails and leaves the shell where it was. */
403
+ const pushd = (operands, dir) => {
404
+ const to = statedDirectory(operands, "-+");
405
+ return to === undefined
406
+ ? ELSEWHERE
407
+ : [
408
+ { dir: dir === undefined ? undefined : resolvePath(dir, to), ok: true },
409
+ { dir, ok: false },
410
+ ];
411
+ };
412
+ /**
413
+ * `exec` with a command replaces the shell, which exits where it cannot; with none it only
414
+ * redirects: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#exec
415
+ */
416
+ const exec = ([command]) => {
417
+ if (command === undefined) {
418
+ return undefined;
419
+ }
420
+ return command.value === undefined || command.value.startsWith("-") ? ELSEWHERE : [];
421
+ };
422
+ /** `exit` ends the shell whatever its operands, in dash and in bash. */
423
+ const exit = () => [];
424
+ /** The builtins that run another command in the same shell, where that one may be any builtin. */
425
+ const runsBuiltin = (operands) => operands.some((word) => word.value === undefined || BUILTINS.has(word.value))
426
+ ? ELSEWHERE
427
+ : undefined;
428
+ /**
429
+ * The shell builtins that may move or end the shell running them: `cd`,
430
+ * https://pubs.opengroup.org/onlinepubs/9799919799/utilities/cd.html; `pushd` and `popd`,
431
+ * https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html; `exit`,
432
+ * `exec`, and `return`, whose effect outside a function POSIX leaves unspecified,
433
+ * https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#return; and `.`,
434
+ * `eval`, `source`, `command` and `builtin`, which run other text or another builtin.
435
+ */
436
+ const BUILTINS = new Map([
437
+ ["cd", cd],
438
+ ["pushd", pushd],
439
+ ["popd", elsewhere],
440
+ ["exit", exit],
441
+ ["exec", exec],
442
+ ["return", elsewhere],
443
+ [".", elsewhere],
444
+ ["source", elsewhere],
445
+ ["eval", elsewhere],
446
+ ["command", runsBuiltin],
447
+ ["builtin", runsBuiltin],
448
+ ]);
449
+ /**
450
+ * Each way the shell running one simple command in its own environment, in `dir`, goes on
451
+ * after a builtin that may move or end it, or `undefined` where the command is no such
452
+ * builtin. A program word the shell computes may be any builtin.
453
+ */
454
+ export function readBuiltin(words, dir) {
455
+ const [program, ...rest] = words;
456
+ if (program === undefined) {
457
+ return undefined;
458
+ }
459
+ return program.value === undefined ? ELSEWHERE : BUILTINS.get(program.value)?.(rest, dir);
460
+ }
@@ -12,7 +12,8 @@ import { bytewise } from "./text/compare.js";
12
12
  import { isRecord } from "./json-document.js";
13
13
  import { filesIn } from "./fs/tree.js";
14
14
  import { DEPENDENCY_SECTIONS, holdsManifest, inInstalledPackages, manifestAt, readJSON, } from "./fs/manifest.js";
15
- import { shellWords } from "./text/shell.js";
15
+ import { readCommands } from "./commands/read.js";
16
+ import { manifestCommands } from "./manifest.js";
16
17
  /** A configuration module's name, each form with the spelling a root reports it by. */
17
18
  const MODULE_FORMS = [
18
19
  { name: /^[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u, form: "<stem>.config.<ext>" },
@@ -64,40 +65,25 @@ export function configurationModuleForm(name) {
64
65
  export function isConfigurationDocumentName(name) {
65
66
  return DOCUMENT_NAME.test(name);
66
67
  }
67
- /** The flags a tool's command line names its configuration file with. */
68
- const CONFIG_FLAGS = new Set(["--config", "-c"]);
69
68
  /** The extensions of a module a configuration flag may name. */
70
69
  const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
71
70
  /**
72
- * The files the scripts of the manifest in one directory name after a configuration flag,
73
- * `--config <file>`, `-c <file>` or `--config=<file>`, each a JSON file or a module below
74
- * the target root and in no `node_modules`.
71
+ * The files the command strings of the manifest in one directory name after a configuration
72
+ * flag, as `commands/read.ts` reads them against the directory each command runs in, each a
73
+ * JSON file or a module below the target root and in no `node_modules`.
75
74
  */
76
75
  function scriptConfigurations(host, root, dir) {
77
- const scripts = manifestAt(host, dir)?.["scripts"];
78
- if (!isRecord(scripts)) {
79
- return [];
80
- }
76
+ const manifest = manifestAt(host, dir);
81
77
  const found = [];
82
- for (const command of Object.values(scripts)) {
83
- if (typeof command !== "string") {
84
- continue;
85
- }
86
- const words = shellWords(command);
87
- words.forEach((word, at) => {
88
- const named = word !== undefined && CONFIG_FLAGS.has(word)
89
- ? words[at + 1]
90
- : word === undefined
91
- ? undefined
92
- : /^--config=(.+)$/su.exec(word)?.[1];
93
- const path = named === undefined ? undefined : resolvePath(dir, named);
94
- if (path !== undefined &&
95
- [".json", ...MODULE_EXTENSIONS].some((extension) => path.endsWith(extension)) &&
78
+ for (const { command } of manifest === undefined ? [] : manifestCommands(host, dir, manifest)) {
79
+ for (const { dir: runs, written } of readCommands(command, dir).configurations) {
80
+ const path = resolvePath(runs, written);
81
+ if ([".json", ...MODULE_EXTENSIONS].some((extension) => path.endsWith(extension)) &&
96
82
  isProjectPath(root, path) &&
97
83
  host.kindOf(path) === "file") {
98
84
  found.push(path);
99
85
  }
100
- });
86
+ }
101
87
  }
102
88
  return found;
103
89
  }
@@ -4,7 +4,7 @@ export const RECORD = {
4
4
  conformance: {
5
5
  corpusVersion: "6.0.0",
6
6
  result: "pass",
7
- digest: "sha256:0d33564807a356fd3e2760ddaa0645d758bdb9a96ee74a392675e444343bbf20",
7
+ digest: "sha256:58ac31fa90fcbefdbbcbcbd6d2c7a8af6dd4cd883c04d79abfe96f979ffe10e1",
8
8
  },
9
9
  gaps: [],
10
10
  };