rman 1.2.5 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -19
- package/cli.d.ts +5 -0
- package/cli.js +214 -88
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changed.command.d.ts +80 -3
- package/commands/changed.command.js +19 -12
- package/commands/changelog.command.d.ts +192 -3
- package/commands/changelog.command.js +87 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +30 -19
- package/commands/diff.command.d.ts +37 -3
- package/commands/diff.command.js +25 -16
- package/commands/exec.command.d.ts +193 -3
- package/commands/exec.command.js +60 -58
- package/commands/github-release.command.d.ts +154 -3
- package/commands/github-release.command.js +67 -37
- package/commands/import.command.d.ts +36 -3
- package/commands/import.command.js +28 -20
- package/commands/info.command.d.ts +35 -7
- package/commands/info.command.js +36 -30
- package/commands/list.command.d.ts +163 -3
- package/commands/list.command.js +109 -71
- package/commands/publish.command.d.ts +231 -0
- package/commands/publish.command.js +304 -0
- package/commands/run.command.d.ts +186 -6
- package/commands/run.command.js +26 -72
- package/commands/test.command.d.ts +173 -3
- package/commands/test.command.js +16 -10
- package/commands/version.command.d.ts +317 -3
- package/commands/version.command.js +149 -69
- package/commands.d.ts +32 -0
- package/commands.js +28 -0
- package/constants.js +1 -1
- package/core/application.d.ts +116 -0
- package/core/application.js +143 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +78 -0
- package/core/config.d.ts +180 -50
- package/core/config.js +332 -153
- package/core/core-services.d.ts +14 -0
- package/core/core-services.js +30 -0
- package/core/core-targets.d.ts +14 -0
- package/core/core-targets.js +16 -0
- package/core/custom-command.d.ts +42 -6
- package/core/custom-command.js +44 -17
- package/core/extends-config.d.ts +13 -5
- package/core/extends-config.js +52 -12
- package/core/load-config-module.d.ts +28 -0
- package/core/load-config-module.js +42 -0
- package/core/manifest.d.ts +47 -25
- package/core/manifest.js +51 -69
- package/core/merge-config.d.ts +33 -34
- package/core/merge-config.js +138 -93
- package/core/package.d.ts +145 -17
- package/core/package.js +128 -36
- package/core/plugin-loader.d.ts +65 -0
- package/core/plugin-loader.js +234 -0
- package/core/plugin.d.ts +135 -90
- package/core/plugin.js +70 -173
- package/core/publish-target.d.ts +124 -0
- package/core/publish-target.js +30 -0
- package/core/registry.d.ts +30 -0
- package/core/registry.js +47 -0
- package/core/repository.d.ts +72 -9
- package/core/repository.js +293 -43
- package/core/resolve-target.d.ts +1 -1
- package/core/resolve-target.js +1 -1
- package/core/service.d.ts +49 -0
- package/core/service.js +40 -0
- package/core/version-scheme.d.ts +23 -1
- package/core/version-scheme.js +29 -1
- package/core/workspace.d.ts +84 -33
- package/core/workspace.js +63 -22
- package/index.d.ts +111 -14
- package/index.js +85 -9
- package/interfaces/rman-config.interface.d.ts +739 -212
- package/interfaces/rman-config.interface.js +61 -1
- package/package.json +2 -1
- package/plugins/builtins.d.ts +44 -0
- package/plugins/builtins.js +33 -0
- package/plugins/detect.d.ts +78 -0
- package/plugins/detect.js +70 -0
- package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
- package/plugins/node/augmentation/rman.augmentation.js +1 -0
- package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/plugins/node/augmentation/system-info.augmentation.js +79 -0
- package/plugins/node/commands/ci.command.d.ts +131 -0
- package/plugins/node/commands/ci.command.js +59 -0
- package/plugins/node/commands/clean.command.d.ts +183 -0
- package/plugins/node/commands/clean.command.js +73 -0
- package/plugins/node/index.d.ts +29 -0
- package/plugins/node/index.js +40 -0
- package/plugins/node/node-config.interface.d.ts +77 -0
- package/plugins/node/node-config.interface.js +7 -0
- package/plugins/node/node-manifest.provider.d.ts +68 -0
- package/plugins/node/node-manifest.provider.js +125 -0
- package/plugins/node/node.platform.d.ts +53 -0
- package/plugins/node/node.platform.js +134 -0
- package/plugins/node/npm-publish-target.d.ts +73 -0
- package/plugins/node/npm-publish-target.js +96 -0
- package/plugins/node/services/ci.service.d.ts +47 -0
- package/plugins/node/services/ci.service.js +213 -0
- package/plugins/node/services/clean.service.d.ts +53 -0
- package/plugins/node/services/clean.service.js +237 -0
- package/plugins/node/services/publish.service.d.ts +114 -0
- package/plugins/node/services/publish.service.js +371 -0
- package/plugins/node/services/version-plan.service.d.ts +44 -0
- package/plugins/node/services/version-plan.service.js +58 -0
- package/plugins/node/utils/npm-view.d.ts +48 -0
- package/plugins/node/utils/npm-view.js +71 -0
- package/plugins/node/utils/workspace-range.d.ts +26 -0
- package/plugins/node/utils/workspace-range.js +28 -0
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/changelog.service.d.ts +62 -51
- package/services/changelog.service.js +14 -11
- package/services/docker-publish.service.d.ts +50 -29
- package/services/docker-publish.service.js +43 -20
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +44 -33
- package/services/github-release.service.js +13 -10
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +62 -10
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +22 -13
- package/services/run.service.js +262 -223
- package/services/version-plan.service.d.ts +27 -4
- package/services/version-plan.service.js +42 -15
- package/services/version.service.d.ts +31 -11
- package/services/version.service.js +29 -13
- package/targets/docker.target.d.ts +53 -0
- package/targets/docker.target.js +40 -0
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +29 -0
- package/utils/branch-guard.js +31 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/logger.d.ts +1 -1
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +127 -7
- package/utils/package-filter.js +197 -16
- package/utils/printable-config.d.ts +1 -1
- package/utils/printable-config.js +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import fastGlob from 'fast-glob';
|
|
4
|
+
import { COMMANDS_KEY } from './merge-config.js';
|
|
5
|
+
import { isDeclared, isPlatform } from './plugin.js';
|
|
6
|
+
/** The `.rmanrc` key naming plugin packages to load. */
|
|
7
|
+
export const PLUGINS_KEY = 'plugins';
|
|
8
|
+
/**
|
|
9
|
+
* The repository's plugins and publish targets, registered onto `app`.
|
|
10
|
+
*
|
|
11
|
+
* **Three keys share one shape** - `plugins`, `publishTargets` and `commands` each take an
|
|
12
|
+
* instance or a **glob** naming `.js` modules that `export default` one, and each appends rather
|
|
13
|
+
* than replaces. Two of them are read here; `commands` is read by `cli.ts`, and the split is not
|
|
14
|
+
* arbitrary:
|
|
15
|
+
*
|
|
16
|
+
* - **`plugins` and `publishTargets` are root-level, because they have to be.** This runs inside
|
|
17
|
+
* `Repository.create` *before* the packages are known, since a plugin's `getWorkspace` is what
|
|
18
|
+
* finds them - so there are no package configs to read yet. A `plugins` entry in a package's own
|
|
19
|
+
* `.rmanrc` is never seen, which has always been true and is now true for the same reason.
|
|
20
|
+
* - **`commands` is read at any level**, after the packages exist, which is why `cli.ts` owns it:
|
|
21
|
+
* it already holds the whole command pipeline - the `.rman/` default, de-duplication by name,
|
|
22
|
+
* and the built-in shadow check. Reading it here as well would register every config-declared
|
|
23
|
+
* command twice, and yargs does not survive that.
|
|
24
|
+
*
|
|
25
|
+
* **Order within this function is load-bearing**: plugins first, then targets. rman's own
|
|
26
|
+
* `publish` builds its `--target` choices from `app.publishTargets` when its factory runs, and a
|
|
27
|
+
* target contributed by a plugin's `init` has to be there before the list is read.
|
|
28
|
+
*
|
|
29
|
+
* **A contribution that cannot be loaded throws**, unlike a broken `.rman/*.mjs`, which is warned
|
|
30
|
+
* about and skipped. The consequence differs: a skipped local command affects only itself, while a
|
|
31
|
+
* missing plugin silently removes commands the repository is built around - `rman publish` would
|
|
32
|
+
* simply not exist, and "not a known command" sends the reader looking in the wrong place.
|
|
33
|
+
*/
|
|
34
|
+
export async function loadPlugins(app, rootConfig) {
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const { value, from } of await resolveEntries(rootConfig.plugins, PLUGINS_KEY)) {
|
|
37
|
+
const plugin = value;
|
|
38
|
+
if (!isPlainObject(plugin) || typeof plugin.name !== 'string' || !plugin.name) {
|
|
39
|
+
throw new Error(`"${PLUGINS_KEY}" takes a plugin or a glob naming modules that export one - ${from} gave ` +
|
|
40
|
+
`${describeExport(plugin)}. Every message about a plugin is keyed by its \`name\`.`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* **Declared through `definePlatform`/`definePlugin`, and checked for it.**
|
|
44
|
+
*
|
|
45
|
+
* The guard used to be `manifestProvider` being required, which worked only while one type was
|
|
46
|
+
* both halves. A 2.x plugin that contributes nothing but an `init` is `{ name, init }` - and so
|
|
47
|
+
* was an rman **1.x plugin**, exactly. They are indistinguishable by shape, so a shape test
|
|
48
|
+
* cannot tell them apart and the declaration has to be explicit.
|
|
49
|
+
*
|
|
50
|
+
* Measured on the real case before there was any guard: `@panates/rman-node`'s 1.x plugin
|
|
51
|
+
* loaded, registered, and died inside its own `init` with
|
|
52
|
+
* `TypeError: ctx.addCommand is not a function`, fifteen times over, naming neither the plugin
|
|
53
|
+
* nor the version it was written against.
|
|
54
|
+
*/
|
|
55
|
+
if (!isDeclared(plugin)) {
|
|
56
|
+
throw new Error(`Plugin "${plugin.name}" (${from}) was not declared with definePlatform() or ` +
|
|
57
|
+
`definePlugin(). A plain object is how an rman 1.x plugin looks, and a 1.x plugin has ` +
|
|
58
|
+
`nothing left to do: commands, publish targets and other plugins are "${COMMANDS_KEY}", ` +
|
|
59
|
+
`"publishTargets" and "${PLUGINS_KEY}" keys of a config now. Wrap a technology in ` +
|
|
60
|
+
`definePlatform({ name, manifestProvider, ... }), anything else in definePlugin({ ... }).`);
|
|
61
|
+
}
|
|
62
|
+
/** One registration per name. Twice would define the same commands twice, which yargs does not
|
|
63
|
+
* survive - and two layers naming one plugin is ordinary rather than a mistake. */
|
|
64
|
+
if (seen.has(plugin.name))
|
|
65
|
+
continue;
|
|
66
|
+
seen.add(plugin.name);
|
|
67
|
+
await registerPlugin(app, plugin).init?.({ app });
|
|
68
|
+
}
|
|
69
|
+
for (const { value, from } of await resolveEntries(rootConfig.publishTargets, 'publishTargets')) {
|
|
70
|
+
const target = value;
|
|
71
|
+
if (!isPlainObject(value) || typeof target.name !== 'string' || !target.name) {
|
|
72
|
+
throw new Error(`"publishTargets" takes a target or a glob naming modules that export one - ${from} gave ` +
|
|
73
|
+
`${describeExport(target)}.`);
|
|
74
|
+
}
|
|
75
|
+
app.publishTargets.add(target);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* **Puts one plugin onto an application** - what `plugins` does with an entry once it has been
|
|
80
|
+
* found and vetted, and the only place that knows how.
|
|
81
|
+
*
|
|
82
|
+
* **A bare `Platform` is sugar**, which is what almost every entry is - a plugin that provides one
|
|
83
|
+
* technology and nothing else. Normalized here, so nothing downstream deals in two shapes.
|
|
84
|
+
*
|
|
85
|
+
* **Exported because a spec must not reimplement it.** The registration is two registries plus a
|
|
86
|
+
* planner assignment, and a fixture writing that out by hand is a second implementation that
|
|
87
|
+
* drifts - which is exactly how a fixture ends up proving the core works when it does not. A spec
|
|
88
|
+
* brings its own technology through this, the same door a config's does.
|
|
89
|
+
*
|
|
90
|
+
* **`init` is not called here, and that is why this returns the normalized plugin.** An `init` runs
|
|
91
|
+
* once, when a *config* brought the plugin in, and it may be asynchronous - so it belongs to the
|
|
92
|
+
* loader above rather than to a function a fixture calls synchronously while building an
|
|
93
|
+
* application.
|
|
94
|
+
*/
|
|
95
|
+
export function registerPlugin(app, entry) {
|
|
96
|
+
const declared = isPlatform(entry) ? { name: entry.name, platforms: [entry] } : entry;
|
|
97
|
+
app.plugins.add(declared);
|
|
98
|
+
for (const platform of declared.platforms ?? []) {
|
|
99
|
+
app.platforms.add(platform);
|
|
100
|
+
/**
|
|
101
|
+
* **The orchestrator, and only that.** A plan is computed for the whole repository at once -
|
|
102
|
+
* groups span packages, the ripple crosses them - so one planner drives the traversal and the
|
|
103
|
+
* last registration wins it.
|
|
104
|
+
*
|
|
105
|
+
* The two decisions that belong to a *technology* are not taken from here: `detectBoundary`
|
|
106
|
+
* and `cascade` are asked of `pkg.platform.versionPlanner` per package, which is why a platform
|
|
107
|
+
* still declares one even when it is not the last to register.
|
|
108
|
+
*/
|
|
109
|
+
if (platform.versionPlanner)
|
|
110
|
+
app.versionPlanner = platform.versionPlanner;
|
|
111
|
+
}
|
|
112
|
+
return declared;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The shared half of the three keys: an instance is itself, a string is a glob whose matches are
|
|
116
|
+
* imported and unwrapped.
|
|
117
|
+
*
|
|
118
|
+
* **A glob is already absolute** by the time it gets here - `mergeConfig` anchors it to the file
|
|
119
|
+
* that declared it as the config is read, which is the last moment that is knowable (see
|
|
120
|
+
* `anchorContributions`). Nothing here resolves a path.
|
|
121
|
+
*
|
|
122
|
+
* **`.js` only.** rman imports these in its own process with no loader registered, so a `.ts`
|
|
123
|
+
* module cannot be one; a TypeScript repository compiles first or writes `.mjs`. Stated rather
|
|
124
|
+
* than attempted, because a `.ts` that happens to load under a test runner's loader and not under
|
|
125
|
+
* the CLI is the worst of both.
|
|
126
|
+
*/
|
|
127
|
+
async function resolveEntries(declared, key) {
|
|
128
|
+
if (declared === undefined)
|
|
129
|
+
return [];
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const entry of (Array.isArray(declared) ? declared : [declared])) {
|
|
132
|
+
if (typeof entry !== 'string') {
|
|
133
|
+
out.push({ value: entry, from: `"${key}"` });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!entry.trim())
|
|
137
|
+
throw new Error(`"${key}" has an empty glob.`);
|
|
138
|
+
const files = await fastGlob(entry.split(path.sep).join('/'), { absolute: true, onlyFiles: true });
|
|
139
|
+
/**
|
|
140
|
+
* **A glob matching nothing is an error here**, unlike in `commands`, and the asymmetry is the
|
|
141
|
+
* point. Losing a plugin silently removes commands and seams the repository is built around -
|
|
142
|
+
* `rman publish` would simply not exist, and "not a known command" sends the reader looking in
|
|
143
|
+
* the wrong place. `commands` can match nothing legitimately: `.rman/*.js` is its default, and
|
|
144
|
+
* most repositories have no such directory.
|
|
145
|
+
*/
|
|
146
|
+
if (!files.length) {
|
|
147
|
+
/**
|
|
148
|
+
* **A package name gets the answer it is actually asking for.** A bare `rman-node` here is a
|
|
149
|
+
* glob that matches nothing, and "matched no file" sends the reader to check their paths -
|
|
150
|
+
* when what they wrote is a *package*, whose config reaches a repository through `extends`.
|
|
151
|
+
* Two documents promised this message named the fix (`docs/rman.md`, `docs/cli-rman.md`) and
|
|
152
|
+
* it did not; measured, `plugins: ['rman-node']` exited 1 saying only that a glob matched
|
|
153
|
+
* nothing.
|
|
154
|
+
*
|
|
155
|
+
* Reaching this intact takes `mergeConfig`'s help: it anchors a contribution glob to the file
|
|
156
|
+
* that declared it, which used to turn `rman-node` into `<dir>/rman-node` and erase the
|
|
157
|
+
* evidence. `looksLikePackageName` there leaves this shape alone; the same predicate is
|
|
158
|
+
* spelled again here rather than shared, because the two modules answer different questions
|
|
159
|
+
* with it and a merge concern importing a loader concern (or the reverse) is the coupling
|
|
160
|
+
* neither wants.
|
|
161
|
+
*/
|
|
162
|
+
const isPackageName = /^(?:@[a-z0-9-~][\w.-]*\/)?[a-z0-9-~][\w.-]*$/i.test(entry) && !/\.[cm]?js$/i.test(entry);
|
|
163
|
+
if (isPackageName) {
|
|
164
|
+
throw new Error(`"${key}" entry "${entry}" looks like a package name, and this key does not take one - ` +
|
|
165
|
+
`it takes a plugin, or a glob naming modules that export one. A plugin package exports ` +
|
|
166
|
+
`an rman config, so write \`extends: "${entry}"\` instead, which merges everything that ` +
|
|
167
|
+
`package declares underneath your own config.`);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`"${key}" glob "${entry}" matched no file. A plugin that does not load is not a plugin.`);
|
|
170
|
+
}
|
|
171
|
+
for (const file of [...new Set(files.map(f => path.resolve(f)))].sort()) {
|
|
172
|
+
const mod = await import(pathToFileURL(file).href);
|
|
173
|
+
const exported = mod?.default;
|
|
174
|
+
if (exported === undefined) {
|
|
175
|
+
throw new Error(`"${key}" matched "${file}", which has no default export. ${describeExport(mod)}`);
|
|
176
|
+
}
|
|
177
|
+
out.push({ value: exported, from: file });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The second half of the error above - what the module *did* export, so the author can see how far
|
|
184
|
+
* off it was.
|
|
185
|
+
*
|
|
186
|
+
* It recognizes a plugin object only to **say so in a message**. That is the one safe use for this
|
|
187
|
+
* shape test: it decides nothing, so a wrong guess costs a slightly less helpful sentence rather
|
|
188
|
+
* than a plugin that silently does not load.
|
|
189
|
+
*/
|
|
190
|
+
function describeExport(exported) {
|
|
191
|
+
if (exported === undefined)
|
|
192
|
+
return 'It has no default export.';
|
|
193
|
+
if (!isPlainObject(exported))
|
|
194
|
+
return `Its default export is a ${typeof exported}.`;
|
|
195
|
+
const looksLikePlugin = PLUGIN_SEAMS.some(seam => exported[seam] !== undefined);
|
|
196
|
+
return looksLikePlugin
|
|
197
|
+
? `Its default export looks like the plugin itself - put it in a config's "${PLUGINS_KEY}".`
|
|
198
|
+
: `Its default export has no "${PLUGINS_KEY}".`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The members only a plugin has, for that sentence alone.
|
|
202
|
+
*
|
|
203
|
+
* **`name` is deliberately not one of them**, and it used to be: a config may carry a `name` too,
|
|
204
|
+
* so anything with one "looked like a plugin" - which sent the author of a perfectly ordinary
|
|
205
|
+
* config to go and wrap it in `plugins`. Each of these belongs to a technology and to nothing
|
|
206
|
+
* else, `manifestProvider` first because it is the one a plugin cannot be without.
|
|
207
|
+
*/
|
|
208
|
+
const PLUGIN_SEAMS = ['manifestProvider', 'getWorkspace', 'getBinPaths', 'getRunSteps', 'versionPlanner'];
|
|
209
|
+
/** A config object, as opposed to an array or anything with its own prototype. */
|
|
210
|
+
function isPlainObject(value) {
|
|
211
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A plugin's `CustomCommand`, checked the same way a `.rman/*.mjs` one is - the name it answers to
|
|
215
|
+
* comes from its own `command` string, since a plugin has no file name to fall back on.
|
|
216
|
+
*
|
|
217
|
+
* Exported because `cli.ts` runs the identical checks on what a *declarative* command's factory
|
|
218
|
+
* returns, and they must not drift: a plugin written in JavaScript reaches both forms with no type
|
|
219
|
+
* checker in the way.
|
|
220
|
+
*/
|
|
221
|
+
export function checkCustomCommand(command, pluginName) {
|
|
222
|
+
const declared = command?.command?.trim();
|
|
223
|
+
if (!declared) {
|
|
224
|
+
throw new Error(`Plugin "${pluginName}" has a command with no "command" name - it cannot be registered.`);
|
|
225
|
+
}
|
|
226
|
+
if (typeof command.handler !== 'function') {
|
|
227
|
+
throw new Error(`Plugin "${pluginName}" command "${declared}" has no "handler" function.`);
|
|
228
|
+
}
|
|
229
|
+
if (typeof command.describe !== 'string' || !command.describe) {
|
|
230
|
+
throw new Error(`Plugin "${pluginName}" command "${declared}" has no "describe" - \`rman --help\` would have ` +
|
|
231
|
+
`nothing to list it by.`);
|
|
232
|
+
}
|
|
233
|
+
return { ...command, command: declared };
|
|
234
|
+
}
|
package/core/plugin.d.ts
CHANGED
|
@@ -1,112 +1,157 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
7
|
-
import { Workspace } from './workspace.js';
|
|
8
|
-
/** The `.rmanrc` key naming plugin packages to load. */
|
|
9
|
-
export declare const PLUGINS_KEY = "plugins";
|
|
1
|
+
import type { RunService } from '../services/run.service.js';
|
|
2
|
+
import type { VersionPlanService } from '../services/version-plan.service.js';
|
|
3
|
+
import type { BinPath } from '../utils/bin-path.js';
|
|
4
|
+
import type { RmanApplication } from './application.js';
|
|
5
|
+
import type { ManifestProvider } from './manifest.js';
|
|
6
|
+
import type { Workspace } from './workspace.js';
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
8
|
+
* **One technology, as a whole** - Node, Cargo, Maven. Everything rman needs in order to treat a
|
|
9
|
+
* directory as a package of that technology.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* **This was called `Plugin`, and the name was a lie.** `manifestProvider` is required, which makes
|
|
12
|
+
* the type a *platform* by definition: a package that only ships commands never touches it, because
|
|
13
|
+
* commands are a config key. So the narrow thing took the narrow name, and `Plugin` became the
|
|
14
|
+
* broad one that may carry platforms - a plugin *provides* platforms, and may provide more than
|
|
15
|
+
* one (a package shipping both `maven` and `gradle` is one plugin, two platforms).
|
|
16
|
+
*
|
|
17
|
+
* These were six independent fields on a plugin, each with its own registry, and declaring one
|
|
18
|
+
* without the others type-checked. It is not a shape that admits sense: `getRunSteps` reads
|
|
19
|
+
* `pkg.manifest.raw?.scripts`, so contributing npm's step source without npm's manifest reader
|
|
20
|
+
* leaves it parsing whatever another technology produced. The coupling was already real; only the
|
|
21
|
+
* type failed to say so.
|
|
22
|
+
*
|
|
23
|
+
* **`RmanPlugin` and `TechStack` used to be two types and are now one.** The plugin existed solely to
|
|
24
|
+
* *register* the stack - its whole `init` was `ctx.addTechStack(...)` plus a command or two - and
|
|
25
|
+
* once a config carries commands and publish targets itself, that registration step has nothing
|
|
26
|
+
* left to do. What remains of a plugin is the technology, so that is what the type is.
|
|
27
|
+
*
|
|
28
|
+
* **`manifestProvider` stays grouped rather than flattened in here**, and it was tried the other
|
|
29
|
+
* way first: nine members about one file - reading it, writing it, what it declares, how its
|
|
30
|
+
* versions are numbered, stamped and looked up on a registry - are more legible as a named group
|
|
31
|
+
* than as nine siblings of `getBinPaths`. The name also keeps its distance from `Package.manifest`,
|
|
32
|
+
* which is the *data* this produces rather than the reader.
|
|
33
|
+
*
|
|
34
|
+
* **Everything optional is answered by its absence**, never by a default rman invented - see
|
|
35
|
+
* `basePlatform`.
|
|
17
36
|
*/
|
|
18
|
-
export interface
|
|
19
|
-
/** For error messages and `--help` grouping. Conventionally the package's own name. */
|
|
20
|
-
name: string;
|
|
21
|
-
commands?: CustomCommand[];
|
|
37
|
+
export interface Platform {
|
|
22
38
|
/**
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
39
|
+
* **The ecosystem this speaks for**, surfaced on every package it reads as `Package.platform` -
|
|
40
|
+
* `'node'` for the built-in of that name. Short and about the technology, not about the file:
|
|
41
|
+
* `manifestProvider.fileName` already says `package.json`, and a name repeating it would tell a
|
|
42
|
+
* caller nothing it did not have.
|
|
25
43
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
44
|
+
* This is what lets code that *does* know one ecosystem check before acting on a package -
|
|
45
|
+
* `if (pkg.platform === 'node')` - which matters most in a repository holding more than one,
|
|
46
|
+
* since a manifest is read per directory and two packages can legitimately answer to different
|
|
47
|
+
* technologies. It is also the de-duplication key: registering twice under one name is refused,
|
|
48
|
+
* because it would define the same commands twice and yargs does not survive that.
|
|
29
49
|
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
* Registered before any package is constructed, since `Package` reads through it. A repository
|
|
45
|
-
* naming no plugin therefore gets packages named after their own directories at version
|
|
46
|
-
* `0.0.0` - see `readManifest`.
|
|
47
|
-
*/
|
|
48
|
-
manifest?: ManifestProvider;
|
|
50
|
+
name: string;
|
|
51
|
+
/** Where this technology's packages keep their identity, and how to change it - reading and
|
|
52
|
+
* writing the manifest, what it declares as dependencies, how its versions are numbered,
|
|
53
|
+
* stamped, and looked up on a registry. */
|
|
54
|
+
manifestProvider: ManifestProvider;
|
|
55
|
+
/** Where this technology's packages are, given the repository root - npm reads `workspaces`,
|
|
56
|
+
* Cargo a `[workspace] members`. `undefined` when it does not recognize the root. */
|
|
57
|
+
getWorkspace?: Workspace.Provider;
|
|
58
|
+
/** Directories to put in front of a child process's PATH, so a command an author wrote
|
|
59
|
+
* (`eslint .`) runs the repository's pinned copy - `node_modules/.bin` walked up the tree, for
|
|
60
|
+
* npm. Every plugin contributes, unlike the seams above where the first answer wins: a PATH is
|
|
61
|
+
* a list, and a polyglot repository wants both ecosystems' binaries reachable. */
|
|
62
|
+
getBinPaths?: BinPath.Provider;
|
|
49
63
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* **`VersionPlanService` is abstract, so `version`/`changed` do not work without one** (they fail
|
|
54
|
-
* naming this key). Unlike `manifest` and `workspace`, which degrade to honest defaults, a plan is
|
|
55
|
-
* either right or it quietly releases the wrong set of packages - see `VersionPlanService`.
|
|
64
|
+
* What the *package itself* declares for a lifecycle script, from this technology's own files -
|
|
65
|
+
* npm's `package.json` `scripts`, with `pre<script>`/`<script>`/`post<script>`.
|
|
56
66
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
67
|
+
* **A query, not a hook, and not build-specific** - which is why it is `getRunSteps` rather than
|
|
68
|
+
* anything beginning `on`. It returns what the package declares and rman decides what to do with
|
|
69
|
+
* it; `run`/`build`/`test` ask, and so does `version`, whose `preversion`/`version`/`postversion`
|
|
70
|
+
* are the same shape (`runLifecycleSlot` -> `contributedSlots`). A name mentioning `build` would
|
|
71
|
+
* have been wrong about half its callers.
|
|
59
72
|
*/
|
|
73
|
+
getRunSteps?: RunService.StepSource;
|
|
74
|
+
/** How this technology's releases are planned - where a package's change boundary comes from
|
|
75
|
+
* when it has no release tag, and how far into its group a bump reaches. */
|
|
60
76
|
versionPlanner?: VersionPlanService;
|
|
61
|
-
/**
|
|
62
|
-
* Where this ecosystem keeps a repository's locally installed executables - `rman-node`
|
|
63
|
-
* contributes npm's `node_modules/.bin`, walked up the directory chain.
|
|
64
|
-
*
|
|
65
|
-
* Prepended to PATH for every `exec`/`runBin` child process, so a command an author wrote runs
|
|
66
|
-
* against the repository's own pinned tools. **Every plugin's entries are used**, not just the
|
|
67
|
-
* first - see `BinPath`.
|
|
68
|
-
*/
|
|
69
|
-
binPaths?: BinPath.Provider;
|
|
70
77
|
}
|
|
71
78
|
/**
|
|
72
|
-
*
|
|
73
|
-
* `defineCommand` pattern again, for the same reason. Returns `plugin` unchanged.
|
|
74
|
-
*
|
|
75
|
-
* **A plugin package's entry point exports a *config*, not this**, so that a package is an
|
|
76
|
-
* `.rmanrc` like any other and can carry a second plugin later without changing shape:
|
|
79
|
+
* **A plugin is whatever a package contributes; a platform is one of the things it can contribute.**
|
|
77
80
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
81
|
+
* The broad half of the split. `Platform` is a technology and nothing else - required manifest
|
|
82
|
+
* reader, workspace layout, bin paths, steps, version planning. What is left over is this: an
|
|
83
|
+
* `init` for whatever the seams do not name yet, and the **declaration** that this plugin provides
|
|
84
|
+
* platforms.
|
|
82
85
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
+
* **`platforms` is a list, and that is not symmetry.** One package can speak for more than one
|
|
87
|
+
* technology - `maven` and `gradle` are the same toolchain family and a single plugin shipping both
|
|
88
|
+
* is the natural shape. A singular field would have made the second one a separate package for no
|
|
89
|
+
* reason.
|
|
86
90
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* test would be a guess, and guessing "plugin" registers nothing while reporting success.
|
|
91
|
+
* A bare `Platform` is accepted anywhere a `Plugin` is, as sugar for `{ name, platforms: [it] }` -
|
|
92
|
+
* which is what almost every entry is.
|
|
90
93
|
*/
|
|
91
|
-
export
|
|
94
|
+
export interface Plugin {
|
|
95
|
+
/** How this plugin is named in messages, and the de-duplication key: registering twice under one
|
|
96
|
+
* name is refused. A platform contributed by it keeps its *own* name, which is what a package
|
|
97
|
+
* reports as `pkg.platform`. */
|
|
98
|
+
name: string;
|
|
99
|
+
/** The technologies this plugin provides, if any. */
|
|
100
|
+
platforms?: Platform[];
|
|
101
|
+
/**
|
|
102
|
+
* Anything this plugin contributes that is not a platform, run once when it is registered.
|
|
103
|
+
*
|
|
104
|
+
* **The escape hatch, not the front door.** Commands and publish targets are `.rmanrc` keys
|
|
105
|
+
* (`commands`, `publishTargets`), so a plugin contributing only those declares them in its own
|
|
106
|
+
* config and needs no `init` at all. What is left for `init` is whatever the seams do not name
|
|
107
|
+
* yet, reached through `ctx.app`.
|
|
108
|
+
*
|
|
109
|
+
* **It runs inside `Repository.create`**, before any package is known, so `ctx.app.repository`
|
|
110
|
+
* throws there. Anything wanting the repository belongs in a command's factory instead, which
|
|
111
|
+
* runs once there is one.
|
|
112
|
+
*/
|
|
113
|
+
init?(ctx: PluginContext): void | Promise<void>;
|
|
114
|
+
}
|
|
92
115
|
/**
|
|
93
|
-
*
|
|
116
|
+
* What `Plugin.init` is handed.
|
|
94
117
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
|
|
118
|
+
* Only the application today. An object rather than a bare parameter so a member added later
|
|
119
|
+
* breaks nothing already written against it - the same reason `CommandContext` is one, which has
|
|
120
|
+
* already paid for itself twice.
|
|
121
|
+
*/
|
|
122
|
+
export interface PluginContext {
|
|
123
|
+
app: RmanApplication;
|
|
124
|
+
}
|
|
125
|
+
/** Whether `value` came from `definePlatform` or `definePlugin`. */
|
|
126
|
+
export declare function isDeclared(value: unknown): boolean;
|
|
127
|
+
/** Declares a **platform** - one technology, whole. Returns it unchanged apart from the mark. */
|
|
128
|
+
export declare function definePlatform(platform: Platform): Platform;
|
|
129
|
+
/** Declares a **plugin** - whatever a package contributes, platforms included. Returns it unchanged
|
|
130
|
+
* apart from the mark. */
|
|
131
|
+
export declare function definePlugin(plugin: Plugin): Plugin;
|
|
132
|
+
/** Whether `value` is a platform rather than the broader plugin - `manifestProvider` is what makes
|
|
133
|
+
* one, and it is the only required member either type has beyond `name`. */
|
|
134
|
+
export declare function isPlatform(value: Plugin | Platform): value is Platform;
|
|
135
|
+
/**
|
|
136
|
+
* The platform a package gets when **none** claimed its directory - a repository naming no
|
|
137
|
+
* platform, or a directory none of the named ones recognized.
|
|
99
138
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
139
|
+
* It exists so `Package.platform` need not be optional. `Package.provider` was an empty string for
|
|
140
|
+
* exactly this case, and every reader had to know that; an object with an empty `name` says the
|
|
141
|
+
* same thing without a guard, and `pkg.provider === 'node'` - the check CLAUDE.md prescribes -
|
|
142
|
+
* reads the same either way.
|
|
102
143
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
144
|
+
* **Every documented behaviour of "no plugin" survives unchanged**, because the absences are the
|
|
145
|
+
* behaviour:
|
|
105
146
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
147
|
+
* - a reader that recognizes nothing -> `Manifest.read` falls through to its own empty manifest,
|
|
148
|
+
* exactly as it does when no plugin answers;
|
|
149
|
+
* - no `getWorkspace` -> a repository naming no plugin has no packages beyond itself, which is the
|
|
150
|
+
* boundary working rather than failing (`workspaces` in a `package.json` is npm's idea);
|
|
151
|
+
* - no `getBinPaths` -> nothing is prepended to a child process's PATH, so the inherited one
|
|
152
|
+
* stands on its own rather than being guessed at;
|
|
153
|
+
* - no `getRunSteps` -> a package's steps come from its `.rmanrc` alone, the core's only source;
|
|
154
|
+
* - no `versionPlanner` -> `version`/`changed` fail naming the key, rather than releasing a
|
|
155
|
+
* plausible but untrue set of packages from a default nobody chose.
|
|
111
156
|
*/
|
|
112
|
-
export declare
|
|
157
|
+
export declare const basePlatform: Platform;
|