@rebasepro/cli 0.24.0 → 0.24.1-canary.gcb918fc

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.
Files changed (122) hide show
  1. package/bin/rebase.js +3 -0
  2. package/dist/agent-setup-B31F00DC.js +324 -0
  3. package/dist/agent-setup-B31F00DC.js.map +1 -0
  4. package/dist/api-keys-BPBi5nOF.js +524 -0
  5. package/dist/api-keys-BPBi5nOF.js.map +1 -0
  6. package/dist/apps-CPXNElX4.js +256 -0
  7. package/dist/apps-CPXNElX4.js.map +1 -0
  8. package/dist/args-BnmsEXvu.js +109 -0
  9. package/dist/args-BnmsEXvu.js.map +1 -0
  10. package/dist/atlas-dev-database-DTMuEScS.js +62 -0
  11. package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
  12. package/dist/auth-DvY94ZuL.js +395 -0
  13. package/dist/auth-DvY94ZuL.js.map +1 -0
  14. package/dist/branch-pointer-DIhKbQLp.js +2 -0
  15. package/dist/build-BHvC6Bjs.js +336 -0
  16. package/dist/build-BHvC6Bjs.js.map +1 -0
  17. package/dist/bundle-BQhP_xiA.js +1617 -0
  18. package/dist/bundle-BQhP_xiA.js.map +1 -0
  19. package/dist/cli.d.ts +2 -2
  20. package/dist/cloud-K6P5IHUb.js +9667 -0
  21. package/dist/cloud-K6P5IHUb.js.map +1 -0
  22. package/dist/collection-drift-BKj_BoMq.js +106 -0
  23. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  24. package/dist/commands/api-keys.d.ts +2 -0
  25. package/dist/commands/console.d.ts +12 -0
  26. package/dist/commands/db.d.ts +64 -24
  27. package/dist/commands/dev.d.ts +103 -0
  28. package/dist/commands/generate_sdk.d.ts +26 -0
  29. package/dist/commands/skills.d.ts +22 -7
  30. package/dist/commands/storage.d.ts +46 -0
  31. package/dist/consent-BWHaYx5p.js +76 -0
  32. package/dist/consent-BWHaYx5p.js.map +1 -0
  33. package/dist/console-Cs00E-dt.js +102 -0
  34. package/dist/console-Cs00E-dt.js.map +1 -0
  35. package/dist/context-BqhRydBg.js +1234 -0
  36. package/dist/context-BqhRydBg.js.map +1 -0
  37. package/dist/daemon-DBn-shxX.js +2 -0
  38. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  39. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  40. package/dist/db-YbPMFY4Y.js +1252 -0
  41. package/dist/db-YbPMFY4Y.js.map +1 -0
  42. package/dist/derive-QMPMcQZJ.js +484 -0
  43. package/dist/derive-QMPMcQZJ.js.map +1 -0
  44. package/dist/dev-CJ_dd9uP.js +1593 -0
  45. package/dist/dev-CJ_dd9uP.js.map +1 -0
  46. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  47. package/dist/dev-db/daemon-entry.d.ts +13 -0
  48. package/dist/dev-preflight-N1evJL3S.js +2 -0
  49. package/dist/doctor-CIcpWl_L.js +771 -0
  50. package/dist/doctor-CIcpWl_L.js.map +1 -0
  51. package/dist/eject-BT6t37D0.js +335 -0
  52. package/dist/eject-BT6t37D0.js.map +1 -0
  53. package/dist/fold-static-C1EtHlfV.js +201 -0
  54. package/dist/fold-static-C1EtHlfV.js.map +1 -0
  55. package/dist/function-portability-BIvlvR9q.js +265 -0
  56. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  57. package/dist/generate_sdk-UV635zSe.js +477 -0
  58. package/dist/generate_sdk-UV635zSe.js.map +1 -0
  59. package/dist/index.es.js +150 -24674
  60. package/dist/index.es.js.map +1 -1
  61. package/dist/init-fXeBcxuN.js +1042 -0
  62. package/dist/init-fXeBcxuN.js.map +1 -0
  63. package/dist/manifest-DzlC60WX.js +871 -0
  64. package/dist/manifest-DzlC60WX.js.map +1 -0
  65. package/dist/normalize-imports-CmVdE9iZ.js +100 -0
  66. package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
  67. package/dist/{package-manager-CXQevsE1.js → package-manager-DSEOwTqM.js} +2 -2
  68. package/dist/{package-manager-CXQevsE1.js.map → package-manager-DSEOwTqM.js.map} +1 -1
  69. package/dist/path-flags-DUBuI2i1.js +77 -0
  70. package/dist/path-flags-DUBuI2i1.js.map +1 -0
  71. package/dist/precompress.d.ts +17 -0
  72. package/dist/prepare-Btn9Ewbn.js +1052 -0
  73. package/dist/prepare-Btn9Ewbn.js.map +1 -0
  74. package/dist/{project-DmaGLmH2.js → project-Wp2zoyeN.js} +33 -4
  75. package/dist/project-Wp2zoyeN.js.map +1 -0
  76. package/dist/resolve-CJLkfFV0.js +2 -0
  77. package/dist/resources-3J3UQg5r.js +161 -0
  78. package/dist/resources-3J3UQg5r.js.map +1 -0
  79. package/dist/schema-ovO8qJDw.js +125 -0
  80. package/dist/schema-ovO8qJDw.js.map +1 -0
  81. package/dist/skills-DTFibT1m.js +642 -0
  82. package/dist/skills-DTFibT1m.js.map +1 -0
  83. package/dist/start-rU2e3Kp5.js +228 -0
  84. package/dist/start-rU2e3Kp5.js.map +1 -0
  85. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  86. package/dist/state-Do7ZJYkF.js.map +1 -0
  87. package/dist/status-Cfy0TtSb.js +351 -0
  88. package/dist/status-Cfy0TtSb.js.map +1 -0
  89. package/dist/storage-DyvrSTH3.js +303 -0
  90. package/dist/storage-DyvrSTH3.js.map +1 -0
  91. package/dist/telemetry-CAWNCugs.js +338 -0
  92. package/dist/telemetry-CAWNCugs.js.map +1 -0
  93. package/dist/telemetry-DbLqGIBW.js +129 -0
  94. package/dist/telemetry-DbLqGIBW.js.map +1 -0
  95. package/dist/unknown-command-DNwDcX3w.js +98 -0
  96. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  97. package/dist/upgrade-D-5-Od96.js +1265 -0
  98. package/dist/upgrade-D-5-Od96.js.map +1 -0
  99. package/dist/utils/project.d.ts +16 -0
  100. package/dist/utils/temp-archive.d.ts +9 -0
  101. package/dist/{version-DsgBmP22.js → version-Buyyo1ut.js} +2 -2
  102. package/dist/{version-DsgBmP22.js.map → version-Buyyo1ut.js.map} +1 -1
  103. package/package.json +7 -7
  104. package/templates/eject/backend/src/env.ts +1 -1
  105. package/templates/eject/backend/src/index.ts +10 -2
  106. package/templates/template/README.md +1 -1
  107. package/templates/template/ai-instructions.md +2 -1
  108. package/templates/template/frontend/vite.config.ts +13 -3
  109. package/templates/template/scripts/example.ts +2 -1
  110. package/dist/branch-pointer-kk9F5FBO.js +0 -111
  111. package/dist/branch-pointer-kk9F5FBO.js.map +0 -1
  112. package/dist/constraints-DKGbNfUW.js +0 -48
  113. package/dist/constraints-DKGbNfUW.js.map +0 -1
  114. package/dist/daemon-C8p32i-n.js +0 -357
  115. package/dist/daemon-C8p32i-n.js.map +0 -1
  116. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  117. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  118. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  119. package/dist/project-DmaGLmH2.js.map +0 -1
  120. package/dist/resolve-BAeRUBhg.js +0 -74
  121. package/dist/resolve-BAeRUBhg.js.map +0 -1
  122. package/dist/state-C59Elrnt.js.map +0 -1
