@navbook/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/args.d.ts +28 -0
- package/dist/args.js +48 -0
- package/dist/args.js.map +1 -0
- package/dist/commands/complete.d.ts +10 -0
- package/dist/commands/complete.js +59 -51
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/compose.d.ts +35 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/entity.d.ts +73 -0
- package/dist/commands/entity.js +59 -44
- package/dist/commands/entity.js.map +1 -1
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +6 -3
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/issue.d.ts +35 -0
- package/dist/commands/issue.js +4 -5
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/plugin.d.ts +36 -0
- package/dist/commands/plugin.js +386 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/commands/policy.d.ts +58 -0
- package/dist/commands/policy.js +13 -0
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/pr-elsewhere.d.ts +40 -0
- package/dist/commands/pr-elsewhere.js +209 -0
- package/dist/commands/pr-elsewhere.js.map +1 -0
- package/dist/commands/pr.d.ts +69 -0
- package/dist/commands/pr.js +86 -60
- package/dist/commands/pr.js.map +1 -1
- package/dist/context.d.ts +42 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -1
- package/dist/editor.d.ts +23 -0
- package/dist/editor.js +4 -0
- package/dist/editor.js.map +1 -1
- package/dist/errors.d.ts +17 -0
- package/dist/install/completions.d.ts +19 -0
- package/dist/install/hook.d.ts +32 -0
- package/dist/install/hook.js +48 -4
- package/dist/install/hook.js.map +1 -1
- package/dist/main.d.ts +21 -0
- package/dist/main.js +112 -8
- package/dist/main.js.map +1 -1
- package/dist/plugins/commands.d.ts +69 -0
- package/dist/plugins/commands.js +148 -0
- package/dist/plugins/commands.js.map +1 -0
- package/dist/plugins/hint.d.ts +23 -0
- package/dist/plugins/hint.js +65 -0
- package/dist/plugins/hint.js.map +1 -0
- package/dist/plugins/host.d.ts +125 -0
- package/dist/plugins/host.js +18 -0
- package/dist/plugins/host.js.map +1 -0
- package/dist/plugins/loader.d.ts +54 -0
- package/dist/plugins/loader.js +165 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/resolve.d.ts +50 -0
- package/dist/plugins/resolve.js +134 -0
- package/dist/plugins/resolve.js.map +1 -0
- package/dist/plugins/runtime.d.ts +69 -0
- package/dist/plugins/runtime.js +256 -0
- package/dist/plugins/runtime.js.map +1 -0
- package/dist/plugins/store.d.ts +96 -0
- package/dist/plugins/store.js +186 -0
- package/dist/plugins/store.js.map +1 -0
- package/dist/program.d.ts +49 -0
- package/dist/program.js +351 -113
- package/dist/program.js.map +1 -1
- package/dist/prompt.d.ts +31 -0
- package/dist/render/colors.d.ts +13 -0
- package/dist/render/detail.d.ts +22 -0
- package/dist/render/table.d.ts +32 -0
- package/dist/render/table.js +123 -13
- package/dist/render/table.js.map +1 -1
- package/dist/sort.d.ts +27 -0
- package/package.json +9 -2
- package/dist/commands/feature.js +0 -202
- package/dist/commands/feature.js.map +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Building Commander commands out of manifests — spec 04 §4.3.
|
|
3
|
+
*
|
|
4
|
+
* Every command a plugin adds is built from its declaration, with no plugin
|
|
5
|
+
* code loaded. That is what makes `nav --help` and shell completion work
|
|
6
|
+
* without paying for an import, and it is what the manifest's shapes exist to
|
|
7
|
+
* make possible. The plugin's own module is imported inside the action, the
|
|
8
|
+
* moment somebody actually runs the command.
|
|
9
|
+
*
|
|
10
|
+
* A name that collides with a built-in noun, or with one an earlier plugin
|
|
11
|
+
* took, is refused and the plugin skipped, because a word that means different
|
|
12
|
+
* things depending on load order is worse than one that means nothing.
|
|
13
|
+
*/
|
|
14
|
+
import { Command, Option } from "commander";
|
|
15
|
+
import { finiteNumber, wholeNumber } from "../args.js";
|
|
16
|
+
/**
|
|
17
|
+
* Sort out which plugin owns what, refusing collisions.
|
|
18
|
+
*
|
|
19
|
+
* `taken` is the built-in noun vocabulary. A plugin colliding with one of
|
|
20
|
+
* those loses outright; two plugins colliding with each other are resolved by
|
|
21
|
+
* which was resolved first, and the loser is named.
|
|
22
|
+
*/
|
|
23
|
+
export function collectCommands(plugins, taken) {
|
|
24
|
+
const nouns = new Map();
|
|
25
|
+
const contributions = new Map();
|
|
26
|
+
const skipped = new Set();
|
|
27
|
+
const problems = [];
|
|
28
|
+
const builtin = new Set(taken);
|
|
29
|
+
for (const plugin of plugins) {
|
|
30
|
+
const declared = plugin.manifest.cli;
|
|
31
|
+
if (declared === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const clash = (declared.commands ?? []).find((spec) => builtin.has(spec.name) || nouns.has(spec.name));
|
|
34
|
+
if (clash !== undefined) {
|
|
35
|
+
const owner = builtin.has(clash.name)
|
|
36
|
+
? "a built-in command"
|
|
37
|
+
: `plugin ${nouns.get(clash.name)?.plugin.name}`;
|
|
38
|
+
problems.push(`nav: plugin ${plugin.name} skipped: '${clash.name}' is already ${owner} (spec 04 §4.3)`);
|
|
39
|
+
skipped.add(plugin.name);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const spec of declared.commands ?? [])
|
|
43
|
+
nouns.set(spec.name, { plugin, spec });
|
|
44
|
+
for (const spec of declared.contributions ?? []) {
|
|
45
|
+
const existing = contributions.get(spec.on) ?? [];
|
|
46
|
+
existing.push({ plugin, spec });
|
|
47
|
+
contributions.set(spec.on, existing);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { nouns, contributions, skipped, problems };
|
|
51
|
+
}
|
|
52
|
+
/** Build one plugin noun into a Commander command tree. */
|
|
53
|
+
export function buildPluginCommand(plugin, spec, getCtx, run) {
|
|
54
|
+
return buildCommand(plugin, spec, spec.name, getCtx, run);
|
|
55
|
+
}
|
|
56
|
+
function buildCommand(plugin, spec, path, getCtx, run) {
|
|
57
|
+
const command = new Command(spec.name).description(spec.description);
|
|
58
|
+
// A group — a noun with verbs under it — has no action of its own, and
|
|
59
|
+
// Commander's implicit `help` verb is dropped for the reason the built-in
|
|
60
|
+
// nouns drop it: it prints exactly what `--help` prints.
|
|
61
|
+
if (spec.commands?.length) {
|
|
62
|
+
command.helpCommand(false);
|
|
63
|
+
for (const child of spec.commands) {
|
|
64
|
+
command.addCommand(buildCommand(plugin, child, `${path} ${child.name}`, getCtx, run));
|
|
65
|
+
}
|
|
66
|
+
return command;
|
|
67
|
+
}
|
|
68
|
+
for (const argument of spec.arguments ?? [])
|
|
69
|
+
command.argument(...argumentOf(argument));
|
|
70
|
+
for (const option of spec.options ?? [])
|
|
71
|
+
applyOption(command, option);
|
|
72
|
+
command.action(async (...received) => {
|
|
73
|
+
// Commander hands the action its positional arguments, then the parsed
|
|
74
|
+
// options, then the command itself. The declared arguments say how many
|
|
75
|
+
// positionals there are, so the split needs no guessing.
|
|
76
|
+
const count = spec.arguments?.length ?? 0;
|
|
77
|
+
const args = received.slice(0, count);
|
|
78
|
+
const opts = (received[count] ?? {});
|
|
79
|
+
// Touch the context before loading the plugin: a command run outside a
|
|
80
|
+
// repository should say so in the CLI's own words rather than in whatever
|
|
81
|
+
// the plugin says when it finds no tree.
|
|
82
|
+
getCtx();
|
|
83
|
+
await run(plugin, path, args, opts);
|
|
84
|
+
});
|
|
85
|
+
return command;
|
|
86
|
+
}
|
|
87
|
+
/** A declared argument, in the `[name]` / `<name...>` shape Commander parses. */
|
|
88
|
+
function argumentOf(spec) {
|
|
89
|
+
const dots = spec.variadic === true ? "..." : "";
|
|
90
|
+
const name = spec.optional === true ? `[${spec.name}${dots}]` : `<${spec.name}${dots}>`;
|
|
91
|
+
return [name, spec.description ?? ""];
|
|
92
|
+
}
|
|
93
|
+
/** Apply one declared option to a command. */
|
|
94
|
+
export function applyOption(command, spec) {
|
|
95
|
+
const option = new Option(spec.flags, spec.description);
|
|
96
|
+
if (spec.repeatable === true) {
|
|
97
|
+
option.argParser((value, previous = []) => [...previous, value]);
|
|
98
|
+
option.default([]);
|
|
99
|
+
}
|
|
100
|
+
else if (spec.parse === "int") {
|
|
101
|
+
// The parsers the built-in options use, so a plugin's count is refused
|
|
102
|
+
// where the built-in ones are: `parseInt` read `2.5` as 2 and `3abc` as 3,
|
|
103
|
+
// and `Number` reads `0x10` and `1e1` and rounds past 2^53 (#kw143sq9).
|
|
104
|
+
option.argParser(wholeNumber(null));
|
|
105
|
+
}
|
|
106
|
+
else if (spec.parse === "number") {
|
|
107
|
+
option.argParser(finiteNumber);
|
|
108
|
+
}
|
|
109
|
+
if (spec.default !== undefined)
|
|
110
|
+
option.default(spec.default);
|
|
111
|
+
command.addOption(option);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Why a declared option cannot be applied, or null when it can.
|
|
115
|
+
*
|
|
116
|
+
* Two different rules, because two different namespaces are at stake.
|
|
117
|
+
*
|
|
118
|
+
* On a plugin's **own** command the plugin owns every flag, so a short one is
|
|
119
|
+
* its business: `nav feature open -m "…"` reads exactly as `nav issue open -m
|
|
120
|
+
* "…"` does, and denying it would make a plugin's commands feel like
|
|
121
|
+
* second-class ones for no gain.
|
|
122
|
+
*
|
|
123
|
+
* On a **contribution** to a built-in verb the namespace is shared, and short
|
|
124
|
+
* flags are the scarce part of it — `-m`, `-y`, `-n` already mean things
|
|
125
|
+
* across the whole CLI. A plugin taking one there would be discovered by
|
|
126
|
+
* whoever installed two plugins rather than by whoever wrote the second, so
|
|
127
|
+
* they are refused outright rather than raced for.
|
|
128
|
+
*
|
|
129
|
+
* A long flag that is already taken is refused either way, and checked before
|
|
130
|
+
* Commander sees it: `.option()` throws on a duplicate, which would take the
|
|
131
|
+
* whole CLI down over one plugin's manifest.
|
|
132
|
+
*/
|
|
133
|
+
export function optionCollision(command, spec, where = "contribution") {
|
|
134
|
+
const flags = spec.flags.split(/[ ,|]+/).filter((part) => part.startsWith("-"));
|
|
135
|
+
if (where === "contribution") {
|
|
136
|
+
const short = flags.find((flag) => /^-[^-]/.test(flag));
|
|
137
|
+
if (short !== undefined) {
|
|
138
|
+
return `'${short}' is a short flag, which a plugin may not add to a built-in verb`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const flag of flags) {
|
|
142
|
+
if (command.options.some((existing) => existing.long === flag || existing.short === flag)) {
|
|
143
|
+
return `'${flag}' is already an option of '${command.name()}'`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/plugins/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgBvD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgC,EAChC,KAAwB;IAExB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuD,CAAC;IAC7E,MAAM,aAAa,GAAG,IAAI,GAAG,EAA8D,CAAC;IAC5F,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAE/B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACrC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QAErC,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAC1C,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CACzD,CAAC;QACF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBACnC,CAAC,CAAC,oBAAoB;gBACtB,CAAC,CAAC,UAAU,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;YACnD,QAAQ,CAAC,IAAI,CACX,eAAe,MAAM,CAAC,IAAI,cAAc,KAAK,CAAC,IAAI,gBAAgB,KAAK,iBAAiB,CACzF,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,QAAQ,IAAI,EAAE;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnF,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACrD,CAAC;AAUD,2DAA2D;AAC3D,MAAM,UAAU,kBAAkB,CAChC,MAAoB,EACpB,IAAiB,EACjB,MAAiB,EACjB,GAAe;IAEf,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,YAAY,CACnB,MAAoB,EACpB,IAAiB,EACjB,IAAY,EACZ,MAAiB,EACjB,GAAe;IAEf,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAErE,uEAAuE;IACvE,0EAA0E;IAC1E,yDAAyD;IACzD,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE;QAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvF,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE;QAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAEtE,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,QAAmB,EAAE,EAAE;QAC9C,uEAAuE;QACvE,wEAAwE;QACxE,yDAAyD;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAa,CAAC;QAClD,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAA4B,CAAC;QAChE,uEAAuE;QACvE,0EAA0E;QAC1E,yCAAyC;QACzC,MAAM,EAAE,CAAC;QACT,MAAM,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iFAAiF;AACjF,SAAS,UAAU,CAAC,IAAkB;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC;IACxF,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,WAAW,CAAC,OAAgB,EAAE,IAAgB;IAC5D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,CAAC,SAAS,CAAC,CAAC,KAAa,EAAE,WAAqB,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QACnF,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACrB,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAChC,uEAAuE;QACvE,2EAA2E;QAC3E,wEAAwE;QACxE,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,IAAgB,EAChB,QAAgC,cAAc;IAE9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAChF,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,KAAK,kEAAkE,CAAC;QACrF,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE,CAAC;YAC1F,OAAO,IAAI,IAAI,8BAA8B,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;QACjE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saying that a tree holds data this `nav` cannot read.
|
|
3
|
+
*
|
|
4
|
+
* Spec 02 §2.12 requires a tool to preserve an extension namespace it does not
|
|
5
|
+
* understand, and allows it to say the namespace is undeclared. Saying so is
|
|
6
|
+
* the whole value of the declaration to somebody who has just cloned: the
|
|
7
|
+
* files are there, `nav` is ignoring them, and nothing else on screen would
|
|
8
|
+
* explain why.
|
|
9
|
+
*
|
|
10
|
+
* One line, on stderr, and never an error. The tree is conforming; this `nav`
|
|
11
|
+
* simply has less of it than the repository expects.
|
|
12
|
+
*/
|
|
13
|
+
import type { PluginDeclarationReading } from "@navbook/core";
|
|
14
|
+
import type { Ctx } from "../context.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Say once that the tree holds namespaces nothing here reads.
|
|
17
|
+
*
|
|
18
|
+
* `loaded` is the directories a plugin claimed, which are read rather than
|
|
19
|
+
* ignored and so are not worth a word. `declaration` is what the marker says:
|
|
20
|
+
* a namespace whose plugin is declared but missing has already been reported
|
|
21
|
+
* by name, and saying it twice helps nobody.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hintUndeclared(ctx: Ctx, declaration: PluginDeclarationReading, loaded: readonly string[]): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saying that a tree holds data this `nav` cannot read.
|
|
3
|
+
*
|
|
4
|
+
* Spec 02 §2.12 requires a tool to preserve an extension namespace it does not
|
|
5
|
+
* understand, and allows it to say the namespace is undeclared. Saying so is
|
|
6
|
+
* the whole value of the declaration to somebody who has just cloned: the
|
|
7
|
+
* files are there, `nav` is ignoring them, and nothing else on screen would
|
|
8
|
+
* explain why.
|
|
9
|
+
*
|
|
10
|
+
* One line, on stderr, and never an error. The tree is conforming; this `nav`
|
|
11
|
+
* simply has less of it than the repository expects.
|
|
12
|
+
*/
|
|
13
|
+
import { readdirSync } from "node:fs";
|
|
14
|
+
/** Names this format defines at the top of the root; never a namespace. */
|
|
15
|
+
const FORMAT_NAMES = new Set(["issues", "prs", "archive", "sync", "navbook.json"]);
|
|
16
|
+
/**
|
|
17
|
+
* Namespaces this project knows the plugin for, so the hint can name it.
|
|
18
|
+
*
|
|
19
|
+
* Only the grandfathered pair is listed, and deliberately: a namespace whose
|
|
20
|
+
* plugin nobody here knows about gets the generic line below, which is the
|
|
21
|
+
* honest thing to say about a directory some other project's plugin wrote.
|
|
22
|
+
*/
|
|
23
|
+
const KNOWN = {
|
|
24
|
+
specs: "@navbook/plugin-kb",
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Directories at the top of the root that this format does not define.
|
|
28
|
+
*
|
|
29
|
+
* One `readdir` rather than a parse of the tree: the caller has usually just
|
|
30
|
+
* read the whole repository, and reading it a second time to find out what the
|
|
31
|
+
* first read ignored would cost more than the hint is worth.
|
|
32
|
+
*/
|
|
33
|
+
function namespaceDirs(navRoot) {
|
|
34
|
+
try {
|
|
35
|
+
return readdirSync(navRoot, { withFileTypes: true })
|
|
36
|
+
.filter((entry) => entry.isDirectory() && !FORMAT_NAMES.has(entry.name))
|
|
37
|
+
.map((entry) => entry.name)
|
|
38
|
+
.sort();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Say once that the tree holds namespaces nothing here reads.
|
|
46
|
+
*
|
|
47
|
+
* `loaded` is the directories a plugin claimed, which are read rather than
|
|
48
|
+
* ignored and so are not worth a word. `declaration` is what the marker says:
|
|
49
|
+
* a namespace whose plugin is declared but missing has already been reported
|
|
50
|
+
* by name, and saying it twice helps nobody.
|
|
51
|
+
*/
|
|
52
|
+
export function hintUndeclared(ctx, declaration, loaded) {
|
|
53
|
+
const claimed = new Set(loaded);
|
|
54
|
+
for (const name of namespaceDirs(ctx.navRoot)) {
|
|
55
|
+
if (claimed.has(name))
|
|
56
|
+
continue;
|
|
57
|
+
const plugin = KNOWN[name];
|
|
58
|
+
if (plugin !== undefined && declaration.plugins.has(plugin))
|
|
59
|
+
continue;
|
|
60
|
+
ctx.stderr.write(plugin === undefined
|
|
61
|
+
? `nav: ${ctx.navDir}/${name}/ is not a shape this nav reads; it is preserved untouched (spec 02 §2.12)\n`
|
|
62
|
+
: `nav: ${ctx.navDir}/${name}/ belongs to ${plugin}, which ${ctx.navDir}/navbook.json does not declare; add it and run 'nav plugin install'\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hint.js","sourceRoot":"","sources":["../../src/plugins/hint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAItC,2EAA2E;AAC3E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AAEnF;;;;;;GAMG;AACH,MAAM,KAAK,GAA2B;IACpC,KAAK,EAAE,oBAAoB;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aACjD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACvE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;aAC1B,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAQ,EACR,WAAqC,EACrC,MAAyB;IAEzB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,MAAM,KAAK,SAAS,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,SAAS;QACtE,GAAG,CAAC,MAAM,CAAC,KAAK,CACd,MAAM,KAAK,SAAS;YAClB,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,IAAI,8EAA8E;YAC1G,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,IAAI,gBAAgB,MAAM,WAAW,GAAG,CAAC,MAAM,uEAAuE,CACjJ,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a plugin's entry module is handed — spec 05 §5.2.
|
|
3
|
+
*
|
|
4
|
+
* Each entry exports `activate(host)`. The host carries three things: the
|
|
5
|
+
* running core, the settings the repository declared, and the hooks for
|
|
6
|
+
* registering what this layer accepts.
|
|
7
|
+
*
|
|
8
|
+
* `core` is the host's own module object, not something the plugin imported.
|
|
9
|
+
* That is the single most important line in this file. A plugin resolving its
|
|
10
|
+
* own `@navbook/core` would get a second copy, and then `instanceof
|
|
11
|
+
* WorkspaceError` would be false across the boundary, two YAML parsers would
|
|
12
|
+
* sit on the startup path, and two `Repo` types that are structurally
|
|
13
|
+
* identical would be nominally different. The store installs with
|
|
14
|
+
* `--omit=peer` so that copy cannot exist; this is how the plugin gets the
|
|
15
|
+
* real one instead.
|
|
16
|
+
*/
|
|
17
|
+
import type { CorePluginHost, EntityRecord, Repo } from "@navbook/core";
|
|
18
|
+
import type { Composed, ComposeOptions } from "../commands/compose.ts";
|
|
19
|
+
import type { ExtraColumn } from "../commands/entity.ts";
|
|
20
|
+
import type { Ctx } from "../context.ts";
|
|
21
|
+
/** What every entry module exports. */
|
|
22
|
+
export interface PluginEntry<H> {
|
|
23
|
+
activate(host: H): void | Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export type { CorePluginHost };
|
|
26
|
+
/**
|
|
27
|
+
* What a plugin adds to a command that already exists.
|
|
28
|
+
*
|
|
29
|
+
* Every field here has a seam in the CLI that predates plugins: `columns` and
|
|
30
|
+
* `jsonExtra` are what `nav pr list --all-refs` already uses to add its `refs`
|
|
31
|
+
* column, and `showSection` is the same idea for a detail view. A plugin
|
|
32
|
+
* contributing through them renders identically to a built-in, which is the
|
|
33
|
+
* point — `nav issue list` should not look like it has a bolt-on.
|
|
34
|
+
*/
|
|
35
|
+
export interface VerbHandlers {
|
|
36
|
+
/**
|
|
37
|
+
* Frontmatter for a newly created entity, read off this verb's options.
|
|
38
|
+
*
|
|
39
|
+
* Returns what `NewEntityInput.ext` takes, so the plugin's keys are written
|
|
40
|
+
* by the same composer that writes the format's own.
|
|
41
|
+
*/
|
|
42
|
+
openFields?(opts: Record<string, unknown>): Record<string, string | readonly string[]>;
|
|
43
|
+
/** Columns appended to this listing, after the built-in ones. */
|
|
44
|
+
columns?: ExtraColumn[];
|
|
45
|
+
/** Keys merged into this command's `--json` object, per entity. */
|
|
46
|
+
jsonExtra?(entity: EntityRecord): Record<string, unknown>;
|
|
47
|
+
/** Lines appended to this `show`, after the built-in detail. */
|
|
48
|
+
showSection?(entity: EntityRecord, repo: Repo): string[];
|
|
49
|
+
/** Candidates offered for this command's query terms, e.g. `feature:auth`. */
|
|
50
|
+
listCompletions?(): string[];
|
|
51
|
+
}
|
|
52
|
+
/** What the `./cli` entry is given: the core host, plus the terminal. */
|
|
53
|
+
export interface CliPluginHost extends CorePluginHost {
|
|
54
|
+
/** Where output goes, who is acting, and where the repository is. */
|
|
55
|
+
ctx: Ctx;
|
|
56
|
+
/**
|
|
57
|
+
* Implement a command the manifest declared.
|
|
58
|
+
*
|
|
59
|
+
* `path` is the command as typed, without `nav`: `"feature open"`, or
|
|
60
|
+
* `"feature spec add"`. A path the manifest does not declare is refused,
|
|
61
|
+
* because the manifest is what built the help and the completions, and a
|
|
62
|
+
* command that runs but is not in either would be worse than one that does
|
|
63
|
+
* not run.
|
|
64
|
+
*/
|
|
65
|
+
command(path: string, run: (args: string[], opts: Record<string, unknown>) => void | Promise<void>): void;
|
|
66
|
+
/** Implement contributions to an existing verb the manifest declared. */
|
|
67
|
+
contribute(on: string, handlers: VerbHandlers): void;
|
|
68
|
+
/** Answer completion for an argument whose spec names this completer. */
|
|
69
|
+
completer(id: string, complete: (words: string[]) => string[]): void;
|
|
70
|
+
/** The terminal conveniences, so a plugin's output matches a built-in's. */
|
|
71
|
+
ui: CliPluginUi;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The parts of the CLI a plugin may use to talk to the terminal.
|
|
75
|
+
*
|
|
76
|
+
* Named rather than handed the modules wholesale: this is the surface the
|
|
77
|
+
* plugin API version promises, and a plugin reaching past it into the CLI's
|
|
78
|
+
* internals would break on a refactor nobody thought was a breaking change.
|
|
79
|
+
*/
|
|
80
|
+
export interface CliPluginUi {
|
|
81
|
+
/** Stop with an operational error (exit 1), as every built-in verb does. */
|
|
82
|
+
fail(message: string, details?: string[]): never;
|
|
83
|
+
/** Stop with a format violation (exit 2), as `doctor` does. */
|
|
84
|
+
failFormat(message: string, details?: string[]): never;
|
|
85
|
+
/** Ask a yes/no question; anything but yes is a no. */
|
|
86
|
+
askYesNo(question: string): boolean;
|
|
87
|
+
/** Print what will happen, ask once, then do it — the `nav install` shape. */
|
|
88
|
+
confirmAndPerform(opts: {
|
|
89
|
+
title: string;
|
|
90
|
+
actions: readonly {
|
|
91
|
+
description: string;
|
|
92
|
+
perform: () => void;
|
|
93
|
+
}[];
|
|
94
|
+
assumeYes?: boolean;
|
|
95
|
+
}): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Produce the text of a new file, from `-m` or from `$EDITOR`.
|
|
98
|
+
*
|
|
99
|
+
* The whole composing path rather than the editor alone, because what makes
|
|
100
|
+
* a hand-edited buffer safe is that it is parsed and validated before
|
|
101
|
+
* anything is written — and a plugin opening `$EDITOR` itself would have to
|
|
102
|
+
* reproduce that, and would eventually reproduce it slightly differently.
|
|
103
|
+
*/
|
|
104
|
+
composeFile(opts: ComposeOptions): Composed;
|
|
105
|
+
/** Open `$EDITOR` on a file already in the tree, as `nav issue edit` does. */
|
|
106
|
+
editFile(absolutePath: string): void;
|
|
107
|
+
/** Render a table sized to the terminal, the way every listing does. */
|
|
108
|
+
renderTable(columns: readonly {
|
|
109
|
+
header: string;
|
|
110
|
+
flexible?: boolean;
|
|
111
|
+
minWidth?: number;
|
|
112
|
+
}[], rows: readonly string[][]): string;
|
|
113
|
+
/**
|
|
114
|
+
* Pad text with spaces to `width` terminal columns, measured as a listing
|
|
115
|
+
* measures its cells — so a wide or combining character lines up.
|
|
116
|
+
*/
|
|
117
|
+
pad(text: string, width: number): string;
|
|
118
|
+
/** The one-line report printed after a `--commit` run. */
|
|
119
|
+
commitReport(result: {
|
|
120
|
+
committed: boolean;
|
|
121
|
+
subject: string;
|
|
122
|
+
}): string;
|
|
123
|
+
/** Collect a repeatable option's values, for a manifest that declares one. */
|
|
124
|
+
collect(value: string, previous: string[]): string[];
|
|
125
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a plugin's entry module is handed — spec 05 §5.2.
|
|
3
|
+
*
|
|
4
|
+
* Each entry exports `activate(host)`. The host carries three things: the
|
|
5
|
+
* running core, the settings the repository declared, and the hooks for
|
|
6
|
+
* registering what this layer accepts.
|
|
7
|
+
*
|
|
8
|
+
* `core` is the host's own module object, not something the plugin imported.
|
|
9
|
+
* That is the single most important line in this file. A plugin resolving its
|
|
10
|
+
* own `@navbook/core` would get a second copy, and then `instanceof
|
|
11
|
+
* WorkspaceError` would be false across the boundary, two YAML parsers would
|
|
12
|
+
* sit on the startup path, and two `Repo` types that are structurally
|
|
13
|
+
* identical would be nominally different. The store installs with
|
|
14
|
+
* `--omit=peer` so that copy cannot exist; this is how the plugin gets the
|
|
15
|
+
* real one instead.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/plugins/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing a plugin's code, as late as possible — spec 04 §4.3, 05 §5.2.
|
|
3
|
+
*
|
|
4
|
+
* The rule this file exists to keep: nothing here runs for a command whose
|
|
5
|
+
* manifest does not name it. `nav issue list` on a machine with five plugins
|
|
6
|
+
* installed imports none of them, and `nav --help` imports none of them
|
|
7
|
+
* either, because the help was built from manifests. An `import()` of one
|
|
8
|
+
* plugin costs more than the whole of `nav issue list`'s budget, so this is
|
|
9
|
+
* not a micro-optimisation: it is the difference between plugins being
|
|
10
|
+
* affordable and not.
|
|
11
|
+
*
|
|
12
|
+
* What triggers a load is therefore always a *declaration* — a command the
|
|
13
|
+
* plugin declared being run, a contribution it declared being reached, a
|
|
14
|
+
* query term it declared being typed.
|
|
15
|
+
*/
|
|
16
|
+
import { type CoreExtensions } from "@navbook/core";
|
|
17
|
+
import type { PluginEntry } from "./host.ts";
|
|
18
|
+
import type { LoadedPlugin } from "./resolve.ts";
|
|
19
|
+
/** Which part of a plugin to import; the `exports` subpath, without the dot. */
|
|
20
|
+
export type PluginPart = "core" | "cli" | "server" | "web";
|
|
21
|
+
/**
|
|
22
|
+
* The file a plugin's `exports` maps a subpath to.
|
|
23
|
+
*
|
|
24
|
+
* Read from the package's own `package.json` rather than guessed, because the
|
|
25
|
+
* whole point of `exports` is that a package decides its own layout — and a
|
|
26
|
+
* published plugin's `./cli` points into `dist/` while the same plugin in a
|
|
27
|
+
* checkout points into `src/`.
|
|
28
|
+
*
|
|
29
|
+
* Null only when the plugin has no such part, which is a plugin's choice. A
|
|
30
|
+
* package that is gone, or an entry it declares that is not on disk, throws:
|
|
31
|
+
* the index still lists the plugin, and "declares a command but does not
|
|
32
|
+
* implement it" would send its author looking for a bug that is not theirs.
|
|
33
|
+
*/
|
|
34
|
+
export declare function entryPath(plugin: LoadedPlugin, part: PluginPart): string | null;
|
|
35
|
+
/** Import one part of one plugin, memoised. */
|
|
36
|
+
export declare function importEntry<H>(plugin: LoadedPlugin, part: PluginPart): Promise<PluginEntry<H> | null>;
|
|
37
|
+
/** Clear the memo. For tests that load the same plugin under two settings. */
|
|
38
|
+
export declare function forgetLoaded(): void;
|
|
39
|
+
/** A plugin's `./core` part, activated, with whatever went wrong. */
|
|
40
|
+
export interface CoreLoad {
|
|
41
|
+
ext: CoreExtensions;
|
|
42
|
+
/** One line per plugin that could not contribute, for stderr. */
|
|
43
|
+
problems: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Activate every plugin's `./core` entry and merge what they registered.
|
|
47
|
+
*
|
|
48
|
+
* A plugin that throws while activating, or that collides with one already
|
|
49
|
+
* merged, is dropped and reported — the run continues with the plugins that
|
|
50
|
+
* worked. Dropping one plugin loses that plugin's data; refusing to start
|
|
51
|
+
* loses the user's whole tracker, and the second is not a better answer to a
|
|
52
|
+
* bad `package.json` somebody published.
|
|
53
|
+
*/
|
|
54
|
+
export declare function loadCoreExtensions(plugins: readonly LoadedPlugin[]): Promise<CoreLoad>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing a plugin's code, as late as possible — spec 04 §4.3, 05 §5.2.
|
|
3
|
+
*
|
|
4
|
+
* The rule this file exists to keep: nothing here runs for a command whose
|
|
5
|
+
* manifest does not name it. `nav issue list` on a machine with five plugins
|
|
6
|
+
* installed imports none of them, and `nav --help` imports none of them
|
|
7
|
+
* either, because the help was built from manifests. An `import()` of one
|
|
8
|
+
* plugin costs more than the whole of `nav issue list`'s budget, so this is
|
|
9
|
+
* not a micro-optimisation: it is the difference between plugins being
|
|
10
|
+
* affordable and not.
|
|
11
|
+
*
|
|
12
|
+
* What triggers a load is therefore always a *declaration* — a command the
|
|
13
|
+
* plugin declared being run, a contribution it declared being reached, a
|
|
14
|
+
* query term it declared being typed.
|
|
15
|
+
*/
|
|
16
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
17
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
18
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
19
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return path;
|
|
23
|
+
};
|
|
24
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { pathToFileURL } from "node:url";
|
|
27
|
+
import * as core from "@navbook/core";
|
|
28
|
+
import { ExtensionConflictError, mergeExtensions, NO_EXTENSIONS, } from "@navbook/core";
|
|
29
|
+
/** One import per part per process, however many commands ask for it. */
|
|
30
|
+
const loaded = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* The file a plugin's `exports` maps a subpath to.
|
|
33
|
+
*
|
|
34
|
+
* Read from the package's own `package.json` rather than guessed, because the
|
|
35
|
+
* whole point of `exports` is that a package decides its own layout — and a
|
|
36
|
+
* published plugin's `./cli` points into `dist/` while the same plugin in a
|
|
37
|
+
* checkout points into `src/`.
|
|
38
|
+
*
|
|
39
|
+
* Null only when the plugin has no such part, which is a plugin's choice. A
|
|
40
|
+
* package that is gone, or an entry it declares that is not on disk, throws:
|
|
41
|
+
* the index still lists the plugin, and "declares a command but does not
|
|
42
|
+
* implement it" would send its author looking for a bug that is not theirs.
|
|
43
|
+
*/
|
|
44
|
+
export function entryPath(plugin, part) {
|
|
45
|
+
let pkg;
|
|
46
|
+
try {
|
|
47
|
+
pkg = JSON.parse(readFileSync(join(plugin.dir, "package.json"), "utf8"));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error(existsSync(plugin.dir)
|
|
51
|
+
? `its package.json cannot be read: ${message(error)}`
|
|
52
|
+
: `its package is not at ${plugin.dir}; reinstall it with 'nav plugin install ${plugin.name}'`);
|
|
53
|
+
}
|
|
54
|
+
const entry = pkg.exports?.[`./${part}`];
|
|
55
|
+
const file = typeof entry === "string" ? entry : resolveConditions(entry);
|
|
56
|
+
if (file === null)
|
|
57
|
+
return null;
|
|
58
|
+
const path = join(plugin.dir, file);
|
|
59
|
+
if (!existsSync(path))
|
|
60
|
+
throw new Error(`its './${part}' entry ${path} does not exist`);
|
|
61
|
+
return path;
|
|
62
|
+
}
|
|
63
|
+
/** Pick a file out of a conditional exports object, preferring what Node runs. */
|
|
64
|
+
function resolveConditions(entry) {
|
|
65
|
+
if (typeof entry !== "object" || entry === null)
|
|
66
|
+
return null;
|
|
67
|
+
const conditions = entry;
|
|
68
|
+
for (const key of ["node", "import", "default", "require"]) {
|
|
69
|
+
const value = conditions[key];
|
|
70
|
+
if (typeof value === "string")
|
|
71
|
+
return value;
|
|
72
|
+
if (typeof value === "object" && value !== null) {
|
|
73
|
+
const nested = resolveConditions(value);
|
|
74
|
+
if (nested !== null)
|
|
75
|
+
return nested;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/** Import one part of one plugin, memoised. */
|
|
81
|
+
export async function importEntry(plugin, part) {
|
|
82
|
+
const key = `${plugin.name}#${part}`;
|
|
83
|
+
const existing = loaded.get(key);
|
|
84
|
+
if (existing !== undefined)
|
|
85
|
+
return (await existing);
|
|
86
|
+
// Outside the memo, so a throw here is reported by each caller that asks.
|
|
87
|
+
const path = entryPath(plugin, part);
|
|
88
|
+
if (path === null) {
|
|
89
|
+
loaded.set(key, Promise.resolve(null));
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const pending = import(__rewriteRelativeImportExtension(pathToFileURL(path).href));
|
|
93
|
+
loaded.set(key, pending);
|
|
94
|
+
return (await pending);
|
|
95
|
+
}
|
|
96
|
+
/** Clear the memo. For tests that load the same plugin under two settings. */
|
|
97
|
+
export function forgetLoaded() {
|
|
98
|
+
loaded.clear();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Activate every plugin's `./core` entry and merge what they registered.
|
|
102
|
+
*
|
|
103
|
+
* A plugin that throws while activating, or that collides with one already
|
|
104
|
+
* merged, is dropped and reported — the run continues with the plugins that
|
|
105
|
+
* worked. Dropping one plugin loses that plugin's data; refusing to start
|
|
106
|
+
* loses the user's whole tracker, and the second is not a better answer to a
|
|
107
|
+
* bad `package.json` somebody published.
|
|
108
|
+
*/
|
|
109
|
+
export async function loadCoreExtensions(plugins) {
|
|
110
|
+
if (plugins.length === 0)
|
|
111
|
+
return { ext: NO_EXTENSIONS, problems: [] };
|
|
112
|
+
const parts = [];
|
|
113
|
+
const problems = [];
|
|
114
|
+
for (const plugin of plugins) {
|
|
115
|
+
const entry = await importEntry(plugin, "core").catch((error) => {
|
|
116
|
+
problems.push(`nav: plugin ${plugin.name} could not be loaded: ${message(error)}`);
|
|
117
|
+
return null;
|
|
118
|
+
});
|
|
119
|
+
if (entry === null)
|
|
120
|
+
continue;
|
|
121
|
+
if (typeof entry.activate !== "function") {
|
|
122
|
+
problems.push(`nav: plugin ${plugin.name} exports no activate() from './core'`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const registered = [];
|
|
126
|
+
try {
|
|
127
|
+
await entry.activate({
|
|
128
|
+
core,
|
|
129
|
+
manifest: plugin.manifest,
|
|
130
|
+
settings: plugin.settings,
|
|
131
|
+
register: (received) => void registered.push(received),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
problems.push(`nav: plugin ${plugin.name} failed to activate: ${message(error)}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
parts.push({ name: plugin.name, registered });
|
|
139
|
+
}
|
|
140
|
+
// Merge one plugin at a time, so a collision names the plugin that lost and
|
|
141
|
+
// the ones before it survive. Merging everything at once would only be able
|
|
142
|
+
// to say that two plugins collided, which is true but not actionable.
|
|
143
|
+
const kept = [];
|
|
144
|
+
for (const part of parts) {
|
|
145
|
+
try {
|
|
146
|
+
mergeExtensions([...kept, ...part.registered]);
|
|
147
|
+
kept.push(...part.registered);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
// Any fault, not only a collision: a registration of the wrong shape
|
|
151
|
+
// (`treeLocations` as an object, say) throws a TypeError from deep in
|
|
152
|
+
// the merge, and rethrowing it would take down every command over one
|
|
153
|
+
// plugin — the outcome this function exists to prevent.
|
|
154
|
+
const why = error instanceof ExtensionConflictError
|
|
155
|
+
? error.message
|
|
156
|
+
: `what it registered could not be used: ${message(error)}`;
|
|
157
|
+
problems.push(`nav: plugin ${part.name} skipped: ${why}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { ext: kept.length === 0 ? NO_EXTENSIONS : mergeExtensions(kept), problems };
|
|
161
|
+
}
|
|
162
|
+
function message(error) {
|
|
163
|
+
return error instanceof Error ? error.message : String(error);
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,OAAO,EAEL,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,eAAe,CAAC;AAOvB,yEAAyE;AACzE,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CAAC,MAAoB,EAAE,IAAgB;IAC9D,IAAI,GAA0C,CAAC;IAC/C,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC;YACpB,CAAC,CAAC,oCAAoC,OAAO,CAAC,KAAK,CAAC,EAAE;YACtD,CAAC,CAAC,yBAAyB,MAAM,CAAC,GAAG,2CAA2C,MAAM,CAAC,IAAI,GAAG,CACjG,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC1E,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,WAAW,IAAI,iBAAiB,CAAC,CAAC;IACvF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7D,MAAM,UAAU,GAAG,KAAgC,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,MAAM,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoB,EACpB,IAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,QAAQ,CAA0B,CAAC;IAE7E,0EAA0E;IAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,kCAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAC,CAAC;IACjD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACzB,OAAO,CAAC,MAAM,OAAO,CAAmB,CAAC;AAC3C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,YAAY;IAC1B,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAgC;IACvE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAEtE,MAAM,KAAK,GAAgF,EAAE,CAAC;IAC9F,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,MAAM,WAAW,CAAiB,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACvF,QAAQ,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,IAAI,yBAAyB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnF,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAC7B,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,IAAI,sCAAsC,CAAC,CAAC;YAChF,SAAS;QACX,CAAC;QACD,MAAM,UAAU,GAAgD,EAAE,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,QAAQ,CAAC;gBACnB,IAAI;gBACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;aACvD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,IAAI,wBAAwB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClF,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,sEAAsE;IACtE,MAAM,IAAI,GAAgD,EAAE,CAAC;IAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,eAAe,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,wDAAwD;YACxD,MAAM,GAAG,GACP,KAAK,YAAY,sBAAsB;gBACrC,CAAC,CAAC,KAAK,CAAC,OAAO;gBACf,CAAC,CAAC,yCAAyC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,QAAQ,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,aAAa,GAAG,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;AACtF,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
|