@navbook/cli 0.3.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.
Files changed (78) hide show
  1. package/dist/args.d.ts +28 -0
  2. package/dist/args.js +48 -0
  3. package/dist/args.js.map +1 -0
  4. package/dist/commands/complete.d.ts +10 -0
  5. package/dist/commands/complete.js +59 -51
  6. package/dist/commands/complete.js.map +1 -1
  7. package/dist/commands/compose.d.ts +35 -0
  8. package/dist/commands/doctor.d.ts +15 -0
  9. package/dist/commands/entity.d.ts +73 -0
  10. package/dist/commands/entity.js +59 -44
  11. package/dist/commands/entity.js.map +1 -1
  12. package/dist/commands/init.d.ts +11 -0
  13. package/dist/commands/install.d.ts +17 -0
  14. package/dist/commands/install.js +6 -3
  15. package/dist/commands/install.js.map +1 -1
  16. package/dist/commands/issue.d.ts +35 -0
  17. package/dist/commands/issue.js +4 -5
  18. package/dist/commands/issue.js.map +1 -1
  19. package/dist/commands/plugin.d.ts +36 -0
  20. package/dist/commands/plugin.js +386 -0
  21. package/dist/commands/plugin.js.map +1 -0
  22. package/dist/commands/policy.d.ts +58 -0
  23. package/dist/commands/policy.js +28 -2
  24. package/dist/commands/policy.js.map +1 -1
  25. package/dist/commands/pr-elsewhere.d.ts +40 -0
  26. package/dist/commands/pr-elsewhere.js +209 -0
  27. package/dist/commands/pr-elsewhere.js.map +1 -0
  28. package/dist/commands/pr.d.ts +69 -0
  29. package/dist/commands/pr.js +132 -68
  30. package/dist/commands/pr.js.map +1 -1
  31. package/dist/context.d.ts +42 -0
  32. package/dist/context.js +2 -0
  33. package/dist/context.js.map +1 -1
  34. package/dist/editor.d.ts +23 -0
  35. package/dist/editor.js +4 -0
  36. package/dist/editor.js.map +1 -1
  37. package/dist/errors.d.ts +17 -0
  38. package/dist/install/completions.d.ts +19 -0
  39. package/dist/install/hook.d.ts +32 -0
  40. package/dist/install/hook.js +48 -4
  41. package/dist/install/hook.js.map +1 -1
  42. package/dist/main.d.ts +21 -0
  43. package/dist/main.js +112 -8
  44. package/dist/main.js.map +1 -1
  45. package/dist/plugins/commands.d.ts +69 -0
  46. package/dist/plugins/commands.js +148 -0
  47. package/dist/plugins/commands.js.map +1 -0
  48. package/dist/plugins/hint.d.ts +23 -0
  49. package/dist/plugins/hint.js +65 -0
  50. package/dist/plugins/hint.js.map +1 -0
  51. package/dist/plugins/host.d.ts +125 -0
  52. package/dist/plugins/host.js +18 -0
  53. package/dist/plugins/host.js.map +1 -0
  54. package/dist/plugins/loader.d.ts +54 -0
  55. package/dist/plugins/loader.js +165 -0
  56. package/dist/plugins/loader.js.map +1 -0
  57. package/dist/plugins/resolve.d.ts +50 -0
  58. package/dist/plugins/resolve.js +134 -0
  59. package/dist/plugins/resolve.js.map +1 -0
  60. package/dist/plugins/runtime.d.ts +69 -0
  61. package/dist/plugins/runtime.js +256 -0
  62. package/dist/plugins/runtime.js.map +1 -0
  63. package/dist/plugins/store.d.ts +96 -0
  64. package/dist/plugins/store.js +186 -0
  65. package/dist/plugins/store.js.map +1 -0
  66. package/dist/program.d.ts +49 -0
  67. package/dist/program.js +353 -114
  68. package/dist/program.js.map +1 -1
  69. package/dist/prompt.d.ts +31 -0
  70. package/dist/render/colors.d.ts +13 -0
  71. package/dist/render/detail.d.ts +22 -0
  72. package/dist/render/table.d.ts +32 -0
  73. package/dist/render/table.js +123 -13
  74. package/dist/render/table.js.map +1 -1
  75. package/dist/sort.d.ts +27 -0
  76. package/package.json +9 -2
  77. package/dist/commands/feature.js +0 -202
  78. package/dist/commands/feature.js.map +0 -1
