@cplieger/deadset-ts 8.0.0-dev.11 → 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 +9 -7
  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
package/README.md CHANGED
@@ -116,3 +116,5 @@ This project was built with AI-assisted tooling using [Claude](https://claude.co
116
116
  ## License
117
117
 
118
118
  GPL-3.0-or-later. See [LICENSE](LICENSE).
119
+
120
+ Third-party attributions are in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,27 @@
1
+ # Third-party notices
2
+
3
+ deadset-ts includes source code from the following project.
4
+
5
+ ## unbash
6
+
7
+ - Source: <https://github.com/webpro-nl/unbash>, release 6.0.0, commit `968c14992a43dc06ce29c9f51858abd4a87851ea`
8
+ - Location: `src/vendor/unbash/` (see `VENDORED.md` there)
9
+ - License: ISC
10
+
11
+ ```text
12
+ ISC License
13
+
14
+ Copyright (c) Lars Kappert
15
+
16
+ Permission to use, copy, modify, and/or distribute this software for any
17
+ purpose with or without fee is hereby granted, provided that the above
18
+ copyright notice and this permission notice appear in all copies.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
21
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
22
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
23
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
24
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
25
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
26
+ PERFORMANCE OF THIS SOFTWARE.
27
+ ```
@@ -8,11 +8,11 @@ import { isArrayBindingPattern, isArrayLiteralExpression, isArrowFunction, isCal
8
8
  import { filesNamedBy } from "./configuration-files.js";
9
9
  import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.js";
10
10
  import { bytewise } from "./text/compare.js";
11
- import { isRecord } from "./json-document.js";
12
11
  import { unwrapped } from "./unwrap.js";
13
12
  import { holdsManifest, inInstalledPackages, manifestAt } from "./fs/manifest.js";
14
13
  import { globExpression } from "./text/glob.js";
15
- import { scriptTokens } from "./text/shell.js";
14
+ import { pathsNamed, readCommands } from "./commands/read.js";
15
+ import { manifestCommands } from "./manifest.js";
16
16
  /**
17
17
  * The bundlers whose build call names its entries: esbuild resolves `entryPoints` against
18
18
  * `absWorkingDir` (https://esbuild.github.io/api/#working-directory), webpack `entry`
@@ -362,19 +362,12 @@ function scriptEntries(file, dir, files) {
362
362
  });
363
363
  return found;
364
364
  }
365
- /** The modules one manifest's scripts run, by absolute path, each below the target root. */
365
+ /** The modules one manifest's command strings run or name, by absolute path, each below the target root. */
366
366
  function scriptModules(host, root, dir) {
367
- const scripts = manifestAt(host, dir)?.["scripts"];
368
- if (!isRecord(scripts)) {
369
- return [];
370
- }
367
+ const manifest = manifestAt(host, dir);
371
368
  const found = new Set();
372
- for (const command of Object.values(scripts)) {
373
- if (typeof command !== "string") {
374
- continue;
375
- }
376
- for (const token of scriptTokens(command)) {
377
- const path = resolvePath(dir, token);
369
+ for (const { command } of manifest === undefined ? [] : manifestCommands(host, dir, manifest)) {
370
+ for (const { path } of pathsNamed(readCommands(command, dir))) {
378
371
  if (MODULE_EXTENSION.test(path) &&
379
372
  relativePath(root, path) !== undefined &&
380
373
  !inInstalledPackages(path) &&
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The one reader of every command string the analysis reads. Layer 3, the holding rule
3
+ * `roots.ts` applies through `text/named-files.ts`, reads all of the string's text whatever
4
+ * the other two layers make of it; layer 1 parses the string and layer 2 reads each simple
5
+ * command a tool's reader understands, and they only add the files and modules it runs.
6
+ */
7
+ import { joinPath, resolvePath } from "../paths.js";
8
+ import { shellLine } from "./shell.js";
9
+ import { configurationsOf, preloadTarget, readBuiltin, readTool, } from "./tools.js";
10
+ /**
11
+ * The outcomes of one pipeline run in `dir`: those of the builtin that is its one member, as
12
+ * `readBuiltin` gives them, a directory the analysis does not know after a part layer 1 does
13
+ * not read, and either status in `dir` otherwise, a pipeline of several members included,
14
+ * whose members each run in a subshell.
15
+ */
16
+ function outcomesOf(pipeline, dir) {
17
+ const [only] = pipeline.alone ? pipeline.commands : [];
18
+ const outcomes = pipeline.opaque
19
+ ? [
20
+ { dir: undefined, ok: true },
21
+ { dir: undefined, ok: false },
22
+ ]
23
+ : ((only === undefined ? undefined : readBuiltin(only, dir)) ?? [
24
+ { dir, ok: true },
25
+ { dir, ok: false },
26
+ ]);
27
+ return outcomes.map((one) => ({ dir: one.dir, ok: one.ok !== pipeline.negated }));
28
+ }
29
+ /** The one directory every outcome is in, or `undefined` where they differ or none is known. */
30
+ function oneDirectory(outcomes) {
31
+ const dirs = new Set(outcomes.map((one) => one.dir));
32
+ return dirs.size === 1 ? [...dirs][0] : undefined;
33
+ }
34
+ /**
35
+ * What `text`, run in `dir`, `undefined` where not known, runs. A pipeline is read where every
36
+ * way its and-or list reaches it leaves the shell in one known directory, none after an `exit`;
37
+ * a background list moves and ends no later one. The words and configuration files of every
38
+ * simple command of the string's own line are read in `dir` as well, wherever they run.
39
+ */
40
+ export function readCommands(text, dir) {
41
+ const runs = [];
42
+ const commands = [];
43
+ const configurations = [];
44
+ const readCommand = (words, at) => {
45
+ commands.push({ dir: at, words: words.map((word) => word.value) });
46
+ let handed = words;
47
+ let reading = readTool(handed);
48
+ while (reading?.kind === "command") {
49
+ handed = reading.words;
50
+ reading = readTool(handed);
51
+ }
52
+ if (reading?.kind === "line") {
53
+ readLine(reading.text, at);
54
+ return;
55
+ }
56
+ configurations.push(...configurationsOf(handed).map((written) => ({ dir: at, written })));
57
+ if (reading !== undefined && (reading.entry !== undefined || reading.preloads.length > 0)) {
58
+ runs.push({
59
+ dir: at,
60
+ entry: reading.entry,
61
+ preloads: reading.preloads,
62
+ runtime: reading.runtime,
63
+ });
64
+ }
65
+ };
66
+ const readLine = (source, start) => {
67
+ let at = start;
68
+ for (const list of shellLine(source)) {
69
+ let outcomes = [{ dir: at, ok: true }];
70
+ for (const pipeline of list.pipelines) {
71
+ const { operator } = pipeline;
72
+ const reached = outcomes.filter((one) => operator === undefined || one.ok === (operator === "&&"));
73
+ const where = reached.length === 0 ? undefined : oneDirectory(reached);
74
+ if (where !== undefined) {
75
+ pipeline.commands.forEach((words) => {
76
+ readCommand(words, where);
77
+ });
78
+ }
79
+ const next = reached.flatMap((one) => outcomesOf(pipeline, one.dir));
80
+ const passed = outcomes.filter((one) => !reached.includes(one));
81
+ outcomes = [
82
+ ...new Map([...passed, ...next].map((one) => [`${String(one.ok)}\u0000${one.dir ?? ""}`, one])).values(),
83
+ ];
84
+ }
85
+ if (!list.background) {
86
+ at = oneDirectory(outcomes);
87
+ }
88
+ }
89
+ };
90
+ readLine(text, dir);
91
+ if (dir !== undefined) {
92
+ for (const words of shellLine(text).flatMap((list) => list.pipelines.flatMap((pipeline) => pipeline.commands))) {
93
+ commands.push({ dir, words: words.map((word) => word.value) });
94
+ configurations.push(...configurationsOf(words).map((written) => ({ dir, written })));
95
+ }
96
+ }
97
+ const unique = (all) => [
98
+ ...new Map(all.map((one) => [JSON.stringify(one), one])).values(),
99
+ ];
100
+ return {
101
+ runs,
102
+ commands: unique(commands),
103
+ configurations: unique(configurations),
104
+ held: text,
105
+ };
106
+ }
107
+ /** The extensions a runtime or a loader tries after a path written without one. */
108
+ const TRIED_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
109
+ /**
110
+ * The files `run` loads: its entry, and each preload written as a path. Each may be at its
111
+ * path, with each extension tried, and as a directory's `index` file.
112
+ */
113
+ export function loadsOf(run) {
114
+ const written = [
115
+ ...(run.entry === undefined
116
+ ? []
117
+ : [{ written: run.entry, path: resolvePath(run.dir, run.entry) }]),
118
+ ...run.preloads.flatMap((preload) => {
119
+ const target = preloadTarget(preload, run.dir);
120
+ return target !== undefined && "path" in target
121
+ ? [{ written: preload.specifier, path: target.path }]
122
+ : [];
123
+ }),
124
+ ];
125
+ return written.map(({ written: one, path }) => ({
126
+ written: one,
127
+ paths: [
128
+ path,
129
+ ...TRIED_EXTENSIONS.map((extension) => `${path}${extension}`),
130
+ ...TRIED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)),
131
+ ],
132
+ }));
133
+ }
134
+ /** The module specifiers `run` preloads that name a package rather than a path. */
135
+ export function preloadedPackages(run) {
136
+ return run.preloads.flatMap((preload) => {
137
+ const target = preloadTarget(preload, run.dir);
138
+ return target !== undefined && "package" in target ? [target.package] : [];
139
+ });
140
+ }
141
+ /**
142
+ * Every path `reading` names: each static word of its commands read against the directory the
143
+ * command runs in, a word holding `*` aside, which a tool reads as a pattern, and every path a
144
+ * file a read command runs or preloads may be at.
145
+ */
146
+ export function pathsNamed(reading) {
147
+ const named = [
148
+ ...reading.commands.flatMap(({ dir, words }) => words.flatMap((word) => word === undefined || word.includes("*")
149
+ ? []
150
+ : [{ path: resolvePath(dir, word), written: word }])),
151
+ ...reading.runs
152
+ .flatMap(loadsOf)
153
+ .flatMap(({ written, paths }) => paths.map((path) => ({ path, written }))),
154
+ ];
155
+ return [...new Map(named.map((one) => [`${one.path}\u0000${one.written}`, one])).values()];
156
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Layer 1 of the command reader: one command line, parsed by the vendored shell parser, as
3
+ * its and-or lists, their pipelines and the words of each simple command. Nothing here
4
+ * tokenizes, unquotes or splits text.
5
+ */
6
+ import { parse, } from "../vendor/unbash/parser.js";
7
+ /** Whether one part of a word has a value the line states: a literal, quoted or escaped text. */
8
+ function isStatic(part) {
9
+ switch (part.type) {
10
+ case "Literal":
11
+ case "SingleQuoted":
12
+ case "AnsiCQuoted":
13
+ return true;
14
+ case "DoubleQuoted":
15
+ return part.parts.every((child) => child.type === "Literal");
16
+ default:
17
+ return false;
18
+ }
19
+ }
20
+ /**
21
+ * One word as layer 1 reads it. A tilde that starts the word unquoted is the parser's literal
22
+ * text but the shell's home directory, so it makes the word dynamic too.
23
+ */
24
+ function wordOf(word) {
25
+ const [first] = word.parts ?? [];
26
+ const lead = word.parts === undefined ? word.text : first?.type === "Literal" ? first.text : "";
27
+ const known = (word.parts ?? []).every(isStatic) && !lead.startsWith("~");
28
+ return { value: known ? word.value : undefined, text: word.text };
29
+ }
30
+ /**
31
+ * Whether one member of a pipeline, run in the line's own shell, may change that shell's
32
+ * directory without being a simple command: POSIX XCU 2.9.4 runs every compound command but
33
+ * `( )` in the current environment, and a function body runs there when called.
34
+ */
35
+ function isOpaque(node) {
36
+ switch (node.type) {
37
+ case "Command":
38
+ case "Subshell":
39
+ case "Coproc":
40
+ case "TestCommand":
41
+ case "ArithmeticCommand":
42
+ return false;
43
+ case "Redirected":
44
+ return isOpaque(node.command);
45
+ default:
46
+ return true;
47
+ }
48
+ }
49
+ function pipelineOf(node, operator) {
50
+ const members = node.type === "Pipeline" ? node.commands : [node];
51
+ const negated = node.type === "Pipeline" && node.prefixes.some((prefix) => prefix.type === "Negation");
52
+ const commands = members.flatMap((member) => member.type === "Command" && member.name !== undefined
53
+ ? [[member.name, ...member.suffix.filter((part) => part.type === "Word")].map(wordOf)]
54
+ : []);
55
+ const alone = members.length === 1;
56
+ return { operator, negated, commands, alone, opaque: alone && members.some(isOpaque) };
57
+ }
58
+ /**
59
+ * The and-or lists one command line runs, or none where any of it does not parse. A simple
60
+ * command's assignments and redirections are no words; a part that is no simple command
61
+ * gives no words, and marks its pipeline where it may change the line's directory.
62
+ */
63
+ export function shellLine(source) {
64
+ const script = parse(source);
65
+ if ((script.errors ?? []).length > 0) {
66
+ return [];
67
+ }
68
+ return script.commands.map((statement) => {
69
+ const { command } = statement;
70
+ const pipelines = command.type === "AndOr"
71
+ ? command.commands.map((one, at) => pipelineOf(one, at === 0 ? undefined : command.operators[at - 1]))
72
+ : [pipelineOf(command, undefined)];
73
+ return { background: statement.background === true, pipelines };
74
+ });
75
+ }