rman 1.0.12 → 1.2.1
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 +90 -70
- package/cli.js +226 -14
- package/commands/build.command.js +1 -0
- package/commands/changed.command.js +2 -2
- package/commands/changelog.command.js +13 -15
- package/commands/config.command.js +61 -0
- package/commands/diff.command.js +9 -4
- package/commands/exec.command.js +2 -8
- package/commands/github-release.command.js +1 -0
- package/commands/info.command.d.ts +9 -0
- package/commands/info.command.js +12 -2
- package/commands/run.command.js +5 -8
- package/commands/test.command.js +1 -0
- package/commands/version.command.js +53 -14
- package/constants.js +1 -1
- package/core/config.d.ts +265 -17
- package/core/config.js +651 -76
- package/core/custom-command.d.ts +133 -0
- package/core/custom-command.js +99 -0
- package/core/extends-config.d.ts +27 -0
- package/core/extends-config.js +89 -0
- package/core/manifest.d.ts +222 -0
- package/core/manifest.js +150 -0
- package/core/merge-config.d.ts +70 -0
- package/core/merge-config.js +193 -0
- package/core/package.d.ts +73 -7
- package/core/package.js +86 -24
- package/core/plugin.d.ts +112 -0
- package/core/plugin.js +189 -0
- package/core/repository.d.ts +91 -1
- package/core/repository.js +277 -132
- package/core/resolve-target.d.ts +12 -0
- package/core/resolve-target.js +33 -0
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/core/version-scheme.d.ts +134 -0
- package/core/version-scheme.js +148 -0
- package/core/workspace.d.ts +68 -0
- package/core/workspace.js +83 -0
- package/index.d.ts +55 -8
- package/index.js +42 -7
- package/interfaces/rman-config.interface.d.ts +222 -46
- package/package.json +16 -7
- package/services/change-hash.service.d.ts +88 -0
- package/services/change-hash.service.js +112 -0
- package/services/changelog.service.d.ts +8 -13
- package/services/changelog.service.js +12 -11
- package/services/conventional-commits.service.d.ts +73 -0
- package/services/conventional-commits.service.js +116 -0
- package/services/docker-publish.service.js +1 -1
- package/services/exec.service.js +1 -1
- package/services/github-release.service.d.ts +2 -2
- package/services/github-release.service.js +10 -5
- package/services/list.service.js +5 -2
- package/services/run.service.d.ts +112 -6
- package/services/run.service.js +265 -89
- package/services/system-info.d.ts +22 -7
- package/services/system-info.js +8 -23
- package/services/version-plan.service.d.ts +244 -0
- package/services/version-plan.service.js +414 -0
- package/services/version.service.d.ts +102 -82
- package/services/version.service.js +226 -434
- package/services.d.ts +5 -3
- package/services.js +5 -3
- package/utils/bin-path.d.ts +59 -0
- package/utils/bin-path.js +82 -0
- package/utils/child-tracker.d.ts +16 -0
- package/utils/child-tracker.js +30 -0
- package/utils/exec.d.ts +13 -2
- package/utils/exec.js +17 -17
- package/utils/git.d.ts +9 -3
- package/utils/git.js +10 -2
- package/utils/package-filter.d.ts +33 -2
- package/utils/package-filter.js +47 -7
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
- package/utils/release-version.js +3 -3
- package/utils/run-bin.d.ts +46 -0
- package/utils/run-bin.js +63 -0
- package/utils/version-stamp.d.ts +14 -6
- package/utils/version-stamp.js +25 -13
- package/commands/ci.command.js +0 -30
- package/commands/clean.command.d.ts +0 -3
- package/commands/clean.command.js +0 -36
- package/commands/publish.command.d.ts +0 -3
- package/commands/publish.command.js +0 -225
- package/rmanrc.schema.json +0 -392
- package/services/ci.service.d.ts +0 -40
- package/services/ci.service.js +0 -204
- package/services/clean.service.d.ts +0 -42
- package/services/clean.service.js +0 -226
- package/services/publish.service.d.ts +0 -79
- package/services/publish.service.js +0 -273
- package/utils/change-hash.d.ts +0 -68
- package/utils/change-hash.js +0 -98
- package/utils/conventional-commits.d.ts +0 -52
- package/utils/conventional-commits.js +0 -90
- package/utils/npm-run-path.d.ts +0 -67
- package/utils/npm-run-path.js +0 -63
- package/utils/workspace-range.d.ts +0 -17
- package/utils/workspace-range.js +0 -28
- /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
|
@@ -1,32 +1,170 @@
|
|
|
1
|
+
import type { RmanPlugin } from '../core/plugin.js';
|
|
2
|
+
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
|
+
}
|
|
1
30
|
/**
|
|
2
31
|
* The shape of `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` (and `package.json`'s own
|
|
3
|
-
* `"rman"` key) - see docs/
|
|
32
|
+
* `"rman"` key) - see docs/rman.md#configuration-rmanrc-rmanrcyml for the full reference. Every
|
|
4
33
|
* field is optional and cascades from the repository root down to each package's own directory.
|
|
5
34
|
* Purely a typing aid (used by `defineConfig` below, and importable on its own for a `.rmanrc.ts`/
|
|
6
35
|
* `.mts` authored config, or a plain `: RmanConfig` annotation) - never read by rman itself, which
|
|
7
36
|
* only ever sees the plain JS object a JS config file exports.
|
|
8
37
|
*/
|
|
9
|
-
export interface RmanConfig {
|
|
10
|
-
|
|
38
|
+
export interface RmanConfig extends RmanConfigKeys, WithAppend<RmanConfigKeys> {
|
|
39
|
+
/**
|
|
40
|
+
* Configs to inherit from, merged **underneath** this one - a shared package
|
|
41
|
+
* (`"@panates/rman-monorepo"`), a relative path, or an array applied in declaration order.
|
|
42
|
+
*
|
|
43
|
+
* A bare name resolves through *this* file's own `node_modules`, so a subpath works too
|
|
44
|
+
* (`"@panates/rman-monorepo/strict"`). The target may be YAML, JSON, or a module exporting a
|
|
45
|
+
* config via `defineConfig`, and may itself `extends` another.
|
|
46
|
+
*
|
|
47
|
+
* Top level only: a `"[selector]"` block naming one is an error rather than a no-op, since
|
|
48
|
+
* inheritance is a statement about this config and not about the packages a selector names.
|
|
49
|
+
*/
|
|
50
|
+
extends?: string | string[];
|
|
51
|
+
}
|
|
52
|
+
/** Every setting a config may carry, without the `+key` append forms or `extends` - the shape
|
|
53
|
+
* `RmanConfig` is built from, kept separate only so `WithAppend` has something to map over. */
|
|
54
|
+
export interface RmanConfigKeys {
|
|
55
|
+
/**
|
|
56
|
+
* **A plugin adds its own keys here, by declaration merging** - `rman-node` contributes
|
|
57
|
+
* `clean` and `publish.directory` from its own
|
|
58
|
+
* `interfaces/rman-config.interface.ts`, so `pkg.config.clean` stays typed wherever it is read
|
|
59
|
+
* without the core having to know npm has a `node_modules` or that TypeScript has build output.
|
|
60
|
+
* `WithAppend` is a mapped type evaluated at use, so an augmented key gets its `+key` form too.
|
|
61
|
+
*
|
|
62
|
+
* A config author annotates with the plugin's own name for the union - `RmanNodeConfig` - which
|
|
63
|
+
* is what makes the import that carries the augmentation explicit rather than incidental.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* Plugins to load, in declaration order - a *package* contributing commands, where `.rman/*.mjs`
|
|
67
|
+
* contributes one repository's own.
|
|
68
|
+
*
|
|
69
|
+
* Each entry is **either a package name (or path) to import, or a plugin object itself**:
|
|
70
|
+
*
|
|
71
|
+
* ```yaml
|
|
72
|
+
* # .rmanrc.yml - imported by name, resolved through the repository's own node_modules
|
|
73
|
+
* plugins: ['rman-node']
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* ```js
|
|
77
|
+
* // .rmanrc.mjs - or handed over directly, which a JS config can do and a YAML one cannot
|
|
78
|
+
* import { defineConfig, definePlugin } from 'rman';
|
|
79
|
+
* export default defineConfig({ plugins: [definePlugin({ name: 'mine', commands: [...] })] });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The object form is what lets a **plugin package export a config** rather than a single plugin:
|
|
83
|
+
* `rman-node`'s entry point is `export default defineConfig({ plugins: [ ... ] })`, so it is an
|
|
84
|
+
* `.rmanrc` like any other and is free to grow a second plugin without changing its shape. When an
|
|
85
|
+
* imported module exports a config this way, **only its `plugins` are read** - a config's other
|
|
86
|
+
* keys reach a repository through `extends`, which is the key that means "merge this underneath
|
|
87
|
+
* mine".
|
|
88
|
+
*
|
|
89
|
+
* This is how everything that only means something in a Node repository lives outside rman's
|
|
90
|
+
* core. A plugin that cannot be loaded is an error, not a skip: silently losing `rman publish` is
|
|
91
|
+
* worse than not starting.
|
|
92
|
+
*
|
|
93
|
+
* Root level only - which commands exist is a property of the repository, not of a package.
|
|
94
|
+
*/
|
|
95
|
+
plugins?: string | RmanPlugin | (string | RmanPlugin)[];
|
|
96
|
+
/**
|
|
97
|
+
* Values for `${{ vars.* }}` to read - a name for something the config would otherwise repeat:
|
|
98
|
+
*
|
|
99
|
+
* ```yaml
|
|
100
|
+
* vars:
|
|
101
|
+
* outDir: build
|
|
102
|
+
* image: 'panates/${{ pkg.basename }}'
|
|
103
|
+
* "[*]":
|
|
104
|
+
* publish:
|
|
105
|
+
* directory: '${{ vars.outDir }}'
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Any shape, and the values may themselves be expressions - they are evaluated for the package
|
|
109
|
+
* reading them, so one `vars.image` gives each package its own.
|
|
110
|
+
*
|
|
111
|
+
* **The one unmarked key that reaches every package**, rather than only the package of the
|
|
112
|
+
* directory declaring it. A package (or a `"[selector]"` block) overrides it **per key**, so
|
|
113
|
+
* redefining one var keeps the rest.
|
|
114
|
+
*/
|
|
115
|
+
vars?: Record<string, unknown>;
|
|
11
116
|
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
12
117
|
allowBranch?: string | string[];
|
|
13
118
|
ignoreBranch?: string | string[];
|
|
119
|
+
/**
|
|
120
|
+
* Leave this package alone: **every command that acts on packages skips it** - `run`/`build`/
|
|
121
|
+
* `test`, `exec`, `clean`, `publish`, `version`, `changelog`. Per-package cascaded, so a root
|
|
122
|
+
* `"[selector]"` block can say it for several at once.
|
|
123
|
+
*
|
|
124
|
+
* One standing statement rather than a `skip` invented per command, which is what it was: three
|
|
125
|
+
* separate keys carried it and none of them meant quite the same thing. The finer-grained ones
|
|
126
|
+
* remain for when only one command should stop - `run.<script>.skip` for a single script,
|
|
127
|
+
* `publish.skip` for "never distributed, by any target" (which `changelog` reuses on purpose).
|
|
128
|
+
*
|
|
129
|
+
* **The commands that *report* deliberately ignore it** - `list` still shows the package, because
|
|
130
|
+
* it is still in the repository and an inventory hiding part of one is answering a different
|
|
131
|
+
* question. `changed` follows `version`, since its whole job is to say what `version` would do.
|
|
132
|
+
*/
|
|
133
|
+
skip?: boolean;
|
|
14
134
|
group?: boolean | string;
|
|
15
135
|
version?: RmanConfig.VersionOptions;
|
|
16
136
|
changelog?: RmanConfig.ChangelogOptions;
|
|
17
|
-
clean?: RmanConfig.CleanOptions;
|
|
18
137
|
publish?: RmanConfig.PublishOptions;
|
|
19
138
|
githubRelease?: RmanConfig.GithubReleaseOptions;
|
|
20
139
|
/** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). A bare string (or array of
|
|
21
140
|
* them) is shorthand for `{ exec: ... }` - `test: "mocha"` and `test: { exec: "mocha" }` mean
|
|
22
|
-
* exactly the same thing. */
|
|
23
|
-
run?:
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
141
|
+
* exactly the same thing, and a bare function is the same shorthand for a function step. */
|
|
142
|
+
run?: RmanConfig.RunConfig;
|
|
143
|
+
/**
|
|
144
|
+
* In-repo packages this one depends on **beyond what its own manifest declares** - purely for
|
|
145
|
+
* rman's own dependency graph (topo-sort, `--deps`/`--dependents`, `run`'s scheduling, the version
|
|
146
|
+
* cascade). Declared from the root via a selector (`"[pkg-a]": { dependencies: [...] }`) or in the
|
|
147
|
+
* package's own `.rmanrc`.
|
|
148
|
+
*
|
|
149
|
+
* Each entry is a package **name, or a repository-relative directory** - tried in that order. The
|
|
150
|
+
* path form is not a convenience: a name identifies a package only where the ecosystem guarantees
|
|
151
|
+
* uniqueness, which npm does and others do not, while a directory is unique by construction. It is
|
|
152
|
+
* the same reason `Workspace.Layout` carries paths and `Package.dependencies` holds references
|
|
153
|
+
* rather than names.
|
|
154
|
+
*
|
|
155
|
+
* **A list, and only a list.** It used to accept a `Record<string, string>` too, documented as "an
|
|
156
|
+
* explicit name -> range map" - and the ranges went nowhere: the one reader took `Object.keys` and
|
|
157
|
+
* dropped the values. Nor could they ever mean anything here, since the cascade works from groups
|
|
158
|
+
* and severities, and `ManifestProvider.updateDependencyVersions` rewrites ranges in the
|
|
159
|
+
* *manifest* - a range declared only in `.rmanrc` has no file to be written to. What this key
|
|
160
|
+
* states is an **edge**, and an edge needs two ends and nothing else.
|
|
161
|
+
*
|
|
162
|
+
* **Core, and it has to be**: it is layered on top of whatever `ManifestProvider.dependencies`
|
|
163
|
+
* read, and it is the *only* way a repository with no provider at all has a graph - a repo whose
|
|
164
|
+
* manifests rman cannot read can still state its edges by hand. Moving it to an ecosystem plugin
|
|
165
|
+
* would take that away from exactly the repositories that need it.
|
|
166
|
+
*/
|
|
167
|
+
dependencies?: string[];
|
|
30
168
|
/**
|
|
31
169
|
* Config for **other** packages, keyed by a `"[selector]"` naming them - `"[*]"` for every
|
|
32
170
|
* package in the repository, `"[*-dialect]"` for a glob over package names, `"[pkg-a]"` for one.
|
|
@@ -57,7 +195,22 @@ export interface RmanConfig {
|
|
|
57
195
|
[selector: `[${string}]`]: RmanConfig;
|
|
58
196
|
}
|
|
59
197
|
export declare namespace RmanConfig {
|
|
60
|
-
|
|
198
|
+
/**
|
|
199
|
+
* One `version.stamp` entry: a path, or a path plus the identifier that holds the version.
|
|
200
|
+
*
|
|
201
|
+
* The object form exists because the identifier was unnameable - only the exact lowercase word
|
|
202
|
+
* `version` was ever matched, so a Go `const Version`, a Python `__version__` and a plain
|
|
203
|
+
* `appVersion` were all silently skipped. What the identifier *means* is the ecosystem's
|
|
204
|
+
* (`ManifestProvider.stampVersion`); which file holds one is the repository's, which is why it is
|
|
205
|
+
* here.
|
|
206
|
+
*/
|
|
207
|
+
type VersionStampEntry = string | {
|
|
208
|
+
file: string;
|
|
209
|
+
constant?: string;
|
|
210
|
+
};
|
|
211
|
+
interface VersionOptions extends VersionOptionsKeys, WithAppend<VersionOptionsKeys>, ScopedVars {
|
|
212
|
+
}
|
|
213
|
+
interface VersionOptionsKeys {
|
|
61
214
|
commitMessage?: string;
|
|
62
215
|
/** Default for `version --changelog` when the CLI flag isn't given - a standing "always fold
|
|
63
216
|
* the changelog into the version-bump commit" policy, rather than something that behaves
|
|
@@ -77,7 +230,7 @@ export declare namespace RmanConfig {
|
|
|
77
230
|
* already declares (never inserts one), and reads the same path `publish --target docker`
|
|
78
231
|
* builds from (`publish.docker.dockerfile`), so a package without one is a no-op. */
|
|
79
232
|
stampDockerfile?: boolean;
|
|
80
|
-
/**
|
|
233
|
+
/** Files whose hard-coded version is rewritten to the version being written, in the same
|
|
81
234
|
* commit as the bump - paths relative to the package's own directory (e.g.
|
|
82
235
|
* `["src/constants.ts"]`). Per-package cascaded; a listed file a package doesn't have is a
|
|
83
236
|
* silent no-op, so one `"[*]"` declaration covers a repo where only some packages carry one.
|
|
@@ -85,27 +238,48 @@ export declare namespace RmanConfig {
|
|
|
85
238
|
* Stamping the source, not the build output: a build-time rewrite leaves the checked-in file
|
|
86
239
|
* claiming a placeholder, so anything running from source reports that placeholder, git never
|
|
87
240
|
* records the released version, and the rewrite has to be redone on every build. */
|
|
88
|
-
stamp?:
|
|
89
|
-
/** Command(s) run
|
|
90
|
-
*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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[];
|
|
96
255
|
}
|
|
97
|
-
interface ChangelogOptions {
|
|
256
|
+
interface ChangelogOptions extends ChangelogOptionsKeys, WithAppend<ChangelogOptionsKeys>, ScopedVars {
|
|
257
|
+
}
|
|
258
|
+
interface ChangelogOptionsKeys {
|
|
98
259
|
ignoreTypes?: string[];
|
|
99
260
|
template?: string;
|
|
100
261
|
filePath?: string;
|
|
101
262
|
tagPattern?: string;
|
|
102
263
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
264
|
+
/**
|
|
265
|
+
* The `run` block: scripts by name.
|
|
266
|
+
*
|
|
267
|
+
* **`run.vars` works at runtime but is deliberately not in this type**, and the reason is a
|
|
268
|
+
* measured trade rather than an oversight. `run` is keyed by script name, so any encoding that
|
|
269
|
+
* lets `vars` through has to widen the index signature's value type to something object-shaped -
|
|
270
|
+
* and TypeScript then stops excess-property-checking *every* script's options. Measured on the
|
|
271
|
+
* same file: with the widened index, `run: { build: { exce: 'tsc' } }` compiles clean.
|
|
272
|
+
*
|
|
273
|
+
* Catching that typo across every script is worth more than typing one key, so a typed JS config
|
|
274
|
+
* writing `run.vars` needs a cast (`run: { vars: { x: 2 }, build: ... } as RmanConfig['run']`).
|
|
275
|
+
* YAML and JSON configs are unchecked anyway and simply work. A key-remapped index signature
|
|
276
|
+
* (`{ [K in string as K extends 'vars' ? never : K]: ... }`) was tried and does not help - the
|
|
277
|
+
* remap still produces an index signature that claims `vars`.
|
|
278
|
+
*/
|
|
279
|
+
type RunConfig = Record<string, RunStepValue | RunStepValue[] | RunScriptOptions>;
|
|
280
|
+
interface RunScriptOptions extends RunScriptOptionsKeys, WithAppend<RunScriptOptionsKeys>, ScopedVars {
|
|
107
281
|
}
|
|
108
|
-
interface
|
|
282
|
+
interface RunScriptOptionsKeys {
|
|
109
283
|
concurrency?: number;
|
|
110
284
|
topo?: boolean;
|
|
111
285
|
bail?: boolean;
|
|
@@ -113,17 +287,23 @@ export declare namespace RmanConfig {
|
|
|
113
287
|
logLevel?: 'silent' | 'error' | 'info' | 'verbose';
|
|
114
288
|
changedSince?: string;
|
|
115
289
|
skip?: boolean;
|
|
116
|
-
|
|
290
|
+
/** Whether this script runs for a package at all - the small `changed and not private` grammar,
|
|
291
|
+
* or a `RunConditionFn` for a condition it cannot express. Both are evaluated per package when
|
|
292
|
+
* the run reaches it; a `${{ }}` expression here is not, having been resolved when the config
|
|
293
|
+
* loaded. */
|
|
294
|
+
if?: string | RunConditionFn;
|
|
117
295
|
/** Command(s) to run as this script itself, when the package's `package.json` doesn't define
|
|
118
|
-
* it. An array runs them in sequence. */
|
|
119
|
-
exec?:
|
|
296
|
+
* it. An array runs them in sequence, and may mix shell commands with functions. */
|
|
297
|
+
exec?: RunStepValue | RunStepValue[];
|
|
120
298
|
/** Same, for this script's `pre<script>` hook. */
|
|
121
|
-
before?:
|
|
299
|
+
before?: RunStepValue | RunStepValue[];
|
|
122
300
|
/** Same, for its `post<script>` hook. */
|
|
123
|
-
after?:
|
|
301
|
+
after?: RunStepValue | RunStepValue[];
|
|
124
302
|
override?: boolean;
|
|
125
303
|
}
|
|
126
|
-
interface PublishOptions {
|
|
304
|
+
interface PublishOptions extends PublishOptionsKeys, WithAppend<PublishOptionsKeys>, ScopedVars {
|
|
305
|
+
}
|
|
306
|
+
interface PublishOptionsKeys {
|
|
127
307
|
/** Which **registry** `publish` ships this package to - default `['npm']` (every existing repo
|
|
128
308
|
* keeps working unchanged). A package that only ever wants Docker images (typically also
|
|
129
309
|
* `"private": true`, since it's not meant for npm at all) sets `['docker']`; both works too.
|
|
@@ -135,14 +315,6 @@ export declare namespace RmanConfig {
|
|
|
135
315
|
* a release happened, and it is never opted into: see `githubRelease` and the
|
|
136
316
|
* `github-release` command. */
|
|
137
317
|
target?: PublishTarget | PublishTarget[];
|
|
138
|
-
/** Where this package's publishable output lives, relative to its own directory (e.g.
|
|
139
|
-
* `"build"`). Per-package cascaded, so a root `"[*]"` block can say it once for the whole
|
|
140
|
-
* repository instead of repeating `publishConfig.directory` in every `package.json` - which
|
|
141
|
-
* still wins when a package declares it, being the more specific statement.
|
|
142
|
-
*
|
|
143
|
-
* Publishing from such a directory means the manifest there is **generated by `publish`**,
|
|
144
|
-
* from the package's own - see `PublishService`. There is nothing to configure about it. */
|
|
145
|
-
directory?: string;
|
|
146
318
|
docker?: DockerPublishOptions;
|
|
147
319
|
/** Excludes this package from `publish` entirely (every target), regardless of
|
|
148
320
|
* `target`/`"private"` - a single, explicit "never published" statement, e.g. for a package
|
|
@@ -155,7 +327,9 @@ export declare namespace RmanConfig {
|
|
|
155
327
|
type PublishTarget = 'npm' | 'docker';
|
|
156
328
|
/** Required once `"docker"` is one of this package's `publish.target`s - `publish --target
|
|
157
329
|
* docker` errors clearly on a package that opts in here but leaves this out. */
|
|
158
|
-
interface DockerPublishOptions {
|
|
330
|
+
interface DockerPublishOptions extends DockerPublishOptionsKeys, WithAppend<DockerPublishOptionsKeys>, ScopedVars {
|
|
331
|
+
}
|
|
332
|
+
interface DockerPublishOptionsKeys {
|
|
159
333
|
/** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
|
|
160
334
|
* `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
|
|
161
335
|
* verbatim. */
|
|
@@ -181,7 +355,9 @@ export declare namespace RmanConfig {
|
|
|
181
355
|
* (which tag, which repository, what the notes say) already has a sensible source. Nothing here
|
|
182
356
|
* decides *whether* a release is cut: a release records that the repository shipped, so it is
|
|
183
357
|
* always cut, and these are only details about how. */
|
|
184
|
-
interface GithubReleaseOptions {
|
|
358
|
+
interface GithubReleaseOptions extends GithubReleaseOptionsKeys, WithAppend<GithubReleaseOptionsKeys>, ScopedVars {
|
|
359
|
+
}
|
|
360
|
+
interface GithubReleaseOptionsKeys {
|
|
185
361
|
/** Files to attach to the release, as glob patterns relative to the package's own directory
|
|
186
362
|
* (e.g. `["dist/*.tar.gz"]`). Read from **every** package, since one release covers the whole
|
|
187
363
|
* source tree. A release with no assets at all is still perfectly valid - it records that the
|
|
@@ -191,8 +367,8 @@ export declare namespace RmanConfig {
|
|
|
191
367
|
repository?: string;
|
|
192
368
|
/** Create the release as an unpublished draft. Default `false`. Root-level only. */
|
|
193
369
|
draft?: boolean;
|
|
194
|
-
/** Default: whether the version being released is itself a
|
|
195
|
-
* Root-level only. */
|
|
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. */
|
|
196
372
|
prerelease?: boolean;
|
|
197
373
|
}
|
|
198
374
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rman",
|
|
3
3
|
"description": "Repository manager",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.1",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@
|
|
8
|
+
"@xmldom/xmldom": "^0.9.12",
|
|
9
9
|
"ansi-colors": "^4.1.3",
|
|
10
10
|
"cross-dirname": "^0.1.0",
|
|
11
11
|
"easy-table": "^1.2.0",
|
|
@@ -35,8 +35,11 @@
|
|
|
35
35
|
"types": "./index.d.ts",
|
|
36
36
|
"default": "./index.js"
|
|
37
37
|
},
|
|
38
|
-
"./
|
|
39
|
-
|
|
38
|
+
"./cli": {
|
|
39
|
+
"types": "./cli.d.ts",
|
|
40
|
+
"default": "./cli.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
40
43
|
},
|
|
41
44
|
"bin": {
|
|
42
45
|
"rman": "cli.js"
|
|
@@ -45,11 +48,15 @@
|
|
|
45
48
|
"node": ">=20.0"
|
|
46
49
|
},
|
|
47
50
|
"contributors": [
|
|
48
|
-
"Eray Hanoglu <e.hanoglu@panates.com>"
|
|
51
|
+
"Eray Hanoglu <e.hanoglu@panates.com>",
|
|
52
|
+
"Ilker Gurelli <i.gurelli@panates.com>",
|
|
53
|
+
"Onur Tokel <o.tokel@panates.com>",
|
|
54
|
+
"Bircan Yuruk <b.yuruk@panates.com>"
|
|
49
55
|
],
|
|
50
56
|
"repository": {
|
|
51
57
|
"type": "git",
|
|
52
|
-
"url": "git+https://github.com/panates/rman.git"
|
|
58
|
+
"url": "git+https://github.com/panates/rman.git",
|
|
59
|
+
"directory": "./packages/rman"
|
|
53
60
|
},
|
|
54
61
|
"keywords": [
|
|
55
62
|
"javascript",
|
|
@@ -59,5 +66,7 @@
|
|
|
59
66
|
"build",
|
|
60
67
|
"lerna"
|
|
61
68
|
],
|
|
62
|
-
"
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
63
72
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Package } from '../core/package.js';
|
|
2
|
+
import type { GitHelper } from '../utils/git.js';
|
|
3
|
+
/**
|
|
4
|
+
* Release boundaries and the tag names that mark them - **the single source for both directions**.
|
|
5
|
+
*
|
|
6
|
+
* Every command that asks question A ("what changed since this package's last release") comes
|
|
7
|
+
* through `detect`, and every command that needs to *name* a tag comes through `expandTag` /
|
|
8
|
+
* `findLatestTag`. No command builds a tag name of its own; that is what keeps `version`'s tag,
|
|
9
|
+
* `changelog`'s boundary and `github-release`'s lookup from drifting apart.
|
|
10
|
+
*
|
|
11
|
+
* A namespace for the reason `Manifest` and `Workspace` are ones: it is one subject with several
|
|
12
|
+
* operations, so the operations are named for what they do rather than carrying the subject in each
|
|
13
|
+
* name (`ChangeHashService.detect`, not `detectChangeHash`), and a plugin can augment it.
|
|
14
|
+
*/
|
|
15
|
+
export declare namespace ChangeHashService {
|
|
16
|
+
/** The one keyword `from` accepts instead of a ref: "work it out per package". Omitting `from`
|
|
17
|
+
* means the same thing - this exists so a pipeline can say it explicitly. */
|
|
18
|
+
const AUTO = "auto";
|
|
19
|
+
interface DetectOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Use this commit/hash directly instead of auto-detecting - applies the same way to every
|
|
22
|
+
* package. `AUTO` (or omitting `from` entirely) asks for auto-detection instead of being
|
|
23
|
+
* treated as a literal ref.
|
|
24
|
+
*
|
|
25
|
+
* The keyword used to be `"npm"`, which named a *source* - and the wrong one: auto-detection is
|
|
26
|
+
* mostly git, and the registry it may consult is now the ecosystem's business (see
|
|
27
|
+
* `ManifestProvider.publishedVersion`). What is being chosen here is a *mode*, so it is spelled
|
|
28
|
+
* as one. **A rename, not an alias**: `--from npm` now means a ref literally called `npm`,
|
|
29
|
+
* which is what it should have meant all along.
|
|
30
|
+
*/
|
|
31
|
+
from?: string;
|
|
32
|
+
/** An existing record of what's already been documented (typically a changelog file) - only
|
|
33
|
+
* consulted while auto-detecting (ignored when `from` is an explicit hash). Guards against a
|
|
34
|
+
* gap: if this file's own last-modifying commit is *older* than the tag detected from the
|
|
35
|
+
* registry - e.g. the file was last updated for 1.1.0, but 1.2.0-1.5.0 were released without
|
|
36
|
+
* ever documenting them, and the registry now reports 1.5.0 - starting from the tag alone would
|
|
37
|
+
* silently skip everything the file never recorded. The boundary becomes the merge-base of the
|
|
38
|
+
* two, so the result always covers at least as much as the file is missing. Has no effect when
|
|
39
|
+
* the file doesn't exist. */
|
|
40
|
+
catchUpFile?: string;
|
|
41
|
+
}
|
|
42
|
+
/** `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
|
|
43
|
+
* release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
|
|
44
|
+
* for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
|
|
45
|
+
* use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
|
|
46
|
+
* shared by every package (e.g. the default `v*`). */
|
|
47
|
+
function tagPattern(pkg: Package): string;
|
|
48
|
+
/** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
|
|
49
|
+
* *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
|
|
50
|
+
* HEAD actually descends from, since no single package "owns" that tag. `undefined` if never
|
|
51
|
+
* tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
|
|
52
|
+
* (reading the last-documented version) and `version` (finding the boundary a bump measures
|
|
53
|
+
* "since"). */
|
|
54
|
+
function findLatestTag(git: GitHelper, pkg: Package): Promise<string | undefined>;
|
|
55
|
+
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
56
|
+
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
57
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
58
|
+
* `github-release` (finding the release that tag belongs to), and `detect`'s own registry
|
|
59
|
+
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
60
|
+
function expandTag(pkg: Package, version: string): string;
|
|
61
|
+
/** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*`
|
|
62
|
+
* becomes `version`. Shared with the repository's own release tag, which uses a different pattern
|
|
63
|
+
* (see `releaseTagPattern`) but names tags the same way. */
|
|
64
|
+
function applyTagPattern(pattern: string, name: string, version: string): string;
|
|
65
|
+
/** Strips the pattern's literal prefix (everything before its first `*`) from `tag` to get just
|
|
66
|
+
* the version part - e.g. tag `@sqb/builder@1.2.3` against pattern `@sqb/builder@*` -> `1.2.3`.
|
|
67
|
+
* A pattern with no `*` is returned as its own "version" verbatim (an exact tag, nothing to
|
|
68
|
+
* strip). */
|
|
69
|
+
function extractVersion(tag: string, expandedPattern: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Resolves the commit/hash a package's changes should be measured "since" - the boundary
|
|
72
|
+
* `changelog --from` uses, but reusable anywhere a command wants to answer "what changed for this
|
|
73
|
+
* package". An explicit `options.from` (anything but `AUTO`) is returned as-is, applying the same
|
|
74
|
+
* way to every package. Otherwise, it's auto-detected in order: (1) this package's own most recent
|
|
75
|
+
* release tag - the same network-free `findLatestTag` lookup `version`/`changed` themselves use,
|
|
76
|
+
* so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
|
|
77
|
+
* actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
|
|
78
|
+
* real release history but no `rman`-created tags), whatever this package's **own ecosystem**
|
|
79
|
+
* reports as its published version (`ManifestProvider.publishedVersion`), mapped to a git tag via
|
|
80
|
+
* `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
|
|
81
|
+
* `catchUpFile` is given and exists, the result is widened to also cover anything that file
|
|
82
|
+
* hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
|
|
83
|
+
* at all (never tagged *and* never published, no catch-up file - a genuinely first-ever release) -
|
|
84
|
+
* callers should fall back to their own default in that case (e.g. the whole history, since
|
|
85
|
+
* nothing has ever been released).
|
|
86
|
+
*/
|
|
87
|
+
function detect(git: GitHelper, pkg: Package, options?: DetectOptions): Promise<string | undefined>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Manifest } from '../core/manifest.js';
|
|
2
|
+
/**
|
|
3
|
+
* Release boundaries and the tag names that mark them - **the single source for both directions**.
|
|
4
|
+
*
|
|
5
|
+
* Every command that asks question A ("what changed since this package's last release") comes
|
|
6
|
+
* through `detect`, and every command that needs to *name* a tag comes through `expandTag` /
|
|
7
|
+
* `findLatestTag`. No command builds a tag name of its own; that is what keeps `version`'s tag,
|
|
8
|
+
* `changelog`'s boundary and `github-release`'s lookup from drifting apart.
|
|
9
|
+
*
|
|
10
|
+
* A namespace for the reason `Manifest` and `Workspace` are ones: it is one subject with several
|
|
11
|
+
* operations, so the operations are named for what they do rather than carrying the subject in each
|
|
12
|
+
* name (`ChangeHashService.detect`, not `detectChangeHash`), and a plugin can augment it.
|
|
13
|
+
*/
|
|
14
|
+
export var ChangeHashService;
|
|
15
|
+
(function (ChangeHashService) {
|
|
16
|
+
/** The one keyword `from` accepts instead of a ref: "work it out per package". Omitting `from`
|
|
17
|
+
* means the same thing - this exists so a pipeline can say it explicitly. */
|
|
18
|
+
ChangeHashService.AUTO = 'auto';
|
|
19
|
+
/** `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
|
|
20
|
+
* release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
|
|
21
|
+
* for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
|
|
22
|
+
* use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
|
|
23
|
+
* shared by every package (e.g. the default `v*`). */
|
|
24
|
+
function tagPattern(pkg) {
|
|
25
|
+
const cfg = pkg.config?.changelog;
|
|
26
|
+
return typeof cfg?.tagPattern === 'string' && cfg.tagPattern ? cfg.tagPattern : DEFAULT_TAG_PATTERN;
|
|
27
|
+
}
|
|
28
|
+
ChangeHashService.tagPattern = tagPattern;
|
|
29
|
+
/** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
|
|
30
|
+
* *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
|
|
31
|
+
* HEAD actually descends from, since no single package "owns" that tag. `undefined` if never
|
|
32
|
+
* tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
|
|
33
|
+
* (reading the last-documented version) and `version` (finding the boundary a bump measures
|
|
34
|
+
* "since"). */
|
|
35
|
+
async function findLatestTag(git, pkg) {
|
|
36
|
+
const pattern = tagPattern(pkg);
|
|
37
|
+
const expanded = pattern.replace('{name}', pkg.name);
|
|
38
|
+
return pattern.includes('{name}') ? (await git.listTags(expanded))[0] : await git.describeTag(expanded);
|
|
39
|
+
}
|
|
40
|
+
ChangeHashService.findLatestTag = findLatestTag;
|
|
41
|
+
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
42
|
+
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
43
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
44
|
+
* `github-release` (finding the release that tag belongs to), and `detect`'s own registry
|
|
45
|
+
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
46
|
+
function expandTag(pkg, version) {
|
|
47
|
+
return applyTagPattern(tagPattern(pkg), pkg.name, version);
|
|
48
|
+
}
|
|
49
|
+
ChangeHashService.expandTag = expandTag;
|
|
50
|
+
/** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*`
|
|
51
|
+
* becomes `version`. Shared with the repository's own release tag, which uses a different pattern
|
|
52
|
+
* (see `releaseTagPattern`) but names tags the same way. */
|
|
53
|
+
function applyTagPattern(pattern, name, version) {
|
|
54
|
+
const expanded = pattern.replace('{name}', name);
|
|
55
|
+
const starIdx = expanded.indexOf('*');
|
|
56
|
+
return starIdx === -1 ? expanded : expanded.slice(0, starIdx) + version + expanded.slice(starIdx + 1);
|
|
57
|
+
}
|
|
58
|
+
ChangeHashService.applyTagPattern = applyTagPattern;
|
|
59
|
+
/** Strips the pattern's literal prefix (everything before its first `*`) from `tag` to get just
|
|
60
|
+
* the version part - e.g. tag `@sqb/builder@1.2.3` against pattern `@sqb/builder@*` -> `1.2.3`.
|
|
61
|
+
* A pattern with no `*` is returned as its own "version" verbatim (an exact tag, nothing to
|
|
62
|
+
* strip). */
|
|
63
|
+
function extractVersion(tag, expandedPattern) {
|
|
64
|
+
const starIdx = expandedPattern.indexOf('*');
|
|
65
|
+
if (starIdx === -1)
|
|
66
|
+
return tag;
|
|
67
|
+
const prefix = expandedPattern.slice(0, starIdx);
|
|
68
|
+
return tag.startsWith(prefix) ? tag.slice(prefix.length) : tag;
|
|
69
|
+
}
|
|
70
|
+
ChangeHashService.extractVersion = extractVersion;
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the commit/hash a package's changes should be measured "since" - the boundary
|
|
73
|
+
* `changelog --from` uses, but reusable anywhere a command wants to answer "what changed for this
|
|
74
|
+
* package". An explicit `options.from` (anything but `AUTO`) is returned as-is, applying the same
|
|
75
|
+
* way to every package. Otherwise, it's auto-detected in order: (1) this package's own most recent
|
|
76
|
+
* release tag - the same network-free `findLatestTag` lookup `version`/`changed` themselves use,
|
|
77
|
+
* so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
|
|
78
|
+
* actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
|
|
79
|
+
* real release history but no `rman`-created tags), whatever this package's **own ecosystem**
|
|
80
|
+
* reports as its published version (`ManifestProvider.publishedVersion`), mapped to a git tag via
|
|
81
|
+
* `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
|
|
82
|
+
* `catchUpFile` is given and exists, the result is widened to also cover anything that file
|
|
83
|
+
* hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
|
|
84
|
+
* at all (never tagged *and* never published, no catch-up file - a genuinely first-ever release) -
|
|
85
|
+
* callers should fall back to their own default in that case (e.g. the whole history, since
|
|
86
|
+
* nothing has ever been released).
|
|
87
|
+
*/
|
|
88
|
+
async function detect(git, pkg, options = {}) {
|
|
89
|
+
if (options.from && options.from !== ChangeHashService.AUTO)
|
|
90
|
+
return options.from;
|
|
91
|
+
let tagHash = await findLatestTag(git, pkg);
|
|
92
|
+
if (!tagHash) {
|
|
93
|
+
/** Through the package's own ecosystem, not through npm: which registry (if any) knows about
|
|
94
|
+
* this package is the `ManifestProvider`'s answer, and in a polyglot repository it differs
|
|
95
|
+
* per package. A repository naming no plugin gets `undefined` here and git tags decide
|
|
96
|
+
* alone. */
|
|
97
|
+
const publishedVersion = await Manifest.publishedVersion(pkg);
|
|
98
|
+
if (publishedVersion) {
|
|
99
|
+
const tag = expandTag(pkg, publishedVersion);
|
|
100
|
+
tagHash = (await git.tagExists(tag)) ? tag : undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const fileHash = options.catchUpFile ? await git.lastCommitTouching(options.catchUpFile) : undefined;
|
|
104
|
+
if (!fileHash)
|
|
105
|
+
return tagHash;
|
|
106
|
+
if (!tagHash)
|
|
107
|
+
return fileHash;
|
|
108
|
+
return (await git.mergeBase(tagHash, fileHash)) ?? tagHash;
|
|
109
|
+
}
|
|
110
|
+
ChangeHashService.detect = detect;
|
|
111
|
+
})(ChangeHashService || (ChangeHashService = {}));
|
|
112
|
+
const DEFAULT_TAG_PATTERN = 'v*';
|