@@ -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":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Which plugins this invocation has, and which it was told about but lacks.
3
+ *
4
+ * Two sources, in order: `NAVBOOK_PLUGIN_PATH`, for a plugin being written and
5
+ * not installed anywhere, and the store, for everything else. A path entry
6
+ * shadows an installed copy of the same package — otherwise developing a
7
+ * plugin you have also installed would silently test the installed one.
8
+ *
9
+ * The declaration (spec 02 §2.12) is read here, and read only to *report*:
10
+ * nothing in this file fetches anything. A plugin the repository names and the
11
+ * machine does not have is one line on stderr and a run that carries on, which
12
+ * is the rule that makes cloning a repository safe.
13
+ */
14
+ import { type PluginDeclarationReading, type WsCtx } from "@navbook/core";
15
+ import { type IndexedPlugin } from "./store.ts";
16
+ export declare const PLUGIN_PATH_ENV = "NAVBOOK_PLUGIN_PATH";
17
+ /** An installed plugin, with the settings the repository declares for it. */
18
+ export interface LoadedPlugin extends IndexedPlugin {
19
+ /** What `navbook.json` declares under this name; empty when undeclared. */
20
+ settings: Record<string, unknown>;
21
+ /** True when the repository's marker names it (spec 02 §2.12). */
22
+ declared: boolean;
23
+ /** True when it came from `NAVBOOK_PLUGIN_PATH` rather than the store. */
24
+ fromPath: boolean;
25
+ }
26
+ export interface ResolvedPlugins {
27
+ /** Usable plugins, in the order their contributions should be applied. */
28
+ active: LoadedPlugin[];
29
+ /** Declared by the repository, absent from this machine. */
30
+ missing: string[];
31
+ /** Found but not used, each with the reason, for one line on stderr. */
32
+ skipped: {
33
+ name: string;
34
+ reason: string;
35
+ }[];
36
+ /** What the marker declared, for a caller that wants the settings or faults. */
37
+ declaration: PluginDeclarationReading;
38
+ }
39
+ /**
40
+ * Resolve the plugins for this invocation.
41
+ *
42
+ * Costs one `plugins.json` read in the common case, and one `package.json`
43
+ * read per `NAVBOOK_PLUGIN_PATH` entry, which is a development path. The
44
+ * marker is read too, but the workspace reads it anyway for the review policy.
45
+ */
46
+ export declare function resolvePlugins(ws: WsCtx, env: NodeJS.ProcessEnv): ResolvedPlugins;
47
+ /** The line printed for a plugin the repository declares and the machine lacks. */
48
+ export declare function missingLine(name: string, navDir: string): string;
49
+ /** The line printed for a plugin that was found but cannot be used. */
50
+ export declare function skippedLine(name: string, reason: string): string;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Which plugins this invocation has, and which it was told about but lacks.
3
+ *
4
+ * Two sources, in order: `NAVBOOK_PLUGIN_PATH`, for a plugin being written and
5
+ * not installed anywhere, and the store, for everything else. A path entry
6
+ * shadows an installed copy of the same package — otherwise developing a
7
+ * plugin you have also installed would silently test the installed one.
8
+ *
9
+ * The declaration (spec 02 §2.12) is read here, and read only to *report*:
10
+ * nothing in this file fetches anything. A plugin the repository names and the
11
+ * machine does not have is one line on stderr and a run that carries on, which
12
+ * is the rule that makes cloning a repository safe.
13
+ */
14
+ import { existsSync } from "node:fs";
15
+ import { isAbsolute, resolve as resolvePath } from "node:path";
16
+ import { PLUGIN_API_VERSION, readPluginDeclaration, satisfiesRange, } from "@navbook/core";
17
+ import { readIndex, readPluginAt } from "./store.js";
18
+ export const PLUGIN_PATH_ENV = "NAVBOOK_PLUGIN_PATH";
19
+ const NOTHING = {
20
+ active: [],
21
+ missing: [],
22
+ skipped: [],
23
+ declaration: { plugins: new Map(), declared: false, problems: [] },
24
+ };
25
+ /**
26
+ * Resolve the plugins for this invocation.
27
+ *
28
+ * Costs one `plugins.json` read in the common case, and one `package.json`
29
+ * read per `NAVBOOK_PLUGIN_PATH` entry, which is a development path. The
30
+ * marker is read too, but the workspace reads it anyway for the review policy.
31
+ */
32
+ export function resolvePlugins(ws, env) {
33
+ const fromPath = pathPlugins(env);
34
+ const index = readIndex(env);
35
+ if (fromPath.active.length === 0 && fromPath.skipped.length === 0 && index === null) {
36
+ // Nothing installed and nothing on the path: the overwhelmingly common
37
+ // case, and it must not cost a marker read of its own.
38
+ return ws.hasNavbook ? undeclaredOnly(ws) : NOTHING;
39
+ }
40
+ const declaration = ws.hasNavbook
41
+ ? readPluginDeclaration(ws)
42
+ : { plugins: new Map(), declared: false, problems: [] };
43
+ const active = [];
44
+ const skipped = [...fromPath.skipped];
45
+ const seen = new Set();
46
+ const admit = (plugin, viaPath) => {
47
+ if (seen.has(plugin.name))
48
+ return;
49
+ const problem = incompatible(plugin);
50
+ if (problem !== null) {
51
+ skipped.push({ name: plugin.name, reason: problem });
52
+ seen.add(plugin.name);
53
+ return;
54
+ }
55
+ seen.add(plugin.name);
56
+ active.push({
57
+ ...plugin,
58
+ settings: declaration.plugins.get(plugin.name) ?? {},
59
+ declared: declaration.plugins.has(plugin.name),
60
+ fromPath: viaPath,
61
+ });
62
+ };
63
+ // Path first, so a plugin being developed shadows an installed copy.
64
+ for (const plugin of fromPath.active)
65
+ admit(plugin, true);
66
+ for (const plugin of Object.values(index?.plugins ?? {}))
67
+ admit(plugin, false);
68
+ const missing = [...declaration.plugins.keys()].filter((name) => !seen.has(name));
69
+ return { active, missing, skipped, declaration };
70
+ }
71
+ /** The store is empty, so everything declared is missing. */
72
+ function undeclaredOnly(ws) {
73
+ const declaration = readPluginDeclaration(ws);
74
+ return {
75
+ active: [],
76
+ missing: [...declaration.plugins.keys()],
77
+ skipped: [],
78
+ declaration,
79
+ };
80
+ }
81
+ /** Read every directory `NAVBOOK_PLUGIN_PATH` names. */
82
+ function pathPlugins(env) {
83
+ const raw = env[PLUGIN_PATH_ENV];
84
+ if (raw === undefined || raw.trim() === "")
85
+ return { active: [], skipped: [] };
86
+ const active = [];
87
+ const skipped = [];
88
+ for (const entry of raw.split(":")) {
89
+ const dir = entry.trim();
90
+ if (dir === "")
91
+ continue;
92
+ const path = isAbsolute(dir) ? dir : resolvePath(dir);
93
+ if (!existsSync(path)) {
94
+ skipped.push({
95
+ name: path,
96
+ reason: `${PLUGIN_PATH_ENV} names a directory that is not there`,
97
+ });
98
+ continue;
99
+ }
100
+ const read = readPluginAt(path);
101
+ if ("error" in read) {
102
+ skipped.push({ name: path, reason: read.error });
103
+ continue;
104
+ }
105
+ active.push(read);
106
+ }
107
+ return { active, skipped };
108
+ }
109
+ /**
110
+ * Why a plugin cannot be used, or null when it can.
111
+ *
112
+ * A version mismatch is reported rather than risked. The alternative is
113
+ * loading it anyway and discovering the mismatch as a missing function in the
114
+ * middle of an operation, by which point the user is reading a stack trace
115
+ * about a tree that is perfectly sound.
116
+ */
117
+ function incompatible(plugin) {
118
+ if (plugin.engines === null) {
119
+ return `it declares no 'engines.navbook', so there is nothing to check it against`;
120
+ }
121
+ if (!satisfiesRange(plugin.engines, PLUGIN_API_VERSION)) {
122
+ return `it needs a Navbook plugin API of ${plugin.engines}; this is ${PLUGIN_API_VERSION}`;
123
+ }
124
+ return null;
125
+ }
126
+ /** The line printed for a plugin the repository declares and the machine lacks. */
127
+ export function missingLine(name, navDir) {
128
+ return `nav: ${name} is declared in ${navDir}/navbook.json but is not installed; run 'nav plugin install'`;
129
+ }
130
+ /** The line printed for a plugin that was found but cannot be used. */
131
+ export function skippedLine(name, reason) {
132
+ return `nav: plugin ${name} skipped: ${reason}`;
133
+ }
134
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/plugins/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The CLI's plugin runtime: what is installed, what it contributes, and when
3
+ * its code is actually loaded.
4
+ *
5
+ * One of these is built per invocation and threaded through the program. It
6
+ * holds the resolution (cheap, always done) and memoises the loading
7
+ * (expensive, done only when a declaration says it is needed).
8
+ *
9
+ * The division of labour with `program.ts` is worth stating: the program asks
10
+ * this for *declarations* while building the command tree, and for
11
+ * *implementations* inside an action. Nothing in the build phase can trigger a
12
+ * load, which is how spec 04 §4.3's rule is kept structurally rather than by
13
+ * remembering to.
14
+ */
15
+ import type { CoreExtensions, EntityKind, QueryKeySpec } from "@navbook/core";
16
+ import type { Ctx } from "../context.ts";
17
+ import { type PluginCommands } from "./commands.ts";
18
+ import type { VerbHandlers } from "./host.ts";
19
+ import type { LoadedPlugin, ResolvedPlugins } from "./resolve.ts";
20
+ export declare class PluginRuntime {
21
+ #private;
22
+ readonly resolved: ResolvedPlugins;
23
+ readonly commands: PluginCommands;
24
+ /** Everything worth saying on stderr once, collected while resolving. */
25
+ readonly notices: string[];
26
+ constructor(resolved: ResolvedPlugins, builtinNouns: readonly string[]);
27
+ /** True when no plugin is installed, which is the common case. */
28
+ get empty(): boolean;
29
+ /**
30
+ * Whether this command line needs the format extensions loaded.
31
+ *
32
+ * Decided from the manifests and the words typed, before anything parses,
33
+ * because loading them means importing plugin code — and spec 04 §4.3 says
34
+ * no plugin code is loaded for a command whose declaration does not name it.
35
+ * `nav issue list` with five plugins installed answers false here and
36
+ * imports nothing.
37
+ *
38
+ * The four cases that answer true:
39
+ *
40
+ * - a plugin noun is the first word, and its declaration asks for core;
41
+ * - a plugin contributes to this exact `noun verb`;
42
+ * - a term on the line names a query key a plugin declared;
43
+ * - the command is `doctor`, which runs every registered check.
44
+ */
45
+ needsCoreFor(argv: readonly string[]): boolean;
46
+ /** Plugins whose commands are still usable after collision checking. */
47
+ get usable(): LoadedPlugin[];
48
+ /**
49
+ * The query terms usable plugins declared, for one noun or for either.
50
+ *
51
+ * From the manifests, so help and completion can offer them without loading
52
+ * a line of plugin code (spec 04 §4.3).
53
+ */
54
+ queryKeys(kind?: EntityKind): QueryKeySpec[];
55
+ /**
56
+ * The registered format extensions, loading the `./core` entries once.
57
+ *
58
+ * Everything that reads or validates a tree needs these; nothing else does.
59
+ * The caller decides whether this invocation is one of those, which is where
60
+ * the startup budget is actually spent or saved.
61
+ */
62
+ coreExtensions(ctx: Ctx): Promise<CoreExtensions>;
63
+ /** What a plugin registered for a verb, loading its `./cli` entry if needed. */
64
+ handlersFor(ctx: Ctx, verb: string): Promise<VerbHandlers[]>;
65
+ /** Run a plugin command, loading its `./cli` entry. */
66
+ run(ctx: Ctx, plugin: LoadedPlugin, path: string, args: string[], opts: Record<string, unknown>): Promise<void>;
67
+ /** Completion candidates a plugin offers for a declared completer. */
68
+ completions(ctx: Ctx, plugin: LoadedPlugin, id: string, words: string[]): Promise<string[]>;
69
+ }