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,98 +1,272 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as yargs from 'yargs';
|
|
2
|
+
import type { RmanApplication } from '../core/application.js';
|
|
3
|
+
import type { ConfigValue } from '../core/config.js';
|
|
4
|
+
import type { CustomCommand } from '../core/custom-command.js';
|
|
5
|
+
import type { Plugin } from '../core/plugin.js';
|
|
6
|
+
import type { PublishTarget } from '../core/publish-target.js';
|
|
2
7
|
import type { RunConditionFn, RunStepValue } from '../core/run-step.js';
|
|
3
|
-
/**
|
|
4
|
-
* Adds a `+key` alongside every key of `T`, which **appends** to whatever that key already resolved
|
|
5
|
-
* to instead of replacing it - see `mergeConfig`.
|
|
6
|
-
*
|
|
7
|
-
* Generated by key remapping rather than written out, so a key added to the interface gets its
|
|
8
|
-
* append form automatically and the two can never drift apart.
|
|
9
|
-
*/
|
|
10
|
-
export type WithAppend<T> = {
|
|
11
|
-
[K in keyof T as `+${K & string}`]?: T[K];
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* The one key every nested config node may carry: `vars` scoping that node's subtree - a fresh copy
|
|
15
|
-
* per level, merged per key over the level above. See `withScopedVars` in `core/config.ts` for what
|
|
16
|
-
* it does at resolution time, and docs/rman.md#scoped-vars for how it reads.
|
|
17
|
-
*
|
|
18
|
-
* **Every nested options interface extends this**, and a new one has to remember to - which is the
|
|
19
|
-
* cost of the runtime rule being general (any object node scopes) while a type can only say it one
|
|
20
|
-
* interface at a time. TypeScript has no way to state "and every object below this may also carry
|
|
21
|
-
* `vars`" without a recursive remap that would wreck the error messages.
|
|
22
|
-
*
|
|
23
|
-
* Extended by the `XOptions` interface rather than declared on `XOptionsKeys`, so `WithAppend` does
|
|
24
|
-
* not generate a `+vars`: appending to `vars` means nothing, since objects merge either way.
|
|
25
|
-
*/
|
|
26
|
-
export interface ScopedVars {
|
|
27
|
-
/** Values for `${{ vars.* }}` to read, for this node and everything under it. */
|
|
28
|
-
vars?: Record<string, unknown>;
|
|
29
|
-
}
|
|
30
8
|
/**
|
|
31
9
|
* The shape of `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` (and `package.json`'s own
|
|
32
10
|
* `"rman"` key) - see docs/rman.md#configuration-rmanrc-rmanrcyml for the full reference. Every
|
|
33
11
|
* field is optional and cascades from the repository root down to each package's own directory.
|
|
34
|
-
* Purely a typing aid
|
|
35
|
-
*
|
|
36
|
-
*
|
|
12
|
+
* Purely a typing aid - never read by rman itself, which only ever sees the plain JS object a
|
|
13
|
+
* config file exports.
|
|
14
|
+
*
|
|
15
|
+
* **Two halves, and the split is who declares a key.** `RmanConfigKeys` holds the ones no command
|
|
16
|
+
* owns; `CommandConfigs` is what every command contributes its own into, from beside the command
|
|
17
|
+
* (see `CommandContribution`). This used to be two files and two types both called `RmanConfig` -
|
|
18
|
+
* one for the config shape, one for the command declarations - which is why the package could not
|
|
19
|
+
* export the second at all.
|
|
20
|
+
*
|
|
21
|
+
* **This is the type an *author* writes**, and `ResolvedConfig` - derived from it - is what a
|
|
22
|
+
* *reader* gets. One type could not be both: a value may be written as a **function** here, because
|
|
23
|
+
* rman calls it while the config resolves, and by the time anything reads `pkg.config` it has
|
|
24
|
+
* already been called. Answering only the reader's question is what this type used to do, and it
|
|
25
|
+
* made `docs/rman.md`'s own `changelog: { filePath: ({ vars }) => ... }` a compile error.
|
|
26
|
+
*
|
|
27
|
+
* **The direction matters, and it was measured the wrong way round first.** Widening this type
|
|
28
|
+
* while `Package.config` still used it moved the cast to every **read** - six sites broke
|
|
29
|
+
* (`version.command.ts`, `publish-target.ts`, `changelog.service.ts`, three in
|
|
30
|
+
* `github-release.service.ts`). The widening was right; leaving the reader on the same name was not.
|
|
31
|
+
* So the author's view is the declared one and the reader's is computed: see `Resolved`, which also
|
|
32
|
+
* records the two guards that make a mechanical transform safe here.
|
|
33
|
+
*
|
|
34
|
+
* **Deriving the other direction is what cannot work, and the reason is this name.** `RmanConfig` is
|
|
35
|
+
* also a **namespace** (`RmanConfig.CommandConfigs`, `CommandContribution`, …) that `rman-node`
|
|
36
|
+
* augments; a `type` alias cannot merge with a namespace, and the resulting circular reference
|
|
37
|
+
* resolves to `{}` **silently** - measured, every key then read as "does not exist", `vars` and
|
|
38
|
+
* `run` included. A derived *reader* view needs no such merge, which is why it is the half that is
|
|
39
|
+
* derived.
|
|
40
|
+
*
|
|
41
|
+
* Which keys may be written as a function is not a judgement made key by key: every option a
|
|
42
|
+
* command declares is one, because an option's value is by construction a value rather than a step
|
|
43
|
+
* (`CommandConfigFromMetadata`). Only the hand-written `Extra` keys are decided individually, since
|
|
44
|
+
* that is where a step key lives.
|
|
37
45
|
*/
|
|
38
|
-
export interface RmanConfig extends RmanConfigKeys,
|
|
46
|
+
export interface RmanConfig extends RmanConfigKeys, RmanConfig.CommandConfigs {
|
|
39
47
|
/**
|
|
40
48
|
* Configs to inherit from, merged **underneath** this one - a shared package
|
|
41
49
|
* (`"@panates/rman-monorepo"`), a relative path, or an array applied in declaration order.
|
|
42
50
|
*
|
|
43
|
-
* A bare name resolves through *this* file's own `node_modules`, so a subpath works too
|
|
44
|
-
*
|
|
45
|
-
*
|
|
51
|
+
* A bare name resolves through *this* file's own `node_modules`, so a subpath works too. The
|
|
52
|
+
* target may be YAML, JSON, or a module exporting a config via `defineConfig`, and may itself
|
|
53
|
+
* `extends` another.
|
|
46
54
|
*
|
|
47
55
|
* Top level only: a `"[selector]"` block naming one is an error rather than a no-op, since
|
|
48
56
|
* inheritance is a statement about this config and not about the packages a selector names.
|
|
57
|
+
*
|
|
58
|
+
* Resolved away long before a reader sees one, and kept on this type because `rman config` prints
|
|
59
|
+
* the object it was resolved from.
|
|
49
60
|
*/
|
|
50
61
|
extends?: string | string[];
|
|
51
62
|
}
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
63
|
+
/**
|
|
64
|
+
* One entry of `.rmanrc "commands"` - a command, or a **glob** naming `.js` modules that
|
|
65
|
+
* `export default` one.
|
|
66
|
+
*
|
|
67
|
+
* Both authoring forms count as "a command": the declarative `app => ({ ... })` factory, which is
|
|
68
|
+
* the one to write, and the older `defineCommand({ ... })` object a `.rman/*.mjs` uses.
|
|
69
|
+
*/
|
|
70
|
+
export type CommandEntry = string | CustomCommand | RmanConfig.CommandRegisterFunction;
|
|
71
|
+
/** One entry of `.rmanrc "publishTargets"` - a target, or a **glob** naming `.js` modules that
|
|
72
|
+
* `export default` one. */
|
|
73
|
+
export type PublishTargetEntry = string | PublishTarget;
|
|
74
|
+
/**
|
|
75
|
+
* **The `.rmanrc` keys no command owns**, without `extends` - kept
|
|
76
|
+
* separate from `RmanConfig` only so the two halves stay legible. A key a *command* owns is
|
|
77
|
+
* declared beside that command and arrives through `RmanConfig.CommandConfigs` instead.
|
|
78
|
+
*
|
|
79
|
+
* **A plugin adds its own keys here, by declaration merging** - `rman-node` contributes `clean`
|
|
80
|
+
* from its own `interfaces/rman-config.interface.ts`, so `pkg.config.clean` stays typed wherever it
|
|
81
|
+
* is read without the core having to know npm has a `node_modules` or that TypeScript has build
|
|
82
|
+
* output. (A *target's* block is different again: `publish.npm.*` goes through the
|
|
83
|
+
* `PublishTargetConfigs` slot, beside the core's own `publish.docker.*`.)
|
|
84
|
+
*
|
|
85
|
+
* A config author annotates with the plugin's own name for the union - `RmanNodeConfig` - which is
|
|
86
|
+
* what makes the import that carries the augmentation explicit rather than incidental.
|
|
87
|
+
*/
|
|
54
88
|
export interface RmanConfigKeys {
|
|
55
89
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
90
|
+
* The technologies this repository holds, in declaration order - how its packages are
|
|
91
|
+
* recognized, where they are, how their versions are planned. One per ecosystem: `rman-node`
|
|
92
|
+
* contributes the `node` one, a Cargo plugin would contribute `cargo`, and a polyglot repository
|
|
93
|
+
* names both.
|
|
94
|
+
*
|
|
95
|
+
* Each entry is **a plugin, or a glob naming `.js` modules that `export default` one** - the same
|
|
96
|
+
* two forms `commands` and `publishTargets` take:
|
|
97
|
+
*
|
|
98
|
+
* ```js
|
|
99
|
+
* // .rmanrc.mjs - the instance, which a JS config can hand over and a YAML one cannot
|
|
100
|
+
* import { defineConfig, definePlugin } from 'rman';
|
|
101
|
+
* export default defineConfig({ plugins: [definePlugin({ name: 'cargo', manifestProvider })] });
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* A *published* plugin is not usually named here at all: its package exports a config carrying
|
|
105
|
+
* it, so the repository writes `extends` and the plugin arrives with everything else the package
|
|
106
|
+
* declares. `rman-node` deliberately exports no `NodePlugin` for this reason - there is nothing a
|
|
107
|
+
* consumer needs to do with it by hand.
|
|
108
|
+
*
|
|
109
|
+
* ```yaml
|
|
110
|
+
* # .rmanrc.yml - a glob, anchored to this file's own directory
|
|
111
|
+
* plugins: './plugins/*.js'
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* **A package name is not one of the forms, and `plugins: ['rman-node']` is never valid.** That
|
|
115
|
+
* package's entry point exports an rman *config* - `{ plugins, commands, publishTargets }` - and
|
|
116
|
+
* a config's way into a repository is `extends`, the key that means "merge this underneath
|
|
117
|
+
* mine". The two are different statements: `extends: 'rman-node'` inherits everything the
|
|
118
|
+
* package declares, while `plugins` names the technologies themselves. Naming the package here
|
|
119
|
+
* is refused with a message saying so, rather than half-read.
|
|
120
|
+
*
|
|
121
|
+
* A plugin that cannot be loaded is an error, not a skip - silently losing a manifest reader
|
|
122
|
+
* means every package reads as nameless at `0.0.0`, which looks like a working repository.
|
|
123
|
+
*
|
|
124
|
+
* **Root level only, and unlike `commands` this is forced rather than chosen**: `loadPlugins`
|
|
125
|
+
* runs inside `Repository.create` *before* the packages are known, because a plugin's
|
|
126
|
+
* `getWorkspace` is what finds them - so there are no package configs to read. An entry in a
|
|
127
|
+
* package's own `.rmanrc` is never seen.
|
|
64
128
|
*/
|
|
129
|
+
plugins?: string | Plugin | (string | Plugin)[];
|
|
65
130
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
131
|
+
* **Which technology a package belongs to**, by `Platform.name`.
|
|
132
|
+
*
|
|
133
|
+
* ```yaml
|
|
134
|
+
* plugins: ['node']
|
|
135
|
+
* "[/]":
|
|
136
|
+
* platform: node
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* **`plugins` loads; this selects.** They were one key for a while and that conflated two
|
|
140
|
+
* statements: `plugins` says which technologies this repository has available, which is a fact
|
|
141
|
+
* about the *repository* and is read once at its root. Which technology a given directory's
|
|
142
|
+
* package belongs to is a fact about the *package*, and a repository may hold more than one - so
|
|
143
|
+
* it is an ordinary cascading key, declared wherever the answer changes.
|
|
144
|
+
*
|
|
145
|
+
* - **A declaration wins over the guess.** Without it, a directory belongs to the first
|
|
146
|
+
* registered platform whose manifest provider recognizes it (`app.platformFor`) - which is a
|
|
147
|
+
* reasonable default and is registration order deciding a question about someone's code. Naming
|
|
148
|
+
* it takes the answer away from load order.
|
|
149
|
+
* - **The named platform validates it, and a mismatch is an error.** If it does not recognize the
|
|
150
|
+
* directory the declaration is simply untrue, and the alternative is a package reading as
|
|
151
|
+
* nameless at `0.0.0` - which looks like a working repository. The message names the file the
|
|
152
|
+
* platform looked for.
|
|
153
|
+
* - **It cascades like any unmarked key**, so one line at the root covers a single-technology
|
|
154
|
+
* repository and a package's own `.rmanrc` overrides it for that subtree. `"[/]"` keeps it on
|
|
155
|
+
* the root package alone.
|
|
156
|
+
* - **At the root it also means the repository said something**, so detection does not run - the
|
|
157
|
+
* same rule `plugins: []` follows. A repository that states its technology is not a repository
|
|
158
|
+
* that stated nothing.
|
|
159
|
+
*
|
|
160
|
+
* **At the root, naming a built-in is the same statement as `plugins: ['node']`** - it is put at
|
|
161
|
+
* the *front* of `plugins`, so the whole built-in arrives: the technology, its commands and its
|
|
162
|
+
* publish targets. Saying which technology this repository is *is* saying it has it, and making
|
|
163
|
+
* an author write both was a distinction only rman could see. At the front rather than appended,
|
|
164
|
+
* because `platformFor` takes the first registered platform that recognizes a directory - the one
|
|
165
|
+
* the repository says it *is* should win over whatever a shared config brought along. Read from
|
|
166
|
+
* the unmarked key or from `"[/]"`, since both are the root speaking.
|
|
68
167
|
*
|
|
69
|
-
*
|
|
168
|
+
* **Below the root it loads the technology alone**, and that is structural rather than chosen:
|
|
169
|
+
* `plugins` is read once, at the root, before any package exists, so a nested declaration cannot
|
|
170
|
+
* contribute commands even in principle. It is also what you want there - a Node package inside a
|
|
171
|
+
* Cargo repository wants npm's manifest read, not a repo-wide `rman clean` sweeping the tree.
|
|
172
|
+
*
|
|
173
|
+
* Only a built-in is promoted. A platform rman does not ship is loaded by the `plugins` entry
|
|
174
|
+
* that brings it, and naming one with no such entry is an error listing what the repository has -
|
|
175
|
+
* putting the bare name into `plugins` would hand the loader a glob matching no file, so the
|
|
176
|
+
* failure would read as the plugin being missing while it is registered perfectly well.
|
|
177
|
+
*
|
|
178
|
+
* **A plain string, never a `${{ }}` expression.** This is read while the packages are still
|
|
179
|
+
* being found, so there is no `pkg` for an expression to be about - it is what decides what a
|
|
180
|
+
* package *is*. An expression here is refused rather than passed through as a literal, which is
|
|
181
|
+
* what silently happened until it was.
|
|
182
|
+
*/
|
|
183
|
+
platform?: string;
|
|
184
|
+
/**
|
|
185
|
+
* **The selector this package answers to** - what a `"[glob]"` block and `--scope`/`--ignore`
|
|
186
|
+
* match against, overriding whatever its platform said.
|
|
70
187
|
*
|
|
71
188
|
* ```yaml
|
|
72
|
-
* #
|
|
73
|
-
*
|
|
189
|
+
* # packages/web/.rmanrc
|
|
190
|
+
* name: web
|
|
74
191
|
* ```
|
|
75
192
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
193
|
+
* **The escape hatch for an ecosystem that cannot name a package, and only that.** npm can:
|
|
194
|
+
* `package.json#name` exists and identifies the package, so a Node repository never writes this.
|
|
195
|
+
* An ecosystem where a package need not be named, or where the name is an import path nobody
|
|
196
|
+
* would type after `--scope`, leaves `ManifestProvider.selector` unanswered and the repository
|
|
197
|
+
* fills it in here.
|
|
198
|
+
*
|
|
199
|
+
* **It does not rename the package.** `pkg.name` is still what the manifest says, so tags,
|
|
200
|
+
* changelogs and the registry are untouched - this is an address, and `Package.selector` is where
|
|
201
|
+
* it lands.
|
|
202
|
+
*
|
|
203
|
+
* **Unique within the repository, and checked.** Two packages answering to one selector make
|
|
204
|
+
* `"[that]"` and `--scope that` ambiguous, and the ambiguity is silent: the config reaches both
|
|
205
|
+
* and `getPackage` returns whichever came first. The error names both directories.
|
|
206
|
+
*
|
|
207
|
+
* **Cascades like any unmarked key, which for this one is almost always a mistake** - one
|
|
208
|
+
* declaration above two packages gives them the same address, so the uniqueness check fires and
|
|
209
|
+
* says so. Declare it in the package's own `.rmanrc`.
|
|
210
|
+
*
|
|
211
|
+
* **Not inside a `"[glob]"` block**, which is refused rather than ignored: the glob matches the
|
|
212
|
+
* selector, so a block that sets it would need its own answer to find itself. Read from the
|
|
213
|
+
* unmarked cascade for the same reason `platform` is - before the package exists.
|
|
214
|
+
*/
|
|
215
|
+
name?: string;
|
|
216
|
+
/**
|
|
217
|
+
* Where this repository keeps command modules of its own - a glob, or a list of them.
|
|
218
|
+
*
|
|
219
|
+
* ```yaml
|
|
220
|
+
* commands: ['tools/commands/*.mjs']
|
|
80
221
|
* ```
|
|
81
222
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
223
|
+
* **Where a command comes from, whoever ships it.** A repository writing one of its own, and a
|
|
224
|
+
* package contributing three, reach yargs by this one key - so there is one source of
|
|
225
|
+
* non-built-in commands and one precedence slot. `.rman/*.{js,mjs,cjs}` is simply this key's
|
|
226
|
+
* default value rather than a second mechanism beside it, and a package needs no plugin to
|
|
227
|
+
* contribute a command: a plugin is a *technology*, and a command is not one.
|
|
228
|
+
*
|
|
229
|
+
* **A relative glob is anchored to the file that declared it**, not to the repository root (see
|
|
230
|
+
* `anchorContributions`), so a shared config can ship commands with `commands: './commands/*.js'`
|
|
231
|
+
* and have it mean its own directory.
|
|
88
232
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
233
|
+
* **Always appends** (`ALWAYS_APPEND`), like the other two contribution keys and for the same
|
|
234
|
+
* reason: naming a directory of your own never means "and stop loading the ones my shared config
|
|
235
|
+
* ships". It follows that a closer layer cannot *un*-say one.
|
|
92
236
|
*
|
|
93
|
-
*
|
|
237
|
+
* **Trap: a glob replaces the `.rman/` default and an instance does not.** The default applies
|
|
238
|
+
* only when *no* glob was declared anywhere - the key appends onto other layers, not onto a
|
|
239
|
+
* built-in fallback - so a shared config shipping its commands by glob silently takes the
|
|
240
|
+
* `.rman/` directory away from every repository inheriting it. Measured both ways on the same
|
|
241
|
+
* pair of repositories: with the shared config declaring instances, the consumer's own
|
|
242
|
+
* `.rman/hello.mjs` is in `rman --help`; with it declaring `'./commands/*.js'`, that command is
|
|
243
|
+
* simply gone. **A config meant to be inherited should therefore list its commands
|
|
244
|
+
* individually.**
|
|
245
|
+
*
|
|
246
|
+
* Declared at any level, unlike `plugins` and `publishTargets`, because by the time this is read
|
|
247
|
+
* the packages exist - so a package's own `.rmanrc` may contribute one. The commands themselves
|
|
248
|
+
* are still repository-wide, because there is one command list; a package declaring one is
|
|
249
|
+
* contributing it to the repository.
|
|
250
|
+
*
|
|
251
|
+
* A module exports either form: the declarative `app => ({ ... })` factory, which is the one to
|
|
252
|
+
* write, or the `defineCommand({ ... })` object. **`.ts` is not loadable** - rman imports these in
|
|
253
|
+
* its own process, with no loader registered - so a TypeScript repository compiles them first or
|
|
254
|
+
* writes them as `.mjs`.
|
|
94
255
|
*/
|
|
95
|
-
|
|
256
|
+
commands?: CommandEntry | CommandEntry[];
|
|
257
|
+
/**
|
|
258
|
+
* Publish targets this repository has - where a package's artifact ships. An instance, or a glob
|
|
259
|
+
* naming `.js` modules that `export default` one.
|
|
260
|
+
*
|
|
261
|
+
* The same two forms as `plugins` and `commands`, and for the same reason: a target is a
|
|
262
|
+
* contribution, so a config declares it rather than a plugin registering it by hand. rman's own
|
|
263
|
+
* `docker` target is built in; `rman-node` contributes `npm` from its own config.
|
|
264
|
+
*
|
|
265
|
+
* Always appends. **Root level only, like `plugins` and for the same mechanical reason** - both
|
|
266
|
+
* are read by `loadPlugins` inside `Repository.create`, before there is a package whose config
|
|
267
|
+
* could be consulted. `commands` is the one of the three that is read at any level.
|
|
268
|
+
*/
|
|
269
|
+
publishTargets?: PublishTargetEntry | PublishTargetEntry[];
|
|
96
270
|
/**
|
|
97
271
|
* Values for `${{ vars.* }}` to read - a name for something the config would otherwise repeat:
|
|
98
272
|
*
|
|
@@ -132,10 +306,6 @@ export interface RmanConfigKeys {
|
|
|
132
306
|
*/
|
|
133
307
|
skip?: boolean;
|
|
134
308
|
group?: boolean | string;
|
|
135
|
-
version?: RmanConfig.VersionOptions;
|
|
136
|
-
changelog?: RmanConfig.ChangelogOptions;
|
|
137
|
-
publish?: RmanConfig.PublishOptions;
|
|
138
|
-
githubRelease?: RmanConfig.GithubReleaseOptions;
|
|
139
309
|
/** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). A bare string (or array of
|
|
140
310
|
* them) is shorthand for `{ exec: ... }` - `test: "mocha"` and `test: { exec: "mocha" }` mean
|
|
141
311
|
* exactly the same thing, and a bare function is the same shorthand for a function step. */
|
|
@@ -155,28 +325,32 @@ export interface RmanConfigKeys {
|
|
|
155
325
|
* **A list, and only a list.** It used to accept a `Record<string, string>` too, documented as "an
|
|
156
326
|
* explicit name -> range map" - and the ranges went nowhere: the one reader took `Object.keys` and
|
|
157
327
|
* dropped the values. Nor could they ever mean anything here, since the cascade works from groups
|
|
158
|
-
* and severities, and `
|
|
328
|
+
* and severities, and `Plugin.updateDependencyVersions` rewrites ranges in the
|
|
159
329
|
* *manifest* - a range declared only in `.rmanrc` has no file to be written to. What this key
|
|
160
330
|
* states is an **edge**, and an edge needs two ends and nothing else.
|
|
161
331
|
*
|
|
162
|
-
* **Core, and it has to be**: it is layered on top of whatever `
|
|
332
|
+
* **Core, and it has to be**: it is layered on top of whatever `Plugin.readDependencies`
|
|
163
333
|
* read, and it is the *only* way a repository with no provider at all has a graph - a repo whose
|
|
164
334
|
* manifests rman cannot read can still state its edges by hand. Moving it to an ecosystem plugin
|
|
165
335
|
* would take that away from exactly the repositories that need it.
|
|
166
336
|
*/
|
|
167
337
|
dependencies?: string[];
|
|
168
338
|
/**
|
|
169
|
-
* Config for **
|
|
170
|
-
* package
|
|
171
|
-
* Everything else in this object
|
|
172
|
-
*
|
|
173
|
-
*
|
|
339
|
+
* Config for a **narrower audience**, keyed by a `"[selector]"` naming it - `"[/]"` for the root
|
|
340
|
+
* package alone, `"[*]"` for the packages below this directory, `"[*-dialect]"` for a glob over
|
|
341
|
+
* their names, `"[pkg-a]"` for one. Everything else in this object reaches this directory *and*
|
|
342
|
+
* every package under it, so a selector is how a statement stops being everyone's.
|
|
343
|
+
*
|
|
344
|
+
* A glob never matches the root, which is nobody's child - so a package-shaped setting cannot
|
|
345
|
+
* reach a root that has no package directory to apply it to, and `"[/]"` is the only way to
|
|
346
|
+
* address the root.
|
|
174
347
|
*
|
|
175
348
|
* ```yaml
|
|
176
349
|
* # the repository root's own .rmanrc.yml
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
350
|
+
* "[/]":
|
|
351
|
+
* run:
|
|
352
|
+
* build:
|
|
353
|
+
* before: node support/generate.cjs # a repo-wide bookend, run once at the root
|
|
180
354
|
* "[*]":
|
|
181
355
|
* run:
|
|
182
356
|
* build:
|
|
@@ -184,8 +358,8 @@ export interface RmanConfigKeys {
|
|
|
184
358
|
* ```
|
|
185
359
|
*
|
|
186
360
|
* In YAML the quotes are **required**: a bare `[*]` parses as a flow sequence, and `*` as an
|
|
187
|
-
* alias indicator. Precedence
|
|
188
|
-
*
|
|
361
|
+
* alias indicator. Precedence: the unmarked keys first, then these blocks **in the order they
|
|
362
|
+
* were written** - later wins. A directory level closer to the package wins over all of them.
|
|
189
363
|
*
|
|
190
364
|
* Recursive, mirroring the schema's own `"$ref": "#"`: whatever a `.rmanrc` may say about its own
|
|
191
365
|
* package it may say here about the ones it names - nested selectors included. Typed as
|
|
@@ -194,73 +368,355 @@ export interface RmanConfigKeys {
|
|
|
194
368
|
*/
|
|
195
369
|
[selector: `[${string}]`]: RmanConfig;
|
|
196
370
|
}
|
|
371
|
+
export declare const commandRegistry: RmanConfig.CommandRegisterFunction[];
|
|
372
|
+
/**
|
|
373
|
+
* The one key every nested config node may carry: `vars` scoping that node's subtree - a fresh copy
|
|
374
|
+
* per level, merged per key over the level above. See `withScopedVars` in `core/config.ts` for what
|
|
375
|
+
* it does at resolution time, and docs/rman.md#scoped-vars for how it reads.
|
|
376
|
+
*
|
|
377
|
+
* **Every nested options interface has to carry this**, and a hand-written one had to remember to -
|
|
378
|
+
* which is the cost of the runtime rule being general (any object node scopes) while a type can only
|
|
379
|
+
* say it one interface at a time. TypeScript has no way to state "and every object below this may
|
|
380
|
+
* also carry `vars`" without a recursive remap that would wreck the error messages. A command's
|
|
381
|
+
* contributed block gets it from `RmanConfig.ConfigBlock` and cannot forget.
|
|
382
|
+
*/
|
|
383
|
+
export interface ScopedVars {
|
|
384
|
+
/** Values for `${{ vars.* }}` to read, for this node and everything under it. */
|
|
385
|
+
vars?: Record<string, unknown>;
|
|
386
|
+
}
|
|
197
387
|
export declare namespace RmanConfig {
|
|
198
388
|
/**
|
|
199
|
-
*
|
|
389
|
+
* **The slot every command merges its own keys into.** Empty here on purpose: the core declares
|
|
390
|
+
* no command's config, each command declares its own beside the command itself, and a plugin's
|
|
391
|
+
* commands do the same from their own package.
|
|
200
392
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
393
|
+
* A command augments it with its derived contribution rather than a hand-written interface, so
|
|
394
|
+
* the option list and the config type cannot drift apart:
|
|
395
|
+
*
|
|
396
|
+
* ```ts
|
|
397
|
+
* const versionCommand = registerCommand(...);
|
|
398
|
+
* export default versionCommand;
|
|
399
|
+
*
|
|
400
|
+
* declare module '../interfaces/rman-config.interface.js' {
|
|
401
|
+
* namespace RmanConfig {
|
|
402
|
+
* interface CommandConfigs extends CommandContribution<ReturnType<typeof versionCommand>> {}
|
|
403
|
+
* }
|
|
404
|
+
* }
|
|
405
|
+
* ```
|
|
406
|
+
*
|
|
407
|
+
* **Two commands may not contribute under the same top-level key.** Interface merging is not a
|
|
408
|
+
* deep merge: `{run: {build: ...}}` and `{run: {test: ...}}` is
|
|
409
|
+
* `Interface 'CommandConfigs' cannot simultaneously extend types ...` - measured. That is the
|
|
410
|
+
* mechanical reason `build` and `test` own nothing and only *read* `run.build`/`run.test`; `run`
|
|
411
|
+
* is the single owner of that subtree.
|
|
206
412
|
*/
|
|
207
|
-
|
|
208
|
-
file: string;
|
|
209
|
-
constant?: string;
|
|
210
|
-
};
|
|
211
|
-
interface VersionOptions extends VersionOptionsKeys, WithAppend<VersionOptionsKeys>, ScopedVars {
|
|
212
|
-
}
|
|
213
|
-
interface VersionOptionsKeys {
|
|
214
|
-
commitMessage?: string;
|
|
215
|
-
/** Default for `version --changelog` when the CLI flag isn't given - a standing "always fold
|
|
216
|
-
* the changelog into the version-bump commit" policy, rather than something that behaves
|
|
217
|
-
* differently on the one run someone forgets to pass `--changelog`. An explicit `--changelog`/
|
|
218
|
-
* `--no-changelog` on the command line still wins either way. Root-level only. Default `false`. */
|
|
219
|
-
changelog?: boolean;
|
|
220
|
-
/** Tag naming the repository's own release, as opposed to the per-package/group tags
|
|
221
|
-
* `changelog.tagPattern` names - only created when the root is on a calendar version (a repo
|
|
222
|
-
* with more than one version line). Root-level only. Default `"release-*"`. Must **not** match
|
|
223
|
-
* any package's own `changelog.tagPattern`, or that package's changelog boundary will resolve
|
|
224
|
-
* to the repository release instead of its own last release. */
|
|
225
|
-
releaseTagPattern?: string;
|
|
226
|
-
/** Keep this package's Dockerfile `org.opencontainers.image.version` label in step with the
|
|
227
|
-
* version being written. Per-package cascaded. Default `true` - the label's value is, by
|
|
228
|
-
* specification, the version of the packaged software, so there is only ever one correct
|
|
229
|
-
* value for it, and `version` is what knows it. Only ever *rewrites* a label the Dockerfile
|
|
230
|
-
* already declares (never inserts one), and reads the same path `publish --target docker`
|
|
231
|
-
* builds from (`publish.docker.dockerfile`), so a package without one is a no-op. */
|
|
232
|
-
stampDockerfile?: boolean;
|
|
233
|
-
/** Files whose hard-coded version is rewritten to the version being written, in the same
|
|
234
|
-
* commit as the bump - paths relative to the package's own directory (e.g.
|
|
235
|
-
* `["src/constants.ts"]`). Per-package cascaded; a listed file a package doesn't have is a
|
|
236
|
-
* silent no-op, so one `"[*]"` declaration covers a repo where only some packages carry one.
|
|
237
|
-
*
|
|
238
|
-
* Stamping the source, not the build output: a build-time rewrite leaves the checked-in file
|
|
239
|
-
* claiming a placeholder, so anything running from source reports that placeholder, git never
|
|
240
|
-
* records the released version, and the rewrite has to be redone on every build. */
|
|
241
|
-
stamp?: VersionStampEntry | VersionStampEntry[];
|
|
242
|
-
/** Command(s) run at the version write itself, when the package does not declare a hook for
|
|
243
|
-
* that slot of its own (`version` in a Node repository's `package.json#scripts`, whatever a
|
|
244
|
-
* plugin's step source answers elsewhere - the package's own declaration wins, as in `run`).
|
|
245
|
-
* An array runs them in sequence. `${{ pkg.targetVersion }}` is bound here and in the two
|
|
246
|
-
* below, and nowhere else.
|
|
247
|
-
*
|
|
248
|
-
* A `RunStepFn` runs in place of a shell command - but note that `${{ pkg.targetVersion }}` is
|
|
249
|
-
* a *string* substitution, so a function reads the written version off `pkg` instead. */
|
|
250
|
-
exec?: RunStepValue | RunStepValue[];
|
|
251
|
-
/** Same, before the write (`preversion` in a Node repository). */
|
|
252
|
-
before?: RunStepValue | RunStepValue[];
|
|
253
|
-
/** Same, after it (`postversion` in a Node repository). */
|
|
254
|
-
after?: RunStepValue | RunStepValue[];
|
|
413
|
+
interface CommandConfigs {
|
|
255
414
|
}
|
|
256
|
-
interface
|
|
415
|
+
interface CommandConfig {
|
|
416
|
+
skip?: boolean;
|
|
417
|
+
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
418
|
+
vars?: RmanConfig.VarsMap;
|
|
419
|
+
[index: string]: any;
|
|
257
420
|
}
|
|
258
|
-
interface
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
filePath?: string;
|
|
262
|
-
tagPattern?: string;
|
|
421
|
+
interface VarsMap {
|
|
422
|
+
/** Values for `${{ vars.* }}` to read, for this node and everything under it. */
|
|
423
|
+
vars?: Record<string, unknown>;
|
|
263
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Called during init, with the application - not at import, and not with just the repository.
|
|
427
|
+
*
|
|
428
|
+
* The application is what a command reaches services through (`app.getService('changelog')`), and
|
|
429
|
+
* `app.repository` is still there for the metadata that reads it: `version`'s help names its own
|
|
430
|
+
* scheme's bump words, which cannot be known before a repository exists.
|
|
431
|
+
*/
|
|
432
|
+
type CommandRegisterFunction = (app: RmanApplication) => CommandMetadata;
|
|
433
|
+
/** One declared option: everything yargs takes, plus where it may be set from. */
|
|
434
|
+
type CommandOption = yargs.Options & {
|
|
435
|
+
array?: boolean;
|
|
436
|
+
conflicts?: string | string[];
|
|
437
|
+
target: 'cli' | 'config' | 'both';
|
|
438
|
+
/** The flag's spelling on the command line when it differs from the config key - `ignoreDirty`
|
|
439
|
+
* is `--ignore-dirty`. The key stays the camelCase one, since that is what a `.rmanrc` writes. */
|
|
440
|
+
cliName?: string;
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* One declared positional, as `CommandMetadata.positionals` holds them - yargs' own type, named
|
|
444
|
+
* here so a command's author can reach it.
|
|
445
|
+
*
|
|
446
|
+
* **An alias with no additions, and it earns its place anyway.** `positionals` was the one part
|
|
447
|
+
* of a declaration that could only be typed by importing yargs, and a plugin does not depend on
|
|
448
|
+
* yargs - rman does. Nothing enforced that, because a returned object literal is checked
|
|
449
|
+
* structurally: measured on a JavaScript plugin, `positionals: { paths: { type: 'string' } }`
|
|
450
|
+
* widens `'string'` to `string` and fails `PositionalOptionsType`, with the error arriving on
|
|
451
|
+
* the whole `commands` key several levels of "is not assignable" away from the word that caused
|
|
452
|
+
* it. A `satisfies Record<string, PositionalOption>` beside the options' own puts it back on the
|
|
453
|
+
* line. Same reasoning as the flat `CommandOption` in `index.ts`.
|
|
454
|
+
*/
|
|
455
|
+
type PositionalOption = yargs.PositionalOptions;
|
|
456
|
+
type CommandMetadata = {
|
|
457
|
+
command: string;
|
|
458
|
+
/** Other names the command answers to - `list` is also `ls`. */
|
|
459
|
+
aliases?: string[];
|
|
460
|
+
/**
|
|
461
|
+
* yargs parser switches this command needs. `exec` is the only one so far and needs two:
|
|
462
|
+
* `populate--` to keep everything after `--` out of its own options, and
|
|
463
|
+
* `unknown-options-as-args` so the flags of the command *being run* pass through untouched.
|
|
464
|
+
*/
|
|
465
|
+
parserConfiguration?: Partial<yargs.ParserConfigurationOptions>;
|
|
466
|
+
describe?: string;
|
|
467
|
+
examples?: {
|
|
468
|
+
command: string;
|
|
469
|
+
description?: string;
|
|
470
|
+
}[];
|
|
471
|
+
/**
|
|
472
|
+
* The options this command **owns** - the ones it contributes to `RmanConfig`, under
|
|
473
|
+
* `configKey`. An option a command merely *reads* belongs to whoever declares it; name it in
|
|
474
|
+
* `configKeys` instead. One key, one owner: two commands declaring the same key differently
|
|
475
|
+
* would collide silently in the augmentation's intersection.
|
|
476
|
+
*/
|
|
477
|
+
config?: Record<string, CommandOption>;
|
|
478
|
+
/**
|
|
479
|
+
* Where `config` lands in a `.rmanrc`. Defaults to the first word of `command`, and a dotted
|
|
480
|
+
* path nests (`'run.build'` -> `{ run: { build: ... } }`).
|
|
481
|
+
*
|
|
482
|
+
* **Needed exactly once across the nine commands today**, which is why it is optional rather
|
|
483
|
+
* than required: `github-release` writes `githubRelease`, since a config key is camelCase and a
|
|
484
|
+
* command name is hyphenated. Deriving that with a kebab-to-camel rule was the alternative and
|
|
485
|
+
* was refused - it is one more rule to remember, and when it goes wrong the key lands somewhere
|
|
486
|
+
* plausible and nobody notices.
|
|
487
|
+
*/
|
|
488
|
+
configKey?: string;
|
|
489
|
+
/**
|
|
490
|
+
* Config subtrees this command reads but does not own - what `--config` should show beside its
|
|
491
|
+
* own. A function of argv where the answer depends on it (`run <script>` reads `run.<script>`).
|
|
492
|
+
*
|
|
493
|
+
* `version` is the clearest case: it owns `version.*`, but `group` decides which packages move
|
|
494
|
+
* together and `changelog.*` is consulted when `--changelog` folds one into the bump commit.
|
|
495
|
+
* Neither is `version`'s to declare.
|
|
496
|
+
*/
|
|
497
|
+
configKeys?: string[] | ((argv: yargs.Arguments) => string[]);
|
|
498
|
+
/**
|
|
499
|
+
* The positionals `command` declares, described - `'version [bump]'` names `bump`, this says
|
|
500
|
+
* what it is. Keyed by name, and **the names are checked against the command string**: a typo
|
|
501
|
+
* or a positional the command never declared fails on its own key and names the string it was
|
|
502
|
+
* checked against, which the `.positional('bump', ...)` builder call could never do.
|
|
503
|
+
*
|
|
504
|
+
* Only the description belongs here. Whether one is required (`<x>`) or variadic (`[x..]`) is
|
|
505
|
+
* part of the command string, which is the only place it can be, since that is what yargs
|
|
506
|
+
* parses.
|
|
507
|
+
*/
|
|
508
|
+
positionals?: Record<string, PositionalOption>;
|
|
509
|
+
/**
|
|
510
|
+
* **The parameter is the command's own business, so this states no type for it.**
|
|
511
|
+
* A handler annotates itself with `ArgsOf<typeof config, typeof COMMAND>`, which this interface
|
|
512
|
+
* cannot compute for it - `M` is inferred from the literal the handler sits in, so naming it
|
|
513
|
+
* here is the circularity `ArgsOf` documents.
|
|
514
|
+
*
|
|
515
|
+
* It was `(argv: yargs.Arguments) => void`, and that was **not** a neutral placeholder: a
|
|
516
|
+
* parameter is checked contravariantly, so every command's own `Args` had to be a *supertype* of
|
|
517
|
+
* yargs' `{ [argName: string]: unknown }`. An index signature does not make a property present,
|
|
518
|
+
* so any required key put the declaration out of assignment - which is what forced `ArgsOf` to
|
|
519
|
+
* mark **everything** optional, required positionals included. Measured when it stopped:
|
|
520
|
+
* `Property 'script' is missing in type '{ [argName: string]: unknown; ... }' but required in
|
|
521
|
+
* type '{ script: string; }'`, on `run` and `import` alone.
|
|
522
|
+
*
|
|
523
|
+
* `never` is the spelling for "any parameter type is acceptable" - it constrains the *shape*
|
|
524
|
+
* (one argument, returning nothing a caller reads) and leaves the type to the annotation. It is
|
|
525
|
+
* not an `any` that could leak: nothing ever reads argv through this field, since
|
|
526
|
+
* `toYargsCommand` casts the handler to yargs' own and that cast is the single place the two
|
|
527
|
+
* descriptions of argv are admitted to disagree.
|
|
528
|
+
*/
|
|
529
|
+
handler: (argv: never) => void;
|
|
530
|
+
};
|
|
531
|
+
/**
|
|
532
|
+
* The names a command string declares as positionals: `'run <script> [args..]'` is
|
|
533
|
+
* `'script' | 'args'`.
|
|
534
|
+
*
|
|
535
|
+
* Handles all four spellings in use - `<required>`, `[optional]`, `[variadic..]`, and the
|
|
536
|
+
* `[three...]` that `list` writes (yargs' own marker is two dots, so that one is a latent bug
|
|
537
|
+
* worth fixing separately; the parser tolerates it rather than silently producing a name with a
|
|
538
|
+
* dot in it).
|
|
539
|
+
*/
|
|
540
|
+
type PositionalsOf<C extends string> = C extends `${string}<${infer N}>${infer Rest}` ? StripDots<N> | PositionalsOf<Rest> : C extends `${string}[${infer N}]${infer Rest}` ? StripDots<N> | PositionalsOf<Rest> : never;
|
|
541
|
+
/**
|
|
542
|
+
* `argv` as a command's own declarations describe it - every option by its config key, every
|
|
543
|
+
* positional the command string names, plus yargs' own two.
|
|
544
|
+
*
|
|
545
|
+
* **It is written as an explicit annotation on the handler, not inferred**, and that is a
|
|
546
|
+
* measured limitation rather than a preference. Inferring it from the sibling `config` works on
|
|
547
|
+
* its own, but not beside the `M & ValidMeta<M>` check: `M` is inferred from the whole literal,
|
|
548
|
+
* `handler` is part of that literal, so `ArgsOf<M>` is circular and resolves to
|
|
549
|
+
* `ArgsOf<CommandMetadata>` - every option "does not exist". Splitting the inference sites gets
|
|
550
|
+
* the names back and loses the values instead, because `M` and `C` then infer from the same
|
|
551
|
+
* `config` property and the literals widen, so every option arrives as `unknown`.
|
|
552
|
+
*
|
|
553
|
+
* So the declarations are hoisted out of the literal and the handler is annotated:
|
|
554
|
+
*
|
|
555
|
+
* ```ts
|
|
556
|
+
* const COMMAND = 'version [bump]' as const;
|
|
557
|
+
* const config = { ... } satisfies Record<string, RmanConfig.CommandOption>;
|
|
558
|
+
*
|
|
559
|
+
* handler: (args: RmanConfig.ArgsOf<typeof config, typeof COMMAND>) => { ... }
|
|
560
|
+
* ```
|
|
561
|
+
*
|
|
562
|
+
* Two lines, and in exchange the typo checking stays and a renamed option can no longer survive
|
|
563
|
+
* silently in the handler behind an `as`.
|
|
564
|
+
*
|
|
565
|
+
* **A `<required>` positional is required here too, and that is the one thing argv genuinely
|
|
566
|
+
* guarantees.** yargs refuses the call before the handler runs - measured on both commands that
|
|
567
|
+
* declare one: `rman run` and `rman import` each exit 1 with `Not enough non-option arguments:
|
|
568
|
+
* got 0, need at least 1`. Typed optional, the two handlers had to say so themselves
|
|
569
|
+
* (`args.script as string`, `args.path!`), and an `as` in a handler is exactly what declaring
|
|
570
|
+
* commands exists to remove - it is also how a renamed positional would survive silently. The
|
|
571
|
+
* rule is purely syntactic (`<x>` vs `[x]`), so it needs nothing but the command string, which
|
|
572
|
+
* is already the only place that says whether a positional is variadic.
|
|
573
|
+
*
|
|
574
|
+
* **What had made everything optional was not a judgement about argv - it was `handler`'s own
|
|
575
|
+
* parameter type**, which is where that story is written down. Worth knowing before adding a
|
|
576
|
+
* required key here: the constraint was three types away from the one that looked responsible.
|
|
577
|
+
* The claim and the parsing are pinned together in `cli.spec.ts` ("cli: a required positional"),
|
|
578
|
+
* since a type asserting a key is present is only honest while yargs refuses the call without it.
|
|
579
|
+
*
|
|
580
|
+
* **Every option stays optional, `default:` included, and that asymmetry is deliberate.**
|
|
581
|
+
* Narrowing on `default` needs *two* conditions, not one: the default must be present **and**
|
|
582
|
+
* `target !== 'config'`, because `toYargsCommand` never registers a `target: 'config'` option, so
|
|
583
|
+
* yargs never applies its default and the key is simply absent from argv. Written with the one
|
|
584
|
+
* obvious condition the type would claim a key is present where the runtime has `undefined` -
|
|
585
|
+
* failing in the direction that crashes. The gain does not pay for that risk yet: rman declares
|
|
586
|
+
* exactly **one** real option default (`config --json`), read as `if (args.json)`, so `undefined`
|
|
587
|
+
* and `false` already behave alike. The day a contributed command wants it, both conditions are
|
|
588
|
+
* written down here. `demandOption` would take the same pair of conditions and no declared
|
|
589
|
+
* command uses it - the only mention in the tree is a `CustomCommand`'s hand-written `builder`,
|
|
590
|
+
* which does not go through `ArgsOf` at all.
|
|
591
|
+
*/
|
|
592
|
+
type ArgsOf<C, Cmd extends string> = {
|
|
593
|
+
[K in keyof C]?: OptionValue<C[K]>;
|
|
594
|
+
} & {
|
|
595
|
+
[K in RequiredPositionalsOf<Cmd>]: PositionalValue<Cmd, K>;
|
|
596
|
+
} & {
|
|
597
|
+
[K in Exclude<PositionalsOf<Cmd>, RequiredPositionalsOf<Cmd>>]?: PositionalValue<Cmd, K>;
|
|
598
|
+
} & GlobalArgs;
|
|
599
|
+
/**
|
|
600
|
+
* What every command's argv carries whatever it declares - yargs' own two, and rman's global
|
|
601
|
+
* options, which are registered on the program rather than per command.
|
|
602
|
+
*
|
|
603
|
+
* **Measured the moment `exec` was converted**: its handler read `args.logLevel`, which compiled
|
|
604
|
+
* only because `yargs.Arguments` is an index signature. With a real type it is
|
|
605
|
+
* `Property 'logLevel' does not exist` - correct, and exactly the kind of thing that was invisible
|
|
606
|
+
* before. Global options have to be stated once, here, or every handler that reads one is wrong.
|
|
607
|
+
*/
|
|
608
|
+
type GlobalArgs = {
|
|
609
|
+
_: (string | number)[];
|
|
610
|
+
$0: string;
|
|
611
|
+
/** `--log-level`, declared on the program in `cli.ts`. */
|
|
612
|
+
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
613
|
+
/** `--config`: print what this command would run with, and run nothing. */
|
|
614
|
+
config?: boolean;
|
|
615
|
+
/** Everything after `--`, present when the command sets `populate--` - `exec` reads it to tell
|
|
616
|
+
* its own flags from the ones belonging to the command it runs. */
|
|
617
|
+
'--'?: (string | number)[];
|
|
618
|
+
};
|
|
619
|
+
/**
|
|
620
|
+
* The config object a command's `config` declaration describes - `{ type: 'string' }` becomes
|
|
621
|
+
* `string`, `{ type: 'string', array: true }` becomes `string[]`.
|
|
622
|
+
*
|
|
623
|
+
* **The literal survives inference, which is the whole reason this works.** `{ type: 'string' }`
|
|
624
|
+
* would normally widen to `string`, but `yargs.Options['type']` is a union of string literals and
|
|
625
|
+
* the generic's constraint types the object literal contextually - so `ReturnType<T>` still holds
|
|
626
|
+
* `'string'` by the time this maps over it. Measured, with negative controls: reading a mapped
|
|
627
|
+
* value as the wrong type errors, and so does naming an option that was not declared.
|
|
628
|
+
*
|
|
629
|
+
* `choices` narrows further, and is the one place the caller has to help: an array literal widens
|
|
630
|
+
* to `string[]` on its own, so `choices: ['a', 'b'] as const` is what yields `'a' | 'b'`. Without
|
|
631
|
+
* the assertion it falls back to the declared `type`.
|
|
632
|
+
*
|
|
633
|
+
* **Every one of them is a `ConfigValue`, and that needs no per-key judgement.** An option
|
|
634
|
+
* declares a `type` yargs can parse, so what it contributes to `.rmanrc` is by construction a
|
|
635
|
+
* *value* - never a step, which is why steps arrive through `Extra` instead and have to be decided
|
|
636
|
+
* one at a time. So the whole derived half widens here, in one place, and a config author may
|
|
637
|
+
* write `changelog: { filePath: ({ vars }) => ... }` for any of them. `Resolved` takes it back off
|
|
638
|
+
* again for the reader.
|
|
639
|
+
*/
|
|
640
|
+
type CommandConfigFromMetadata<T extends CommandMetadata> = T['config'] extends infer C ? C extends Record<string, CommandOption> ? {
|
|
641
|
+
[K in keyof ConfigOnly<C>]?: ConfigValue<OptionValue<ConfigOnly<C>[K]>>;
|
|
642
|
+
} : {} : {};
|
|
643
|
+
/**
|
|
644
|
+
* What the command adds to `RmanConfig`: its config options, under the key they live at -
|
|
645
|
+
* `{ version: { changelog: boolean } }`, or `{ run: { build: { exec: string } } }` for a dotted
|
|
646
|
+
* `configKey`.
|
|
647
|
+
*
|
|
648
|
+
* **`Extra` is for what an option cannot describe**, and only that. A `CommandOption` says
|
|
649
|
+
* `type: 'string'` or `type: 'boolean'`; it has no way to say `{ file: string; constant?: string }`
|
|
650
|
+
* or "a shell command or a function". So `version.stamp` and `version.before`/`.exec`/`.after`
|
|
651
|
+
* arrive through `Extra`, hand-written beside the command, while `version.commitMessage` and
|
|
652
|
+
* `version.releaseTagPattern` are ordinary `target: 'config'` options and are derived like the
|
|
653
|
+
* rest. Reach for `Extra` when the shape genuinely resists, never to avoid declaring an option.
|
|
654
|
+
*
|
|
655
|
+
* **One key, one declaration - which is what makes `Extra` necessary rather than convenient.**
|
|
656
|
+
* Interface merging is not a deep merge, so a key that arrives from two places is
|
|
657
|
+
* `Interface 'RmanConfig' cannot simultaneously extend types ... Named property 'publish' of
|
|
658
|
+
* types ... are not identical` (measured). A command therefore contributes its key *whole*: the
|
|
659
|
+
* derived half intersected with the hand-written one, never one half here and the other in some
|
|
660
|
+
* central interface.
|
|
661
|
+
*
|
|
662
|
+
* **`run` is the one command this cannot describe**, and it is worth knowing before relying on
|
|
663
|
+
* this: `run.<script>.*` is keyed by script name, so there is no flat option map to derive from
|
|
664
|
+
* and `Extra` would be the entire block. Its contribution stays hand-written in `RmanConfig`.
|
|
665
|
+
*
|
|
666
|
+
* **A widened key is refused rather than used**, because the damage otherwise is not local:
|
|
667
|
+
* `Nest<string, V>` is an index signature, so a `configKey` that lost its literal (a missing
|
|
668
|
+
* `as const`) turns `CommandConfigs` into "every string key has this command's shape" and every
|
|
669
|
+
* other key in `RmanConfig` then fails to satisfy it. Measured, on `github-release`: four errors
|
|
670
|
+
* pointing at `extends`, `logLevel`, `vars` and `changelog`, none of them anywhere near the
|
|
671
|
+
* mistake. The named property fails at the augmentation instead, and says what to add.
|
|
672
|
+
*/
|
|
673
|
+
type CommandContribution<T extends CommandMetadata, Extra = {}> = string extends ConfigKeyOf<T> ? {
|
|
674
|
+
__configKeyMustBeALiteral: 'add `as const` to `command` (or to `configKey`)';
|
|
675
|
+
} : Nest<ConfigKeyOf<T>, ConfigBlock<CommandConfigFromMetadata<T> & Extra>>;
|
|
676
|
+
/**
|
|
677
|
+
* One config block as a `.rmanrc` may actually write it: the keys themselves, their `+key` append
|
|
678
|
+
* forms, and the `vars` that scopes the subtree.
|
|
679
|
+
*
|
|
680
|
+
* This is what a hand-written options interface said by extending
|
|
681
|
+
* `XOptionsKeys, ScopedVars` - clauses every new one had to remember. Derived, it cannot be
|
|
682
|
+
* forgotten. `ScopedVars` is folded in **before**
|
|
683
|
+
* `ScopedVars` is added, so no `+vars` is generated: appending to `vars` means nothing, since
|
|
684
|
+
* objects merge either way.
|
|
685
|
+
*/
|
|
686
|
+
type ConfigBlock<K> = K & ScopedVars;
|
|
687
|
+
/** The declared `configKey`, or the first word of `command`. */
|
|
688
|
+
type ConfigKeyOf<T> = T extends {
|
|
689
|
+
configKey: infer K extends string;
|
|
690
|
+
} ? K : T extends {
|
|
691
|
+
command: infer C extends string;
|
|
692
|
+
} ? C extends `${infer N} ${string}` ? N : C : never;
|
|
693
|
+
/** One option's value type: the scalar its `type` denotes, in a list when it is one. */
|
|
694
|
+
type OptionValue<O> = O extends {
|
|
695
|
+
array: true;
|
|
696
|
+
} ? OptionScalar<O>[] : O extends {
|
|
697
|
+
type: 'array';
|
|
698
|
+
} ? OptionScalar<O>[] : OptionScalar<O>;
|
|
699
|
+
/**
|
|
700
|
+
* The scalar behind a yargs `type`, in decreasing order of how much each says.
|
|
701
|
+
*
|
|
702
|
+
* `coerce` first: it is a function that *produces* the value, so its return type is the answer and
|
|
703
|
+
* whatever `type` claims is a description of the input. `run --parallel` is the case - it takes a
|
|
704
|
+
* flag, a number or `false` and coerces them into `boolean | number | undefined`, which no `type`
|
|
705
|
+
* can state. Then `choices`, which narrows a `type` to the values it allows; then the `type`
|
|
706
|
+
* itself. An option declaring none of the three is `unknown` rather than `any` - a value nothing
|
|
707
|
+
* described should not silently type-check against everything.
|
|
708
|
+
*/
|
|
709
|
+
type OptionScalar<O> = O extends {
|
|
710
|
+
coerce: (...args: never[]) => infer R;
|
|
711
|
+
} ? R : O extends {
|
|
712
|
+
choices: readonly (infer C)[];
|
|
713
|
+
} ? C : O extends {
|
|
714
|
+
type: 'string';
|
|
715
|
+
} ? string : O extends {
|
|
716
|
+
type: 'number' | 'count';
|
|
717
|
+
} ? number : O extends {
|
|
718
|
+
type: 'boolean';
|
|
719
|
+
} ? boolean : unknown;
|
|
264
720
|
/**
|
|
265
721
|
* The `run` block: scripts by name.
|
|
266
722
|
*
|
|
@@ -277,14 +733,29 @@ export declare namespace RmanConfig {
|
|
|
277
733
|
* remap still produces an index signature that claims `vars`.
|
|
278
734
|
*/
|
|
279
735
|
type RunConfig = Record<string, RunStepValue | RunStepValue[] | RunScriptOptions>;
|
|
280
|
-
interface RunScriptOptions extends RunScriptOptionsKeys,
|
|
736
|
+
interface RunScriptOptions extends RunScriptOptionsKeys, ScopedVars {
|
|
281
737
|
}
|
|
738
|
+
/**
|
|
739
|
+
* **Which level a key is read at is not uniform, and it follows what the key decides.**
|
|
740
|
+
* `RunService` reads `concurrency`, `progress`, `changed` and `changedSince` off the **root
|
|
741
|
+
* package only** - one scheduler, one answer for the whole batch - so those belong under
|
|
742
|
+
* `"[/]"`, and a `"[*]"` block declaring them is silently ignored (measured: `concurrency: 1`
|
|
743
|
+
* under `"[*]"` still ran two packages at once). `logLevel`, `skip`, `if`, `override` and the
|
|
744
|
+
* step slots are per package. `topo` and `bail` are read **both** ways and mean different things
|
|
745
|
+
* at each: the root's `topo` picks the sort (topological vs alphabetical), a package's own
|
|
746
|
+
* decides whether *it* waits for its dependencies; the root's `bail` is the default, a package's
|
|
747
|
+
* own outranks even an explicit CLI flag.
|
|
748
|
+
*/
|
|
282
749
|
interface RunScriptOptionsKeys {
|
|
283
750
|
concurrency?: number;
|
|
284
751
|
topo?: boolean;
|
|
285
752
|
bail?: boolean;
|
|
286
753
|
progress?: boolean;
|
|
287
754
|
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
755
|
+
/** Only run in packages that have changed since their last release - the config twin of
|
|
756
|
+
* `--changed`, read off the root. It was read at runtime long before it was declared here, so
|
|
757
|
+
* a typed config could not say the thing that already worked. */
|
|
758
|
+
changed?: boolean;
|
|
288
759
|
changedSince?: string;
|
|
289
760
|
skip?: boolean;
|
|
290
761
|
/** Whether this script runs for a package at all - the small `changed and not private` grammar,
|
|
@@ -301,74 +772,130 @@ export declare namespace RmanConfig {
|
|
|
301
772
|
after?: RunStepValue | RunStepValue[];
|
|
302
773
|
override?: boolean;
|
|
303
774
|
}
|
|
304
|
-
interface PublishOptions extends PublishOptionsKeys, WithAppend<PublishOptionsKeys>, ScopedVars {
|
|
305
|
-
}
|
|
306
|
-
interface PublishOptionsKeys {
|
|
307
|
-
/** Which **registry** `publish` ships this package to - default `['npm']` (every existing repo
|
|
308
|
-
* keeps working unchanged). A package that only ever wants Docker images (typically also
|
|
309
|
-
* `"private": true`, since it's not meant for npm at all) sets `['docker']`; both works too.
|
|
310
|
-
* Each target answers "is this version already out there?" against its own registry, so a
|
|
311
|
-
* package is never left without one: npm via `npm view`, docker via `docker manifest inspect`.
|
|
312
|
-
*
|
|
313
|
-
* Note this is strictly about *package distribution*. The repository's GitHub Release is not
|
|
314
|
-
* a target here - it isn't a place a package ships to, it's the repository's own record that
|
|
315
|
-
* a release happened, and it is never opted into: see `githubRelease` and the
|
|
316
|
-
* `github-release` command. */
|
|
317
|
-
target?: PublishTarget | PublishTarget[];
|
|
318
|
-
docker?: DockerPublishOptions;
|
|
319
|
-
/** Excludes this package from `publish` entirely (every target), regardless of
|
|
320
|
-
* `target`/`"private"` - a single, explicit "never published" statement, e.g. for a package
|
|
321
|
-
* released through some separate, unrelated process. `changelog` also skips it by default
|
|
322
|
-
* (see its own `--include-skipped`) - there's little point changelogging something that's
|
|
323
|
-
* never actually released. Independent of `version`, which never consults this at all - a
|
|
324
|
-
* package can still be meaningfully versioned without ever being published. */
|
|
325
|
-
skip?: boolean;
|
|
326
|
-
}
|
|
327
|
-
type PublishTarget = 'npm' | 'docker';
|
|
328
|
-
/** Required once `"docker"` is one of this package's `publish.target`s - `publish --target
|
|
329
|
-
* docker` errors clearly on a package that opts in here but leaves this out. */
|
|
330
|
-
interface DockerPublishOptions extends DockerPublishOptionsKeys, WithAppend<DockerPublishOptionsKeys>, ScopedVars {
|
|
331
|
-
}
|
|
332
|
-
interface DockerPublishOptionsKeys {
|
|
333
|
-
/** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
|
|
334
|
-
* `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
|
|
335
|
-
* verbatim. */
|
|
336
|
-
image: string;
|
|
337
|
-
/** Relative to the package's own directory. Default `"Dockerfile"`. */
|
|
338
|
-
dockerfile?: string;
|
|
339
|
-
/** Default `["linux/amd64"]`. */
|
|
340
|
-
platforms?: string[];
|
|
341
|
-
/** Build `cwd` override, relative to the repository root - only needed when the Dockerfile's
|
|
342
|
-
* own `COPY`/`ADD` paths expect something other than the package's own directory (rare). */
|
|
343
|
-
cwd?: string;
|
|
344
|
-
/** Named `docker buildx build --build-context <name>=<path>` entries, keyed by name - each
|
|
345
|
-
* path is relative to the package's own directory (or absolute). */
|
|
346
|
-
buildContexts?: Record<string, string>;
|
|
347
|
-
/** `docker buildx build --build-arg <name>=<value>` entries - a value of exactly `"$NAME"`
|
|
348
|
-
* expands to `process.env.NAME` at build time (e.g. to pass a CI secret through). */
|
|
349
|
-
buildArgs?: Record<string, string>;
|
|
350
|
-
/** A file (relative to the package's own directory) whose contents become the DockerHub repo's
|
|
351
|
-
* full description, if present. Default `"DOCKER_README.md"`. */
|
|
352
|
-
readme?: string;
|
|
353
|
-
}
|
|
354
|
-
/** Entirely optional - `github-release` needs no configuration at all, since every required fact
|
|
355
|
-
* (which tag, which repository, what the notes say) already has a sensible source. Nothing here
|
|
356
|
-
* decides *whether* a release is cut: a release records that the repository shipped, so it is
|
|
357
|
-
* always cut, and these are only details about how. */
|
|
358
|
-
interface GithubReleaseOptions extends GithubReleaseOptionsKeys, WithAppend<GithubReleaseOptionsKeys>, ScopedVars {
|
|
359
|
-
}
|
|
360
|
-
interface GithubReleaseOptionsKeys {
|
|
361
|
-
/** Files to attach to the release, as glob patterns relative to the package's own directory
|
|
362
|
-
* (e.g. `["dist/*.tar.gz"]`). Read from **every** package, since one release covers the whole
|
|
363
|
-
* source tree. A release with no assets at all is still perfectly valid - it records that the
|
|
364
|
-
* version shipped, which is all a deploy-elsewhere package needs. */
|
|
365
|
-
assets?: string[];
|
|
366
|
-
/** `owner/repo`. Default: parsed from the `origin` remote's URL. Root-level only. */
|
|
367
|
-
repository?: string;
|
|
368
|
-
/** Create the release as an unpublished draft. Default `false`. Root-level only. */
|
|
369
|
-
draft?: boolean;
|
|
370
|
-
/** Default: whether the version being released is itself a prerelease by the root's own version
|
|
371
|
-
* scheme (`1.3.0-beta.0` under semver) - see `VersionScheme.isPrerelease`. Root-level only. */
|
|
372
|
-
prerelease?: boolean;
|
|
373
|
-
}
|
|
374
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Registers a command and hands the register function straight back.
|
|
778
|
+
*
|
|
779
|
+
* **It returns the function, not the command's config, and that is the point.** The function has
|
|
780
|
+
* not run yet - it runs during init, once there is a `Repository` - so at registration time no
|
|
781
|
+
* config object exists to return. Declaring one anyway is what made the old body
|
|
782
|
+
* (`return def['config']`) uncompilable: `def` is a function, and `config` belongs to what it
|
|
783
|
+
* *returns*.
|
|
784
|
+
*
|
|
785
|
+
* Nothing is lost by being honest, because the metadata type rides along on the return type:
|
|
786
|
+
* `ReturnType<typeof someCommand>` is the `CommandMetadata`, and `CommandConfigFromMetadata` maps
|
|
787
|
+
* it to the config object the command contributes. A phantom property carrying the config type was
|
|
788
|
+
* tried and is unnecessary for exactly this reason - and it would not have survived being stored in
|
|
789
|
+
* `commandRegistry`, whose element type erases it.
|
|
790
|
+
*
|
|
791
|
+
* **`M` is inferred from the metadata rather than from the function, so that the same position can
|
|
792
|
+
* check it.** With the plainer `<T extends CommandRegisterFunction>(def: T)`, `T` is inferred from
|
|
793
|
+
* the literal itself, so the constraint is a subtype check - and a subtype check does no
|
|
794
|
+
* excess-property checking. Measured: `anotherBogusTopLevelKey: true` and a `totallyBogusKey`
|
|
795
|
+
* inside an option both compiled silently, which is how `cliName` and `examples` went unnoticed in
|
|
796
|
+
* `version.command.ts`. `M & ValidMeta<M>` keeps the bare `M` as the inference site and puts the
|
|
797
|
+
* check beside it, so a misspelled key now errors on its own line and names itself.
|
|
798
|
+
*
|
|
799
|
+
* **A command's `command` string needs `as const`** for the config key to be derivable from it
|
|
800
|
+
* (`'version [bump]' as const` -> `'version'`). `type: 'string'` survives inference on its own,
|
|
801
|
+
* since `yargs.Options['type']` is a union of literals and a union suppresses widening; `command`
|
|
802
|
+
* is a plain `string`, so it widens without the assertion. Spelling it `` `${string}` `` does not
|
|
803
|
+
* help - measured; that trick only applies where the constraint sits on the generic parameter.
|
|
804
|
+
*/
|
|
805
|
+
export declare function registerCommand<M extends RmanConfig.CommandMetadata>(def: (app: RmanApplication) => M & ValidMeta<M>): (app: RmanApplication) => M;
|
|
806
|
+
/**
|
|
807
|
+
* The same declaration, **without** the registration - for a command that must exist only when
|
|
808
|
+
* something asks for it.
|
|
809
|
+
*
|
|
810
|
+
* That is exactly a contributed command's situation: `commandRegistry` is a module-level array
|
|
811
|
+
* walked by every `runCli`, so a package pushing onto it would give its commands to repositories
|
|
812
|
+
* that never named it - the module is imported as soon as anything imports the package. A package
|
|
813
|
+
* puts the function in its config's `commands` instead, and `cli.ts` calls it once the repository
|
|
814
|
+
* exists.
|
|
815
|
+
*
|
|
816
|
+
* **Why a factory rather than the metadata itself**: the config is resolved *inside*
|
|
817
|
+
* `Repository.create`, before the packages are known (plugins are what find them), so
|
|
818
|
+
* `app.repository` throws at that point. The function is stored unrun and called later, in
|
|
819
|
+
* `cli.ts`, where the built-ins' own factories run.
|
|
820
|
+
*
|
|
821
|
+
* Everything `registerCommand` documents about inference - `M & ValidMeta<M>`, the `as const` on
|
|
822
|
+
* `command`, the metadata riding on the return type - applies here unchanged; the two differ in one
|
|
823
|
+
* line.
|
|
824
|
+
*/
|
|
825
|
+
export declare function declareCommand<M extends RmanConfig.CommandMetadata>(def: (app: RmanApplication) => M & ValidMeta<M>): (app: RmanApplication) => M;
|
|
826
|
+
/**
|
|
827
|
+
* Only the options a `.rmanrc` may actually set. `target: 'cli'` says the flag exists on the
|
|
828
|
+
* command line and nowhere else - `--interactive`, `--yes`, `--show` - and putting those in the
|
|
829
|
+
* config type would invite writing them where nothing reads them.
|
|
830
|
+
*/
|
|
831
|
+
type ConfigOnly<C> = {
|
|
832
|
+
[K in keyof C as C[K] extends {
|
|
833
|
+
target: 'config' | 'both';
|
|
834
|
+
} ? K : never]: C[K];
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* A dotted path into nested objects: `'run.build'` + shape -> `{ run?: { build?: shape } }`.
|
|
838
|
+
*
|
|
839
|
+
* **Optional at every level**, and that is not a detail: a required key would make the whole
|
|
840
|
+
* contribution mandatory, so `defineConfig({})` stopped compiling with
|
|
841
|
+
* `Property 'version' is missing in type '{}'` (measured). A `.rmanrc` may say nothing about a
|
|
842
|
+
* command, and usually does.
|
|
843
|
+
*/
|
|
844
|
+
type Nest<P extends string, V> = P extends `${infer H}.${infer R}` ? {
|
|
845
|
+
[K in H]?: Nest<R, V>;
|
|
846
|
+
} : {
|
|
847
|
+
[K in P]?: V;
|
|
848
|
+
};
|
|
849
|
+
/** Keys `T` declares that `Shape` does not know about. */
|
|
850
|
+
type Excess<T, Shape> = Exclude<keyof T, keyof Shape>;
|
|
851
|
+
/**
|
|
852
|
+
* `T` itself when it declares nothing extra; otherwise a shape it cannot satisfy, so the assignment
|
|
853
|
+
* fails on the offending key and says which one it is.
|
|
854
|
+
*
|
|
855
|
+
* The failure payload is an **object** rather than a tuple or a string literal. Intersecting a
|
|
856
|
+
* property's real type with an object leaves a real object type, so the error reads `Property
|
|
857
|
+
* '__unknownKey' is missing`; with a string or a tuple the intersection collapses to `never` and the
|
|
858
|
+
* message stops naming anything.
|
|
859
|
+
*/
|
|
860
|
+
type Exact<T, Shape> = [Excess<T, Shape>] extends [never] ? T : {
|
|
861
|
+
[K in Excess<T, Shape>]: {
|
|
862
|
+
__unknownKey: K;
|
|
863
|
+
};
|
|
864
|
+
};
|
|
865
|
+
/** Every option checked against `CommandOption`, so a typo inside one is caught too. */
|
|
866
|
+
type ValidConfig<C> = {
|
|
867
|
+
[K in keyof C]: Exact<C[K], RmanConfig.CommandOption>;
|
|
868
|
+
};
|
|
869
|
+
type ValidMeta<M> = Exact<M, RmanConfig.CommandMetadata> & {
|
|
870
|
+
config?: M extends {
|
|
871
|
+
config: infer C;
|
|
872
|
+
} ? ValidConfig<C> : unknown;
|
|
873
|
+
positionals?: ValidPositionals<M>;
|
|
874
|
+
};
|
|
875
|
+
/** A positional the `command` string never declared fails on its own key, naming the string it was
|
|
876
|
+
* checked against. */
|
|
877
|
+
type ValidPositionals<M> = M extends {
|
|
878
|
+
command: infer C extends string;
|
|
879
|
+
positionals: infer P;
|
|
880
|
+
} ? {
|
|
881
|
+
[K in keyof P]: K extends RmanConfig.PositionalsOf<C> ? P[K] : {
|
|
882
|
+
__notDeclaredIn: C;
|
|
883
|
+
};
|
|
884
|
+
} : unknown;
|
|
885
|
+
/** A trailing `..`/`...` is yargs' variadic marker, not part of the positional's name. */
|
|
886
|
+
type StripDots<S extends string> = S extends `${infer N}...` ? N : S extends `${infer N}..` ? N : S;
|
|
887
|
+
/**
|
|
888
|
+
* The names a command string declares as **required** positionals - `'run <script> [args..]'` is
|
|
889
|
+
* `'script'` alone. `ArgsOf`'s only caller, which is why it is private: whether a positional is
|
|
890
|
+
* required is `ArgsOf`'s question, while `PositionalsOf` answers the different one `ValidPositionals`
|
|
891
|
+
* asks - which names the string declares at all.
|
|
892
|
+
*
|
|
893
|
+
* Unlike `PositionalsOf` it recurses through **one** branch, so a `[bracketed]` name is never
|
|
894
|
+
* emitted; the recursion still steps past one, since `${string}` matches whatever precedes the next
|
|
895
|
+
* `<`.
|
|
896
|
+
*/
|
|
897
|
+
type RequiredPositionalsOf<C extends string> = C extends `${string}<${infer N}>${infer Rest}` ? StripDots<N> | RequiredPositionalsOf<Rest> : never;
|
|
898
|
+
/** A variadic positional (`[command..]`) collects a list; every other one is a single value. The
|
|
899
|
+
* command string is the only place that says which, since it is what yargs parses. */
|
|
900
|
+
type PositionalValue<Cmd extends string, N extends string> = Cmd extends `${string}${'<' | '['}${N}..${string}` ? string[] : string;
|
|
901
|
+
export {};
|