@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.
- 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 +9 -7
- 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/src/dependencies.ts
CHANGED
|
@@ -54,18 +54,24 @@ import type { DependencySection, Module } from "./ref.ts";
|
|
|
54
54
|
import { UNANSWERED } from "./query.ts";
|
|
55
55
|
import type { Handle, ProjectView } from "./session.ts";
|
|
56
56
|
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
57
|
-
import { manifestCommands } from "./manifest.ts";
|
|
58
|
-
import {
|
|
57
|
+
import { manifestCommands, nodemonTexts } from "./manifest.ts";
|
|
58
|
+
import { commandFileTexts, workflowCommands } from "./workflow-steps.ts";
|
|
59
|
+
import {
|
|
60
|
+
pathsNamed,
|
|
61
|
+
preloadedPackages,
|
|
62
|
+
readCommands,
|
|
63
|
+
type CommandReading,
|
|
64
|
+
} from "./commands/read.ts";
|
|
65
|
+
import { heldNames } from "./text/named-files.ts";
|
|
59
66
|
import { bytewise } from "./text/compare.ts";
|
|
60
67
|
import { isRecord } from "./json-document.ts";
|
|
61
68
|
import { inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, readDocument } from "./fs/manifest.ts";
|
|
62
|
-
import { commandsOf, scriptTokens } from "./text/shell.ts";
|
|
63
69
|
|
|
64
70
|
const MODULES_SEGMENT = `/${INSTALLED_PACKAGES}/`;
|
|
65
71
|
const TYPES_SCOPE = "@types/";
|
|
66
72
|
|
|
67
|
-
/** A
|
|
68
|
-
const SCRIPT_FILE =
|
|
73
|
+
/** A path a command names that is a JavaScript file's. */
|
|
74
|
+
const SCRIPT_FILE = /\.(?:js|mjs|cjs)$/u;
|
|
69
75
|
|
|
70
76
|
/** The manifest sections a dependency is declared in, each by the selector its reference spells. */
|
|
71
77
|
const SECTIONS: readonly (readonly [string, DependencySection])[] = [
|
|
@@ -444,10 +450,10 @@ export function projectNeeds<Brand>(
|
|
|
444
450
|
}
|
|
445
451
|
|
|
446
452
|
/**
|
|
447
|
-
* The JavaScript files no program holds that a
|
|
448
|
-
* project's files runs, each with the directory of that manifest, and those a
|
|
449
|
-
* step or git hook
|
|
450
|
-
* target root and in no `node_modules
|
|
453
|
+
* The JavaScript files no program holds that a command string of a manifest at or above one of
|
|
454
|
+
* the project's files runs or names, each with the directory of that manifest, and those a
|
|
455
|
+
* workflow step or git hook names, with the target root's: a path `pathsNamed` reads, below the
|
|
456
|
+
* target root and in no `node_modules`.
|
|
451
457
|
*/
|
|
452
458
|
function scriptRunFiles(
|
|
453
459
|
host: Host,
|
|
@@ -464,23 +470,28 @@ function scriptRunFiles(
|
|
|
464
470
|
dirs.add(dir);
|
|
465
471
|
}
|
|
466
472
|
}
|
|
467
|
-
const
|
|
468
|
-
[
|
|
473
|
+
const named = new Map<string, readonly string[]>([
|
|
474
|
+
[
|
|
475
|
+
root,
|
|
476
|
+
workflowReadings(host, root)
|
|
477
|
+
.flatMap(pathsNamed)
|
|
478
|
+
.map((one) => one.path),
|
|
479
|
+
],
|
|
469
480
|
]);
|
|
470
481
|
for (const dir of dirs) {
|
|
471
482
|
const read = readDocument(host, joinPath(dir, MANIFEST));
|
|
472
483
|
if (read !== undefined && isRecord(read.value)) {
|
|
473
|
-
const own =
|
|
474
|
-
|
|
484
|
+
const own = manifestReadings(host, dir, read.value)
|
|
485
|
+
.flatMap(pathsNamed)
|
|
486
|
+
.map((one) => one.path);
|
|
487
|
+
named.set(dir, [...own, ...(named.get(dir) ?? [])]);
|
|
475
488
|
}
|
|
476
489
|
}
|
|
477
490
|
const found: { path: string; manifestDir: string }[] = [];
|
|
478
|
-
for (const [manifestDir,
|
|
479
|
-
for (const
|
|
480
|
-
const
|
|
481
|
-
const below = path === undefined ? undefined : relativePath(root, path);
|
|
491
|
+
for (const [manifestDir, paths] of named) {
|
|
492
|
+
for (const path of new Set(paths)) {
|
|
493
|
+
const below = SCRIPT_FILE.test(path) ? relativePath(root, path) : undefined;
|
|
482
494
|
if (
|
|
483
|
-
path !== undefined &&
|
|
484
495
|
below !== undefined &&
|
|
485
496
|
!inInstalledPackages(below) &&
|
|
486
497
|
!files.byName.has(path) &&
|
|
@@ -493,6 +504,65 @@ function scriptRunFiles(
|
|
|
493
504
|
return found;
|
|
494
505
|
}
|
|
495
506
|
|
|
507
|
+
/** What the command strings of one manifest, its scripts and nodemon's command, run. */
|
|
508
|
+
function manifestReadings(
|
|
509
|
+
host: Host,
|
|
510
|
+
dir: string,
|
|
511
|
+
manifest: Record<string, unknown>,
|
|
512
|
+
): readonly CommandReading[] {
|
|
513
|
+
return manifestCommands(host, dir, manifest).map(({ command }) => readCommands(command, dir));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** What one manifest's command strings use, held with the whole text of nodemon's configuration. */
|
|
517
|
+
function manifestUses(host: Host, dir: string, manifest: Record<string, unknown>): CommandUses {
|
|
518
|
+
const uses = usesOf(manifestReadings(host, dir, manifest));
|
|
519
|
+
const texts = nodemonTexts(host, dir, manifest).map(({ text }) => text);
|
|
520
|
+
return { ...uses, held: [uses.held, ...texts].join("\n") };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** What the target root's workflow steps, git hooks and recipes use, held with their files' whole text. */
|
|
524
|
+
function workflowUses(host: Host, targetRoot: string): CommandUses {
|
|
525
|
+
const uses = usesOf(workflowReadings(host, targetRoot));
|
|
526
|
+
return { ...uses, held: [uses.held, ...commandFileTexts(host, targetRoot)].join("\n") };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* What the target root's workflow steps, git hooks, hook managers and recipes run, each held
|
|
531
|
+
* with the directory the file writes for it.
|
|
532
|
+
*/
|
|
533
|
+
function workflowReadings(host: Host, targetRoot: string): readonly CommandReading[] {
|
|
534
|
+
return workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
|
|
535
|
+
const reading = readCommands(text, dir);
|
|
536
|
+
return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** What some command strings say about the packages they use. */
|
|
541
|
+
interface CommandUses {
|
|
542
|
+
/** Every static word of their commands, which uses a dependency whose command it is. */
|
|
543
|
+
readonly words: ReadonlySet<string>;
|
|
544
|
+
/** The package each module a read command preloads by name is in. */
|
|
545
|
+
readonly preloads: ReadonlySet<string>;
|
|
546
|
+
/** The text layer 3 reads, all of every string, whose every package or command name is a use. */
|
|
547
|
+
readonly held: string;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function usesOf(readings: readonly CommandReading[]): CommandUses {
|
|
551
|
+
return {
|
|
552
|
+
words: new Set(
|
|
553
|
+
readings.flatMap((one) =>
|
|
554
|
+
one.commands.flatMap((command) => command.words.flatMap((word) => word ?? [])),
|
|
555
|
+
),
|
|
556
|
+
),
|
|
557
|
+
preloads: new Set(
|
|
558
|
+
readings
|
|
559
|
+
.flatMap((one) => one.runs.flatMap(preloadedPackages))
|
|
560
|
+
.flatMap((specifier) => packageOfSpecifier(specifier) ?? []),
|
|
561
|
+
),
|
|
562
|
+
held: readings.map((one) => one.held).join("\n"),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
496
566
|
/** The declarations of the inventory a use written at one node belongs to. */
|
|
497
567
|
function ownersOf(file: SourceFile, held: Inventory, node: Node): readonly string[] {
|
|
498
568
|
const parent = node.parent as Node | undefined;
|
|
@@ -846,102 +916,6 @@ export function declarationKey(dependency: DeclaredDependency): string {
|
|
|
846
916
|
return `${dependency.position.path}\u0000${dependency.name}`;
|
|
847
917
|
}
|
|
848
918
|
|
|
849
|
-
/** Every token of every value of one manifest's `scripts` member. */
|
|
850
|
-
function scriptTokensOf(manifest: Record<string, unknown>): ReadonlySet<string> {
|
|
851
|
-
const scripts = manifest["scripts"];
|
|
852
|
-
const found = new Set<string>();
|
|
853
|
-
if (isRecord(scripts)) {
|
|
854
|
-
for (const command of Object.values(scripts)) {
|
|
855
|
-
if (typeof command === "string") {
|
|
856
|
-
scriptTokens(command).forEach((token) => found.add(token));
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
return found;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* The runtime flags whose value is a module loaded before the entry, as `--flag value` or
|
|
865
|
-
* `--flag=value`: https://nodejs.org/api/cli.html#--importmodule,
|
|
866
|
-
* https://nodejs.org/api/cli.html#--experimental-loadermodule,
|
|
867
|
-
* https://nodejs.org/api/cli.html#-r---require-module
|
|
868
|
-
*/
|
|
869
|
-
const PRELOAD_FLAGS: ReadonlySet<string> = new Set([
|
|
870
|
-
"--experimental-loader",
|
|
871
|
-
"--import",
|
|
872
|
-
"--loader",
|
|
873
|
-
"--require",
|
|
874
|
-
"-r",
|
|
875
|
-
]);
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* The programs that pass a preload flag to the runtime they start: Node itself, and the
|
|
879
|
-
* runners that forward Node's flags or read the same `--require`:
|
|
880
|
-
* https://mochajs.org/#-require-module-r-module, https://tsx.is/node-enhancement,
|
|
881
|
-
* https://typestrong.org/ts-node/docs/options#require,
|
|
882
|
-
* https://github.com/remy/nodemon#running-non-node-scripts
|
|
883
|
-
*/
|
|
884
|
-
const PRELOADING_PROGRAMS: ReadonlySet<string> = new Set([
|
|
885
|
-
"mocha",
|
|
886
|
-
"node",
|
|
887
|
-
"nodemon",
|
|
888
|
-
"ts-node",
|
|
889
|
-
"tsx",
|
|
890
|
-
]);
|
|
891
|
-
|
|
892
|
-
/** The programs that run the command after them, and an environment assignment before it. */
|
|
893
|
-
const LAUNCHERS: ReadonlySet<string> = new Set(["cross-env", "env", "npx"]);
|
|
894
|
-
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* The packages `scripts` preload by a runtime flag's bare module specifier, a flag
|
|
898
|
-
* counting only in a command whose program passes it to the runtime, and for Node only
|
|
899
|
-
* before the script it runs, whose own arguments come after it.
|
|
900
|
-
*/
|
|
901
|
-
function preloadsOf(scripts: readonly string[]): ReadonlySet<string> {
|
|
902
|
-
const found = new Set<string>();
|
|
903
|
-
for (const script of scripts) {
|
|
904
|
-
for (const command of commandsOf(script)) {
|
|
905
|
-
const tokens = scriptTokens(command);
|
|
906
|
-
let at = 0;
|
|
907
|
-
while (
|
|
908
|
-
at < tokens.length &&
|
|
909
|
-
(ASSIGNMENT.test(tokens[at] ?? "") || LAUNCHERS.has(tokens[at] ?? ""))
|
|
910
|
-
) {
|
|
911
|
-
at += 1;
|
|
912
|
-
}
|
|
913
|
-
const path = tokens[at] ?? "";
|
|
914
|
-
const program = path.slice(path.lastIndexOf("/") + 1);
|
|
915
|
-
if (!PRELOADING_PROGRAMS.has(program)) {
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
for (at += 1; at < tokens.length; at += 1) {
|
|
919
|
-
const token = tokens[at] ?? "";
|
|
920
|
-
if (!token.startsWith("-")) {
|
|
921
|
-
if (program === "node") {
|
|
922
|
-
break;
|
|
923
|
-
}
|
|
924
|
-
continue;
|
|
925
|
-
}
|
|
926
|
-
const equals = token.indexOf("=");
|
|
927
|
-
const flag = equals < 0 ? token : token.slice(0, equals);
|
|
928
|
-
if (!PRELOAD_FLAGS.has(flag)) {
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
|
-
if (equals < 0) {
|
|
932
|
-
at += 1;
|
|
933
|
-
}
|
|
934
|
-
const value = equals < 0 ? tokens[at] : token.slice(equals + 1);
|
|
935
|
-
const name = value === undefined ? undefined : packageOfSpecifier(value);
|
|
936
|
-
if (name !== undefined) {
|
|
937
|
-
found.add(name);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return found;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
919
|
/** Whether a path below the target root lies in the directory `dir` below it, `""` being the root. */
|
|
946
920
|
function isInside(path: string, dir: string): boolean {
|
|
947
921
|
return dir === "" || path.startsWith(`${dir}/`);
|
|
@@ -1036,8 +1010,7 @@ export function dependenciesOf(
|
|
|
1036
1010
|
unbuilt.add(path);
|
|
1037
1011
|
}
|
|
1038
1012
|
}
|
|
1039
|
-
const workflow =
|
|
1040
|
-
const workflowPreloads = preloadsOf(workflowCommands(host, targetRoot));
|
|
1013
|
+
const workflow = workflowUses(host, targetRoot);
|
|
1041
1014
|
const declared: DeclaredDependency[] = [];
|
|
1042
1015
|
const ran = new Set<string>();
|
|
1043
1016
|
const peers = new Map<string, readonly string[]>();
|
|
@@ -1047,25 +1020,28 @@ export function dependenciesOf(
|
|
|
1047
1020
|
if (read === undefined || !isRecord(read.value)) {
|
|
1048
1021
|
continue;
|
|
1049
1022
|
}
|
|
1050
|
-
const
|
|
1051
|
-
const
|
|
1052
|
-
manifestCommands(host, dir, read.value).map(({ command }) => command),
|
|
1053
|
-
);
|
|
1023
|
+
const own = manifestUses(host, dir, read.value);
|
|
1024
|
+
const held = `${own.held}\n${workflow.held}`;
|
|
1054
1025
|
for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
|
|
1055
1026
|
const found = installedOf(host, dir, dependency.name);
|
|
1056
1027
|
if (found === undefined && uninstalledHere.has(dir)) {
|
|
1057
1028
|
continue;
|
|
1058
1029
|
}
|
|
1059
1030
|
declared.push(dependency);
|
|
1060
|
-
if (
|
|
1031
|
+
if (
|
|
1032
|
+
own.preloads.has(dependency.name) ||
|
|
1033
|
+
workflow.preloads.has(dependency.name) ||
|
|
1034
|
+
heldNames(held, [dependency.name]).size > 0
|
|
1035
|
+
) {
|
|
1061
1036
|
ran.add(declarationKey(dependency));
|
|
1062
1037
|
}
|
|
1063
1038
|
if (found === undefined) {
|
|
1064
1039
|
continue;
|
|
1065
1040
|
}
|
|
1066
1041
|
peers.set(declarationKey(dependency), found.peers);
|
|
1067
|
-
const runs = (command: string): boolean =>
|
|
1068
|
-
|
|
1042
|
+
const runs = (command: string): boolean =>
|
|
1043
|
+
own.words.has(command) || workflow.words.has(command);
|
|
1044
|
+
if (found.commands.some(runs) || heldNames(held, found.commands).size > 0) {
|
|
1069
1045
|
ran.add(declarationKey(dependency));
|
|
1070
1046
|
}
|
|
1071
1047
|
}
|
package/src/load/synthesize.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* is written for.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { readCommands } from "../commands/read.ts";
|
|
8
9
|
import { CONVENTION_ROWS } from "../convention-rows.ts";
|
|
9
10
|
import type { Conventions } from "../conventions.ts";
|
|
10
11
|
import { holdsManifest, MANIFEST, readManifestAt, type ManifestRead } from "../fs/manifest.ts";
|
|
@@ -12,6 +13,7 @@ import { filesBelow } from "../fs/tree.ts";
|
|
|
12
13
|
import type { Host } from "../host.ts";
|
|
13
14
|
import { JAVASCRIPT_FILE, minified, minifiedByName } from "../javascript-file.ts";
|
|
14
15
|
import { isRecord } from "../json-document.ts";
|
|
16
|
+
import { manifestCommands } from "../manifest.ts";
|
|
15
17
|
import { ancestors, dirnamePath, joinPath, relativePath } from "../paths.ts";
|
|
16
18
|
import type { Engine } from "../session.ts";
|
|
17
19
|
import { bytewise } from "../text/compare.ts";
|
|
@@ -133,12 +135,22 @@ const RUNTIME_OPTIONS = {
|
|
|
133
135
|
/** The manifest members that say a package is written for Node. */
|
|
134
136
|
const NODE_MEMBERS = ["main", "exports", "bin", "type"] as const;
|
|
135
137
|
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Whether one manifest says its package runs under Node: a Node member, `engines.node`
|
|
140
|
+
* included, or a command string of it that runs a file with `node`, as `commands/read.ts` reads it.
|
|
141
|
+
*/
|
|
142
|
+
function writtenForNode(
|
|
143
|
+
host: Host,
|
|
144
|
+
dir: string,
|
|
145
|
+
manifest: Readonly<Record<string, unknown>>,
|
|
146
|
+
): boolean {
|
|
138
147
|
const { engines } = manifest;
|
|
139
148
|
return (
|
|
140
149
|
NODE_MEMBERS.some((member) => manifest[member] !== undefined) ||
|
|
141
|
-
(isRecord(engines) && engines["node"] !== undefined)
|
|
150
|
+
(isRecord(engines) && engines["node"] !== undefined) ||
|
|
151
|
+
manifestCommands(host, dir, manifest).some(({ command }) =>
|
|
152
|
+
readCommands(command, dir).runs.some((run) => run.runtime === "node"),
|
|
153
|
+
)
|
|
142
154
|
);
|
|
143
155
|
}
|
|
144
156
|
|
|
@@ -172,7 +184,8 @@ interface Forming {
|
|
|
172
184
|
/**
|
|
173
185
|
* Each group's program, given the module system of the runtime its package is written for: a
|
|
174
186
|
* bundler's where a row of a tool that bundles applies through the group's manifest or the
|
|
175
|
-
* root's, else Node's where its manifest says the package runs under Node
|
|
187
|
+
* root's, else Node's where its manifest says the package runs under Node or a command string of
|
|
188
|
+
* it runs a file with `node`, under which a file's
|
|
176
189
|
* extension and its package's `type` decide its module format, else a bundler's, which reads a
|
|
177
190
|
* file with no module syntax as a classic script, as a browser loads it.
|
|
178
191
|
*/
|
|
@@ -189,7 +202,7 @@ export function formedPrograms(
|
|
|
189
202
|
!bundling.has(MANIFEST) &&
|
|
190
203
|
(below === undefined || !bundling.has(below)) &&
|
|
191
204
|
group.manifest.kind === "read" &&
|
|
192
|
-
writtenForNode(group.manifest.manifest)
|
|
205
|
+
writtenForNode(host, group.dir, group.manifest.manifest)
|
|
193
206
|
? "node"
|
|
194
207
|
: "bundler";
|
|
195
208
|
const configFile = writtenPath(host, joinPath(group.dir, GROUP_CONFIGURATION));
|
package/src/manifest.ts
CHANGED
|
@@ -9,9 +9,7 @@ import type { Host } from "./host.ts";
|
|
|
9
9
|
import { isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
10
10
|
import { isRecord } from "./json-document.ts";
|
|
11
11
|
import { MANIFEST, manifestAt, readJSON } from "./fs/manifest.ts";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
/** The manifest's own name. */
|
|
12
|
+
import { pathsNamed, readCommands } from "./commands/read.ts";
|
|
15
13
|
|
|
16
14
|
/** The three members that name an entry point beside `exports` and `bin`. */
|
|
17
15
|
const LEGACY_MEMBERS = ["main", "module", "types"] as const;
|
|
@@ -22,17 +20,17 @@ type EntryRole =
|
|
|
22
20
|
| "import"
|
|
23
21
|
/** A shell running the package's command, which is a caller that exists. */
|
|
24
22
|
| "run"
|
|
25
|
-
/** A
|
|
23
|
+
/** A path one of the package's scripts names or runs, or a text layer 3 holds for the package. */
|
|
26
24
|
| "script";
|
|
27
25
|
|
|
28
|
-
/** One entry point the manifest names, or
|
|
26
|
+
/** One entry point the manifest names, or one text layer 3 holds for the package. */
|
|
29
27
|
export interface ManifestEntry {
|
|
30
28
|
/**
|
|
31
29
|
* The member that named it as a maintainer edits it, `main`, `bin["deadset-ts"]` or
|
|
32
30
|
* `exports["."]["import"]`: what a root reports as the string that named it.
|
|
33
31
|
*/
|
|
34
32
|
readonly member: string;
|
|
35
|
-
/** The target the member carries, resolved against the manifest's directory, or that directory for a
|
|
33
|
+
/** The target the member carries, resolved against the manifest's directory, or that directory for a held text. */
|
|
36
34
|
readonly path: string;
|
|
37
35
|
readonly role: EntryRole;
|
|
38
36
|
/**
|
|
@@ -43,11 +41,12 @@ export interface ManifestEntry {
|
|
|
43
41
|
/** The directory of the manifest that names it, which `path` was resolved against. */
|
|
44
42
|
readonly dir: string;
|
|
45
43
|
/**
|
|
46
|
-
* On
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* On an entry that names no entry point, whose path is the manifest's directory, the text
|
|
45
|
+
* layer 3 holds: a whole command string the package runs, or the whole text of nodemon's
|
|
46
|
+
* configuration one is read from. Rooting enters no file for it and holds each file
|
|
47
|
+
* `spelledFiles` reads the text to name.
|
|
49
48
|
*/
|
|
50
|
-
readonly
|
|
49
|
+
readonly held?: string;
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
/**
|
|
@@ -67,6 +66,29 @@ function below(member: string, key: string): string {
|
|
|
67
66
|
return `${member}[${JSON.stringify(key)}]`;
|
|
68
67
|
}
|
|
69
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The whole text of nodemon's configuration, the manifest's `nodemonConfig` member and the
|
|
71
|
+
* `nodemon.json` beside it, by the member each is. Layer 3 holds it whole, because nodemon runs
|
|
72
|
+
* commands from keys besides `exec`, `events` among them: https://github.com/remy/nodemon#config-files
|
|
73
|
+
*/
|
|
74
|
+
export function nodemonTexts(
|
|
75
|
+
host: Host,
|
|
76
|
+
dir: string,
|
|
77
|
+
manifest: Record<string, unknown>,
|
|
78
|
+
): readonly { readonly member: string; readonly text: string }[] {
|
|
79
|
+
const member = manifest["nodemonConfig"];
|
|
80
|
+
let file: string | undefined;
|
|
81
|
+
try {
|
|
82
|
+
file = host.readFile(joinPath(dir, "nodemon.json"));
|
|
83
|
+
} catch {
|
|
84
|
+
file = undefined;
|
|
85
|
+
}
|
|
86
|
+
return [
|
|
87
|
+
...(member === undefined ? [] : [{ member: "nodemonConfig", text: JSON.stringify(member) }]),
|
|
88
|
+
...(file === undefined ? [] : [{ member: "nodemon.json", text: file }]),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
70
92
|
/**
|
|
71
93
|
* The command strings one manifest runs, each at the member it is written under: each of its
|
|
72
94
|
* `scripts`, and the `exec` command of nodemon's configuration, in the manifest's `nodemonConfig`
|
|
@@ -127,9 +149,10 @@ function exportTargets(value: unknown, member: string, found: Omit<ManifestEntry
|
|
|
127
149
|
export interface Manifest {
|
|
128
150
|
/**
|
|
129
151
|
* Every entry the manifest names, by absolute path, in the order read: the entry points `main`,
|
|
130
|
-
* `module`, `types`, `browser` and each file it maps, each `bin` command, every `exports` target
|
|
131
|
-
* and per
|
|
132
|
-
*
|
|
152
|
+
* `module`, `types`, `browser` and each file it maps, each `bin` command, every `exports` target,
|
|
153
|
+
* and per command string {@link manifestCommands} reads each path `pathsNamed` reads, which
|
|
154
|
+
* names a file only where one is at it, then one entry of its whole text, and one entry of
|
|
155
|
+
* each of {@link nodemonTexts}, which are no entry point.
|
|
133
156
|
*/
|
|
134
157
|
readonly entries: readonly ManifestEntry[];
|
|
135
158
|
/**
|
|
@@ -191,20 +214,15 @@ export function readManifest(host: Host, targetRoot: string): Manifest {
|
|
|
191
214
|
|
|
192
215
|
exportTargets(manifest["exports"], "exports", found);
|
|
193
216
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
for (const
|
|
197
|
-
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const member = below("scripts", name);
|
|
201
|
-
for (const path of new Set(scriptTokens(command))) {
|
|
202
|
-
found.push({ member, path, role: "script", published: false });
|
|
203
|
-
}
|
|
217
|
+
for (const { member, command } of manifestCommands(host, targetRoot, manifest)) {
|
|
218
|
+
// Read against ".", so each path stays relative until the entries are resolved below.
|
|
219
|
+
for (const path of new Set(pathsNamed(readCommands(command, ".")).map((one) => one.path))) {
|
|
220
|
+
found.push({ member, path, role: "script", published: false });
|
|
204
221
|
}
|
|
222
|
+
found.push({ member, path: ".", role: "script", published: false, held: command });
|
|
205
223
|
}
|
|
206
|
-
for (const { member,
|
|
207
|
-
found.push({ member, path: ".", role: "script", published: false,
|
|
224
|
+
for (const { member, text } of nodemonTexts(host, targetRoot, manifest)) {
|
|
225
|
+
found.push({ member, path: ".", role: "script", published: false, held: text });
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
return {
|
package/src/roots.ts
CHANGED
|
@@ -54,7 +54,8 @@ import { bytewise } from "./text/compare.ts";
|
|
|
54
54
|
import { holdsManifest } from "./fs/manifest.ts";
|
|
55
55
|
import { globExpression } from "./text/glob.ts";
|
|
56
56
|
import { spelledFiles } from "./text/named-files.ts";
|
|
57
|
-
import {
|
|
57
|
+
import { commandFileTexts, workflowCommands } from "./workflow-steps.ts";
|
|
58
|
+
import { readCommands } from "./commands/read.ts";
|
|
58
59
|
|
|
59
60
|
/** Why one declaration is a root. */
|
|
60
61
|
export type RootKind =
|
|
@@ -66,7 +67,7 @@ export type RootKind =
|
|
|
66
67
|
| "manifest-entry"
|
|
67
68
|
/** A file the manifest names as a command, and what it exports outside the published API. */
|
|
68
69
|
| "manifest-binary"
|
|
69
|
-
/** A file a
|
|
70
|
+
/** A file a manifest's script names or runs, as `commands/read.ts` reads it, and what it exports. */
|
|
70
71
|
| "script"
|
|
71
72
|
/**
|
|
72
73
|
* A global a declaration file declares as an alias of a module's export, and the
|
|
@@ -123,7 +124,7 @@ export interface Roots {
|
|
|
123
124
|
readonly unanswered: readonly string[];
|
|
124
125
|
/** Every manifest entry naming a file to import or run that reads back to no file of this project. */
|
|
125
126
|
readonly unmappedEntries: readonly ManifestEntry[];
|
|
126
|
-
/** Every package whose command strings
|
|
127
|
+
/** Every package whose command strings, in what no command reader understands, name an own file no root enters with all it exports. */
|
|
127
128
|
readonly heldScripts: readonly HeldScripts[];
|
|
128
129
|
}
|
|
129
130
|
|
|
@@ -346,11 +347,11 @@ function childrenOf(held: Inventory): ReadonlyMap<string, readonly InventorySymb
|
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
/**
|
|
349
|
-
* Per package, the own files its
|
|
350
|
-
* with all they export: a command can use no more of such a file than the root
|
|
351
|
-
* manifest's are its entries'
|
|
352
|
-
* workflow steps, git hooks and makefile recipes beside its own, with the
|
|
353
|
-
*
|
|
350
|
+
* Per package, the own files its held texts name, as `spelledFiles` reads them, but those a
|
|
351
|
+
* root enters with all they export: a command can use no more of such a file than the root
|
|
352
|
+
* keeps live. A manifest's are its entries' held texts, and the target root's package has the
|
|
353
|
+
* workflow steps, git hooks and makefile recipes beside its own, with the whole text of each
|
|
354
|
+
* file `commandFileTexts` reads.
|
|
354
355
|
*/
|
|
355
356
|
function scriptHolds(
|
|
356
357
|
entries: readonly ManifestEntry[],
|
|
@@ -373,13 +374,19 @@ function scriptHolds(
|
|
|
373
374
|
const commands = new Map<string, string[]>([
|
|
374
375
|
[
|
|
375
376
|
resolvePath(cwd, targetRoot),
|
|
376
|
-
[
|
|
377
|
+
[
|
|
378
|
+
...workflowCommands(host, targetRoot).flatMap(({ text, dir, writtenDirectory }) => [
|
|
379
|
+
readCommands(text, dir).held,
|
|
380
|
+
...(writtenDirectory === undefined ? [] : [writtenDirectory]),
|
|
381
|
+
]),
|
|
382
|
+
...commandFileTexts(host, targetRoot),
|
|
383
|
+
],
|
|
377
384
|
],
|
|
378
385
|
]);
|
|
379
386
|
for (const entry of entries) {
|
|
380
|
-
if (entry.
|
|
387
|
+
if (entry.held !== undefined) {
|
|
381
388
|
const dir = resolvePath(cwd, entry.dir);
|
|
382
|
-
commands.set(dir, [...(commands.get(dir) ?? []), entry.
|
|
389
|
+
commands.set(dir, [...(commands.get(dir) ?? []), entry.held]);
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
const held: HeldScripts[] = [];
|
|
@@ -464,7 +471,7 @@ export function roots<Brand>(
|
|
|
464
471
|
})),
|
|
465
472
|
];
|
|
466
473
|
const scripted = entries.flatMap(({ entry }) =>
|
|
467
|
-
entry.role === "script" && entry.
|
|
474
|
+
entry.role === "script" && entry.held === undefined ? files.named(entry.path) : [],
|
|
468
475
|
);
|
|
469
476
|
const readBackTo = [
|
|
470
477
|
...scripted,
|
|
@@ -517,7 +524,7 @@ export function roots<Brand>(
|
|
|
517
524
|
const unmapped: ManifestEntry[] = [];
|
|
518
525
|
const mapped = patterned.map(({ file }) => file);
|
|
519
526
|
for (const { entry } of entries) {
|
|
520
|
-
if (entry.
|
|
527
|
+
if (entry.held !== undefined) {
|
|
521
528
|
continue;
|
|
522
529
|
}
|
|
523
530
|
const named = entryFiles(entry);
|
package/src/text/named-files.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* skipped first.
|
|
2
|
+
* Layer 3 of the command reader: the files and the package names a command's text names, read
|
|
3
|
+
* as plain text. The text is searched for each name, which every spelling of what it names
|
|
4
|
+
* contains, so no part of it is split, unquoted or skipped first.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { dirnamePath, normalizePath, relativePath } from "../paths.ts";
|
|
@@ -66,3 +66,26 @@ export function spelledFiles<File>(
|
|
|
66
66
|
}
|
|
67
67
|
return [...found];
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/** A character a package's or a command's name may hold, so one beside a name makes it part of a longer one. */
|
|
71
|
+
const NAME_CHARACTER = /[\w.@-]/u;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Each of `names`, a package's or a command's name, that `text` holds with no name character
|
|
75
|
+
* before it and no character after it but one a path or a version may go on with, `/`, `.` or
|
|
76
|
+
* `@` among them: `module-alias/register` holds `module-alias`, and `tsxx` holds no `tsx`.
|
|
77
|
+
*/
|
|
78
|
+
export function heldNames(text: string, names: readonly string[]): ReadonlySet<string> {
|
|
79
|
+
const found = new Set<string>();
|
|
80
|
+
for (const name of names) {
|
|
81
|
+
for (let at = text.indexOf(name); at >= 0 && name !== ""; at = text.indexOf(name, at + 1)) {
|
|
82
|
+
const before = text.charAt(at - 1);
|
|
83
|
+
const after = text.charAt(at + name.length);
|
|
84
|
+
if (!NAME_CHARACTER.test(before) && !/[\w-]/u.test(after)) {
|
|
85
|
+
found.add(name);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return found;
|
|
91
|
+
}
|