blogwright 0.3.2 → 0.4.0-beta.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 (70) hide show
  1. package/README.md +11 -11
  2. package/agent/agent-manifest.json +1 -1
  3. package/agent/server.js +37 -19
  4. package/dist/adapters/fetch-ping.d.ts +1 -1
  5. package/dist/adapters/fetch-ping.js +3 -4
  6. package/dist/adapters/node-module-loader.d.ts +11 -0
  7. package/dist/adapters/node-module-loader.js +146 -0
  8. package/dist/adapters/process-package-manager.d.ts +41 -0
  9. package/dist/adapters/process-package-manager.js +116 -0
  10. package/dist/adapters/process-vcs.d.ts +5 -4
  11. package/dist/adapters/process-vcs.js +6 -6
  12. package/dist/agent-package.d.ts +1 -1
  13. package/dist/agent-package.js +4 -4
  14. package/dist/bin.js +13 -3
  15. package/dist/cli.d.ts +68 -1
  16. package/dist/cli.js +270 -89
  17. package/dist/commands.d.ts +70 -3
  18. package/dist/commands.js +180 -32
  19. package/dist/config-block.d.ts +34 -0
  20. package/dist/config-block.js +262 -0
  21. package/dist/context.d.ts +76 -6
  22. package/dist/context.js +98 -19
  23. package/dist/deploy.d.ts +2 -2
  24. package/dist/deploy.js +14 -14
  25. package/dist/graph.d.ts +27 -16
  26. package/dist/graph.js +1 -2
  27. package/dist/init.d.ts +37 -3
  28. package/dist/init.js +146 -23
  29. package/dist/known-commands.d.ts +63 -0
  30. package/dist/known-commands.js +78 -0
  31. package/dist/logger.js +0 -1
  32. package/dist/microvms.d.ts +2 -2
  33. package/dist/microvms.js +3 -4
  34. package/dist/nodes.d.ts +5 -3
  35. package/dist/nodes.js +97 -31
  36. package/dist/plugin-commands.d.ts +298 -0
  37. package/dist/plugin-commands.js +990 -0
  38. package/dist/plugins.d.ts +194 -0
  39. package/dist/plugins.js +523 -0
  40. package/dist/ports.d.ts +89 -1
  41. package/dist/ports.js +0 -1
  42. package/dist/render.d.ts +55 -0
  43. package/dist/render.js +89 -2
  44. package/dist/repo.d.ts +3 -3
  45. package/dist/repo.js +8 -9
  46. package/dist/rkey.js +0 -1
  47. package/dist/seo.d.ts +1 -1
  48. package/dist/seo.js +1 -2
  49. package/package.json +6 -6
  50. package/dist/adapters/fetch-ping.js.map +0 -1
  51. package/dist/adapters/process-vcs.js.map +0 -1
  52. package/dist/agent-package.js.map +0 -1
  53. package/dist/bin.js.map +0 -1
  54. package/dist/cli.js.map +0 -1
  55. package/dist/commands.js.map +0 -1
  56. package/dist/context.js.map +0 -1
  57. package/dist/deploy.js.map +0 -1
  58. package/dist/graph.js.map +0 -1
  59. package/dist/init.js.map +0 -1
  60. package/dist/logger.js.map +0 -1
  61. package/dist/microvms.js.map +0 -1
  62. package/dist/nodes.js.map +0 -1
  63. package/dist/ports.js.map +0 -1
  64. package/dist/render.js.map +0 -1
  65. package/dist/repo.js.map +0 -1
  66. package/dist/rkey.js.map +0 -1
  67. package/dist/seo.js.map +0 -1
  68. package/dist/test-support.d.ts +0 -45
  69. package/dist/test-support.js +0 -126
  70. package/dist/test-support.js.map +0 -1
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Plugin discovery: finds every installed `blogwright-*` package that
3
+ * declares a `blogwright.plugin` manifest field, loads it, and validates its
4
+ * default export against core's `Plugin` contract.
5
+ *
6
+ * The candidate set is the UNION of two sources, each resolved from its own
7
+ * directory - see `collectCandidates` below - because a consuming repo
8
+ * depends on `blogwright`, not on `blogwright-pds`: scanning the consumer's
9
+ * own `package.json` alone would never find a plugin bundled inside the CLI
10
+ * itself.
11
+ *
12
+ * Collect, never throw, for anything candidate-specific: a broken plugin -
13
+ * one that fails to resolve, whose manifest is malformed, or whose default
14
+ * export fails validation - becomes an entry in `failures`, not a thrown
15
+ * error that aborts the whole discovery pass. One bad dependency must not
16
+ * make `blogwright deploy` (or any other built-in command) unusable. The
17
+ * only things this module *does* throw for are the two preconditions
18
+ * discovery cannot proceed without at all: the repo's own `package.json` and
19
+ * the CLI's own `package.json`, both read before any candidate is resolved.
20
+ *
21
+ * The same collect-never-throw rule governs the two namespace-collision
22
+ * checks this module applies itself, after a candidate has already loaded
23
+ * and validated cleanly: a plugin whose declared name is one the CLI
24
+ * dispatches itself (`RESERVED_COMMANDS`, `known-commands.ts` - a leaf
25
+ * module with no imports of its own, so this domain module never has to
26
+ * import the composition root just to read it), and two plugins that
27
+ * declare the same name as each other. §CLI → Namespace collisions calls
28
+ * this "rejected with an error", but - exactly like a malformed manifest or
29
+ * a failed `validatePlugin` check above - that rejection is a reported
30
+ * `failures` entry, not a thrown one. Throwing here would let a single
31
+ * colliding plugin abort discovery for every other, unrelated plugin and
32
+ * every built-in command that runs it; `blogwright plugin list` (task 17)
33
+ * depends on the collect outcome too, since it is the one place a collision
34
+ * becomes visible to a human.
35
+ *
36
+ * `pds` is deliberately absent from `RESERVED_COMMANDS`, and adding it would
37
+ * now BREAK the namespace rather than merely shadow it. Task 29 deleted
38
+ * `cli.ts`'s hardcoded `command === 'pds'` branch: there is no built-in
39
+ * `pds` command left for a reservation to protect, and `blogwright pds
40
+ * <action>` is answered by the bundled `blogwright-pds` package, which
41
+ * declares the plugin name `pds` and is discovered here like any other
42
+ * plugin. Reserving the name would therefore aim
43
+ * `resolveNamespaceCollisions` below at that bundled plugin itself: it would
44
+ * become a `failures` entry rather than an installed one, and the namespace
45
+ * would stop working outright - `blogwright pds sync` exiting 1 with `no
46
+ * built-in command or installed plugin claims "pds"`, `blogwright --help`
47
+ * listing none of its six actions, and `blogwright plugin list` reporting it
48
+ * as reserved for a built-in command that no longer exists. Verified by
49
+ * adding `'pds'` to the set: discovery rejects the real bundled package and
50
+ * this file's real-disk integration cases fail on exactly that reason
51
+ * string. So the name stays unreserved on purpose, pinned by a test below.
52
+ *
53
+ * DECISION (task 13, record here for task 16 to find): a plugin's declared
54
+ * ACTIONS can collide with a generic action the CLI contributes, distinct
55
+ * from the namespace collisions above. §CLI → `blogwright <plugin> init`
56
+ * names exactly one such collision a boundary check can reject: a plugin
57
+ * declaring BOTH an `init` command in its own `commands` AND an `init?(io)`
58
+ * contributor is unsatisfiable, because a declared command always wins
59
+ * dispatch (`plugin-commands.ts`'s `matchAction` matches a plugin's own
60
+ * `commands` before the generic action is ever considered), so the
61
+ * contributor would ask its questions nowhere. That check - `rejectDeclaredInitCollisions`
62
+ * below - lives HERE, in this module's collision pass, rather than in core's
63
+ * `validatePlugin` (`blogwright-core`'s `plugin.ts`), for the same reason the
64
+ * namespace checks do: it is about actions the *CLI* contributes generically
65
+ * (the config-writing `init`), which core must not know exists, and this
66
+ * module already reports a plugin-level rejection as a `failures` entry
67
+ * rather than a thrown error. §CLI → Plugin lifecycle adds the sibling rule
68
+ * for `bootstrap`/`destroy` (always generic; a plugin may never declare
69
+ * either, full stop - no "unless paired with a contributor" nuance, since
70
+ * there is no bootstrap/destroy contributor to pair with). Task 16 adds
71
+ * `rejectDeclaredLifecycleCollisions` below, a sibling function called from
72
+ * the same place in `discover`, with that rule, so every declared-action
73
+ * collision rejection greps to this one module instead of splitting across
74
+ * whichever of the two tasks happened to land first. `status` is deliberately
75
+ * NOT part of that rule: a plugin may declare its own `status` command
76
+ * freely - `read()` lives on the plugin's own nodes, so no engine call is
77
+ * required the way `bootstrap`/`destroy` need one - and `plugin-commands.ts`'s
78
+ * ordinary `matchAction` precedence (a plugin's own commands win before any
79
+ * generic fallback is even considered) already gives a declared `status`
80
+ * command priority with no boundary check needed here.
81
+ *
82
+ * DECISION (task 19, recorded here plainly because task 28 has to reason
83
+ * about it when pds's config validation moves out of core): a plugin's own
84
+ * config block is validated for the ONE plugin being DISPATCHED, in the
85
+ * dispatch path (`runPlugin` calls {@link resolvePluginConfig} below), and
86
+ * never for every discovered plugin. Two reasons, and neither is taste:
87
+ *
88
+ * - `createContext` (`context.ts`) is the path every built-in command
89
+ * takes and it accepts no plugin list, so validating there would have to
90
+ * run `discover` on `deploy`, `status` and `bootstrap` - breaking the
91
+ * laziness rule (§CLI -> Plugin discovery: a built-in command loads no
92
+ * plugin module) that task 10's own test pins. There is no seam in
93
+ * `createContext` through which the dispatched plugin ALONE could be
94
+ * reached, because at that point no plugin has been chosen yet.
95
+ * - Validating every discovered plugin, wherever it happened, would let an
96
+ * unrelated plugin's malformed block abort a command that has nothing to
97
+ * do with it. A block for a plugin that is not installed is already
98
+ * valid and inert - the same contract `pds` has today - and a block for
99
+ * an installed plugin that is not the one being run is inert for exactly
100
+ * the same reason: nothing reads it.
101
+ *
102
+ * The corollary is that `blogwright <plugin> <action>` is the only thing
103
+ * that reports a bad block, and it reports only its own plugin's.
104
+ */
105
+ import { type Plugin } from 'blogwright-core';
106
+ import type { Ports } from './ports.js';
107
+ /** Why one candidate package failed to become a usable plugin. */
108
+ interface PluginLoadFailure {
109
+ readonly packageName: string;
110
+ readonly reason: string;
111
+ }
112
+ /**
113
+ * A successfully loaded, `validatePlugin`-passing plugin together with the
114
+ * package it came from - the provenance `blogwright plugin list` (task 17)
115
+ * reports, and nothing else needs.
116
+ *
117
+ * `packageJsonPath` is the path `ModuleLoader.packageJsonPathFor` resolved,
118
+ * NOT one derived from `ModuleLoader.resolve`'s entry file: a package
119
+ * published with the standard dual-package layout has its entry point in
120
+ * `dist/` beside a name-less `{"type":"module"}` stub, so walking up one
121
+ * directory from the entry file finds the wrong `package.json` entirely (see
122
+ * that port method's own doc comment). Carried as the PATH rather than a
123
+ * parsed `version`, so the one command that shows a version reads it lazily
124
+ * and `--help`/dispatch, which never show one, pay nothing for a field they
125
+ * do not use.
126
+ */
127
+ interface InstalledPlugin {
128
+ readonly packageName: string;
129
+ readonly packageJsonPath: string;
130
+ readonly plugin: Plugin;
131
+ }
132
+ /**
133
+ * `discover`'s result: every collection is always an array, never
134
+ * `null`/`undefined`.
135
+ *
136
+ * `plugins` is exactly `installed.map((entry) => entry.plugin)`, derived once
137
+ * inside `discover` so the two can never disagree. It stays alongside
138
+ * `installed` because the three callers that dispatch or render a plugin -
139
+ * `runPlugin`, `buildHelp` and the `init` wizard - want the `Plugin` and
140
+ * nothing else, and should not have to unwrap an envelope to serve the one
141
+ * caller (`blogwright plugin list`) that also names the package it came from.
142
+ */
143
+ export interface DiscoveryResult {
144
+ readonly plugins: readonly Plugin[];
145
+ readonly installed: readonly InstalledPlugin[];
146
+ readonly failures: readonly PluginLoadFailure[];
147
+ }
148
+ /**
149
+ * Discover every installed plugin reachable from `repoRoot` (the consuming
150
+ * repo) and `cliPackageDir` (the CLI's own package directory, from
151
+ * {@link cliPackageDir} in `context.ts`). Never throws for a candidate-level
152
+ * problem - see the module comment - only for the two repo-level
153
+ * preconditions `collectCandidates` reads first.
154
+ */
155
+ export declare function discover(repoRoot: string, cliPackageDir: string, ports: Pick<Ports, 'fs' | 'loader'>): Promise<DiscoveryResult>;
156
+ /**
157
+ * Resolve the config block ONE plugin owns into the value the dispatcher puts
158
+ * on `ctx.pluginConfig` - `runPlugin`'s single call, made for the plugin
159
+ * being DISPATCHED and no other (see the module comment's task-19 DECISION
160
+ * for why the scope is one plugin rather than every discovered one).
161
+ *
162
+ * The block is read off the RAW config document (`OpsContext.configDocument`,
163
+ * `context.ts`), never off `OpsConfig`, which has no index signature to reach
164
+ * a plugin's key through. `pluginBlock` returning `unknown` and the plugin's
165
+ * own `validateConfig` narrowing it is the sanctioned boundary: the very next
166
+ * step after the read validates it.
167
+ *
168
+ * The validator IS called when the plugin's key is ABSENT from the document,
169
+ * with `undefined`. That is the whole point of it: a validator is the only
170
+ * thing that can turn an absent block into the plugin's own defaults, and a
171
+ * repo that installs a plugin without writing its block is a valid,
172
+ * documented configuration. Handing `{}` straight through instead would put a
173
+ * block on `ctx.pluginConfig` that never went through the plugin's own
174
+ * defaulting - typed as total, `undefined` at runtime in every defaulted
175
+ * field - and nothing downstream could catch it, because the dispatcher
176
+ * erases `TConfig` (`Plugin<unknown>`, `PluginContext<unknown>`).
177
+ *
178
+ * `{}` is returned ONLY where there is no validator to call: a plugin that
179
+ * declares no `configKey` (a `Plugin<never>`, which cannot read
180
+ * `pluginConfig` at all) or no `validateConfig` - probed with `typeof ===
181
+ * 'function'`, the way this module and `plugin-commands.ts` both probe the
182
+ * `init` contributor, because core's `validatePlugin` type-checks neither
183
+ * member. `pluginConfig` is a required member, so `undefined` is not an
184
+ * option there - DEVELOPMENT.md's no-null rule.
185
+ *
186
+ * A validator's own rejection is re-raised with the plugin's name and the key
187
+ * in front of it and the plugin's message VERBATIM behind it, so an operator
188
+ * reading `blogwright analytics bootstrap`'s failure learns which plugin
189
+ * refused which key without the plugin having to name itself in every message
190
+ * it writes. It propagates - never swallowed, never downgraded to a warning -
191
+ * and exits non-zero through `bin.ts`'s error path.
192
+ */
193
+ export declare function resolvePluginConfig(plugin: Plugin<unknown>, configDocument: Readonly<Record<string, unknown>>): unknown;
194
+ export {};