@@ -0,0 +1,1265 @@
1
+ import { n as getPMCommands } from "./package-manager-DSEOwTqM.js";
2
+ import { i as failAsJson, p as requireProjectRoot } from "./project-Wp2zoyeN.js";
3
+ import { n as parseCommandArgs, r as wantsHelp } from "./args-BnmsEXvu.js";
4
+ import { o as ManifestError, p as loadManifest } from "./manifest-DzlC60WX.js";
5
+ import chalk from "chalk";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { execFileSync } from "child_process";
9
+ import { execa } from "execa";
10
+ import { isDeepStrictEqual } from "util";
11
+ //#region src/upgrade.ts
12
+ /**
13
+ * Moving a project's `@rebasepro/*` pins to one release.
14
+ *
15
+ * Every `@rebasepro/*` package ships at one version, and a project declares them
16
+ * in several `package.json` files — the root, `backend/`, `frontend/`,
17
+ * `config/`, and whatever else the repository holds. Bumping them by hand means
18
+ * finding every one, keeping each range's `^` or `~`, and noticing the override
19
+ * that quietly wins over all of them. `rebase upgrade` is that job, done the same
20
+ * way every time; the control plane runs it too, when it rebuilds a managed
21
+ * project on a newer release.
22
+ *
23
+ * Everything here is file work — git is asked only which files are the
24
+ * project's — so it can be tested against a temporary directory: which files
25
+ * are read, which specs move, the rewrite itself, and what overrides say. The
26
+ * command in `commands/upgrade.ts` adds the registry lookup, the install and the
27
+ * printing.
28
+ *
29
+ * ## The rewrite is textual
30
+ *
31
+ * A `package.json` is authored. Parsing it and writing `JSON.stringify` back
32
+ * reformats the file, reorders nothing but reindents everything, and drops a
33
+ * trailing newline or a two-space indent somebody chose — a one-line version
34
+ * bump that arrives as a whole-file diff. So the file is scanned for the exact
35
+ * byte range of each value, only those bytes are replaced, and the result is
36
+ * parsed again and compared against what was intended. A file whose rewrite
37
+ * does not parse back to exactly the intended values is not written.
38
+ */
39
+ /** The dependency blocks a pin is moved in. `peerDependencies` is not one. */
40
+ var PIN_FIELDS = [
41
+ "dependencies",
42
+ "devDependencies",
43
+ "optionalDependencies"
44
+ ];
45
+ /** The package whose published versions stand for the release as a whole. */
46
+ var RELEASE_PACKAGE = "@rebasepro/cli";
47
+ /** `MAJOR.MINOR.PATCH`, with an optional prerelease and build, and nothing else. */
48
+ var SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
49
+ /** Whether `value` is one exact version — `0.21.0`, `0.21.1-canary.g8c5a265`. */
50
+ function isExactVersion(value) {
51
+ return SEMVER.test(value);
52
+ }
53
+ /** An error that carries the `--json` envelope's code and a remedy. */
54
+ var UpgradeError = class extends Error {
55
+ code;
56
+ hint;
57
+ constructor(message, code, hint) {
58
+ super(message);
59
+ this.code = code;
60
+ this.hint = hint;
61
+ this.name = "UpgradeError";
62
+ }
63
+ };
64
+ /**
65
+ * What a dependency spec is, as far as moving it goes.
66
+ *
67
+ * Only a plain version is moved, because only a plain version has an obvious
68
+ * answer: `^0.19.1` becomes `^0.21.0`. A `>=` floor, a `||` union or a partial
69
+ * `^0.19` could each be rewritten several ways, and a tag or a workspace link
70
+ * already means something other than "this release" — so each is reported with
71
+ * its reason and left as written.
72
+ */
73
+ function classifySpec(spec) {
74
+ const movable = /^([\^~]?)(.+)$/.exec(spec);
75
+ if (movable && isExactVersion(movable[2])) return {
76
+ kind: "movable",
77
+ prefix: movable[1],
78
+ version: movable[2]
79
+ };
80
+ for (const protocol of [
81
+ "link:",
82
+ "file:",
83
+ "portal:"
84
+ ]) if (spec.startsWith(protocol)) return {
85
+ kind: "local",
86
+ protocol
87
+ };
88
+ const catalog = /^catalog:(.*)$/.exec(spec.trim());
89
+ if (catalog) return {
90
+ kind: "catalog",
91
+ catalog: catalog[1].trim() || "default"
92
+ };
93
+ return {
94
+ kind: "other",
95
+ reason: otherReason(spec)
96
+ };
97
+ }
98
+ /** Why a spec that does not move is left alone, as the report says it. */
99
+ function leftAloneReason(cls) {
100
+ if (cls.kind === "local") return `a local ${cls.protocol} path, which points at a checkout on this machine rather than a release`;
101
+ if (cls.kind === "catalog") return "a catalog: reference inside a catalog, which pnpm does not resolve";
102
+ return cls.reason;
103
+ }
104
+ function otherReason(spec) {
105
+ const trimmed = spec.trim();
106
+ if (trimmed.startsWith("workspace:")) return "a workspace: link, resolved inside this repository rather than from the registry";
107
+ if (trimmed.startsWith("npm:")) return "an npm: alias; move the aliased version by hand";
108
+ if (/^(git\+|git:|github:|gitlab:|bitbucket:|git@)/.test(trimmed) || /^https?:\/\//.test(trimmed)) return "a git or URL dependency, not a registry version";
109
+ if (trimmed === "" || trimmed === "*" || /^[xX]$/.test(trimmed)) return "a wildcard, which already takes whatever the registry has";
110
+ if (trimmed.includes("||")) return "a union of ranges, with no single version to move";
111
+ if (/^[<>=]/.test(trimmed) || /\s-\s/.test(trimmed)) return "a comparator range, with no single version to move";
112
+ if (/^[\^~]?v?\d/.test(trimmed)) return "not a plain MAJOR.MINOR.PATCH version";
113
+ return "a dist-tag, not a version";
114
+ }
115
+ /**
116
+ * The `@rebasepro/*` package an override key targets, or null.
117
+ *
118
+ * pnpm and npm key an override by name, optionally narrowed: `@rebasepro/x@<1`,
119
+ * `parent>@rebasepro/x`; yarn's `resolutions` by a path, `**\/@rebasepro/x`. The
120
+ * target is the last segment either way.
121
+ */
122
+ function overrideTarget(key) {
123
+ const match = /(?:^|[/>])(@rebasepro\/[^/@>\s]+)(?:@[^/>]*)?$/.exec(key);
124
+ return match ? match[1] : null;
125
+ }
126
+ /**
127
+ * Directories the walk never enters: installed packages, build output (`dist`,
128
+ * `dist-bundle`, `dist-bundle-admin`, …) and anything hidden (`.git`,
129
+ * `.rebase`, an editor's worktrees). None of them hold a manifest anybody
130
+ * authored for this project, and `dist-bundle/package.json` in particular is a
131
+ * generated copy that `rebase build` rewrites anyway. They are skipped whether
132
+ * or not git ignores them.
133
+ */
134
+ function skipDirectory(name) {
135
+ return name === "node_modules" || name.startsWith("dist") || name.startsWith(".");
136
+ }
137
+ var runGit = (cwd, args) => execFileSync("git", [
138
+ "-C",
139
+ cwd,
140
+ ...args
141
+ ], {
142
+ encoding: "utf8",
143
+ stdio: [
144
+ "ignore",
145
+ "pipe",
146
+ "ignore"
147
+ ],
148
+ maxBuffer: 256 * 1024 * 1024
149
+ });
150
+ /**
151
+ * The files git counts as part of the project — tracked, or untracked and not
152
+ * ignored — relative to `projectRoot`, POSIX. Null when git has no say: no git,
153
+ * no repository, or a repository that lists nothing under the project root.
154
+ *
155
+ * Listing nothing means the repository ignores the project as a whole: a copy
156
+ * of it under a gitignored scratch directory, say. Running the upgrade in that
157
+ * copy is asking for the copy to move, and the repository's rules would leave
158
+ * nothing in it to move. Asked with `git ls-files` rather than
159
+ * `git check-ignore`, which answers differently across git versions for a
160
+ * directory and for a negated pattern.
161
+ */
162
+ function gitProjectFiles(projectRoot, git) {
163
+ let listed;
164
+ try {
165
+ listed = git(projectRoot, [
166
+ "ls-files",
167
+ "-z",
168
+ "--cached",
169
+ "--others",
170
+ "--exclude-standard"
171
+ ]);
172
+ } catch {
173
+ return null;
174
+ }
175
+ const files = new Set(listed.split("\0").filter(Boolean));
176
+ return files.size > 0 ? files : null;
177
+ }
178
+ /**
179
+ * Every `package.json` and `pnpm-workspace.yaml` under the project root that
180
+ * belongs to the project.
181
+ *
182
+ * In a git repository that means what git lists: a gitignored build context,
183
+ * scratch copy or vendored package is not the project's, and rewriting it
184
+ * would change files nobody committed. The walk enters only directories that
185
+ * hold a listed file, so an ignored tree is not read at all. Outside a
186
+ * repository every directory is walked. {@link skipDirectory} applies either
187
+ * way.
188
+ */
189
+ function discoverProjectFiles(projectRoot, git = runGit) {
190
+ const found = {
191
+ packageJsons: [],
192
+ workspaceYamls: []
193
+ };
194
+ const listed = gitProjectFiles(projectRoot, git);
195
+ /** Every directory a listed file sits in, at any depth. */
196
+ const listedDirectories = /* @__PURE__ */ new Set();
197
+ for (const file of listed ?? []) for (let dir = path.posix.dirname(file); dir !== "." && !listedDirectories.has(dir); dir = path.posix.dirname(dir)) listedDirectories.add(dir);
198
+ const walk = (dir, relativeDir) => {
199
+ let entries;
200
+ try {
201
+ entries = fs.readdirSync(dir, { withFileTypes: true });
202
+ } catch {
203
+ return;
204
+ }
205
+ entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
206
+ for (const entry of entries) {
207
+ const full = path.join(dir, entry.name);
208
+ const relative = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
209
+ if (entry.isDirectory()) {
210
+ if (!skipDirectory(entry.name) && (!listed || listedDirectories.has(relative))) walk(full, relative);
211
+ } else if (entry.isFile() && (!listed || listed.has(relative))) {
212
+ if (entry.name === "package.json") found.packageJsons.push(full);
213
+ else if (entry.name === "pnpm-workspace.yaml") found.workspaceYamls.push(full);
214
+ }
215
+ }
216
+ };
217
+ walk(projectRoot, "");
218
+ return found;
219
+ }
220
+ function catalogKey(catalog, pkg) {
221
+ return `${catalog}\u0000${pkg}`;
222
+ }
223
+ /** Project-relative, POSIX. What every file path in the output looks like. */
224
+ function relativeTo(projectRoot, absolute) {
225
+ return path.relative(projectRoot, absolute).split(path.sep).join("/");
226
+ }
227
+ /**
228
+ * Work out every change the upgrade makes, without writing anything.
229
+ *
230
+ * `--dry-run` prints this; a real run writes `writes` and nothing else.
231
+ */
232
+ function planUpgrade(projectRoot, target, options = {}) {
233
+ if (!isExactVersion(target)) throw new UpgradeError(`"${target}" is not an exact version.`, "target_invalid");
234
+ const plan = {
235
+ target,
236
+ changed: [],
237
+ skipped: [],
238
+ overrides: [],
239
+ unreadable: [],
240
+ writes: [],
241
+ unchanged: 0
242
+ };
243
+ const ledger = {
244
+ refs: [],
245
+ defined: /* @__PURE__ */ new Set()
246
+ };
247
+ const files = discoverProjectFiles(projectRoot);
248
+ for (const absolute of files.packageJsons) {
249
+ const file = relativeTo(projectRoot, absolute);
250
+ const original = fs.readFileSync(absolute, "utf8");
251
+ const content = planPackageJson(file, original, target, options, plan, ledger);
252
+ if (content !== null && content !== original) plan.writes.push({
253
+ file,
254
+ absolute,
255
+ content
256
+ });
257
+ }
258
+ for (const absolute of files.workspaceYamls) {
259
+ const file = relativeTo(projectRoot, absolute);
260
+ const original = fs.readFileSync(absolute, "utf8");
261
+ const content = planWorkspaceYaml(file, original, target, options, plan, ledger);
262
+ if (content !== original) plan.writes.push({
263
+ file,
264
+ absolute,
265
+ content
266
+ });
267
+ }
268
+ for (const ref of ledger.refs) {
269
+ if (ledger.defined.has(catalogKey(ref.catalog, ref.pkg))) continue;
270
+ plan.skipped.push({
271
+ file: ref.file,
272
+ name: ref.name,
273
+ field: ref.field,
274
+ spec: ref.spec,
275
+ reason: `a pnpm catalog: reference, and no catalog under this project defines it — move the version where the "${ref.catalog}" catalog is declared`
276
+ });
277
+ }
278
+ return plan;
279
+ }
280
+ /** Write what the plan changes. Nothing else on disk is touched. */
281
+ function applyUpgradePlan(plan) {
282
+ for (const write of plan.writes) fs.writeFileSync(write.absolute, write.content, "utf8");
283
+ }
284
+ /** Override blocks in a `package.json`, as paths from the root object. */
285
+ var JSON_OVERRIDE_BLOCKS = [
286
+ ["pnpm", "overrides"],
287
+ ["overrides"],
288
+ ["resolutions"]
289
+ ];
290
+ function planPackageJson(file, original, target, options, plan, ledger) {
291
+ const bom = original.startsWith("") ? "" : "";
292
+ const text = original.slice(bom.length);
293
+ let parsed;
294
+ let tree;
295
+ try {
296
+ parsed = JSON.parse(text);
297
+ tree = scanJson(text);
298
+ } catch (err) {
299
+ plan.unreadable.push({
300
+ file,
301
+ reason: `not valid JSON: ${err instanceof Error ? err.message : String(err)}`
302
+ });
303
+ return null;
304
+ }
305
+ if (!isRecord(parsed) || tree.kind !== "object") {
306
+ plan.unreadable.push({
307
+ file,
308
+ reason: "not a JSON object"
309
+ });
310
+ return null;
311
+ }
312
+ const expected = structuredClone(parsed);
313
+ const edits = [];
314
+ /** Override members to remove, as [block path, key]. */
315
+ const removals = [];
316
+ for (const field of PIN_FIELDS) {
317
+ const block = memberValue(tree, [field]);
318
+ if (block?.kind !== "object") continue;
319
+ for (const member of block.members) {
320
+ if (!member.key.startsWith("@rebasepro/")) continue;
321
+ if (member.value.kind !== "string") {
322
+ plan.skipped.push({
323
+ file,
324
+ name: member.key,
325
+ field,
326
+ spec: text.slice(member.value.start, member.value.end),
327
+ reason: "not a string"
328
+ });
329
+ continue;
330
+ }
331
+ const spec = member.value.value;
332
+ const cls = classifySpec(spec);
333
+ if (cls.kind === "movable") {
334
+ const to = `${cls.prefix}${target}`;
335
+ if (to === spec) {
336
+ plan.unchanged++;
337
+ continue;
338
+ }
339
+ edits.push({
340
+ start: member.value.start,
341
+ end: member.value.end,
342
+ replacement: JSON.stringify(to)
343
+ });
344
+ setPath(expected, [field, member.key], to);
345
+ plan.changed.push({
346
+ file,
347
+ name: member.key,
348
+ field,
349
+ from: spec,
350
+ to
351
+ });
352
+ } else if (cls.kind === "catalog") ledger.refs.push({
353
+ file,
354
+ name: member.key,
355
+ field,
356
+ spec,
357
+ catalog: cls.catalog,
358
+ pkg: member.key
359
+ });
360
+ else plan.skipped.push({
361
+ file,
362
+ name: member.key,
363
+ field,
364
+ spec,
365
+ reason: leftAloneReason(cls)
366
+ });
367
+ }
368
+ }
369
+ for (const blockPath of JSON_OVERRIDE_BLOCKS) {
370
+ const block = memberValue(tree, blockPath);
371
+ if (block?.kind !== "object") continue;
372
+ const field = blockPath.join(".");
373
+ for (const member of block.members) {
374
+ if (!overrideTarget(member.key)) continue;
375
+ if (member.value.kind !== "string") {
376
+ plan.skipped.push({
377
+ file,
378
+ name: member.key,
379
+ field,
380
+ spec: text.slice(member.value.start, member.value.end),
381
+ reason: "a nested override, which has no single version to move; edit it by hand"
382
+ });
383
+ continue;
384
+ }
385
+ const spec = member.value.value;
386
+ const cls = classifySpec(spec);
387
+ const pkg = overrideTarget(member.key);
388
+ if (cls.kind === "movable") {
389
+ const to = `${cls.prefix}${target}`;
390
+ if (to === spec) {
391
+ plan.unchanged++;
392
+ continue;
393
+ }
394
+ edits.push({
395
+ start: member.value.start,
396
+ end: member.value.end,
397
+ replacement: JSON.stringify(to)
398
+ });
399
+ setPath(expected, [...blockPath, member.key], to);
400
+ plan.overrides.push({
401
+ file,
402
+ name: member.key,
403
+ spec,
404
+ action: "bumped",
405
+ to
406
+ });
407
+ } else if (cls.kind === "catalog" && pkg) ledger.refs.push({
408
+ file,
409
+ name: member.key,
410
+ field,
411
+ spec,
412
+ catalog: cls.catalog,
413
+ pkg
414
+ });
415
+ else if (cls.kind === "local") if (options.dropLocalOverrides) {
416
+ removals.push([blockPath, member.key]);
417
+ deletePath(expected, [...blockPath, member.key]);
418
+ plan.overrides.push({
419
+ file,
420
+ name: member.key,
421
+ spec,
422
+ action: "removed-local"
423
+ });
424
+ } else plan.overrides.push({
425
+ file,
426
+ name: member.key,
427
+ spec,
428
+ action: "kept-local"
429
+ });
430
+ else plan.skipped.push({
431
+ file,
432
+ name: member.key,
433
+ field,
434
+ spec,
435
+ reason: leftAloneReason(cls)
436
+ });
437
+ }
438
+ }
439
+ if (edits.length === 0 && removals.length === 0) return original;
440
+ let next = applyEdits(text, edits);
441
+ for (const [blockPath, key] of removals) next = removeJsonMember(next, blockPath, key);
442
+ for (const blockPath of JSON_OVERRIDE_BLOCKS) {
443
+ if (!removals.some(([removedFrom]) => removedFrom === blockPath)) continue;
444
+ for (let depth = blockPath.length; depth > 0; depth--) {
445
+ const containerPath = blockPath.slice(0, depth);
446
+ const node = memberValue(scanJson(next), containerPath);
447
+ if (node?.kind !== "object" || node.members.length > 0) break;
448
+ next = removeJsonMember(next, containerPath.slice(0, -1), containerPath[containerPath.length - 1]);
449
+ deletePath(expected, containerPath);
450
+ }
451
+ }
452
+ let reparsed;
453
+ try {
454
+ reparsed = JSON.parse(next);
455
+ } catch {
456
+ reparsed = void 0;
457
+ }
458
+ if (!isDeepStrictEqual(reparsed, expected)) throw new UpgradeError(`Could not rewrite ${file} without disturbing it; nothing was written.`, "rewrite_failed", "Move its @rebasepro versions by hand, then run `rebase upgrade` again.");
459
+ return bom + next;
460
+ }
461
+ /**
462
+ * The structure of a JSON document, with where each value starts and ends.
463
+ *
464
+ * `JSON.parse` answers what a file says; this answers where it says it, which is
465
+ * what a rewrite that leaves every other byte alone needs. Throws on anything
466
+ * that is not JSON — callers have already parsed the same text, so that never
467
+ * happens in practice.
468
+ */
469
+ function scanJson(text) {
470
+ let i = 0;
471
+ const WHITESPACE = " \n\r";
472
+ const skipWhitespace = () => {
473
+ while (i < text.length && WHITESPACE.includes(text[i])) i++;
474
+ };
475
+ const unexpected = () => {
476
+ throw new Error(`unexpected ${i < text.length ? `"${text[i]}"` : "end of input"} at offset ${i}`);
477
+ };
478
+ const readString = () => {
479
+ const start = i;
480
+ if (text[i] !== "\"") unexpected();
481
+ i++;
482
+ while (i < text.length && text[i] !== "\"") i += text[i] === "\\" ? 2 : 1;
483
+ if (i >= text.length) unexpected();
484
+ i++;
485
+ return {
486
+ kind: "string",
487
+ start,
488
+ end: i,
489
+ value: JSON.parse(text.slice(start, i))
490
+ };
491
+ };
492
+ const readValue = () => {
493
+ skipWhitespace();
494
+ const start = i;
495
+ if (text[i] === "{") {
496
+ i++;
497
+ const members = [];
498
+ skipWhitespace();
499
+ if (text[i] === "}") {
500
+ i++;
501
+ return {
502
+ kind: "object",
503
+ start,
504
+ end: i,
505
+ members
506
+ };
507
+ }
508
+ for (;;) {
509
+ skipWhitespace();
510
+ const key = readString();
511
+ skipWhitespace();
512
+ if (text[i] !== ":") unexpected();
513
+ i++;
514
+ members.push({
515
+ key: key.value,
516
+ keyStart: key.start,
517
+ value: readValue()
518
+ });
519
+ skipWhitespace();
520
+ if (text[i] === ",") {
521
+ i++;
522
+ continue;
523
+ }
524
+ if (text[i] === "}") {
525
+ i++;
526
+ return {
527
+ kind: "object",
528
+ start,
529
+ end: i,
530
+ members
531
+ };
532
+ }
533
+ unexpected();
534
+ }
535
+ }
536
+ if (text[i] === "[") {
537
+ i++;
538
+ skipWhitespace();
539
+ if (text[i] === "]") {
540
+ i++;
541
+ return {
542
+ kind: "array",
543
+ start,
544
+ end: i
545
+ };
546
+ }
547
+ for (;;) {
548
+ readValue();
549
+ skipWhitespace();
550
+ if (text[i] === ",") {
551
+ i++;
552
+ continue;
553
+ }
554
+ if (text[i] === "]") {
555
+ i++;
556
+ return {
557
+ kind: "array",
558
+ start,
559
+ end: i
560
+ };
561
+ }
562
+ unexpected();
563
+ }
564
+ }
565
+ if (text[i] === "\"") return readString();
566
+ while (i < text.length && !",:{}[]\"".includes(text[i]) && !WHITESPACE.includes(text[i])) i++;
567
+ if (i === start) unexpected();
568
+ return {
569
+ kind: "scalar",
570
+ start,
571
+ end: i
572
+ };
573
+ };
574
+ const root = readValue();
575
+ skipWhitespace();
576
+ if (i !== text.length) unexpected();
577
+ return root;
578
+ }
579
+ /** The value at a path of object keys, or undefined. The last duplicate wins, as in `JSON.parse`. */
580
+ function memberValue(root, keys) {
581
+ let node = root;
582
+ for (const key of keys) {
583
+ if (node?.kind !== "object") return void 0;
584
+ const members = node.members.filter((m) => m.key === key);
585
+ node = members[members.length - 1]?.value;
586
+ }
587
+ return node;
588
+ }
589
+ /** Apply non-overlapping edits, last first so the earlier offsets stay valid. */
590
+ function applyEdits(text, edits) {
591
+ let out = text;
592
+ for (const edit of [...edits].sort((a, b) => b.start - a.start)) out = out.slice(0, edit.start) + edit.replacement + out.slice(edit.end);
593
+ return out;
594
+ }
595
+ /**
596
+ * Remove one member from the object at `objectPath`, taking its comma with it.
597
+ *
598
+ * Rescans rather than trusting offsets from before an earlier edit. A middle or
599
+ * first member takes everything up to the next key, so the next member inherits
600
+ * its indentation; the last takes the comma and whitespace after the previous
601
+ * value, so the closing brace keeps its line; an only member leaves `{}`.
602
+ */
603
+ function removeJsonMember(text, objectPath, key) {
604
+ const container = memberValue(scanJson(text), objectPath);
605
+ if (container?.kind !== "object") return text;
606
+ const index = container.members.findIndex((m) => m.key === key);
607
+ if (index === -1) return text;
608
+ const members = container.members;
609
+ const member = members[index];
610
+ if (members.length === 1) return text.slice(0, container.start + 1) + text.slice(container.end - 1);
611
+ if (index < members.length - 1) return text.slice(0, member.keyStart) + text.slice(members[index + 1].keyStart);
612
+ return text.slice(0, members[index - 1].value.end) + text.slice(member.value.end);
613
+ }
614
+ function isRecord(value) {
615
+ return typeof value === "object" && value !== null && !Array.isArray(value);
616
+ }
617
+ function setPath(root, keys, value) {
618
+ let node = root;
619
+ for (const key of keys.slice(0, -1)) {
620
+ const next = node[key];
621
+ if (!isRecord(next)) return;
622
+ node = next;
623
+ }
624
+ node[keys[keys.length - 1]] = value;
625
+ }
626
+ function deletePath(root, keys) {
627
+ let node = root;
628
+ for (const key of keys.slice(0, -1)) {
629
+ const next = node[key];
630
+ if (!isRecord(next)) return;
631
+ node = next;
632
+ }
633
+ delete node[keys[keys.length - 1]];
634
+ }
635
+ /** Lines, each without its `\r`, and whether the file used them. */
636
+ function splitLines(text) {
637
+ return text.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
638
+ }
639
+ /**
640
+ * A YAML scalar at the start of `rest`: its text, and where that text sits.
641
+ *
642
+ * Double- and single-quoted and plain scalars, which is every shape an override
643
+ * value is written in. A double-quoted one with an escape in it is refused
644
+ * rather than decoded: no version or path needs one, and getting the decoding
645
+ * wrong would rewrite a value this does not understand.
646
+ */
647
+ function readYamlScalar(rest) {
648
+ if (rest.startsWith("\"")) {
649
+ const close = rest.indexOf("\"", 1);
650
+ if (close === -1 || rest.slice(1, close).includes("\\")) return null;
651
+ if (!/^\s*(#.*)?$/.test(rest.slice(close + 1))) return null;
652
+ return {
653
+ value: rest.slice(1, close),
654
+ start: 1,
655
+ end: close
656
+ };
657
+ }
658
+ if (rest.startsWith("'")) {
659
+ const close = rest.indexOf("'", 1);
660
+ if (close === -1 || rest[close + 1] === "'") return null;
661
+ if (!/^\s*(#.*)?$/.test(rest.slice(close + 1))) return null;
662
+ return {
663
+ value: rest.slice(1, close),
664
+ start: 1,
665
+ end: close
666
+ };
667
+ }
668
+ const comment = rest.search(/\s#/);
669
+ const plain = (comment === -1 ? rest : rest.slice(0, comment)).trimEnd();
670
+ if (plain === "" || /^[[{&*!|>%@`]/.test(plain)) return null;
671
+ return {
672
+ value: plain,
673
+ start: 0,
674
+ end: plain.length
675
+ };
676
+ }
677
+ /**
678
+ * A top-level block mapping of a `pnpm-workspace.yaml` — `overrides:`,
679
+ * `catalog:` — read line by line.
680
+ *
681
+ * Deliberately narrow. The file is YAML, and the CLI carries no YAML parser; the
682
+ * shape pnpm documents and every project writes is a block mapping of one
683
+ * scalar per line, which a line reader handles exactly. Anything else in the
684
+ * block is counted rather than guessed at, and an inline `overrides: { … }` is
685
+ * reported as not handled.
686
+ */
687
+ function readYamlBlock(lines, key) {
688
+ const keyLine = lines.findIndex((line) => new RegExp(`^${key}:\\s*(#.*)?$`).test(line));
689
+ if (keyLine === -1) return null;
690
+ const block = {
691
+ keyLine,
692
+ lines: [],
693
+ entries: [],
694
+ otherContent: 0
695
+ };
696
+ for (let index = keyLine + 1; index < lines.length; index++) {
697
+ const line = lines[index];
698
+ if (line.trim() !== "" && !/^\s/.test(line) && !line.startsWith("#")) break;
699
+ block.lines.push(index);
700
+ if (line.trim() === "" || /^\s*#/.test(line)) continue;
701
+ const entry = readYamlEntry(line, index);
702
+ if (entry) block.entries.push(entry);
703
+ else block.otherContent++;
704
+ }
705
+ return block;
706
+ }
707
+ /** One `key: scalar` line of a block mapping, or null when it is anything else. */
708
+ function readYamlEntry(line, index) {
709
+ const entry = /^(\s+)("[^"]*"|'[^']*'|[^\s"'#][^:#]*?)\s*:(\s+|$)/.exec(line);
710
+ const valueOffset = entry ? entry[0].length : -1;
711
+ const scalar = entry ? readYamlScalar(line.slice(valueOffset)) : null;
712
+ if (!entry || !scalar) return null;
713
+ const rawKey = entry[2];
714
+ return {
715
+ line: index,
716
+ key: /^["']/.test(rawKey) ? rawKey.slice(1, -1) : rawKey,
717
+ value: scalar.value,
718
+ valueStart: valueOffset + scalar.start,
719
+ valueEnd: valueOffset + scalar.end
720
+ };
721
+ }
722
+ /**
723
+ * Every catalog a `pnpm-workspace.yaml` declares: the top-level `catalog:`
724
+ * block, and each named block under `catalogs:`.
725
+ *
726
+ * `catalogs:` is one level deeper — a header line per catalog, its entries
727
+ * indented under it — and is read with the same one-scalar-per-line rule.
728
+ */
729
+ function readYamlCatalogs(lines) {
730
+ const catalogs = [];
731
+ const single = readYamlBlock(lines, "catalog");
732
+ if (single) catalogs.push({
733
+ name: "default",
734
+ field: "catalog",
735
+ entries: single.entries
736
+ });
737
+ const keyLine = lines.findIndex((line) => /^catalogs:\s*(#.*)?$/.test(line));
738
+ if (keyLine === -1) return catalogs;
739
+ let current = null;
740
+ let headerIndent = -1;
741
+ for (let index = keyLine + 1; index < lines.length; index++) {
742
+ const line = lines[index];
743
+ if (line.trim() !== "" && !/^\s/.test(line) && !line.startsWith("#")) break;
744
+ if (line.trim() === "" || /^\s*#/.test(line)) continue;
745
+ const indent = line.length - line.trimStart().length;
746
+ const header = /^\s+("[^"]*"|'[^']*'|[^\s"'#][^:#]*?)\s*:\s*(#.*)?$/.exec(line);
747
+ if (header && (headerIndent === -1 || indent === headerIndent)) {
748
+ headerIndent = indent;
749
+ const name = /^["']/.test(header[1]) ? header[1].slice(1, -1) : header[1];
750
+ current = {
751
+ name,
752
+ field: `catalogs.${name}`,
753
+ entries: []
754
+ };
755
+ catalogs.push(current);
756
+ continue;
757
+ }
758
+ if (!current || indent <= headerIndent) continue;
759
+ const entry = readYamlEntry(line, index);
760
+ if (entry) current.entries.push(entry);
761
+ }
762
+ return catalogs;
763
+ }
764
+ /** A `pnpm-workspace.yaml`, with its overrides and then its catalogs moved. */
765
+ function planWorkspaceYaml(file, original, target, options, plan, ledger) {
766
+ return planYamlCatalogs(file, planYamlOverrides(file, original, target, options, plan, ledger), target, plan, ledger);
767
+ }
768
+ /**
769
+ * Move the framework entries of every catalog. Entries are rewritten in place
770
+ * — a catalog loses no line — and the result is read back and held to what was
771
+ * meant, like every other rewrite here.
772
+ */
773
+ function planYamlCatalogs(file, original, target, plan, ledger) {
774
+ const lines = splitLines(original);
775
+ const catalogs = readYamlCatalogs(lines);
776
+ for (const key of ["catalog", "catalogs"]) {
777
+ const inline = lines.find((line) => new RegExp(`^${key}:\\s*[^\\s#]`).test(line));
778
+ if (inline && inline.includes("@rebasepro/")) plan.skipped.push({
779
+ file,
780
+ name: key,
781
+ field: key,
782
+ spec: inline.slice(key.length + 1).trim(),
783
+ reason: `an inline ${key} map, which this does not rewrite; write it as a block, or edit it by hand`
784
+ });
785
+ }
786
+ if (catalogs.length === 0) return original;
787
+ const next = [...lines];
788
+ const expected = [];
789
+ for (const catalog of catalogs) for (const entry of catalog.entries) {
790
+ const meant = [
791
+ catalog.field,
792
+ entry.key,
793
+ entry.value
794
+ ];
795
+ expected.push(meant);
796
+ if (!entry.key.startsWith("@rebasepro/")) continue;
797
+ ledger.defined.add(catalogKey(catalog.name, entry.key));
798
+ const cls = classifySpec(entry.value);
799
+ if (cls.kind !== "movable") {
800
+ plan.skipped.push({
801
+ file,
802
+ name: entry.key,
803
+ field: catalog.field,
804
+ spec: entry.value,
805
+ reason: leftAloneReason(cls)
806
+ });
807
+ continue;
808
+ }
809
+ const to = `${cls.prefix}${target}`;
810
+ if (to === entry.value) {
811
+ plan.unchanged++;
812
+ continue;
813
+ }
814
+ meant[2] = to;
815
+ const line = next[entry.line];
816
+ next[entry.line] = line.slice(0, entry.valueStart) + to + line.slice(entry.valueEnd);
817
+ plan.changed.push({
818
+ file,
819
+ name: entry.key,
820
+ field: catalog.field,
821
+ from: entry.value,
822
+ to
823
+ });
824
+ }
825
+ const content = next.join(original.includes("\r\n") ? "\r\n" : "\n");
826
+ if (!isDeepStrictEqual(readYamlCatalogs(splitLines(content)).flatMap((catalog) => catalog.entries.map((e) => [
827
+ catalog.field,
828
+ e.key,
829
+ e.value
830
+ ])), expected)) throw new UpgradeError(`Could not rewrite ${file} without disturbing it; nothing was written.`, "rewrite_failed", "Move its @rebasepro catalog entries by hand, then run `rebase upgrade` again.");
831
+ return content;
832
+ }
833
+ function planYamlOverrides(file, original, target, options, plan, ledger) {
834
+ const lines = splitLines(original);
835
+ const crlf = original.includes("\r\n");
836
+ const block = readYamlBlock(lines, "overrides");
837
+ if (!block) {
838
+ const inline = lines.find((line) => /^overrides:\s*[^\s#]/.test(line));
839
+ if (inline && inline.includes("@rebasepro/")) plan.skipped.push({
840
+ file,
841
+ name: "overrides",
842
+ field: "overrides",
843
+ spec: inline.slice(10).trim(),
844
+ reason: "an inline overrides map, which this does not rewrite; write it as a block, or edit it by hand"
845
+ });
846
+ return original;
847
+ }
848
+ const next = [...lines];
849
+ const removed = /* @__PURE__ */ new Set();
850
+ const expected = /* @__PURE__ */ new Map();
851
+ for (const entry of block.entries) {
852
+ if (!overrideTarget(entry.key)) {
853
+ expected.set(entry.key, entry.value);
854
+ continue;
855
+ }
856
+ const cls = classifySpec(entry.value);
857
+ const pkg = overrideTarget(entry.key);
858
+ if (cls.kind === "movable") {
859
+ const to = `${cls.prefix}${target}`;
860
+ expected.set(entry.key, to);
861
+ if (to === entry.value) {
862
+ plan.unchanged++;
863
+ continue;
864
+ }
865
+ const line = lines[entry.line];
866
+ next[entry.line] = line.slice(0, entry.valueStart) + to + line.slice(entry.valueEnd);
867
+ plan.overrides.push({
868
+ file,
869
+ name: entry.key,
870
+ spec: entry.value,
871
+ action: "bumped",
872
+ to
873
+ });
874
+ } else if (cls.kind === "catalog" && pkg) {
875
+ expected.set(entry.key, entry.value);
876
+ ledger.refs.push({
877
+ file,
878
+ name: entry.key,
879
+ field: "overrides",
880
+ spec: entry.value,
881
+ catalog: cls.catalog,
882
+ pkg
883
+ });
884
+ } else if (cls.kind === "local") if (options.dropLocalOverrides) {
885
+ removed.add(entry.line);
886
+ plan.overrides.push({
887
+ file,
888
+ name: entry.key,
889
+ spec: entry.value,
890
+ action: "removed-local"
891
+ });
892
+ } else {
893
+ expected.set(entry.key, entry.value);
894
+ plan.overrides.push({
895
+ file,
896
+ name: entry.key,
897
+ spec: entry.value,
898
+ action: "kept-local"
899
+ });
900
+ }
901
+ else {
902
+ expected.set(entry.key, entry.value);
903
+ plan.skipped.push({
904
+ file,
905
+ name: entry.key,
906
+ field: "overrides",
907
+ spec: entry.value,
908
+ reason: leftAloneReason(cls)
909
+ });
910
+ }
911
+ }
912
+ const survivors = block.entries.filter((entry) => !removed.has(entry.line)).length + block.otherContent;
913
+ if (removed.size > 0 && survivors === 0) removed.add(block.keyLine);
914
+ const content = next.filter((_, index) => !removed.has(index)).join(crlf ? "\r\n" : "\n");
915
+ const after = readYamlBlock(splitLines(content), "overrides");
916
+ if (!isDeepStrictEqual(after ? after.entries.map((e) => [e.key, e.value]) : [], [...expected.entries()])) throw new UpgradeError(`Could not rewrite ${file} without disturbing it; nothing was written.`, "rewrite_failed", "Move its @rebasepro overrides by hand, then run `rebase upgrade` again.");
917
+ return content;
918
+ }
919
+ /** Lockfiles, in the order they are looked for in each directory. */
920
+ var LOCKFILES = [
921
+ ["pnpm-lock.yaml", "pnpm"],
922
+ ["package-lock.json", "npm"],
923
+ ["yarn.lock", "yarn"],
924
+ ["bun.lockb", "bun"],
925
+ ["bun.lock", "bun"]
926
+ ];
927
+ /**
928
+ * The package manager this project installs with, from what is on disk.
929
+ *
930
+ * A lockfile is the project's own statement, so it wins. The search starts at
931
+ * the project root and walks up, because a project inside a workspace has its
932
+ * lockfile at the workspace root; it stops at the repository's root, so a stray
933
+ * lockfile in a home directory is never read as this project's choice. With no
934
+ * lockfile anywhere, a `pnpm-workspace.yaml` still says pnpm; otherwise npm,
935
+ * the one every Node install has.
936
+ */
937
+ function detectInstaller(projectRoot) {
938
+ let dir = path.resolve(projectRoot);
939
+ let workspaceYaml = false;
940
+ for (;;) {
941
+ for (const [lockfile, installer] of LOCKFILES) if (fs.existsSync(path.join(dir, lockfile))) return installer;
942
+ if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) workspaceYaml = true;
943
+ const parent = path.dirname(dir);
944
+ if (parent === dir || fs.existsSync(path.join(dir, ".git"))) break;
945
+ dir = parent;
946
+ }
947
+ return workspaceYaml ? "pnpm" : "npm";
948
+ }
949
+ /**
950
+ * The install command for each package manager — pnpm's and npm's from the
951
+ * helpers every other command uses, yarn's and bun's spelled the same way.
952
+ */
953
+ function installCommand(installer) {
954
+ const [bin, ...args] = installer === "pnpm" || installer === "npm" ? getPMCommands(installer).install : [installer, "install"];
955
+ return [bin, args];
956
+ }
957
+ /**
958
+ * The exact version `--to` names.
959
+ *
960
+ * An exact version is used as written, with no network call: the control plane
961
+ * passes one when it rebuilds a project, and that rebuild must not depend on a
962
+ * registry lookup succeeding for anything but the install itself. Anything
963
+ * else is a dist-tag, asked of the registry through `npmView` — which runs npm
964
+ * in the project, so its `.npmrc` and the user's registry config apply.
965
+ */
966
+ async function resolveTarget(requested, npmView) {
967
+ const trimmed = requested.trim();
968
+ const bare = trimmed.replace(/^v(?=\d)/, "");
969
+ if (isExactVersion(bare)) return bare;
970
+ if (!/^[A-Za-z][\w.-]*$/.test(trimmed)) throw new UpgradeError(`"${requested}" is neither an exact version nor a dist-tag.`, "target_invalid", "Pass an exact version such as 0.21.0, or a dist-tag such as latest or canary.");
971
+ let answer;
972
+ try {
973
+ answer = (await npmView(`${RELEASE_PACKAGE}@${trimmed}`)).trim();
974
+ } catch (err) {
975
+ const detail = err instanceof Error ? firstLine(err.message) : String(err);
976
+ throw new UpgradeError(`Could not resolve "${trimmed}" on the npm registry${detail ? `: ${detail}` : "."}`, "target_unresolved", "Check the tag and your registry access, or pass an exact version with --to.");
977
+ }
978
+ if (!isExactVersion(answer)) throw new UpgradeError(`The registry has no single version for ${RELEASE_PACKAGE}@${trimmed}${answer ? ` (it answered "${firstLine(answer)}")` : ""}.`, "target_unresolved", "Check the tag, or pass an exact version with --to.");
979
+ return answer;
980
+ }
981
+ function firstLine(text) {
982
+ return text.split("\n").map((line) => line.trim()).filter(Boolean)[0] ?? "";
983
+ }
984
+ /**
985
+ * Whether moving from `from` to `to` goes backwards, for the one-word note the
986
+ * summary prints. Prerelease identifiers compare as SemVer orders them: a
987
+ * release outranks its own prereleases.
988
+ */
989
+ function isDowngrade(from, to) {
990
+ const a = SEMVER.exec(from.replace(/^[\^~]/, ""));
991
+ const b = SEMVER.exec(to.replace(/^[\^~]/, ""));
992
+ if (!a || !b) return false;
993
+ for (let i = 1; i <= 3; i++) {
994
+ const diff = Number(a[i]) - Number(b[i]);
995
+ if (diff !== 0) return diff > 0;
996
+ }
997
+ const preA = a[4];
998
+ const preB = b[4];
999
+ if (preA === preB) return false;
1000
+ if (preA === void 0) return true;
1001
+ if (preB === void 0) return false;
1002
+ const partsA = preA.split(".");
1003
+ const partsB = preB.split(".");
1004
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
1005
+ const x = partsA[i];
1006
+ const y = partsB[i];
1007
+ if (x === void 0) return false;
1008
+ if (y === void 0) return true;
1009
+ if (x === y) continue;
1010
+ const nx = /^\d+$/.test(x) ? Number(x) : NaN;
1011
+ const ny = /^\d+$/.test(y) ? Number(y) : NaN;
1012
+ if (!Number.isNaN(nx) && !Number.isNaN(ny)) return nx > ny;
1013
+ if (!Number.isNaN(nx)) return false;
1014
+ if (!Number.isNaN(ny)) return true;
1015
+ return x > y;
1016
+ }
1017
+ return false;
1018
+ }
1019
+ //#endregion
1020
+ //#region src/commands/upgrade.ts
1021
+ /**
1022
+ * CLI command: rebase upgrade [--to <version|tag>]
1023
+ *
1024
+ * Moves every `@rebasepro/*` package the project pins to one release, then
1025
+ * installs. The file work — which specs move, the rewrite, what overrides say —
1026
+ * is `../upgrade.ts`; this adds the registry lookup for a tag, the install, and
1027
+ * the two ways of saying what happened.
1028
+ */
1029
+ /** Every flag `rebase upgrade` accepts. Its help page and the docs verifier read this. */
1030
+ var UPGRADE_FLAGS = {
1031
+ "--to": String,
1032
+ "--drop-local-overrides": Boolean,
1033
+ "--no-install": Boolean,
1034
+ "--dry-run": Boolean,
1035
+ "--json": Boolean
1036
+ };
1037
+ function printHelp() {
1038
+ console.log(`
1039
+ ${chalk.bold("rebase upgrade")} — move every @rebasepro package this project pins to one release
1040
+
1041
+ ${chalk.bold("Usage")}
1042
+ rebase upgrade [--to <version|tag>] [options]
1043
+
1044
+ ${chalk.bold("Options")}
1045
+ --to <version|tag> The release: an exact version (0.21.0) or a dist-tag
1046
+ (latest, canary). Default: latest
1047
+ --drop-local-overrides Remove link: and file: overrides of @rebasepro packages,
1048
+ which win over every pin
1049
+ --no-install Rewrite the package.json files, but do not install
1050
+ --dry-run Print what would change, and write nothing
1051
+ --json One JSON document on stdout
1052
+ -h, --help Show this help
1053
+
1054
+ ${chalk.bold("What it changes")}
1055
+ Every @rebasepro/* entry in dependencies, devDependencies and optionalDependencies,
1056
+ in every package.json under the project that git does not ignore (node_modules,
1057
+ dist* and hidden directories excepted, in a repository or not). ^ and ~ are kept.
1058
+ peerDependencies are left alone, and so are workspace:, link:, file:, git and tag
1059
+ specs, which are listed with the reason.
1060
+ Overrides in pnpm-workspace.yaml and package.json are bumped the same way, and so
1061
+ are the entries of pnpm-workspace.yaml's catalog: and catalogs: blocks, which is
1062
+ where a catalog: pin's version lives.
1063
+
1064
+ ${chalk.bold("Examples")}
1065
+ rebase upgrade Move to the latest release and install
1066
+ rebase upgrade --to canary --dry-run See what the canary would change
1067
+ rebase upgrade --to 0.21.0 --no-install --json
1068
+ `.trim());
1069
+ }
1070
+ var defaultIo = {
1071
+ npmView: async (spec, cwd) => {
1072
+ try {
1073
+ return (await execa("npm", [
1074
+ "view",
1075
+ spec,
1076
+ "version"
1077
+ ], { cwd })).stdout;
1078
+ } catch (err) {
1079
+ throw new Error(npmFailure(err));
1080
+ }
1081
+ },
1082
+ install: async ([bin, args], cwd, quietStdout) => {
1083
+ await execa(bin, args, {
1084
+ cwd,
1085
+ stdio: quietStdout ? [
1086
+ "ignore",
1087
+ 2,
1088
+ "inherit"
1089
+ ] : "inherit"
1090
+ });
1091
+ }
1092
+ };
1093
+ /**
1094
+ * The one line of a failed `npm view` worth printing.
1095
+ *
1096
+ * execa's own message is the command line that failed, which says nothing the
1097
+ * reader does not know. npm's stderr says why — `404 No match found for version
1098
+ * nosuchtag` — under an `npm error code E404` line that says less.
1099
+ */
1100
+ function npmFailure(err) {
1101
+ return (typeof err === "object" && err !== null && "stderr" in err && typeof err.stderr === "string" ? err.stderr : "").split("\n").map((line) => line.replace(/^npm (?:error|ERR!)\s*/, "").trim()).find((line) => line !== "" && !/^code\s/.test(line) && !/^\d{3}$/.test(line)) ?? (err instanceof Error ? err.message : String(err));
1102
+ }
1103
+ /**
1104
+ * The one release every moved pin came from, or `null` when they disagreed.
1105
+ * A range's `^`/`~` is not part of it: `upgrade --to` keeps each pin's operator.
1106
+ */
1107
+ function previousRelease(plan) {
1108
+ const versions = new Set(plan.changed.map((pin) => pin.from.replace(/^[\^~]/, "")));
1109
+ if (versions.size !== 1) return null;
1110
+ const [version] = versions;
1111
+ return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version) ? version : null;
1112
+ }
1113
+ async function upgradeCommand(rawArgs, io = defaultIo) {
1114
+ if (wantsHelp(rawArgs)) {
1115
+ printHelp();
1116
+ return;
1117
+ }
1118
+ const { flags } = parseCommandArgs({
1119
+ spec: UPGRADE_FLAGS,
1120
+ rawArgs,
1121
+ commandWords: 1,
1122
+ command: "upgrade",
1123
+ maxPositionals: 0
1124
+ });
1125
+ const json = flags["--json"] === true;
1126
+ const dryRun = flags["--dry-run"] === true;
1127
+ const projectRoot = requireProjectRoot();
1128
+ /** Every refusal, in whichever of the two languages was asked for. */
1129
+ const refuse = (message, code, hint, issues) => {
1130
+ if (json) failAsJson(message, code, hint, issues);
1131
+ console.error(chalk.red(`✗ ${message}`));
1132
+ for (const issue of issues ?? []) console.error(` ${chalk.gray(issue.path ?? "")} ${issue.message}`);
1133
+ if (hint) console.error(chalk.gray(` ${hint}`));
1134
+ process.exit(1);
1135
+ };
1136
+ try {
1137
+ loadManifest(projectRoot);
1138
+ } catch (err) {
1139
+ if (err instanceof ManifestError) refuse(err.message, "manifest_invalid", "Fix rebase.json, then run this again.", err.issues.map((issue) => ({
1140
+ path: issue.path,
1141
+ message: issue.message
1142
+ })));
1143
+ throw err;
1144
+ }
1145
+ let plan;
1146
+ try {
1147
+ plan = planUpgrade(projectRoot, await resolveTarget(flags["--to"] ?? "latest", (spec) => io.npmView(spec, projectRoot)), { dropLocalOverrides: flags["--drop-local-overrides"] === true });
1148
+ } catch (err) {
1149
+ if (err instanceof UpgradeError) refuse(err.message, err.code, err.hint);
1150
+ throw err;
1151
+ }
1152
+ if (!dryRun) applyUpgradePlan(plan);
1153
+ const wrote = !dryRun && plan.writes.length > 0;
1154
+ const install = wrote && flags["--no-install"] !== true;
1155
+ const installer = detectInstaller(projectRoot);
1156
+ const [bin, args] = installCommand(installer);
1157
+ const installLine = [bin, ...args].join(" ");
1158
+ if (!json) printSummary(plan, dryRun);
1159
+ let installed = false;
1160
+ if (install) {
1161
+ if (!json) console.log(chalk.gray(` Installing with ${installer}...\n`));
1162
+ try {
1163
+ await io.install([bin, args], projectRoot, json);
1164
+ installed = true;
1165
+ } catch {
1166
+ const previous = previousRelease(plan);
1167
+ refuse(`\`${installLine}\` failed.`, "install_failed", `The package.json edits were kept. Fix what the install reported and run \`${installLine}\` again, ` + (previous ? `or put every pin back with \`rebase upgrade --to ${previous}\`.` : "or revert the edits with git."));
1168
+ }
1169
+ }
1170
+ const result = {
1171
+ target: plan.target,
1172
+ changed: plan.changed,
1173
+ skipped: plan.skipped,
1174
+ overrides: plan.overrides,
1175
+ unreadable: plan.unreadable,
1176
+ installed,
1177
+ dryRun
1178
+ };
1179
+ if (json) {
1180
+ console.log(JSON.stringify(result, null, 2));
1181
+ return;
1182
+ }
1183
+ printOutcome(plan, {
1184
+ dryRun,
1185
+ wrote,
1186
+ installed,
1187
+ installLine
1188
+ });
1189
+ }
1190
+ /** Changes grouped by file, then what was left alone and why, then overrides. */
1191
+ function printSummary(plan, dryRun) {
1192
+ console.log("");
1193
+ console.log(`${chalk.bold("Rebase")} — ${dryRun ? "what upgrading" : "upgrading"} @rebasepro packages to ${chalk.cyan(plan.target)}${dryRun ? " would change" : ""}`);
1194
+ const byFile = /* @__PURE__ */ new Map();
1195
+ for (const pin of plan.changed) byFile.set(pin.file, [...byFile.get(pin.file) ?? [], pin]);
1196
+ const nameWidth = Math.max(0, ...plan.changed.map((pin) => pin.name.length));
1197
+ for (const [file, pins] of byFile) {
1198
+ console.log("");
1199
+ console.log(` ${chalk.bold(file)}`);
1200
+ for (const pin of pins) {
1201
+ const field = pin.field === "dependencies" ? "" : chalk.gray(` ${pin.field}`);
1202
+ const older = isDowngrade(pin.from, pin.to) ? chalk.yellow(" (older)") : "";
1203
+ console.log(` ${pin.name.padEnd(nameWidth)} ${chalk.gray(pin.from)} → ${chalk.green(pin.to)}${field}${older}`);
1204
+ }
1205
+ }
1206
+ if (plan.skipped.length > 0) {
1207
+ console.log("");
1208
+ console.log(` ${chalk.bold("Left alone")}`);
1209
+ for (const skip of plan.skipped) {
1210
+ console.log(` ${skip.name} ${chalk.gray(skip.spec)} ${chalk.gray(`(${skip.file}, ${skip.field})`)}`);
1211
+ console.log(chalk.gray(` ${skip.reason}`));
1212
+ }
1213
+ }
1214
+ if (plan.overrides.length > 0) {
1215
+ console.log("");
1216
+ console.log(` ${chalk.bold("Overrides")}`);
1217
+ for (const override of plan.overrides) {
1218
+ const where = chalk.gray(`(${override.file})`);
1219
+ if (override.action === "bumped") console.log(` ${override.name} ${chalk.gray(override.spec)} → ${chalk.green(override.to ?? "")} ${where}`);
1220
+ else if (override.action === "removed-local") console.log(` ${override.name} ${chalk.gray(override.spec)} ${where} ${chalk.green(dryRun ? "would be removed" : "removed")}`);
1221
+ else {
1222
+ console.log(` ${chalk.yellow("⚠")} ${override.name} → ${override.spec} ${where}`);
1223
+ console.log(chalk.yellow(" local override — wins over every pin, the upgrade will not reach these packages"));
1224
+ }
1225
+ }
1226
+ if (plan.overrides.some((o) => o.action === "kept-local")) console.log(chalk.gray(" Run again with --drop-local-overrides to remove them."));
1227
+ }
1228
+ if (plan.unreadable.length > 0) {
1229
+ console.log("");
1230
+ console.log(` ${chalk.bold("Not read")}`);
1231
+ for (const entry of plan.unreadable) console.log(` ${entry.file} ${chalk.gray(entry.reason)}`);
1232
+ }
1233
+ console.log("");
1234
+ }
1235
+ function printOutcome(plan, outcome) {
1236
+ const moved = plan.changed.length + plan.overrides.filter((o) => o.action !== "kept-local").length;
1237
+ const leftAlone = plan.skipped.length + plan.overrides.filter((o) => o.action === "kept-local").length;
1238
+ const entries = (n) => `${n} entr${n === 1 ? "y" : "ies"}`;
1239
+ if (moved === 0 && leftAlone > 0) {
1240
+ if (plan.unchanged > 0) console.log(chalk.green(`✓ Every pin this can move is already on ${plan.target}; ${entries(leftAlone)} ${leftAlone === 1 ? "was" : "were"} left alone, above.`));
1241
+ else if (outcome.dryRun) console.log(chalk.yellow(` Nothing to move to ${plan.target}: ${entries(leftAlone)} ${leftAlone === 1 ? "is" : "are"} left alone, above.`));
1242
+ else console.log(chalk.yellow(`⚠ Nothing moved to ${plan.target}: ${entries(leftAlone)} ${leftAlone === 1 ? "was" : "were"} left alone, above.`));
1243
+ console.log("");
1244
+ return;
1245
+ }
1246
+ if (outcome.dryRun) {
1247
+ console.log(chalk.gray(moved === 0 ? ` Nothing to change: this project is already on ${plan.target}.` : " Dry run — nothing was written. Run it without --dry-run to apply."));
1248
+ console.log("");
1249
+ return;
1250
+ }
1251
+ if (!outcome.wrote) {
1252
+ console.log(chalk.green(`✓ This project is already on ${plan.target}.`));
1253
+ console.log("");
1254
+ return;
1255
+ }
1256
+ const files = plan.writes.length;
1257
+ console.log(chalk.green(`✓ Moved ${moved} entr${moved === 1 ? "y" : "ies"} in ${files} file${files === 1 ? "" : "s"} to ${plan.target}${outcome.installed ? " and installed" : ""}.`));
1258
+ if (!outcome.installed) console.log(chalk.gray(` Install with \`${outcome.installLine}\`, then run \`rebase build\`, then \`rebase cloud deploy\`.`));
1259
+ else console.log(chalk.gray(" Run `rebase build`, then `rebase cloud deploy`."));
1260
+ console.log("");
1261
+ }
1262
+ //#endregion
1263
+ export { upgradeCommand };
1264
+
1265
+ //# sourceMappingURL=upgrade-D-5-Od96.js.map