rman 1.3.0 → 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 +140 -19
- package/core/config.js +258 -74
- 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 +137 -92
- 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 -44
- 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 +725 -202
- package/interfaces/rman-config.interface.js +61 -1
- package/package.json +2 -1
- package/plugins/builtins.d.ts +44 -0
- package/plugins/builtins.js +33 -0
- package/plugins/detect.d.ts +78 -0
- package/plugins/detect.js +70 -0
- package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
- package/plugins/node/augmentation/rman.augmentation.js +1 -0
- package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/plugins/node/augmentation/system-info.augmentation.js +79 -0
- package/plugins/node/commands/ci.command.d.ts +131 -0
- package/plugins/node/commands/ci.command.js +59 -0
- package/plugins/node/commands/clean.command.d.ts +183 -0
- package/plugins/node/commands/clean.command.js +73 -0
- package/plugins/node/index.d.ts +29 -0
- package/plugins/node/index.js +40 -0
- package/plugins/node/node-config.interface.d.ts +77 -0
- package/plugins/node/node-config.interface.js +7 -0
- package/plugins/node/node-manifest.provider.d.ts +68 -0
- package/plugins/node/node-manifest.provider.js +125 -0
- package/plugins/node/node.platform.d.ts +53 -0
- package/plugins/node/node.platform.js +134 -0
- package/plugins/node/npm-publish-target.d.ts +73 -0
- package/plugins/node/npm-publish-target.js +96 -0
- package/plugins/node/services/ci.service.d.ts +47 -0
- package/plugins/node/services/ci.service.js +213 -0
- package/plugins/node/services/clean.service.d.ts +53 -0
- package/plugins/node/services/clean.service.js +237 -0
- package/plugins/node/services/publish.service.d.ts +114 -0
- package/plugins/node/services/publish.service.js +371 -0
- package/plugins/node/services/version-plan.service.d.ts +44 -0
- package/plugins/node/services/version-plan.service.js +58 -0
- package/plugins/node/utils/npm-view.d.ts +48 -0
- package/plugins/node/utils/npm-view.js +71 -0
- package/plugins/node/utils/workspace-range.d.ts +26 -0
- package/plugins/node/utils/workspace-range.js +28 -0
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/changelog.service.d.ts +62 -51
- package/services/changelog.service.js +14 -11
- package/services/docker-publish.service.d.ts +50 -29
- package/services/docker-publish.service.js +43 -20
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +44 -33
- package/services/github-release.service.js +13 -10
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +62 -10
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +22 -13
- package/services/run.service.js +262 -223
- package/services/version-plan.service.d.ts +27 -4
- package/services/version-plan.service.js +42 -15
- package/services/version.service.d.ts +31 -11
- package/services/version.service.js +29 -13
- package/targets/docker.target.d.ts +53 -0
- package/targets/docker.target.js +40 -0
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +29 -0
- package/utils/branch-guard.js +31 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/logger.d.ts +1 -1
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +127 -7
- package/utils/package-filter.js +197 -16
- package/utils/printable-config.d.ts +1 -1
- package/utils/printable-config.js +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepares the `package.json` that `npm publish` will actually read, and returns a function that
|
|
3
|
+
* undoes it - always call that from a `finally`, so a failed publish leaves nothing behind.
|
|
4
|
+
*
|
|
5
|
+
* Which file that is depends on where the output lives:
|
|
6
|
+
*
|
|
7
|
+
* - **Publishing the package directory itself** - its own `package.json` is the manifest, rewritten
|
|
8
|
+
* in place: every `"workspace:"` range becomes a real, registry-consumable one (the same
|
|
9
|
+
* substitution pnpm/yarn's own publish performs - see `resolveWorkspaceRange`), since `npm
|
|
10
|
+
* publish` reads what is on disk rather than packing from a staging tarball.
|
|
11
|
+
* - **Publishing a build directory** - there is no manifest there until something writes one, and
|
|
12
|
+
* that something is this: see `derivePublishManifest`.
|
|
13
|
+
*
|
|
14
|
+
* Returns `undefined` when there is nothing to do at all (the package directory, with no
|
|
15
|
+
* `"workspace:"` range in it) - no disk write, nothing to restore.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { exec } from '../../../utils/exec.js';
|
|
20
|
+
import { GitHelper } from '../../../utils/git.js';
|
|
21
|
+
import { filterPackages } from '../../../utils/package-filter.js';
|
|
22
|
+
import { isCalendarVersion } from '../../../utils/release-version.js';
|
|
23
|
+
import { DEPENDENCY_KEYS } from '../node-manifest.provider.js';
|
|
24
|
+
import { npmViewPackage } from '../utils/npm-view.js';
|
|
25
|
+
import { parseWorkspaceRange, resolveWorkspaceRange } from '../utils/workspace-range.js';
|
|
26
|
+
import { CiService } from './ci.service.js';
|
|
27
|
+
function preparePublishManifest(pkg, publishDir, packagesByName) {
|
|
28
|
+
if (path.resolve(publishDir) !== path.resolve(pkg.dirname)) {
|
|
29
|
+
const file = path.join(publishDir, 'package.json');
|
|
30
|
+
const previous = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : undefined;
|
|
31
|
+
fs.mkdirSync(publishDir, { recursive: true });
|
|
32
|
+
fs.writeFileSync(file, JSON.stringify(derivePublishManifest(pkg, packagesByName), undefined, 2) + '\n', 'utf-8');
|
|
33
|
+
return () => {
|
|
34
|
+
if (previous === undefined)
|
|
35
|
+
fs.rmSync(file, { force: true });
|
|
36
|
+
else
|
|
37
|
+
fs.writeFileSync(file, previous, 'utf-8');
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const hasWorkspaceRange = DEPENDENCY_KEYS.some(depKey => {
|
|
41
|
+
const deps = pkg.manifest.raw[depKey];
|
|
42
|
+
return deps && Object.values(deps).some(v => parseWorkspaceRange(v));
|
|
43
|
+
});
|
|
44
|
+
if (!hasWorkspaceRange)
|
|
45
|
+
return undefined;
|
|
46
|
+
/** The file's exact bytes, not a re-serialization: this is restored verbatim afterwards, so a
|
|
47
|
+
* round-trip through `JSON.stringify` would rewrite the author's formatting as a side effect of
|
|
48
|
+
* publishing. */
|
|
49
|
+
const original = fs.readFileSync(pkg.manifestFileName, 'utf-8');
|
|
50
|
+
resolveWorkspaceRanges(pkg.manifest.raw, packagesByName);
|
|
51
|
+
pkg.writeManifest();
|
|
52
|
+
return () => {
|
|
53
|
+
fs.writeFileSync(pkg.manifestFileName, original, 'utf-8');
|
|
54
|
+
pkg.reloadManifest();
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The manifest to publish from a build directory, derived from the package's own - generated here
|
|
59
|
+
* rather than by a build script, and deliberately with nothing to configure.
|
|
60
|
+
*
|
|
61
|
+
* Generated at *publish* time, which is the whole point: a build script writes it when the build
|
|
62
|
+
* runs, so bumping the version afterwards (or building before a bump) publishes a manifest that
|
|
63
|
+
* disagrees with the package - and the `"workspace:"` rewrite above, which only ever touched the
|
|
64
|
+
* package's own file, never reached the copy at all.
|
|
65
|
+
*
|
|
66
|
+
* What comes out is the package's `package.json` minus what a consumer of the tarball can neither
|
|
67
|
+
* see nor use:
|
|
68
|
+
*
|
|
69
|
+
* - `devDependencies` - npm never installs a dependency's own, so they are pure noise.
|
|
70
|
+
* - `scripts`, **except** `preinstall`/`install`/`postinstall`. Those three are the only ones a
|
|
71
|
+
* consumer's install actually runs, and dropping them would silently break every package that
|
|
72
|
+
* builds a native module on install. The rest (`build`, `test`, `prepare`, ...) never reach the
|
|
73
|
+
* consumer - `prepare` runs for a git dependency, which builds from the repository, not from this
|
|
74
|
+
* tarball.
|
|
75
|
+
* - `private` - rman refuses to publish a private package in the first place, so carrying the flag
|
|
76
|
+
* into a manifest that is being published can only be wrong.
|
|
77
|
+
* - `publishConfig.directory` - it pointed *here*; kept, it would point one level deeper again.
|
|
78
|
+
*/
|
|
79
|
+
function derivePublishManifest(pkg, packagesByName) {
|
|
80
|
+
const json = structuredClone(pkg.manifest.raw);
|
|
81
|
+
resolveWorkspaceRanges(json, packagesByName);
|
|
82
|
+
delete json.devDependencies;
|
|
83
|
+
delete json.private;
|
|
84
|
+
if (json.scripts) {
|
|
85
|
+
const kept = Object.fromEntries(Object.entries(json.scripts).filter(([name]) => CONSUMER_SCRIPTS.has(name)));
|
|
86
|
+
if (Object.keys(kept).length)
|
|
87
|
+
json.scripts = kept;
|
|
88
|
+
else
|
|
89
|
+
delete json.scripts;
|
|
90
|
+
}
|
|
91
|
+
if (json.publishConfig) {
|
|
92
|
+
delete json.publishConfig.directory;
|
|
93
|
+
if (!Object.keys(json.publishConfig).length)
|
|
94
|
+
delete json.publishConfig;
|
|
95
|
+
}
|
|
96
|
+
return json;
|
|
97
|
+
}
|
|
98
|
+
/** The only lifecycle scripts a consumer's `npm install` of this package runs - see
|
|
99
|
+
* https://docs.npmjs.com/cli/using-npm/scripts. */
|
|
100
|
+
const CONSUMER_SCRIPTS = new Set(['preinstall', 'install', 'postinstall']);
|
|
101
|
+
/** Rewrites `json`'s `"workspace:"` ranges in place, resolving each against the in-repo package it
|
|
102
|
+
* names. Shared by both manifest paths, so they can never disagree about the substitution. */
|
|
103
|
+
function resolveWorkspaceRanges(json, packagesByName) {
|
|
104
|
+
for (const depKey of DEPENDENCY_KEYS) {
|
|
105
|
+
const deps = json[depKey];
|
|
106
|
+
if (!deps)
|
|
107
|
+
continue;
|
|
108
|
+
for (const depName of Object.keys(deps)) {
|
|
109
|
+
const parsed = parseWorkspaceRange(deps[depName]);
|
|
110
|
+
if (!parsed)
|
|
111
|
+
continue;
|
|
112
|
+
const depPkg = packagesByName.get(depName);
|
|
113
|
+
if (depPkg)
|
|
114
|
+
deps[depName] = resolveWorkspaceRange(parsed, depPkg.version);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export var PublishService;
|
|
119
|
+
(function (PublishService) {
|
|
120
|
+
/**
|
|
121
|
+
* Computes what `publish` *would* do, across every non-private package (topological order,
|
|
122
|
+
* dependencies before dependents) - never touches the registry to publish anything, just
|
|
123
|
+
* queries it to decide, so it's safe to call any time, including as the plan a bare `rman
|
|
124
|
+
* publish` shows before asking for confirmation.
|
|
125
|
+
*
|
|
126
|
+
* A `private: true` package is always `'skip'`ped outright. Each remaining package's **dist-tag**
|
|
127
|
+
* is settled next (`distTagFor`): usually derived from the version itself, `'error'` in the two
|
|
128
|
+
* cases with nothing to derive - that one is about the invocation rather than the package, so it
|
|
129
|
+
* is answered before anything touches the network. A package with uncommitted local changes is
|
|
130
|
+
* `'error'` too (aborts the whole plan) unless `options.ignoreDirty` downgrades it to `'skip'`
|
|
131
|
+
* instead - same rule `version` uses, since publishing untracked local edits is worse than a bad
|
|
132
|
+
* commit.
|
|
133
|
+
*
|
|
134
|
+
* Otherwise the registry decides, via one `npmViewPackage` per remaining package (run
|
|
135
|
+
* concurrently): **the local version being among the published `versions`** is `'up-to-date'`,
|
|
136
|
+
* anything else - including never having been published at all - is `'publish'`. Deliberately not
|
|
137
|
+
* a comparison against `latest`, which answers a different question and disagrees with this one
|
|
138
|
+
* the moment a prerelease ships under its own dist-tag; see `npmViewPackage`.
|
|
139
|
+
*
|
|
140
|
+
* Deliberately decoupled from `version`: this only ever looks at what's *currently* on disk and
|
|
141
|
+
* on the registry, never at whether `version` was just run - so it works equally well right
|
|
142
|
+
* after a version bump, or standing alone in a release pipeline that bumped days earlier.
|
|
143
|
+
*
|
|
144
|
+
* A monorepo's root package is never a candidate at all - `repository.getPackages()` already
|
|
145
|
+
* excludes it for a real monorepo (it only doubles as "the" package in a single-package repo,
|
|
146
|
+
* where it's a normal candidate like any other).
|
|
147
|
+
*/
|
|
148
|
+
async function getPlan(repository, options = {}, deps = {}) {
|
|
149
|
+
const git = new GitHelper({ cwd: repository.dirname });
|
|
150
|
+
const packages = filterPackages(repository.getPackages({ toposort: true }), options);
|
|
151
|
+
const dirtyFiles = await git.listDirtyFiles({ absolute: true });
|
|
152
|
+
const isDirty = (pkg) => dirtyFiles.some(f => !path.relative(pkg.dirname, f).startsWith('..'));
|
|
153
|
+
const viewPackage = deps.npmViewPackage ?? ((name, cwd) => npmViewPackage(name, cwd, options));
|
|
154
|
+
const entries = new Map();
|
|
155
|
+
const toCheck = [];
|
|
156
|
+
for (const pkg of packages) {
|
|
157
|
+
/** Asked before the registry is, because it is about the *invocation* rather than the
|
|
158
|
+
* package: a prerelease heading for `latest` is wrong whatever the registry says, and
|
|
159
|
+
* finding that out after a round trip per package would be a slower way to the same
|
|
160
|
+
* refusal. */
|
|
161
|
+
const distTag = distTagFor(pkg, options.tag);
|
|
162
|
+
if (retiredDirectoryKey(pkg)) {
|
|
163
|
+
entries.set(pkg.name, {
|
|
164
|
+
package: pkg,
|
|
165
|
+
version: pkg.version,
|
|
166
|
+
status: 'error',
|
|
167
|
+
reason: '.rmanrc "publish.directory" is now "publish.npm.directory" - see the npm publish target',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else if (pkg.config.publish?.skip) {
|
|
171
|
+
entries.set(pkg.name, {
|
|
172
|
+
package: pkg,
|
|
173
|
+
version: pkg.version,
|
|
174
|
+
status: 'skip',
|
|
175
|
+
reason: 'excluded via .rmanrc "publish.skip"',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (pkg.isPrivate) {
|
|
179
|
+
entries.set(pkg.name, { package: pkg, version: pkg.version, status: 'skip', reason: 'private package' });
|
|
180
|
+
}
|
|
181
|
+
else if (distTag.error) {
|
|
182
|
+
entries.set(pkg.name, { package: pkg, version: pkg.version, status: 'error', reason: distTag.error });
|
|
183
|
+
}
|
|
184
|
+
else if (isDirty(pkg)) {
|
|
185
|
+
entries.set(pkg.name, {
|
|
186
|
+
package: pkg,
|
|
187
|
+
version: pkg.version,
|
|
188
|
+
status: options.ignoreDirty ? 'skip' : 'error',
|
|
189
|
+
reason: 'uncommitted local changes',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
toCheck.push(pkg);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
await Promise.all(toCheck.map(async (pkg) => {
|
|
197
|
+
const view = await viewPackage(pkg.name, pkg.dirname);
|
|
198
|
+
const registryVersion = view?.latest;
|
|
199
|
+
/** **This version**, not `latest` - the two part company the moment a prerelease is
|
|
200
|
+
* published under its own dist-tag, and `latest` then never moves however many betas go
|
|
201
|
+
* out. Asking it kept proposing an already-published version until npm answered 403. */
|
|
202
|
+
const published = !!view?.versions.includes(pkg.version);
|
|
203
|
+
const distTag = distTagFor(pkg, options.tag).tag;
|
|
204
|
+
entries.set(pkg.name, {
|
|
205
|
+
package: pkg,
|
|
206
|
+
version: pkg.version,
|
|
207
|
+
registryVersion,
|
|
208
|
+
distTag,
|
|
209
|
+
/** Printed beside the package in the plan, so where a version is going is something the
|
|
210
|
+
* reader confirms rather than something they have to infer from the version string. */
|
|
211
|
+
detail: distTag ? `-> dist-tag "${distTag}"` : undefined,
|
|
212
|
+
status: published ? 'up-to-date' : 'publish',
|
|
213
|
+
reason: published
|
|
214
|
+
? `registry already has ${pkg.version}`
|
|
215
|
+
: registryVersion
|
|
216
|
+
? `registry has ${registryVersion}`
|
|
217
|
+
: 'never published',
|
|
218
|
+
});
|
|
219
|
+
}));
|
|
220
|
+
return packages.map(pkg => entries.get(pkg.name));
|
|
221
|
+
}
|
|
222
|
+
PublishService.getPlan = getPlan;
|
|
223
|
+
/**
|
|
224
|
+
* Publishes every `'publish'` entry in `plan`, topological order (already `plan`'s own order -
|
|
225
|
+
* see `getPlan`), via the configured `packageManager`'s own `publish` command. Sequential, not
|
|
226
|
+
* concurrent: unlike `run`/`build`, a package genuinely needs its own dependencies to have
|
|
227
|
+
* landed on the registry first, and publishing is rare enough (once per release) that the
|
|
228
|
+
* simplicity is worth more than the parallelism `run` gets from `power-tasks`.
|
|
229
|
+
*
|
|
230
|
+
* If a package fails, every other still-pending entry depending on it (transitively) is marked
|
|
231
|
+
* `'error'` too and never attempted - publishing a package whose own new dependency range points
|
|
232
|
+
* at a version that never actually reached the registry would hand consumers a broken install.
|
|
233
|
+
* A package's own failure doesn't stop unrelated packages elsewhere in the plan, though.
|
|
234
|
+
*/
|
|
235
|
+
async function applyPlan(repository, plan, options = {}) {
|
|
236
|
+
const packageManager = CiService.resolvePackageManager(repository, options.packageManager);
|
|
237
|
+
const failed = new Set();
|
|
238
|
+
const result = [];
|
|
239
|
+
/** The tuple is explicit: `[p.name, p]` widens to `(string | Package)[]`, and whether `new Map`
|
|
240
|
+
* still infers `Map<string, Package>` from that came down to which declaration of
|
|
241
|
+
* `getPackages()` was in scope - the source one inferred it, the built `.d.ts` did not
|
|
242
|
+
* (measured, once the tests started type-checking). */
|
|
243
|
+
const packagesByName = new Map(repository.getPackages().map(p => [p.name, p]));
|
|
244
|
+
for (const entry of plan) {
|
|
245
|
+
if (entry.status !== 'publish') {
|
|
246
|
+
result.push(entry);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const pkg = entry.package;
|
|
250
|
+
const blocker = pkg.dependencies.find(d => failed.has(d.name));
|
|
251
|
+
if (blocker) {
|
|
252
|
+
failed.add(pkg.name);
|
|
253
|
+
result.push({ ...entry, status: 'error', reason: `dependency "${blocker.name}" failed to publish` });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const publishDir = resolvePublishDir(pkg, options.contents);
|
|
257
|
+
const restore = preparePublishManifest(pkg, publishDir, packagesByName);
|
|
258
|
+
try {
|
|
259
|
+
/** The plan's own tag, not a recomputed one: `getPlan` decided where this package goes,
|
|
260
|
+
* the reader confirmed that, and `applyPlan` is here to carry it out. `options.tag` is
|
|
261
|
+
* the fallback only for a plan built by something other than `getPlan`. */
|
|
262
|
+
await exec(buildPublishCommand(packageManager, { ...options, tag: entry.distTag ?? options.tag }), {
|
|
263
|
+
cwd: publishDir,
|
|
264
|
+
app: pkg.repository.app,
|
|
265
|
+
stdio: 'inherit',
|
|
266
|
+
});
|
|
267
|
+
result.push(entry);
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
failed.add(pkg.name);
|
|
271
|
+
result.push({ ...entry, status: 'error', reason: e.message });
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
restore?.();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
PublishService.applyPlan = applyPlan;
|
|
280
|
+
})(PublishService || (PublishService = {}));
|
|
281
|
+
/** Where the publishable output lives, most specific statement first: the package's own
|
|
282
|
+
* `publishConfig.directory` (npm/pnpm's native spelling, and a statement about that one package),
|
|
283
|
+
* then `.rmanrc "publish.npm.directory"` (which a `"[*]"` block can say once for a whole repository
|
|
284
|
+
* instead of repeating in every `package.json`), then `--contents` for a single run. */
|
|
285
|
+
function resolvePublishDir(pkg, contentsOverride) {
|
|
286
|
+
const native = pkg.manifest.raw.publishConfig?.directory;
|
|
287
|
+
const configured = pkg.config?.publish?.npm?.directory;
|
|
288
|
+
const rel = (typeof native === 'string' && native) || configured || contentsOverride;
|
|
289
|
+
return rel ? path.resolve(pkg.dirname, rel) : pkg.dirname;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* The retired `publish.directory` spelling, if a config still carries it.
|
|
293
|
+
*
|
|
294
|
+
* **Caught and refused, never ignored**, and the reason is what ignoring it would do: the key would
|
|
295
|
+
* silently stop being read, `resolvePublishDir` would fall back to the package's own directory, and
|
|
296
|
+
* the run would publish the *source tree* to npm instead of the build output. A rename that fails
|
|
297
|
+
* loudly costs one error message; one that goes quiet ships TypeScript sources to the registry.
|
|
298
|
+
*
|
|
299
|
+
* Checked in `getPlan` rather than at the write, for the same reason `version` validates its stamp
|
|
300
|
+
* list first: a configuration mistake has to surface before anything is published, and an `'error'`
|
|
301
|
+
* entry aborts the whole plan.
|
|
302
|
+
*
|
|
303
|
+
* YAML and JSON configs are unchecked at author time - there is no JSON Schema any more - so this is
|
|
304
|
+
* the only thing standing between the old spelling and a wrong publish.
|
|
305
|
+
*/
|
|
306
|
+
function retiredDirectoryKey(pkg) {
|
|
307
|
+
return pkg.config?.publish?.directory !== undefined;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Which dist-tag a package publishes under, or why the plan must refuse to publish it at all.
|
|
311
|
+
*
|
|
312
|
+
* **A prerelease names its own tag, so rman uses it**: `2.0.0-beta.1` goes to `beta`. That is a
|
|
313
|
+
* reading rather than a guess - the identifier is written in the version - and it is the whole
|
|
314
|
+
* point, because `npm publish` with no `--tag` writes **`latest`**. A beta that lands there is what
|
|
315
|
+
* every plain `npm install <name>` resolves to from then on; npm is content to point `latest` at a
|
|
316
|
+
* prerelease, and `npm dist-tag` can move it back only after everyone who installed in between
|
|
317
|
+
* already has the beta.
|
|
318
|
+
*
|
|
319
|
+
* **It is recorded in the plan rather than applied silently.** The derived tag becomes the entry's
|
|
320
|
+
* `distTag` and is printed beside the package, so where a version is going is something the reader
|
|
321
|
+
* confirms. Deriving it and saying nothing would be the same class of mistake in the other
|
|
322
|
+
* direction - the wrong tag is recoverable (`npm dist-tag add`), but only by someone who noticed.
|
|
323
|
+
*
|
|
324
|
+
* Two cases still refuse, because there is no honest answer to derive:
|
|
325
|
+
*
|
|
326
|
+
* - **an explicit `--tag latest` on a prerelease.** Someone who typed it is far likelier to have
|
|
327
|
+
* confused themselves than to mean it, and the escape hatch for genuinely meaning it - a bare
|
|
328
|
+
* `npm publish --tag latest` - is one command away.
|
|
329
|
+
* - **a prerelease with no identifier to name** (`2.0.0-1`, whose prerelease part is numeric).
|
|
330
|
+
* `VersionScheme.prereleaseId` answers `undefined` there rather than inventing a tag called `1`.
|
|
331
|
+
*
|
|
332
|
+
* **Both questions are the scheme's, not semver's** - `isPrerelease` and `prereleaseId` - and a
|
|
333
|
+
* **calendar version has to be ruled out first**: `2026.9.15-1430` carries a semver prerelease
|
|
334
|
+
* identifier by construction, because that is how the time is spelled, and it says nothing about
|
|
335
|
+
* the release being a preview. `github-release`'s own `resolvePrerelease` makes the same pair of
|
|
336
|
+
* checks; they agree deliberately.
|
|
337
|
+
*/
|
|
338
|
+
function distTagFor(pkg, tag) {
|
|
339
|
+
const isPreview = !isCalendarVersion(pkg.version) && pkg.versionScheme.isPrerelease(pkg.version);
|
|
340
|
+
if (!isPreview)
|
|
341
|
+
return { tag };
|
|
342
|
+
if (tag) {
|
|
343
|
+
if (tag !== 'latest')
|
|
344
|
+
return { tag };
|
|
345
|
+
return {
|
|
346
|
+
error: `${pkg.version} is a prerelease, so --tag latest would make it what every plain ` +
|
|
347
|
+
'install resolves to - drop the flag to publish it under its own identifier instead',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const derived = pkg.versionScheme.prereleaseId(pkg.version);
|
|
351
|
+
if (derived)
|
|
352
|
+
return { tag: derived };
|
|
353
|
+
return {
|
|
354
|
+
error: `${pkg.version} is a prerelease with no identifier to name a dist-tag after, and with ` +
|
|
355
|
+
'no --tag npm would put it on "latest" - pass --tag <name>',
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function buildPublishCommand(packageManager, options) {
|
|
359
|
+
const args = ['publish'];
|
|
360
|
+
if (options.access)
|
|
361
|
+
args.push('--access', options.access);
|
|
362
|
+
if (options.tag)
|
|
363
|
+
args.push('--tag', options.tag);
|
|
364
|
+
if (options.otp)
|
|
365
|
+
args.push('--otp', options.otp);
|
|
366
|
+
if (options.registry)
|
|
367
|
+
args.push('--registry', options.registry);
|
|
368
|
+
if (options.userconfig)
|
|
369
|
+
args.push('--userconfig', options.userconfig);
|
|
370
|
+
return `${packageManager} ${args.join(' ')}`;
|
|
371
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Package } from '../../../core/package.js';
|
|
2
|
+
import { VersionPlanService } from '../../../services/version-plan.service.js';
|
|
3
|
+
import type { GitHelper } from '../../../utils/git.js';
|
|
4
|
+
/**
|
|
5
|
+
* How a release is planned for an npm repository.
|
|
6
|
+
*
|
|
7
|
+
* rman's core holds the parts that are true of any repository - groups, conventional-commit
|
|
8
|
+
* severities, the cascade mechanics, the monorepo root's release identity - and leaves two decisions
|
|
9
|
+
* abstract because they are statements about an *ecosystem* rather than about releases. This is
|
|
10
|
+
* npm's pair of answers.
|
|
11
|
+
*
|
|
12
|
+
* Registered through the plugin's `versionPlanner`, so `rman version`/`rman changed` work in a
|
|
13
|
+
* repository that registered the `node` built-in and say what is missing in one that did not.
|
|
14
|
+
*/
|
|
15
|
+
export declare class NodeVersionPlanService extends VersionPlanService {
|
|
16
|
+
/**
|
|
17
|
+
* The shared `ChangeHashService.detect`, unmodified: this package's own latest release tag, and failing
|
|
18
|
+
* that whatever `Plugin.publishedVersion` reports, mapped back onto a tag name.
|
|
19
|
+
*
|
|
20
|
+
* **Nothing npm-specific is passed in any more**, and that is the point of the seam moving: the
|
|
21
|
+
* registry lookup is `packageJsonManifest.publishedVersion`'s now, dispatched per package, so this
|
|
22
|
+
* override exists only because `detectBoundary` is abstract. If the core ever makes it a concrete
|
|
23
|
+
* default, this method can go entirely.
|
|
24
|
+
*/
|
|
25
|
+
protected detectBoundary(git: GitHelper, pkg: Package): Promise<string | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* npm's dependency ranges, read as a release policy. What decides each case is whether a
|
|
28
|
+
* dependent's **range floor** has to move for a consumer to get a correct install:
|
|
29
|
+
*
|
|
30
|
+
* - **patch** - only the changed packages. A dependent's `^1.2.0` already resolves to `1.2.1`,
|
|
31
|
+
* and a patch adds nothing for the dependent to require, so nothing downstream has to ship for
|
|
32
|
+
* consumers to receive it.
|
|
33
|
+
* - **minor** - also every transitive in-group dependent. A minor adds API; a dependent that uses
|
|
34
|
+
* it is only correct once its own published range requires the new floor, and a range lives in
|
|
35
|
+
* a manifest, which only a release puts on the registry.
|
|
36
|
+
* - **major** - the whole group, changed or not. A breaking change restates every member's
|
|
37
|
+
* compatibility, including the members that merely point at one.
|
|
38
|
+
*
|
|
39
|
+
* None of this is about versions, which is why it is not in the core: an ecosystem that pins exact
|
|
40
|
+
* versions instead has to release every dependent for a patch as well, and one that resolves
|
|
41
|
+
* dependencies from source may not need a release for any of it.
|
|
42
|
+
*/
|
|
43
|
+
protected cascade(bump: string): VersionPlanService.Cascade;
|
|
44
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ChangeHashService } from '../../../services/change-hash.service.js';
|
|
2
|
+
import { VersionPlanService } from '../../../services/version-plan.service.js';
|
|
3
|
+
/**
|
|
4
|
+
* How a release is planned for an npm repository.
|
|
5
|
+
*
|
|
6
|
+
* rman's core holds the parts that are true of any repository - groups, conventional-commit
|
|
7
|
+
* severities, the cascade mechanics, the monorepo root's release identity - and leaves two decisions
|
|
8
|
+
* abstract because they are statements about an *ecosystem* rather than about releases. This is
|
|
9
|
+
* npm's pair of answers.
|
|
10
|
+
*
|
|
11
|
+
* Registered through the plugin's `versionPlanner`, so `rman version`/`rman changed` work in a
|
|
12
|
+
* repository that registered the `node` built-in and say what is missing in one that did not.
|
|
13
|
+
*/
|
|
14
|
+
export class NodeVersionPlanService extends VersionPlanService {
|
|
15
|
+
/**
|
|
16
|
+
* The shared `ChangeHashService.detect`, unmodified: this package's own latest release tag, and failing
|
|
17
|
+
* that whatever `Plugin.publishedVersion` reports, mapped back onto a tag name.
|
|
18
|
+
*
|
|
19
|
+
* **Nothing npm-specific is passed in any more**, and that is the point of the seam moving: the
|
|
20
|
+
* registry lookup is `packageJsonManifest.publishedVersion`'s now, dispatched per package, so this
|
|
21
|
+
* override exists only because `detectBoundary` is abstract. If the core ever makes it a concrete
|
|
22
|
+
* default, this method can go entirely.
|
|
23
|
+
*/
|
|
24
|
+
detectBoundary(git, pkg) {
|
|
25
|
+
return ChangeHashService.detect(git, pkg);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* npm's dependency ranges, read as a release policy. What decides each case is whether a
|
|
29
|
+
* dependent's **range floor** has to move for a consumer to get a correct install:
|
|
30
|
+
*
|
|
31
|
+
* - **patch** - only the changed packages. A dependent's `^1.2.0` already resolves to `1.2.1`,
|
|
32
|
+
* and a patch adds nothing for the dependent to require, so nothing downstream has to ship for
|
|
33
|
+
* consumers to receive it.
|
|
34
|
+
* - **minor** - also every transitive in-group dependent. A minor adds API; a dependent that uses
|
|
35
|
+
* it is only correct once its own published range requires the new floor, and a range lives in
|
|
36
|
+
* a manifest, which only a release puts on the registry.
|
|
37
|
+
* - **major** - the whole group, changed or not. A breaking change restates every member's
|
|
38
|
+
* compatibility, including the members that merely point at one.
|
|
39
|
+
*
|
|
40
|
+
* None of this is about versions, which is why it is not in the core: an ecosystem that pins exact
|
|
41
|
+
* versions instead has to release every dependent for a patch as well, and one that resolves
|
|
42
|
+
* dependencies from source may not need a release for any of it.
|
|
43
|
+
*/
|
|
44
|
+
cascade(bump) {
|
|
45
|
+
/** Only semver's three can arrive - `getPlan` validates against `bumpNames` and
|
|
46
|
+
* `packageJsonManifest` leaves the scheme at the semver default. The fallback is for a scheme
|
|
47
|
+
* someone swaps in underneath this planner, and it over-reaches on purpose: releasing a package
|
|
48
|
+
* that did not need it is noise, while under-reaching ships a dependent whose published range
|
|
49
|
+
* floor is wrong, which is a broken install. */
|
|
50
|
+
return CASCADE_BY_BUMP[bump] ?? 'group';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** semver's bump names against how far each has to reach - see `NodeVersionPlanService.cascade`. */
|
|
54
|
+
const CASCADE_BY_BUMP = {
|
|
55
|
+
patch: 'changed',
|
|
56
|
+
minor: 'dependents',
|
|
57
|
+
major: 'group',
|
|
58
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** What the registry knows about one package - the two halves `publish` needs, from one call. */
|
|
2
|
+
export interface NpmPackageView {
|
|
3
|
+
/** The version the **`latest` dist-tag** points at, which is what a bare `npm install <name>`
|
|
4
|
+
* resolves to. Not necessarily the highest: a prerelease published under its own tag, or a
|
|
5
|
+
* patch to an older line, leaves `latest` where it was. */
|
|
6
|
+
latest?: string;
|
|
7
|
+
/** Every version ever published, `latest` included. */
|
|
8
|
+
versions: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `npm view <name> version versions --json` - `undefined` for anything that is not an answer (never
|
|
12
|
+
* published, no network, private or restricted with no access). Catch-everything on purpose: every
|
|
13
|
+
* caller treats "no answer" as a legitimate state rather than a failure.
|
|
14
|
+
*
|
|
15
|
+
* **Two fields in one call, because the two questions are genuinely different and `publish` needs
|
|
16
|
+
* both.** Whether *this* version is already out there decides `up-to-date` vs `publish`, and only
|
|
17
|
+
* `versions` can answer it; what `latest` points at is what the plan *reports*, so a reader can see
|
|
18
|
+
* where the registry stands. Asking `latest` alone was the same approximation in both roles, and it
|
|
19
|
+
* is wrong in both directions: a prerelease on its own dist-tag never moves `latest`, so `publish`
|
|
20
|
+
* kept proposing an already-published version until npm answered 403; and a package whose local
|
|
21
|
+
* version is *behind* `latest` was proposed just as wrongly.
|
|
22
|
+
*
|
|
23
|
+
* **`versions` is normalized to an array defensively, and the reason is what happens if it is
|
|
24
|
+
* not.** `includes` exists on a string too, so a bare `"1.0.0"` would answer `versions.includes()`
|
|
25
|
+
* without any error at all - and answer it by substring, where `"1.0.10".includes("1.0.1")` is
|
|
26
|
+
* `true` (measured). npm's own `--json` output *is* a real array here on this npm (measured: a
|
|
27
|
+
* single-entry `maintainers` comes back as `[...]`, not unwrapped), so this guards a shape
|
|
28
|
+
* difference across npm versions rather than one I reproduced - but a silent wrong answer about
|
|
29
|
+
* whether a version is published is worth two lines.
|
|
30
|
+
*/
|
|
31
|
+
export declare function npmViewPackage(name: string, cwd: string, options?: {
|
|
32
|
+
registry?: string;
|
|
33
|
+
userconfig?: string;
|
|
34
|
+
}): Promise<NpmPackageView | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* `npm view <name> version` - the **`latest` dist-tag's** version, or `undefined` for anything that
|
|
37
|
+
* is not an answer.
|
|
38
|
+
*
|
|
39
|
+
* `packageJsonManifest.publishedVersion` asks this so that `detectChangeHash` can guess a tag name
|
|
40
|
+
* for a package with no release tag at all, and `latest` is the right question for it: it wants the
|
|
41
|
+
* one version the ecosystem considers current, not the set of everything ever published. It passes
|
|
42
|
+
* no options - a bare `npm view` already picks up the repository's own `.npmrc` from `cwd`, which
|
|
43
|
+
* is what that path wants. `publish` asks `npmViewPackage` instead; see there for why.
|
|
44
|
+
*/
|
|
45
|
+
export declare function npmViewVersion(name: string, cwd: string, options?: {
|
|
46
|
+
registry?: string;
|
|
47
|
+
userconfig?: string;
|
|
48
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
/**
|
|
4
|
+
* `npm view <name> version versions --json` - `undefined` for anything that is not an answer (never
|
|
5
|
+
* published, no network, private or restricted with no access). Catch-everything on purpose: every
|
|
6
|
+
* caller treats "no answer" as a legitimate state rather than a failure.
|
|
7
|
+
*
|
|
8
|
+
* **Two fields in one call, because the two questions are genuinely different and `publish` needs
|
|
9
|
+
* both.** Whether *this* version is already out there decides `up-to-date` vs `publish`, and only
|
|
10
|
+
* `versions` can answer it; what `latest` points at is what the plan *reports*, so a reader can see
|
|
11
|
+
* where the registry stands. Asking `latest` alone was the same approximation in both roles, and it
|
|
12
|
+
* is wrong in both directions: a prerelease on its own dist-tag never moves `latest`, so `publish`
|
|
13
|
+
* kept proposing an already-published version until npm answered 403; and a package whose local
|
|
14
|
+
* version is *behind* `latest` was proposed just as wrongly.
|
|
15
|
+
*
|
|
16
|
+
* **`versions` is normalized to an array defensively, and the reason is what happens if it is
|
|
17
|
+
* not.** `includes` exists on a string too, so a bare `"1.0.0"` would answer `versions.includes()`
|
|
18
|
+
* without any error at all - and answer it by substring, where `"1.0.10".includes("1.0.1")` is
|
|
19
|
+
* `true` (measured). npm's own `--json` output *is* a real array here on this npm (measured: a
|
|
20
|
+
* single-entry `maintainers` comes back as `[...]`, not unwrapped), so this guards a shape
|
|
21
|
+
* difference across npm versions rather than one I reproduced - but a silent wrong answer about
|
|
22
|
+
* whether a version is published is worth two lines.
|
|
23
|
+
*/
|
|
24
|
+
export async function npmViewPackage(name, cwd, options = {}) {
|
|
25
|
+
const argv = ['view', name, 'version', 'versions', '--json'];
|
|
26
|
+
if (options.registry)
|
|
27
|
+
argv.push('--registry', options.registry);
|
|
28
|
+
if (options.userconfig)
|
|
29
|
+
argv.push('--userconfig', options.userconfig);
|
|
30
|
+
try {
|
|
31
|
+
const { stdout } = await execFileAsync('npm', argv, { cwd });
|
|
32
|
+
const text = stdout.trim();
|
|
33
|
+
if (!text)
|
|
34
|
+
return undefined;
|
|
35
|
+
const raw = JSON.parse(text);
|
|
36
|
+
return { latest: raw.version, versions: toVersionList(raw.versions) };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `npm view <name> version` - the **`latest` dist-tag's** version, or `undefined` for anything that
|
|
44
|
+
* is not an answer.
|
|
45
|
+
*
|
|
46
|
+
* `packageJsonManifest.publishedVersion` asks this so that `detectChangeHash` can guess a tag name
|
|
47
|
+
* for a package with no release tag at all, and `latest` is the right question for it: it wants the
|
|
48
|
+
* one version the ecosystem considers current, not the set of everything ever published. It passes
|
|
49
|
+
* no options - a bare `npm view` already picks up the repository's own `.npmrc` from `cwd`, which
|
|
50
|
+
* is what that path wants. `publish` asks `npmViewPackage` instead; see there for why.
|
|
51
|
+
*/
|
|
52
|
+
export async function npmViewVersion(name, cwd, options = {}) {
|
|
53
|
+
const argv = ['view', name, 'version'];
|
|
54
|
+
if (options.registry)
|
|
55
|
+
argv.push('--registry', options.registry);
|
|
56
|
+
if (options.userconfig)
|
|
57
|
+
argv.push('--userconfig', options.userconfig);
|
|
58
|
+
try {
|
|
59
|
+
const { stdout } = await execFileAsync('npm', argv, { cwd });
|
|
60
|
+
return stdout.trim() || undefined;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function toVersionList(versions) {
|
|
67
|
+
if (Array.isArray(versions))
|
|
68
|
+
return versions;
|
|
69
|
+
return versions ? [versions] : [];
|
|
70
|
+
}
|
|
71
|
+
const execFileAsync = promisify(execFile);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `"workspace:"` dependency protocol - pnpm/yarn's spelling for "this dependency is a sibling in
|
|
3
|
+
* this repository", which npm's own workspaces understand too.
|
|
4
|
+
*
|
|
5
|
+
* **The `node` built-in's, because it is a statement about a `package.json` dependency field.** It sat in
|
|
6
|
+
* rman's core with a comment admitting it was only there because `publish` needed it from out here -
|
|
7
|
+
* and now `publish`, the manifest provider and the dependency-range rewrite all live in this
|
|
8
|
+
* package, so nothing in the core ever looked at it.
|
|
9
|
+
*/
|
|
10
|
+
export interface ParsedWorkspaceRange {
|
|
11
|
+
/** `'*'`/`'^'`/`'~'` for the bare selector forms; `'explicit'` when the protocol is followed by
|
|
12
|
+
* a concrete semver version/range instead (e.g. `"workspace:^1.0.0"`, `"workspace:1.0.0"`). */
|
|
13
|
+
selector: '*' | '^' | '~' | 'explicit';
|
|
14
|
+
/** Only set when `selector === 'explicit'` - the literal range following `"workspace:"`. */
|
|
15
|
+
range?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Parses a dependency range value for the pnpm/yarn `"workspace:"` protocol - `undefined` when
|
|
18
|
+
* `value` isn't a workspace range at all (a plain semver range, or not a string). */
|
|
19
|
+
export declare function parseWorkspaceRange(value: unknown): ParsedWorkspaceRange | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a parsed workspace range against `version` (the dependency's actual current version)
|
|
22
|
+
* into the real range a registry consumer would need - the same substitution pnpm/yarn's own
|
|
23
|
+
* publish performs: `"*"` pins the exact version (no operator), `"^"`/`"~"` prepend themselves to
|
|
24
|
+
* it, and an explicit range is used verbatim (it was already a real range, just workspace-prefixed).
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveWorkspaceRange(parsed: ParsedWorkspaceRange, version: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Parses a dependency range value for the pnpm/yarn `"workspace:"` protocol - `undefined` when
|
|
2
|
+
* `value` isn't a workspace range at all (a plain semver range, or not a string). */
|
|
3
|
+
export function parseWorkspaceRange(value) {
|
|
4
|
+
if (typeof value !== 'string' || !value.startsWith('workspace:'))
|
|
5
|
+
return undefined;
|
|
6
|
+
const rest = value.slice('workspace:'.length);
|
|
7
|
+
if (rest === '*' || rest === '^' || rest === '~')
|
|
8
|
+
return { selector: rest };
|
|
9
|
+
return { selector: 'explicit', range: rest };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a parsed workspace range against `version` (the dependency's actual current version)
|
|
13
|
+
* into the real range a registry consumer would need - the same substitution pnpm/yarn's own
|
|
14
|
+
* publish performs: `"*"` pins the exact version (no operator), `"^"`/`"~"` prepend themselves to
|
|
15
|
+
* it, and an explicit range is used verbatim (it was already a real range, just workspace-prefixed).
|
|
16
|
+
*/
|
|
17
|
+
export function resolveWorkspaceRange(parsed, version) {
|
|
18
|
+
switch (parsed.selector) {
|
|
19
|
+
case '*':
|
|
20
|
+
return version;
|
|
21
|
+
case '^':
|
|
22
|
+
return `^${version}`;
|
|
23
|
+
case '~':
|
|
24
|
+
return `~${version}`;
|
|
25
|
+
case 'explicit':
|
|
26
|
+
return parsed.range;
|
|
27
|
+
}
|
|
28
|
+
}
|