@cplieger/deadset-ts 8.0.0-dev.12 → 8.0.0-dev.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/src/build-scripts.js +6 -13
- package/dist/src/commands/read.js +156 -0
- package/dist/src/commands/shell.js +75 -0
- package/dist/src/commands/tools.js +460 -0
- package/dist/src/configuration-files.js +11 -25
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/dependencies.js +67 -115
- package/dist/src/load/synthesize.js +12 -5
- package/dist/src/manifest.js +27 -14
- package/dist/src/roots.js +18 -11
- package/dist/src/text/named-files.js +24 -3
- package/dist/src/text/yaml.js +464 -173
- package/dist/src/vendor/unbash/ansi-c.js +132 -0
- package/dist/src/vendor/unbash/arithmetic-parts.js +37 -0
- package/dist/src/vendor/unbash/arithmetic.js +591 -0
- package/dist/src/vendor/unbash/assignment.js +131 -0
- package/dist/src/vendor/unbash/chars.js +48 -0
- package/dist/src/vendor/unbash/command.js +41 -0
- package/dist/src/vendor/unbash/expansion.js +80 -0
- package/dist/src/vendor/unbash/glob.js +268 -0
- package/dist/src/vendor/unbash/heredoc.js +27 -0
- package/dist/src/vendor/unbash/internal-types.js +1 -0
- package/dist/src/vendor/unbash/lexer.js +4274 -0
- package/dist/src/vendor/unbash/limits.js +3 -0
- package/dist/src/vendor/unbash/parser.js +1366 -0
- package/dist/src/vendor/unbash/parts.js +57 -0
- package/dist/src/vendor/unbash/substitution-context.js +8 -0
- package/dist/src/vendor/unbash/traversal.js +212 -0
- package/dist/src/vendor/unbash/types.js +1 -0
- package/dist/src/vendor/unbash/word-value.js +61 -0
- package/dist/src/vendor/unbash/word.js +40 -0
- package/dist/src/workflow-steps.js +111 -133
- package/jsr.json +2 -1
- package/package.json +4 -2
- package/src/build-scripts.ts +6 -13
- package/src/commands/read.ts +230 -0
- package/src/commands/shell.ts +126 -0
- package/src/commands/tools.ts +618 -0
- package/src/configuration-files.ts +10 -26
- package/src/conformance-record.ts +1 -1
- package/src/dependencies.ts +99 -123
- package/src/load/synthesize.ts +18 -5
- package/src/manifest.ts +43 -25
- package/src/roots.ts +20 -13
- package/src/text/named-files.ts +26 -3
- package/src/text/yaml.ts +503 -179
- package/src/vendor/unbash/LICENSE +15 -0
- package/src/vendor/unbash/VENDORED.md +16 -0
- package/src/vendor/unbash/ansi-c.ts +132 -0
- package/src/vendor/unbash/arithmetic-parts.ts +53 -0
- package/src/vendor/unbash/arithmetic.ts +641 -0
- package/src/vendor/unbash/assignment.ts +163 -0
- package/src/vendor/unbash/chars.ts +48 -0
- package/src/vendor/unbash/command.ts +47 -0
- package/src/vendor/unbash/expansion.ts +144 -0
- package/src/vendor/unbash/glob.ts +284 -0
- package/src/vendor/unbash/heredoc.ts +34 -0
- package/src/vendor/unbash/internal-types.ts +94 -0
- package/src/vendor/unbash/lexer.ts +4352 -0
- package/src/vendor/unbash/limits.ts +3 -0
- package/src/vendor/unbash/parser.ts +1612 -0
- package/src/vendor/unbash/parts.ts +96 -0
- package/src/vendor/unbash/substitution-context.ts +20 -0
- package/src/vendor/unbash/traversal.ts +182 -0
- package/src/vendor/unbash/types.ts +660 -0
- package/src/vendor/unbash/word-value.ts +64 -0
- package/src/vendor/unbash/word.ts +50 -0
- package/src/workflow-steps.ts +153 -154
- package/dist/src/text/shell.js +0 -159
- package/src/text/named-files.inputs.json +0 -1522
- package/src/text/shell.ts +0 -161
package/README.md
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
368
|
-
if (!isRecord(scripts)) {
|
|
369
|
-
return [];
|
|
370
|
-
}
|
|
367
|
+
const manifest = manifestAt(host, dir);
|
|
371
368
|
const found = new Set();
|
|
372
|
-
for (const command of
|
|
373
|
-
|
|
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
|
+
}
|