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
|
@@ -1 +1,61 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const commandRegistry = [];
|
|
2
|
+
/**
|
|
3
|
+
* Registers a command and hands the register function straight back.
|
|
4
|
+
*
|
|
5
|
+
* **It returns the function, not the command's config, and that is the point.** The function has
|
|
6
|
+
* not run yet - it runs during init, once there is a `Repository` - so at registration time no
|
|
7
|
+
* config object exists to return. Declaring one anyway is what made the old body
|
|
8
|
+
* (`return def['config']`) uncompilable: `def` is a function, and `config` belongs to what it
|
|
9
|
+
* *returns*.
|
|
10
|
+
*
|
|
11
|
+
* Nothing is lost by being honest, because the metadata type rides along on the return type:
|
|
12
|
+
* `ReturnType<typeof someCommand>` is the `CommandMetadata`, and `CommandConfigFromMetadata` maps
|
|
13
|
+
* it to the config object the command contributes. A phantom property carrying the config type was
|
|
14
|
+
* tried and is unnecessary for exactly this reason - and it would not have survived being stored in
|
|
15
|
+
* `commandRegistry`, whose element type erases it.
|
|
16
|
+
*
|
|
17
|
+
* **`M` is inferred from the metadata rather than from the function, so that the same position can
|
|
18
|
+
* check it.** With the plainer `<T extends CommandRegisterFunction>(def: T)`, `T` is inferred from
|
|
19
|
+
* the literal itself, so the constraint is a subtype check - and a subtype check does no
|
|
20
|
+
* excess-property checking. Measured: `anotherBogusTopLevelKey: true` and a `totallyBogusKey`
|
|
21
|
+
* inside an option both compiled silently, which is how `cliName` and `examples` went unnoticed in
|
|
22
|
+
* `version.command.ts`. `M & ValidMeta<M>` keeps the bare `M` as the inference site and puts the
|
|
23
|
+
* check beside it, so a misspelled key now errors on its own line and names itself.
|
|
24
|
+
*
|
|
25
|
+
* **A command's `command` string needs `as const`** for the config key to be derivable from it
|
|
26
|
+
* (`'version [bump]' as const` -> `'version'`). `type: 'string'` survives inference on its own,
|
|
27
|
+
* since `yargs.Options['type']` is a union of literals and a union suppresses widening; `command`
|
|
28
|
+
* is a plain `string`, so it widens without the assertion. Spelling it `` `${string}` `` does not
|
|
29
|
+
* help - measured; that trick only applies where the constraint sits on the generic parameter.
|
|
30
|
+
*/
|
|
31
|
+
export function registerCommand(def) {
|
|
32
|
+
/** The same cast `declareCommand` makes, rather than a call to it: passing `def` through would
|
|
33
|
+
* re-infer `M` from a type that already carries `ValidMeta<M>`, and the check then compounds
|
|
34
|
+
* onto itself (`Exact<M & Exact<M, …>, …>`) and fails on every command. Two lines, one cast
|
|
35
|
+
* each, is the honest shape. */
|
|
36
|
+
const fn = def;
|
|
37
|
+
commandRegistry.push(fn);
|
|
38
|
+
return fn;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The same declaration, **without** the registration - for a command that must exist only when
|
|
42
|
+
* something asks for it.
|
|
43
|
+
*
|
|
44
|
+
* That is exactly a contributed command's situation: `commandRegistry` is a module-level array
|
|
45
|
+
* walked by every `runCli`, so a package pushing onto it would give its commands to repositories
|
|
46
|
+
* that never named it - the module is imported as soon as anything imports the package. A package
|
|
47
|
+
* puts the function in its config's `commands` instead, and `cli.ts` calls it once the repository
|
|
48
|
+
* exists.
|
|
49
|
+
*
|
|
50
|
+
* **Why a factory rather than the metadata itself**: the config is resolved *inside*
|
|
51
|
+
* `Repository.create`, before the packages are known (plugins are what find them), so
|
|
52
|
+
* `app.repository` throws at that point. The function is stored unrun and called later, in
|
|
53
|
+
* `cli.ts`, where the built-ins' own factories run.
|
|
54
|
+
*
|
|
55
|
+
* Everything `registerCommand` documents about inference - `M & ValidMeta<M>`, the `as const` on
|
|
56
|
+
* `command`, the metadata riding on the return type - applies here unchanged; the two differ in one
|
|
57
|
+
* line.
|
|
58
|
+
*/
|
|
59
|
+
export function declareCommand(def) {
|
|
60
|
+
return def;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rman",
|
|
3
3
|
"description": "Repository manager",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0-beta.2",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"@netlify/parse-npm-script": "^0.1.2",
|
|
8
9
|
"@xmldom/xmldom": "^0.9.12",
|
|
9
10
|
"ansi-colors": "^4.1.3",
|
|
10
11
|
"cross-dirname": "^0.1.0",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Platform } from '../core/plugin.js';
|
|
2
|
+
import type { RmanConfig } from '../interfaces/rman-config.interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* **The plugins rman ships in the box, by the name a config calls them.**
|
|
5
|
+
*
|
|
6
|
+
* `plugins: ['node']` is the whole of it - no new key, because there is no new concept. A
|
|
7
|
+
* repository was always able to say which technologies it holds; this is that same statement with
|
|
8
|
+
* a name instead of an instance, which is the form a YAML config and a zero-config repository both
|
|
9
|
+
* need.
|
|
10
|
+
*
|
|
11
|
+
* **Bundled is not the same as always on, and keeping those apart is the point.** Nothing an entry
|
|
12
|
+
* contributes exists until a repository names it or detection finds it, so the core still assumes
|
|
13
|
+
* no ecosystem and `rman clean` is still `Unknown argument` in a repository that is not a Node one.
|
|
14
|
+
* What changed is only where the code is shipped from - one install instead of two, which is what
|
|
15
|
+
* `extends: 'rman-node'` was costing every Node repository for a benefit that belonged to polyglot
|
|
16
|
+
* ones.
|
|
17
|
+
*
|
|
18
|
+
* The key is `Plugin.name`, so what a config writes is what `${{ pkg.provider }}` reads back.
|
|
19
|
+
*
|
|
20
|
+
* **A published package is still not one of these.** `plugins: ['rman-node']` names a package, whose
|
|
21
|
+
* config reaches a repository through `extends`; it is refused, saying so. The two are different
|
|
22
|
+
* statements and a built-in name is neither of them - it is the plugin itself, called by name.
|
|
23
|
+
*/
|
|
24
|
+
export declare const BUILTIN_PLUGINS: Record<string, Builtin>;
|
|
25
|
+
/**
|
|
26
|
+
* One built-in, in the two halves that must not be the same call.
|
|
27
|
+
*
|
|
28
|
+
* **`plugin` answers questions; `contribute` changes the run.** Detection asks a platform whether a
|
|
29
|
+
* directory is its own, which has to be possible without registering anything - and registering is
|
|
30
|
+
* what `contribute` does: it hands over commands, publish targets and (for `node`) an augmentation
|
|
31
|
+
* that rewrites the core's `SystemInfo` in place. One call for both would have "is this a Node
|
|
32
|
+
* repository?" print npm's tooling in a Cargo one, which is exactly the line between *bundled* and
|
|
33
|
+
* *on*.
|
|
34
|
+
*/
|
|
35
|
+
export interface Builtin {
|
|
36
|
+
/** The platform itself, for asking - constructed, registered nowhere, with no side effect. */
|
|
37
|
+
platform: () => Platform;
|
|
38
|
+
/** Everything this built-in adds to a repository that asked for it. */
|
|
39
|
+
contribute: () => RmanConfig;
|
|
40
|
+
}
|
|
41
|
+
/** Whether `name` is one rman ships - the check `plugins` makes before treating a string as a glob. */
|
|
42
|
+
export declare function isBuiltinPlugin(name: string): boolean;
|
|
43
|
+
/** The built-in names, for an error message that has to list what a repository could have meant. */
|
|
44
|
+
export declare function builtinPluginNames(): string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { nodeBuiltin, nodePlatform } from './node/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* **The plugins rman ships in the box, by the name a config calls them.**
|
|
4
|
+
*
|
|
5
|
+
* `plugins: ['node']` is the whole of it - no new key, because there is no new concept. A
|
|
6
|
+
* repository was always able to say which technologies it holds; this is that same statement with
|
|
7
|
+
* a name instead of an instance, which is the form a YAML config and a zero-config repository both
|
|
8
|
+
* need.
|
|
9
|
+
*
|
|
10
|
+
* **Bundled is not the same as always on, and keeping those apart is the point.** Nothing an entry
|
|
11
|
+
* contributes exists until a repository names it or detection finds it, so the core still assumes
|
|
12
|
+
* no ecosystem and `rman clean` is still `Unknown argument` in a repository that is not a Node one.
|
|
13
|
+
* What changed is only where the code is shipped from - one install instead of two, which is what
|
|
14
|
+
* `extends: 'rman-node'` was costing every Node repository for a benefit that belonged to polyglot
|
|
15
|
+
* ones.
|
|
16
|
+
*
|
|
17
|
+
* The key is `Plugin.name`, so what a config writes is what `${{ pkg.provider }}` reads back.
|
|
18
|
+
*
|
|
19
|
+
* **A published package is still not one of these.** `plugins: ['rman-node']` names a package, whose
|
|
20
|
+
* config reaches a repository through `extends`; it is refused, saying so. The two are different
|
|
21
|
+
* statements and a built-in name is neither of them - it is the plugin itself, called by name.
|
|
22
|
+
*/
|
|
23
|
+
export const BUILTIN_PLUGINS = {
|
|
24
|
+
node: { platform: () => nodePlatform, contribute: nodeBuiltin },
|
|
25
|
+
};
|
|
26
|
+
/** Whether `name` is one rman ships - the check `plugins` makes before treating a string as a glob. */
|
|
27
|
+
export function isBuiltinPlugin(name) {
|
|
28
|
+
return Object.hasOwn(BUILTIN_PLUGINS, name);
|
|
29
|
+
}
|
|
30
|
+
/** The built-in names, for an error message that has to list what a repository could have meant. */
|
|
31
|
+
export function builtinPluginNames() {
|
|
32
|
+
return Object.keys(BUILTIN_PLUGINS).sort();
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Which platform a repository looks like, when it has not said.**
|
|
3
|
+
*
|
|
4
|
+
* The 2.0 split made every Node repository write a config file before `rman clean` would run, and
|
|
5
|
+
* the cost of that landed on exactly the repositories the split was not for. This is the other half
|
|
6
|
+
* of the fix: a repository that declares no technology gets the one its own files already imply.
|
|
7
|
+
*
|
|
8
|
+
* **The core does not know what any repository looks like, and must not.** It asks the *platforms*
|
|
9
|
+
* - `manifestProvider.read(dir)` has always meant "is this directory one of mine?", so there is no
|
|
10
|
+
* new seam and no list of filenames anywhere in `core/`. This file listed `package.json` for one
|
|
11
|
+
* commit, which is the same mistake as the hardcoded `['npm']` publish default that
|
|
12
|
+
* `PublishTarget.claims` exists to have replaced: true of npm, written in a place that speaks for
|
|
13
|
+
* every ecosystem, and invisible while only one platform ships.
|
|
14
|
+
*
|
|
15
|
+
* The platforms it can ask are the ones it has, which are the built-ins - by construction, not by
|
|
16
|
+
* choice: a third-party platform arrives through `plugins` or `extends`, and a repository that
|
|
17
|
+
* wrote either has declared its technology, so detection never runs for it.
|
|
18
|
+
*
|
|
19
|
+
* **Only ever consulted when nothing was declared** - see `Repository.create`, which checks both the
|
|
20
|
+
* config and the application. That boundary is the whole safety of it, because registering a
|
|
21
|
+
* platform changes *which directories are packages at all* (`Workspace.resolve` takes the first
|
|
22
|
+
* provider that answers), so guessing on top of an explicit statement could quietly change what
|
|
23
|
+
* `rman list` reports.
|
|
24
|
+
*
|
|
25
|
+
* **It is announced, not silent.** A guess the reader cannot see is one they cannot correct.
|
|
26
|
+
*/
|
|
27
|
+
export interface DetectedBuiltin {
|
|
28
|
+
/** The built-in's name - what `plugins: [...]` would have said. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** The file that gave it away, for the message - the platform's own `manifestFile`. */
|
|
31
|
+
because: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Marks a config whose `plugins` was **detected rather than declared**, so the one caller that
|
|
35
|
+
* should say so can tell.
|
|
36
|
+
*
|
|
37
|
+
* A non-enumerable symbol, the way `ORIGINS` and `PREVIOUS_VALUES` already carry bookkeeping: it
|
|
38
|
+
* travels with the config through `mergeConfig`, `JSON.stringify` and `rman config` without any of
|
|
39
|
+
* them knowing it is there, and `expect`'s `toEqual` compares symbol properties - so a plain
|
|
40
|
+
* property would turn every config-shape spec into a diff about this.
|
|
41
|
+
*
|
|
42
|
+
* The alternative was re-deriving the answer where the message is printed, which means reading the
|
|
43
|
+
* config twice and two places that can disagree about what "declared" means.
|
|
44
|
+
*/
|
|
45
|
+
export declare const DETECTED_BUILTIN: unique symbol;
|
|
46
|
+
/** What detection put on `config`, if anything. */
|
|
47
|
+
export declare function detectedBuiltinOf(config: object): DetectedBuiltin | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The first built-in platform that claims `dir`, or `undefined` for a repository none of them
|
|
50
|
+
* recognizes - which is the honest answer, not a default.
|
|
51
|
+
*
|
|
52
|
+
* **First match, in declaration order**, the same rule `Manifest.read` and `Workspace.resolve`
|
|
53
|
+
* already follow, so a repository two platforms both recognize resolves the same way here as it
|
|
54
|
+
* will once they are loaded.
|
|
55
|
+
*
|
|
56
|
+
* Memoized per directory: this is asked once per directory per package and each ask reads the disk.
|
|
57
|
+
* Nothing here writes, so the answer cannot change under a run in any way that matters.
|
|
58
|
+
*
|
|
59
|
+
* **Async only because `builtins.js` is imported dynamically, and that is a cycle rather than a
|
|
60
|
+
* style** - the same one `expandBuiltinPlugins` in `config.ts` already documents, reached from the
|
|
61
|
+
* other side. A built-in pulls in its commands and services; one of those services extends a core
|
|
62
|
+
* service, and the core's own module graph comes back through here:
|
|
63
|
+
*
|
|
64
|
+
* ```
|
|
65
|
+
* services/version-plan.service -> core/application -> core/core-services
|
|
66
|
+
* -> services/github-release.service -> core/repository -> plugins/detect
|
|
67
|
+
* -> plugins/builtins -> plugins/node/index -> plugins/node/node.platform
|
|
68
|
+
* -> plugins/node/services/version-plan.service (extends the first, still initialising)
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* **Measured, and the suite could not see it**: the *built* CLI died on every command with
|
|
72
|
+
* `ReferenceError: Cannot access 'VersionPlanService' before initialization`, while `npm test`
|
|
73
|
+
* passed - mocha resolves `src` through `tsconfig-test.json`'s `paths`, which is a different module
|
|
74
|
+
* graph and happens to reach the two in the other order. `npm run smoke` is what looks now.
|
|
75
|
+
*/
|
|
76
|
+
export declare function detectBuiltin(dir: string): Promise<DetectedBuiltin | undefined>;
|
|
77
|
+
/** Forgets what was detected - for a spec that writes a fixture, detects, then writes another. */
|
|
78
|
+
export declare function clearDetectionCache(): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Marks a config whose `plugins` was **detected rather than declared**, so the one caller that
|
|
4
|
+
* should say so can tell.
|
|
5
|
+
*
|
|
6
|
+
* A non-enumerable symbol, the way `ORIGINS` and `PREVIOUS_VALUES` already carry bookkeeping: it
|
|
7
|
+
* travels with the config through `mergeConfig`, `JSON.stringify` and `rman config` without any of
|
|
8
|
+
* them knowing it is there, and `expect`'s `toEqual` compares symbol properties - so a plain
|
|
9
|
+
* property would turn every config-shape spec into a diff about this.
|
|
10
|
+
*
|
|
11
|
+
* The alternative was re-deriving the answer where the message is printed, which means reading the
|
|
12
|
+
* config twice and two places that can disagree about what "declared" means.
|
|
13
|
+
*/
|
|
14
|
+
export const DETECTED_BUILTIN = Symbol('rman.detectedBuiltin');
|
|
15
|
+
/** What detection put on `config`, if anything. */
|
|
16
|
+
export function detectedBuiltinOf(config) {
|
|
17
|
+
return config[DETECTED_BUILTIN];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The first built-in platform that claims `dir`, or `undefined` for a repository none of them
|
|
21
|
+
* recognizes - which is the honest answer, not a default.
|
|
22
|
+
*
|
|
23
|
+
* **First match, in declaration order**, the same rule `Manifest.read` and `Workspace.resolve`
|
|
24
|
+
* already follow, so a repository two platforms both recognize resolves the same way here as it
|
|
25
|
+
* will once they are loaded.
|
|
26
|
+
*
|
|
27
|
+
* Memoized per directory: this is asked once per directory per package and each ask reads the disk.
|
|
28
|
+
* Nothing here writes, so the answer cannot change under a run in any way that matters.
|
|
29
|
+
*
|
|
30
|
+
* **Async only because `builtins.js` is imported dynamically, and that is a cycle rather than a
|
|
31
|
+
* style** - the same one `expandBuiltinPlugins` in `config.ts` already documents, reached from the
|
|
32
|
+
* other side. A built-in pulls in its commands and services; one of those services extends a core
|
|
33
|
+
* service, and the core's own module graph comes back through here:
|
|
34
|
+
*
|
|
35
|
+
* ```
|
|
36
|
+
* services/version-plan.service -> core/application -> core/core-services
|
|
37
|
+
* -> services/github-release.service -> core/repository -> plugins/detect
|
|
38
|
+
* -> plugins/builtins -> plugins/node/index -> plugins/node/node.platform
|
|
39
|
+
* -> plugins/node/services/version-plan.service (extends the first, still initialising)
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* **Measured, and the suite could not see it**: the *built* CLI died on every command with
|
|
43
|
+
* `ReferenceError: Cannot access 'VersionPlanService' before initialization`, while `npm test`
|
|
44
|
+
* passed - mocha resolves `src` through `tsconfig-test.json`'s `paths`, which is a different module
|
|
45
|
+
* graph and happens to reach the two in the other order. `npm run smoke` is what looks now.
|
|
46
|
+
*/
|
|
47
|
+
export async function detectBuiltin(dir) {
|
|
48
|
+
const resolved = path.resolve(dir);
|
|
49
|
+
if (cache.has(resolved))
|
|
50
|
+
return cache.get(resolved);
|
|
51
|
+
const { BUILTIN_PLUGINS } = await import('./builtins.js');
|
|
52
|
+
let found;
|
|
53
|
+
for (const [name, builtin] of Object.entries(BUILTIN_PLUGINS)) {
|
|
54
|
+
const platform = builtin.platform();
|
|
55
|
+
/** `read` is the question. Its answer is thrown away - the manifest is read again for real once
|
|
56
|
+
* the platform is registered, and reading it twice is cheaper than keeping a half-built
|
|
57
|
+
* package around to decide whether it should exist. */
|
|
58
|
+
if (platform.manifestProvider.read(resolved)) {
|
|
59
|
+
found = { name, because: platform.manifestProvider.fileName };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
cache.set(resolved, found);
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
/** Forgets what was detected - for a spec that writes a fixture, detects, then writes another. */
|
|
67
|
+
export function clearDetectionCache() {
|
|
68
|
+
cache.clear();
|
|
69
|
+
}
|
|
70
|
+
const cache = new Map();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type cleanCommand from '../commands/clean.command.js';
|
|
2
|
+
import type { CleanExtraKeys } from '../commands/clean.command.js';
|
|
3
|
+
import type { NodeConfigKeys, RmanNodeConfig } from '../node-config.interface.js';
|
|
4
|
+
import type { CiService } from '../services/ci.service.js';
|
|
5
|
+
/**
|
|
6
|
+
* **Every type this plugin adds to rman, in one `declare module` block.**
|
|
7
|
+
*
|
|
8
|
+
* **One block was once forced and is now only convenient.** While this shipped as its own package
|
|
9
|
+
* it augmented the *package name* `'rman'`, and a second such block anywhere silently disabled the
|
|
10
|
+
* first - measured, `SystemInfo.PackageManager` went unresolved at four call sites with nothing
|
|
11
|
+
* pointing at the cause. Bundled into rman, these augment a **module path** like every built-in
|
|
12
|
+
* command's own contribution does, and that limit is gone; they stay together because the plugin's
|
|
13
|
+
* types read better in one place, not because they have to.
|
|
14
|
+
*
|
|
15
|
+
* Type-only. The runtime half of each augmentation lives with its own subject
|
|
16
|
+
* (`augmentSystemInfo()`, `augmentManifest()`, ...); this file is imported by the plugin's entry
|
|
17
|
+
* point purely so a consumer's compiler loads it.
|
|
18
|
+
*/
|
|
19
|
+
declare module '../../../interfaces/rman-config.interface.js' {
|
|
20
|
+
/**
|
|
21
|
+
* The `.rmanrc` keys that only mean something in a Node repository - merged into the core's own
|
|
22
|
+
* key list, so `pkg.config.clean` is typed wherever it is read (`CleanService` included) without
|
|
23
|
+
* a cast, and without the core declaring a key it knows nothing about.
|
|
24
|
+
*
|
|
25
|
+
* The augmentation is evaluated where it is used, so `clean` is typed at the place it is read.
|
|
26
|
+
*/
|
|
27
|
+
interface RmanConfigKeys extends NodeConfigKeys {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* **`clean.*`, contributed by the command that reads it**, the way every built-in contributes its
|
|
31
|
+
* own key - `skip` derived from the command's `config` block, `include`/`exclude` hand-written in
|
|
32
|
+
* `CleanExtraKeys` because a `CommandOption` cannot say "a glob *or* a list of them".
|
|
33
|
+
*
|
|
34
|
+
* It is declared here rather than beside the command only because of the one-block rule above.
|
|
35
|
+
* rman's own commands put theirs next to themselves, augmenting a module path instead of a
|
|
36
|
+
* package name, which has no such limit.
|
|
37
|
+
*/
|
|
38
|
+
namespace RmanConfig {
|
|
39
|
+
interface CommandConfigs extends RmanConfig.CommandContribution<ReturnType<typeof cleanCommand>, CleanExtraKeys> {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* **The `npm` target's own `publish.*` block** - declared into the slot `publish` exports, exactly
|
|
45
|
+
* where the core's own `docker` target declares `publish.docker`.
|
|
46
|
+
*
|
|
47
|
+
* A *different* module from the block above, and that is the shape the fold made possible: while
|
|
48
|
+
* this shipped as `rman-node` everything had to augment the one package name `'rman'`, so a slot
|
|
49
|
+
* declared in `publish.command.ts` was reachable only by re-declaring it - which now reads as two
|
|
50
|
+
* interfaces of one name and is refused. Each interface is augmented where it lives.
|
|
51
|
+
*/
|
|
52
|
+
declare module '../../../commands/publish.command.js' {
|
|
53
|
+
/**
|
|
54
|
+
* The `npm` publish target's own `publish.*` block, declared the way the core's `docker` target
|
|
55
|
+
* declares `publish.docker` - through the slot `publish` contributes, rather than into a central
|
|
56
|
+
* `PublishOptionsKeys` that the core owned.
|
|
57
|
+
*
|
|
58
|
+
* That is the config half of a target being a contribution: this package brings the flags, the
|
|
59
|
+
* registry check, *and* the keys, and none of it is written down in rman.
|
|
60
|
+
*/
|
|
61
|
+
interface PublishTargetConfigs {
|
|
62
|
+
/** The `npm` target's block, named after the target exactly as the core's `docker` one is. */
|
|
63
|
+
npm?: RmanNodeConfig.NpmPublishOptions;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* **The npm half of `SystemInfo`** - what `rman info` reports once a repository is a Node one. The
|
|
68
|
+
* runtime half is `augmentSystemInfo()` in the file beside this one, called when the built-in is
|
|
69
|
+
* registered rather than at import, so a repository that never named it reports no npm.
|
|
70
|
+
*/
|
|
71
|
+
declare module '../../../services/system-info.js' {
|
|
72
|
+
namespace SystemInfo {
|
|
73
|
+
type PackageManager = CiService.PackageManager;
|
|
74
|
+
interface Options {
|
|
75
|
+
/**
|
|
76
|
+
* Report this package manager's version under `Binaries`, plus the `npmPackages` sections.
|
|
77
|
+
*
|
|
78
|
+
* Defaults to `.rmanrc "packageManager"` (read off `Options.repository`), then to `npm` -
|
|
79
|
+
* this package being installed is itself the statement that the repository is a Node one.
|
|
80
|
+
*/
|
|
81
|
+
packageManager?: PackageManager;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the npm half of `SystemInfo` to rman's own types.
|
|
3
|
+
*
|
|
4
|
+
* The core's `Options` carries nothing language-specific on purpose, so `packageManager` is not a
|
|
5
|
+
* setting it hid behind a flag - it is a setting it does not have. Declaration merging is what lets
|
|
6
|
+
* this package add it as if it had always been there, `SystemInfo.PackageManager` included, without
|
|
7
|
+
* the core naming npm anywhere.
|
|
8
|
+
*
|
|
9
|
+
* The union itself is `CiService.PackageManager`, not a second copy: `ci` and `publish` already
|
|
10
|
+
* shell out to one of these, and two lists of the same four names drift the moment a fifth appears.
|
|
11
|
+
*/
|
|
12
|
+
/** The import above is aliased precisely so this block can use the real name: declaration merging
|
|
13
|
+
* keys off the module specifier, not off any local binding, so `namespace SystemInfo` here *is*
|
|
14
|
+
* rman's - and nothing shadows anything. */
|
|
15
|
+
/** The `SystemInfo` types this file's wrapper relies on are declared in
|
|
16
|
+
* [`rman.augmentation.ts`](rman.augmentation.ts), with the rest of this plugin's - one
|
|
17
|
+
* `declare module` block per package, or the others stop applying. */
|
|
18
|
+
/**
|
|
19
|
+
* Turns the npm half on at runtime.
|
|
20
|
+
*
|
|
21
|
+
* Applied by wrapping rather than by a flag on the core service: "is this a Node repository" is not
|
|
22
|
+
* a question rman's core should be able to ask, and a `nodejs: true` option there would be exactly
|
|
23
|
+
* that question wearing a different hat. The wrapper translates `packageManager` into the `envinfo`
|
|
24
|
+
* categories the core knows nothing about.
|
|
25
|
+
*/
|
|
26
|
+
export declare function augmentSystemInfo(): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SystemInfo as OrgSystemInfo } from '../../../services/system-info.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the npm half of `SystemInfo` to rman's own types.
|
|
4
|
+
*
|
|
5
|
+
* The core's `Options` carries nothing language-specific on purpose, so `packageManager` is not a
|
|
6
|
+
* setting it hid behind a flag - it is a setting it does not have. Declaration merging is what lets
|
|
7
|
+
* this package add it as if it had always been there, `SystemInfo.PackageManager` included, without
|
|
8
|
+
* the core naming npm anywhere.
|
|
9
|
+
*
|
|
10
|
+
* The union itself is `CiService.PackageManager`, not a second copy: `ci` and `publish` already
|
|
11
|
+
* shell out to one of these, and two lists of the same four names drift the moment a fifth appears.
|
|
12
|
+
*/
|
|
13
|
+
/** The import above is aliased precisely so this block can use the real name: declaration merging
|
|
14
|
+
* keys off the module specifier, not off any local binding, so `namespace SystemInfo` here *is*
|
|
15
|
+
* rman's - and nothing shadows anything. */
|
|
16
|
+
/** The `SystemInfo` types this file's wrapper relies on are declared in
|
|
17
|
+
* [`rman.augmentation.ts`](rman.augmentation.ts), with the rest of this plugin's - one
|
|
18
|
+
* `declare module` block per package, or the others stop applying. */
|
|
19
|
+
/**
|
|
20
|
+
* Turns the npm half on at runtime.
|
|
21
|
+
*
|
|
22
|
+
* Applied by wrapping rather than by a flag on the core service: "is this a Node repository" is not
|
|
23
|
+
* a question rman's core should be able to ask, and a `nodejs: true` option there would be exactly
|
|
24
|
+
* that question wearing a different hat. The wrapper translates `packageManager` into the `envinfo`
|
|
25
|
+
* categories the core knows nothing about.
|
|
26
|
+
*/
|
|
27
|
+
export function augmentSystemInfo() {
|
|
28
|
+
const base = OrgSystemInfo.getSystemInfo;
|
|
29
|
+
/**
|
|
30
|
+
* Idempotent, and marked on **the function** rather than in a module-level flag: the flag made
|
|
31
|
+
* re-application impossible once anything had replaced `getSystemInfo` - a test substituting a
|
|
32
|
+
* stub, or a second augmentation layering on - so whether the wrapper was actually installed came
|
|
33
|
+
* down to import order. Asking the current implementation whether it is already wrapped cannot go
|
|
34
|
+
* stale that way.
|
|
35
|
+
*/
|
|
36
|
+
if (base[AUGMENTED])
|
|
37
|
+
return;
|
|
38
|
+
/** A namespace's exported function is a property of a plain object at runtime, so this is an
|
|
39
|
+
* ordinary assignment - the ESM *binding* is immutable, the object it points at is not. */
|
|
40
|
+
const wrapped = options => {
|
|
41
|
+
/** An explicit argument first, then the repository's own config, then npm. The config step is
|
|
42
|
+
* why the core declares `Options.repository`: `rman info` passes only that, so without it a
|
|
43
|
+
* pnpm repository would silently be reported as an npm one. */
|
|
44
|
+
const packageManager = options?.packageManager ?? asPackageManager(options?.repository?.config?.packageManager) ?? 'npm';
|
|
45
|
+
return base({
|
|
46
|
+
...options,
|
|
47
|
+
envinfo: {
|
|
48
|
+
/** Replaces the core's `Binaries: ['Node']` - `envinfo` categories merge by key, not by
|
|
49
|
+
* concatenation, so the whole list has to be restated. */
|
|
50
|
+
Binaries: ['Node', PACKAGE_MANAGER_BINARY[packageManager]],
|
|
51
|
+
/** This package's own version alongside rman's - when `info` is being read to work out why
|
|
52
|
+
* a command behaved oddly, which plugin version is installed is half the answer. */
|
|
53
|
+
npmPackages: ['rman', 'typescript'],
|
|
54
|
+
npmGlobalPackages: ['typescript'],
|
|
55
|
+
...options?.envinfo,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
wrapped[AUGMENTED] = true;
|
|
60
|
+
OrgSystemInfo.getSystemInfo = wrapped;
|
|
61
|
+
}
|
|
62
|
+
/** `.rmanrc "packageManager"` value -> the `Binaries` key `envinfo` recognizes for it (`npm`/
|
|
63
|
+
* `pnpm`/`bun` are lowercase, `Yarn` isn't - envinfo's own naming, not ours). */
|
|
64
|
+
const PACKAGE_MANAGER_BINARY = {
|
|
65
|
+
npm: 'npm',
|
|
66
|
+
yarn: 'Yarn',
|
|
67
|
+
pnpm: 'pnpm',
|
|
68
|
+
bun: 'bun',
|
|
69
|
+
};
|
|
70
|
+
/** A config value is whatever the file said, so an unrecognized one is ignored rather than used to
|
|
71
|
+
* index `PACKAGE_MANAGER_BINARY` and produce `Binaries: ['Node', undefined]`. */
|
|
72
|
+
function asPackageManager(value) {
|
|
73
|
+
return typeof value === 'string' && value in PACKAGE_MANAGER_BINARY
|
|
74
|
+
? value
|
|
75
|
+
: undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Marks an implementation as already wrapped. A symbol rather than a property name so it cannot
|
|
78
|
+
* collide with anything `envinfo` or a future core option carries. */
|
|
79
|
+
const AUGMENTED = Symbol.for('rman.node.systemInfo.augmented');
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ArgsOf } from '../../../index.js';
|
|
2
|
+
/** Hoisted out of the metadata literal so the handler can be annotated against them - see
|
|
3
|
+
* `RmanConfig.ArgsOf` for why an inferred `argv` and the metadata's own typo checking cannot both
|
|
4
|
+
* work in one signature. */
|
|
5
|
+
declare const COMMAND: "ci";
|
|
6
|
+
declare const config: {
|
|
7
|
+
packageManager: {
|
|
8
|
+
target: "cli";
|
|
9
|
+
cliName: string;
|
|
10
|
+
describe: string;
|
|
11
|
+
choices: readonly ["npm", "yarn", "pnpm", "bun"];
|
|
12
|
+
};
|
|
13
|
+
progress: {
|
|
14
|
+
target: "cli";
|
|
15
|
+
describe: string;
|
|
16
|
+
type: "boolean";
|
|
17
|
+
};
|
|
18
|
+
allowBranch: {
|
|
19
|
+
target: "cli";
|
|
20
|
+
cliName: string;
|
|
21
|
+
describe: string;
|
|
22
|
+
type: "string";
|
|
23
|
+
};
|
|
24
|
+
ignoreBranch: {
|
|
25
|
+
target: "cli";
|
|
26
|
+
cliName: string;
|
|
27
|
+
describe: string;
|
|
28
|
+
type: "string";
|
|
29
|
+
};
|
|
30
|
+
scope: {
|
|
31
|
+
target: "cli";
|
|
32
|
+
describe: string;
|
|
33
|
+
type: "string";
|
|
34
|
+
};
|
|
35
|
+
ignore: {
|
|
36
|
+
target: "cli";
|
|
37
|
+
describe: string;
|
|
38
|
+
type: "string";
|
|
39
|
+
};
|
|
40
|
+
platform: {
|
|
41
|
+
target: "cli";
|
|
42
|
+
describe: string;
|
|
43
|
+
type: "string";
|
|
44
|
+
};
|
|
45
|
+
deps: {
|
|
46
|
+
target: "cli";
|
|
47
|
+
describe: string;
|
|
48
|
+
type: "boolean";
|
|
49
|
+
};
|
|
50
|
+
dependents: {
|
|
51
|
+
target: "cli";
|
|
52
|
+
describe: string;
|
|
53
|
+
type: "boolean";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
type Args = ArgsOf<typeof config, typeof COMMAND>;
|
|
57
|
+
/**
|
|
58
|
+
* `rman ci` - contributed by the `node` built-in rather than always present.
|
|
59
|
+
*
|
|
60
|
+
* **Declared, not built.** This was a `CustomCommand` with a hand-written `builder` chaining
|
|
61
|
+
* `applyBranchGuardOptions(applyPackageFilterOptions(cmd))`, which is the shape every built-in had
|
|
62
|
+
* before the options became data. A plugin's command uses the identical declaration now -
|
|
63
|
+
* `declareCommand` rather than `registerCommand`, which is the one difference and the reason for
|
|
64
|
+
* it: `registerCommand` pushes onto a module-level registry that `runCli` always walks, so a plugin
|
|
65
|
+
* using it would hand `ci` to repositories that are not Node ones.
|
|
66
|
+
*
|
|
67
|
+
* The repository arrives through `app` when the factory runs (in `cli.ts`, after `Repository.create`
|
|
68
|
+
* has attached one), rather than through a `CommandContext` per invocation.
|
|
69
|
+
*/
|
|
70
|
+
declare const ciCommand: (app: import("../../../index.js").RmanApplication) => {
|
|
71
|
+
command: "ci";
|
|
72
|
+
describe: string;
|
|
73
|
+
/** Read, not owned: `packageManager` is a root-level key this package declares in
|
|
74
|
+
* `NodeConfigKeys`, and the `npm` publish target reads it too. */
|
|
75
|
+
configKeys: string[];
|
|
76
|
+
config: {
|
|
77
|
+
packageManager: {
|
|
78
|
+
target: "cli";
|
|
79
|
+
cliName: string;
|
|
80
|
+
describe: string;
|
|
81
|
+
choices: readonly ["npm", "yarn", "pnpm", "bun"];
|
|
82
|
+
};
|
|
83
|
+
progress: {
|
|
84
|
+
target: "cli";
|
|
85
|
+
describe: string;
|
|
86
|
+
type: "boolean";
|
|
87
|
+
};
|
|
88
|
+
allowBranch: {
|
|
89
|
+
target: "cli";
|
|
90
|
+
cliName: string;
|
|
91
|
+
describe: string;
|
|
92
|
+
type: "string";
|
|
93
|
+
};
|
|
94
|
+
ignoreBranch: {
|
|
95
|
+
target: "cli";
|
|
96
|
+
cliName: string;
|
|
97
|
+
describe: string;
|
|
98
|
+
type: "string";
|
|
99
|
+
};
|
|
100
|
+
scope: {
|
|
101
|
+
target: "cli";
|
|
102
|
+
describe: string;
|
|
103
|
+
type: "string";
|
|
104
|
+
};
|
|
105
|
+
ignore: {
|
|
106
|
+
target: "cli";
|
|
107
|
+
describe: string;
|
|
108
|
+
type: "string";
|
|
109
|
+
};
|
|
110
|
+
platform: {
|
|
111
|
+
target: "cli";
|
|
112
|
+
describe: string;
|
|
113
|
+
type: "string";
|
|
114
|
+
};
|
|
115
|
+
deps: {
|
|
116
|
+
target: "cli";
|
|
117
|
+
describe: string;
|
|
118
|
+
type: "boolean";
|
|
119
|
+
};
|
|
120
|
+
dependents: {
|
|
121
|
+
target: "cli";
|
|
122
|
+
describe: string;
|
|
123
|
+
type: "boolean";
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
examples: {
|
|
127
|
+
command: string;
|
|
128
|
+
}[];
|
|
129
|
+
handler: (args: Args) => Promise<void>;
|
|
130
|
+
};
|
|
131
|
+
export default ciCommand;
|