@pob/version 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.editorconfig +13 -0
  2. package/.yo-rc.json +13 -0
  3. package/CHANGELOG.md +35 -0
  4. package/LICENSE +21 -0
  5. package/README.md +163 -0
  6. package/dist/index-node.mjs +1419 -0
  7. package/dist/index-node.mjs.map +1 -0
  8. package/package.json +81 -0
  9. package/rollup.config.mjs +12 -0
  10. package/src/__fixtures__/bun-basic/package.json +4 -0
  11. package/src/__fixtures__/bun-no-version/package.json +3 -0
  12. package/src/__fixtures__/yarn-basic/package.json +4 -0
  13. package/src/__fixtures__/yarn-basic/yarn.lock +12 -0
  14. package/src/__fixtures__/yarn-monorepo/package.json +7 -0
  15. package/src/__fixtures__/yarn-monorepo/packages/package-1/package.json +4 -0
  16. package/src/__fixtures__/yarn-monorepo/packages/package-2/package.json +7 -0
  17. package/src/__fixtures__/yarn-monorepo/packages/package-3/package.json +7 -0
  18. package/src/__fixtures__/yarn-monorepo/packages/package-4/package.json +7 -0
  19. package/src/__fixtures__/yarn-monorepo/packages/package-5/package.json +8 -0
  20. package/src/__fixtures__/yarn-monorepo/yarn.lock +0 -0
  21. package/src/__fixtures__/yarn-monorepo-duplicate-names/package.json +7 -0
  22. package/src/__fixtures__/yarn-monorepo-duplicate-names/packages/pkg-a/package.json +4 -0
  23. package/src/__fixtures__/yarn-monorepo-duplicate-names/packages/pkg-b/package.json +4 -0
  24. package/src/__fixtures__/yarn-monorepo-invalid-package-json/package.json +7 -0
  25. package/src/__fixtures__/yarn-monorepo-invalid-package-json/packages/pkg-with-invalid-json/package.json +5 -0
  26. package/src/__fixtures__/yarn-monorepo-nested/package.json +9 -0
  27. package/src/__fixtures__/yarn-monorepo-nested/packages/deep-nested/package.json +4 -0
  28. package/src/__fixtures__/yarn-monorepo-nested/packages/nested-1/packages/package-nested.json +4 -0
  29. package/src/__fixtures__/yarn-monorepo-nested/packages/nested-1/packages/package.json +4 -0
  30. package/src/__fixtures__/yarn-monorepo-nested/packages/package-1/package.json +4 -0
  31. package/src/__fixtures__/yarn-monorepo-no-name/package.json +7 -0
  32. package/src/__fixtures__/yarn-monorepo-no-name/packages/no-name-package/package.json +3 -0
  33. package/src/__fixtures__/yarn-monorepo-object-form/package.json +9 -0
  34. package/src/__fixtures__/yarn-monorepo-object-form/packages/package-1/package.json +4 -0
  35. package/src/__fixtures__/yarn-monorepo-object-form/packages/package-2/package.json +4 -0
  36. package/src/__fixtures__/yarn-monorepo-object-form/packages/package-3/package.json +4 -0
  37. package/src/__fixtures__/yarn-monorepo-object-form/packages/package-4/package.json +4 -0
  38. package/src/__fixtures__/yarn-monorepo-object-form/packages/package-5/package.json +4 -0
  39. package/src/__fixtures__/yarn-no-version/package.json +3 -0
  40. package/src/__fixtures__/yarn-no-version/yarn.lock +0 -0
  41. package/src/__fixtures__/yarn-with-lifecycle/.yarn/install-state.gz +0 -0
  42. package/src/__fixtures__/yarn-with-lifecycle/package.json +7 -0
  43. package/src/__fixtures__/yarn-with-lifecycle/yarn.lock +11 -0
  44. package/src/commands/version.test.ts +114 -0
  45. package/src/commands/version.ts +936 -0
  46. package/src/index.ts +13 -0
  47. package/src/missing-types.d.ts +10 -0
  48. package/src/pm/BunPackageManager.ts +12 -0
  49. package/src/pm/PackageManager.ts +66 -0
  50. package/src/pm/YarnPackageManager.ts +34 -0
  51. package/src/utils/UsageError.ts +6 -0
  52. package/src/utils/bumpTypeUtils.ts +113 -0
  53. package/src/utils/conventionalChangelogUtils.test.ts +762 -0
  54. package/src/utils/conventionalChangelogUtils.ts +104 -0
  55. package/src/utils/conventionalCommitConfigUtils.ts +20 -0
  56. package/src/utils/execCommand.ts +214 -0
  57. package/src/utils/generatorUtils.ts +11 -0
  58. package/src/utils/gitUtils.ts +193 -0
  59. package/src/utils/githubUtils.ts +114 -0
  60. package/src/utils/mapUtils.ts +7 -0
  61. package/src/utils/mapWorkspaces.test.ts +89 -0
  62. package/src/utils/packageDependenciesUtils.test.ts +25 -0
  63. package/src/utils/packageDependenciesUtils.ts +80 -0
  64. package/src/utils/packageUtils.test.ts +32 -0
  65. package/src/utils/packageUtils.ts +127 -0
  66. package/src/utils/updateChangelog.ts +39 -0
  67. package/src/utils/workspaceUtils.ts +142 -0
  68. package/tsconfig.json +21 -0
  69. package/vitest.config.js +9 -0
@@ -0,0 +1,1419 @@
1
+ import fs$1, { existsSync } from 'node:fs';
2
+ import { program, Option } from 'commander';
3
+ import { LoggerCLI } from 'nightingale';
4
+ import semver, { satisfies } from 'semver';
5
+ import childProcess from 'node:child_process';
6
+ import path from 'node:path';
7
+ import { writeChangelogString } from 'conventional-changelog-writer';
8
+ import { parseCommits } from 'conventional-commits-parser';
9
+ import { loadPreset } from 'conventional-changelog-preset-loader';
10
+ import fs, { glob } from 'node:fs/promises';
11
+
12
+ async function execvp(command, args, {
13
+ cwd = process.cwd(),
14
+ env = process.env,
15
+ encoding,
16
+ strict,
17
+ stdo = "pipe",
18
+ input
19
+ }) {
20
+ const stdoutChunks = [];
21
+ const stderrChunks = [];
22
+ if (env.PWD !== void 0) {
23
+ env = { ...env, PWD: cwd };
24
+ }
25
+ const subprocess = childProcess.spawn(command, args, {
26
+ cwd,
27
+ env,
28
+ stdio: [input ? "pipe" : "ignore", stdo, stdo]
29
+ });
30
+ if (input && subprocess.stdin) {
31
+ subprocess.stdin.write(input);
32
+ subprocess.stdin.end();
33
+ }
34
+ subprocess.stdout?.on("data", (chunk) => {
35
+ stdoutChunks.push(chunk);
36
+ });
37
+ subprocess.stderr?.on("data", (chunk) => {
38
+ stderrChunks.push(chunk);
39
+ });
40
+ return new Promise((resolve, reject) => {
41
+ subprocess.on("error", (err) => {
42
+ reject(new Error(`Process ${command} failed to spawn`));
43
+ });
44
+ subprocess.on("close", (code, signal) => {
45
+ const chunksToString = (chunks) => stdo === "inherit" ? "" : Buffer.concat(chunks).toString(encoding ?? "utf8");
46
+ const stdout = chunksToString(stdoutChunks);
47
+ const stderr = chunksToString(stderrChunks);
48
+ if (code === 0 || !strict) {
49
+ resolve({
50
+ code,
51
+ signal,
52
+ stdout,
53
+ stderr
54
+ });
55
+ } else {
56
+ reject(
57
+ new Error(
58
+ `Process ${[command, ...args].join(" ")} exited ${code !== null ? `with code ${code}` : `with signal ${signal || ""}`}:
59
+ stdout: ${stdout}
60
+ stderr: ${stderr}`
61
+ )
62
+ );
63
+ }
64
+ });
65
+ });
66
+ }
67
+ async function* spawnStreamStdout(command, args, {
68
+ cwd = process.cwd(),
69
+ env = process.env,
70
+ encoding,
71
+ strict,
72
+ separator
73
+ }) {
74
+ const stderrChunks = [];
75
+ if (env.PWD !== void 0) {
76
+ env = { ...env, PWD: cwd };
77
+ }
78
+ const subprocess = childProcess.spawn(command, args, {
79
+ cwd,
80
+ env,
81
+ stdio: ["ignore", "pipe", "pipe"]
82
+ });
83
+ subprocess.stderr.on("data", (chunk2) => {
84
+ stderrChunks.push(chunk2);
85
+ });
86
+ let chunk;
87
+ let payload;
88
+ let buffer = "";
89
+ for await (chunk of subprocess.stdout) {
90
+ buffer += chunk.toString();
91
+ if (buffer.includes(separator)) {
92
+ payload = buffer.split(separator);
93
+ buffer = payload.pop() || "";
94
+ yield* payload;
95
+ }
96
+ }
97
+ if (buffer) {
98
+ yield buffer;
99
+ }
100
+ return new Promise((resolve, reject) => {
101
+ subprocess.on("error", (err) => {
102
+ reject(new Error(`Process ${command} failed to spawn`));
103
+ });
104
+ subprocess.on("close", (code, signal) => {
105
+ const chunksToString = (chunks) => Buffer.concat(chunks).toString(encoding ?? "utf8");
106
+ const stderr = chunksToString(stderrChunks);
107
+ if (code === 0 || !strict) {
108
+ resolve({
109
+ code,
110
+ signal,
111
+ stderr
112
+ });
113
+ } else {
114
+ reject(
115
+ new Error(
116
+ `Process ${[command, ...args].join(" ")} exited ${code !== null ? `with code ${code}` : `with signal ${signal || ""}`}:
117
+ stderr: ${stderr}`
118
+ )
119
+ );
120
+ }
121
+ });
122
+ });
123
+ }
124
+ const execCommand = (workspace, commandAndArgs, stdo = "pipe") => {
125
+ const [command, ...args] = commandAndArgs;
126
+ if (command === void 0) {
127
+ throw new Error("Command is required");
128
+ }
129
+ return execvp(command, args, {
130
+ cwd: workspace.cwd,
131
+ strict: true,
132
+ stdo
133
+ });
134
+ };
135
+ const execCommandWithInput = (workspace, commandAndArgs, input, stdo = "pipe") => {
136
+ const [command, ...args] = commandAndArgs;
137
+ if (command === void 0) {
138
+ throw new Error("Command is required");
139
+ }
140
+ return execvp(command, args, {
141
+ cwd: workspace.cwd,
142
+ strict: true,
143
+ stdo,
144
+ input
145
+ });
146
+ };
147
+ const execCommandStreamStdout = (workspace, commandAndArgs, separator = "\n") => {
148
+ const [command, ...args] = commandAndArgs;
149
+ if (command === void 0) {
150
+ throw new Error("Command is required");
151
+ }
152
+ return spawnStreamStdout(command, args, {
153
+ cwd: workspace.cwd,
154
+ strict: true,
155
+ separator
156
+ });
157
+ };
158
+
159
+ const execCommand$1 = /*#__PURE__*/Object.defineProperty({
160
+ __proto__: null,
161
+ execCommand,
162
+ execCommandStreamStdout,
163
+ execCommandWithInput
164
+ }, Symbol.toStringTag, { value: 'Module' });
165
+
166
+ const BunPackageManager = {
167
+ async runScript(workspace, scriptName) {
168
+ await execCommand(workspace, ["bun", "run", scriptName], "inherit");
169
+ },
170
+ async publish(workspace) {
171
+ await execCommand(workspace, ["bun", "publish"], "inherit");
172
+ }
173
+ };
174
+
175
+ const YarnPackageManager = {
176
+ async installOnPackageContentChange(rootWorkspace) {
177
+ await execCommand(rootWorkspace, ["yarn", "install"], "inherit");
178
+ },
179
+ async runScript(workspace, scriptName) {
180
+ await execCommand(workspace, ["yarn", "run", scriptName], "inherit");
181
+ },
182
+ async publish(workspace) {
183
+ await execCommand(workspace, ["yarn", "npm", "publish"], "inherit");
184
+ },
185
+ async publishWorkspaces(rootWorkspace) {
186
+ await execCommand(
187
+ rootWorkspace,
188
+ [
189
+ "yarn",
190
+ "workspaces",
191
+ "foreach",
192
+ "--all",
193
+ "--parallel",
194
+ "--no-private",
195
+ "npm",
196
+ "publish"
197
+ ],
198
+ "inherit"
199
+ );
200
+ }
201
+ };
202
+
203
+ const autoDetectPackageManager = (rootWorkspace) => {
204
+ const hasYarnLock = existsSync(`${rootWorkspace.cwd}/yarn.lock`);
205
+ const hasBunLockLegacy = existsSync(`${rootWorkspace.cwd}/bun.lockb`);
206
+ const hasBunLock = existsSync(`${rootWorkspace.cwd}/bun.lock`);
207
+ const hasNpmPackageLock = existsSync(
208
+ `${rootWorkspace.cwd}/package-lock.json`
209
+ );
210
+ if ([hasYarnLock, hasBunLockLegacy, hasBunLock, hasNpmPackageLock].filter(
211
+ Boolean
212
+ ).length > 1) {
213
+ throw new Error(
214
+ `Multiple lock files detected in workspace at ${rootWorkspace.cwd}. Please ensure only one lock file is present, or pass --package-manager option to bypass auto-detection.`
215
+ );
216
+ }
217
+ if (hasYarnLock) {
218
+ return YarnPackageManager;
219
+ }
220
+ if (hasBunLockLegacy || hasBunLock) {
221
+ return BunPackageManager;
222
+ }
223
+ if (hasNpmPackageLock) {
224
+ throw new Error(
225
+ "NPM is not currently supported as a package manager. Please migrate to Yarn or Bun, or open an issue to request support."
226
+ );
227
+ }
228
+ throw new Error(
229
+ `No lock file detected in workspace at ${rootWorkspace.cwd}. Please ensure a supported lock file is present, or pass --package-manager option to bypass auto-detection.`
230
+ );
231
+ };
232
+ const getPackageManager = (rootWorkspace, specifiedPackageManager) => {
233
+ if (specifiedPackageManager === "bun") {
234
+ return BunPackageManager;
235
+ }
236
+ if (specifiedPackageManager === "yarn") {
237
+ return YarnPackageManager;
238
+ }
239
+ throw new Error(
240
+ "Invalid package manager specified. Supported values are 'bun' and 'yarn'."
241
+ );
242
+ };
243
+
244
+ class UsageError extends Error {
245
+ constructor(message) {
246
+ super(message);
247
+ this.name = "UsageError";
248
+ }
249
+ }
250
+
251
+ function getMapArrayItemForKey(map, key) {
252
+ let value = map.get(key);
253
+ if (value === void 0) {
254
+ map.set(key, value = []);
255
+ }
256
+ return value;
257
+ }
258
+
259
+ const PackageDescriptorNameUtils = {
260
+ parse: (value) => {
261
+ if (value.startsWith("@")) {
262
+ const [scope, name] = value.slice(1).split("/", 2);
263
+ if (!scope || !name) {
264
+ throw new Error(`Invalid package descriptor name: ${value}`);
265
+ }
266
+ return { scope, name };
267
+ }
268
+ return { name: value };
269
+ },
270
+ stringify: (descriptor) => {
271
+ return descriptor.scope === void 0 ? descriptor.name : `@${descriptor.scope}/${descriptor.name}`;
272
+ }
273
+ };
274
+ const PackageDependencyDescriptorUtils = {
275
+ make: (descriptor, selector) => {
276
+ return { key: descriptor.key, name: descriptor.name, selector };
277
+ },
278
+ parse: (dependencyKey, dependencyValue) => {
279
+ const [name, selector] = dependencyValue.startsWith("npm:") ? (() => {
280
+ const v = dependencyValue.slice("npm:".length);
281
+ if (!v.startsWith("@")) return v.split("@", 2);
282
+ const [packageNameWithoutFirstChar, selector2] = v.slice(1).split("@", 2);
283
+ if (!packageNameWithoutFirstChar || !selector2) {
284
+ throw new Error(`Invalid package descriptor: ${dependencyValue}`);
285
+ }
286
+ return [`@${packageNameWithoutFirstChar}`, selector2];
287
+ })() : [dependencyKey, dependencyValue];
288
+ return {
289
+ key: dependencyKey,
290
+ name: PackageDescriptorNameUtils.parse(name),
291
+ selector
292
+ };
293
+ },
294
+ stringify: (descriptor) => {
295
+ return [descriptor.key, descriptor.selector];
296
+ }
297
+ };
298
+
299
+ const allDependenciesTypes = [
300
+ "dependencies",
301
+ "devDependencies",
302
+ "peerDependencies"
303
+ ];
304
+ const getWorkspaceName = (workspace) => {
305
+ return workspace.pkg.name ?? (path.basename(workspace.cwd) || "unnamed-workspace");
306
+ };
307
+ const buildDependenciesMaps = (project) => {
308
+ const dependenciesMap = /* @__PURE__ */ new Map();
309
+ for (const dependent of project.children.values()) {
310
+ for (const set of allDependenciesTypes) {
311
+ for (const [dependencyKey, dependencyValue] of Object.entries(
312
+ dependent.pkg[set] || {}
313
+ )) {
314
+ if (!dependencyValue) continue;
315
+ const descriptor = PackageDependencyDescriptorUtils.parse(
316
+ dependencyKey,
317
+ dependencyValue
318
+ );
319
+ const workspace = project.children.get(
320
+ PackageDescriptorNameUtils.stringify(descriptor.name)
321
+ );
322
+ if (!workspace) continue;
323
+ const dependencies = getMapArrayItemForKey(dependenciesMap, dependent);
324
+ dependencies.push([workspace, set, descriptor]);
325
+ }
326
+ }
327
+ }
328
+ return dependenciesMap;
329
+ };
330
+ const buildTopologicalOrderBatches = (project, dependenciesMap) => {
331
+ const batches = [];
332
+ const added = /* @__PURE__ */ new Set();
333
+ const toAdd = /* @__PURE__ */ new Set([
334
+ project.root,
335
+ ...project.children.values()
336
+ ]);
337
+ while (toAdd.size > 0) {
338
+ const batch = /* @__PURE__ */ new Set();
339
+ for (const workspace of toAdd) {
340
+ if (workspace === project.root && toAdd.size > 1) {
341
+ continue;
342
+ }
343
+ const dependencies = dependenciesMap.get(workspace);
344
+ if (!dependencies || dependencies.every((w) => added.has(w[0]))) {
345
+ batch.add(workspace);
346
+ }
347
+ }
348
+ for (const workspace of batch) {
349
+ added.add(workspace);
350
+ toAdd.delete(workspace);
351
+ }
352
+ if (batch.size === 0) {
353
+ throw new Error("Circular dependency detected");
354
+ }
355
+ batches.push([...batch]);
356
+ }
357
+ return batches;
358
+ };
359
+
360
+ const SUPPORTED_UPGRADE_REGEXP = (
361
+ // eslint-disable-next-line regexp/no-unused-capturing-group
362
+ /^((?:>=|[~^])?)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-z-][0-9a-z-]*)(\.(0|[1-9]\d*|\d*[a-z-][0-9a-z-]*))*)?(\+[0-9a-z-]+(\.[0-9a-z-]+)*)?$/i
363
+ );
364
+ const yarnWorkspaceProtocol = "workspace:";
365
+ const calcBumpRange = (workspace, range, newVersion) => {
366
+ if (range === "*") {
367
+ return range;
368
+ }
369
+ let useWorkspaceProtocol = false;
370
+ if (range.startsWith(yarnWorkspaceProtocol)) {
371
+ const slicedRange = range.slice(yarnWorkspaceProtocol.length);
372
+ if (slicedRange === workspace.relativeCwd) {
373
+ return range;
374
+ }
375
+ if (slicedRange === "*") {
376
+ return range;
377
+ }
378
+ range = slicedRange;
379
+ useWorkspaceProtocol = true;
380
+ }
381
+ const parsed = SUPPORTED_UPGRADE_REGEXP.exec(range);
382
+ if (!parsed) {
383
+ const workspaceName = getWorkspaceName(workspace);
384
+ throw new Error(`Couldn't bump range ${range} in ${workspaceName}`);
385
+ }
386
+ return `${useWorkspaceProtocol ? yarnWorkspaceProtocol : ""}${parsed[1] ?? ""}${newVersion}`;
387
+ };
388
+ const getHighestBumpType = (bumpTypes) => {
389
+ if (bumpTypes.includes("major")) {
390
+ return "major";
391
+ }
392
+ if (bumpTypes.includes("minor")) {
393
+ return "minor";
394
+ }
395
+ return "patch";
396
+ };
397
+ const calcBumpType = (bumpType, maxBumpType) => {
398
+ if (maxBumpType === "major") {
399
+ return bumpType;
400
+ }
401
+ if (maxBumpType === "minor") {
402
+ return bumpType === "major" || bumpType === "minor" ? "minor" : "patch";
403
+ }
404
+ return "patch";
405
+ };
406
+ const incVersion = (version, bumpType) => {
407
+ if (bumpType === "major" && semver.major(version) === 0) {
408
+ bumpType = "minor";
409
+ }
410
+ return semver.inc(version, bumpType);
411
+ };
412
+ const incrementVersion = (workspace, currentVersion, bumpType) => {
413
+ const newVersion = incVersion(currentVersion, bumpType);
414
+ if (!newVersion) {
415
+ throw new UsageError(
416
+ `Could not determine next version for "${getWorkspaceName(
417
+ workspace
418
+ )}" (currentVersion: ${currentVersion}, bumpType: ${bumpType}})`
419
+ );
420
+ }
421
+ return newVersion;
422
+ };
423
+
424
+ const versions = [
425
+ "major",
426
+ "minor",
427
+ "patch"
428
+ ];
429
+ const recommendBump = async (commits, config) => {
430
+ const whatBump = config.whatBump;
431
+ if (!whatBump) {
432
+ throw new Error("whatBump method is missing in config");
433
+ }
434
+ const result = { ...await whatBump(commits) };
435
+ return {
436
+ ...result,
437
+ releaseType: result.level != null ? versions[result.level] : void 0
438
+ };
439
+ };
440
+ const generateChangelog = (workspace, pkg, config, previousTag, newTag, commits, date) => {
441
+ if (!newTag) {
442
+ throw new Error(`Missing new tag for package "${pkg.name ?? ""}"`);
443
+ }
444
+ const originUrl = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
445
+ const match = originUrl && typeof originUrl === "string" && /^(?:git@|https?:\/\/)(?:([^./:]+(?:\.com)?)[/:])?([^/:]+)\/([^./:]+)(?:.git)?/.exec(
446
+ originUrl
447
+ );
448
+ const [, gitHost, gitAccount, repoName] = match || [
449
+ void 0,
450
+ void 0,
451
+ void 0,
452
+ void 0
453
+ ];
454
+ return writeChangelogString(
455
+ commits ?? [],
456
+ {
457
+ // @ts-expect-error - missing types
458
+ previousTag,
459
+ currentTag: newTag,
460
+ linkCompare: previousTag != null,
461
+ version: pkg.version,
462
+ host: gitHost ? `https://${gitHost}` : void 0,
463
+ owner: gitAccount,
464
+ repository: repoName,
465
+ date
466
+ },
467
+ // @ts-expect-error - missing types
468
+ config.writer
469
+ );
470
+ };
471
+ const createCommitsParser = (config) => {
472
+ return parseCommits(config.parser);
473
+ };
474
+
475
+ const loadConventionalCommitConfig = async (rootWorkspace, preset) => {
476
+ try {
477
+ return await loadPreset(preset);
478
+ } catch (error) {
479
+ throw new UsageError(
480
+ `Failed to require preset "${preset}": ${error.message}`
481
+ );
482
+ }
483
+ };
484
+
485
+ async function asyncIterableToArray(asyncIterable) {
486
+ const result = [];
487
+ for await (const value of asyncIterable) {
488
+ result.push(value);
489
+ }
490
+ return result;
491
+ }
492
+
493
+ const getGitCurrentBranch = async (workspace) => {
494
+ const { stdout } = await execCommand(workspace, [
495
+ "git",
496
+ "rev-parse",
497
+ "--abbrev-ref",
498
+ "HEAD"
499
+ ]);
500
+ const currentBranch = stdout.trim();
501
+ if (currentBranch === "HEAD") {
502
+ throw new UsageError("HEAD is detached. Please checkout a branch.");
503
+ }
504
+ return currentBranch;
505
+ };
506
+ const createGitCommit = async (workspace, commitMessage) => {
507
+ await execCommand(workspace, ["git", "add", "-A"]);
508
+ await execCommand(workspace, [
509
+ "git",
510
+ "commit",
511
+ "--no-verify",
512
+ "-m",
513
+ commitMessage
514
+ ]);
515
+ };
516
+ const createGitTag = async (workspace, newTag) => {
517
+ await execCommand(workspace, ["git", "tag", newTag, "-m", newTag]);
518
+ };
519
+ const pushCommitsAndTags = async (workspace, gitRemote, currentBranch) => {
520
+ await execCommand(workspace, [
521
+ "git",
522
+ "push",
523
+ "--follow-tags",
524
+ "--no-verify",
525
+ "--atomic",
526
+ gitRemote,
527
+ currentBranch
528
+ ]);
529
+ };
530
+ const isBehindRemote = async (workspace, gitRemote, currentBranch) => {
531
+ await execCommand(workspace, ["git", "remote", "update", gitRemote]);
532
+ const { stdout } = await execCommand(workspace, [
533
+ "git",
534
+ "rev-list",
535
+ "--left-right",
536
+ "--count",
537
+ `${gitRemote}/${currentBranch}..${currentBranch}`
538
+ ]);
539
+ const [behind] = stdout.split(" ").map((val) => parseInt(val, 10));
540
+ return behind ? behind > 0 : false;
541
+ };
542
+ const getDirtyFiles = async (workspace) => {
543
+ const { stdout: dirtyFiles } = await execCommand(workspace, [
544
+ "git",
545
+ "status",
546
+ "--porcelain"
547
+ ]);
548
+ return dirtyFiles;
549
+ };
550
+ const toArray = (input) => Array.isArray(input) ? input : [input];
551
+ const getGitLatestTagVersion = async (workspace, {
552
+ prefix,
553
+ skipUnstable,
554
+ since,
555
+ from = "",
556
+ to = "HEAD",
557
+ path
558
+ } = {}) => {
559
+ const tagRegex = /tag:\s*(.+?)[,)]/gi;
560
+ for await (const chunk of execCommandStreamStdout(workspace, [
561
+ "git",
562
+ "log",
563
+ "--no-color",
564
+ "--date-order",
565
+ "--format=%d",
566
+ ...since ? [`--since=${since instanceof Date ? since.toISOString() : since}`] : [],
567
+ [from, to].filter(Boolean).join(".."),
568
+ ...path ? ["--", ...toArray(path)] : []
569
+ ])) {
570
+ const trimmed = chunk.trim();
571
+ if (!trimmed) continue;
572
+ const matches = trimmed.matchAll(tagRegex);
573
+ for (const match of matches) {
574
+ const tag = match[1];
575
+ if (!tag) continue;
576
+ if (prefix && !tag.startsWith(prefix)) {
577
+ continue;
578
+ }
579
+ const version = prefix ? tag.slice(prefix.length) : tag;
580
+ if (!semver.valid(version)) {
581
+ continue;
582
+ }
583
+ if (skipUnstable && semver.prerelease(version)) {
584
+ continue;
585
+ }
586
+ return { tag, version };
587
+ }
588
+ }
589
+ return null;
590
+ };
591
+ const GIT_COMMIT_SEPARATOR = "------------------------ >8 ------------------------";
592
+ const getGitCommits = (workspace, {
593
+ path,
594
+ from = "",
595
+ to = "HEAD",
596
+ format = "%B",
597
+ reverse,
598
+ merges,
599
+ since
600
+ } = {}) => {
601
+ return execCommandStreamStdout(
602
+ workspace,
603
+ [
604
+ "git",
605
+ "log",
606
+ `--format=${format}%n${GIT_COMMIT_SEPARATOR}`,
607
+ ...since ? [`--since=${since instanceof Date ? since.toISOString() : since}`] : [],
608
+ ...reverse ? ["--reverse"] : [],
609
+ ...merges ? ["--merges"] : [],
610
+ ...merges === false ? ["--no-merges"] : [],
611
+ [from, to].filter(Boolean).join(".."),
612
+ ...path ? ["--", ...toArray(path)] : []
613
+ ],
614
+ GIT_COMMIT_SEPARATOR
615
+ );
616
+ };
617
+
618
+ async function ensureGhCliAvailable(workspace) {
619
+ const { GH_TOKEN } = process.env;
620
+ if (!GH_TOKEN) {
621
+ throw new UsageError('"GH_TOKEN" environment variable required');
622
+ }
623
+ try {
624
+ const { execCommand } = await Promise.resolve().then(() => execCommand$1);
625
+ await execCommand(workspace, ["gh", "--version"]);
626
+ } catch {
627
+ throw new UsageError(
628
+ 'The "gh" CLI is required. Please install GitHub CLI: https://cli.github.com/'
629
+ );
630
+ }
631
+ }
632
+ const githubRegex = /^https?:\/\/github.com\/([^#/]+)\/([^#/]+?)(?:\.git)?$/;
633
+ const parseGithubRepoUrl = (workspace) => {
634
+ const repository = workspace.pkg.repository;
635
+ const url = typeof repository === "string" ? repository : repository?.url;
636
+ if (!url) {
637
+ throw new Error(
638
+ "No repository URL found in manifest. Please add one and try again. https://docs.npmjs.com/cli/v9/configuring-npm/package-json#repository"
639
+ );
640
+ }
641
+ const match = githubRegex.exec(url);
642
+ if (!match) {
643
+ throw new Error(`Invalid GitHub repository URL: "${url}"`);
644
+ }
645
+ const [, username, reponame] = match;
646
+ if (!username || !reponame) {
647
+ throw new Error(`Invalid GitHub repository URL: "${url}"`);
648
+ }
649
+ return { username, reponame };
650
+ };
651
+ const extractHostnameFromGheApiUrl = (gheApiUrl) => {
652
+ try {
653
+ const url = new URL(gheApiUrl);
654
+ return url.hostname || null;
655
+ } catch {
656
+ const m = /^https?:\/\/([^/]+)/.exec(gheApiUrl);
657
+ return m?.[1] || null;
658
+ }
659
+ };
660
+ const createGhRelease = async (workspace, opts) => {
661
+ const { parsedRepoUrl, tag, body, prerelease } = opts;
662
+ const { GH_TOKEN, GHE_API_URL } = process.env;
663
+ if (!GH_TOKEN) {
664
+ await ensureGhCliAvailable(workspace);
665
+ return;
666
+ }
667
+ const args = [
668
+ "release",
669
+ "create",
670
+ tag,
671
+ "--repo",
672
+ `${parsedRepoUrl.username}/${parsedRepoUrl.reponame}`,
673
+ "--notes",
674
+ "-"
675
+ ];
676
+ if (prerelease) {
677
+ args.push("--prerelease");
678
+ }
679
+ if (GHE_API_URL) {
680
+ const hostname = extractHostnameFromGheApiUrl(GHE_API_URL);
681
+ if (hostname) {
682
+ args.push("--hostname", hostname);
683
+ }
684
+ }
685
+ try {
686
+ await execCommandWithInput(workspace, ["gh", ...args], body);
687
+ } catch (error) {
688
+ throw new Error(
689
+ `gh release create failed: ${String(error instanceof Error ? error.message : error)}`
690
+ );
691
+ }
692
+ };
693
+
694
+ async function mapWorkspacesFromPkg({
695
+ cwd,
696
+ pkg
697
+ }) {
698
+ const workspacesPatterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages || [];
699
+ const map = /* @__PURE__ */ new Map();
700
+ for (const pattern of workspacesPatterns) {
701
+ const normalized = pattern.endsWith("package.json") ? pattern : `${pattern.replace(/\/*$/, "")}/package.json`;
702
+ const files = glob(normalized, { cwd });
703
+ for await (const file of files) {
704
+ const packageDir = path.join(cwd, path.dirname(file));
705
+ const packageJson = await readPkg(packageDir);
706
+ const workspaceName = packageJson.name || path.basename(packageDir);
707
+ if (map.has(workspaceName)) {
708
+ const existingWorkspace = map.get(workspaceName);
709
+ if (existingWorkspace && existingWorkspace.cwd !== packageDir) {
710
+ throw new Error(
711
+ `must not have multiple workspaces with the same name
712
+ package '${workspaceName}' has conflicts in the following paths:
713
+ ${existingWorkspace.cwd}
714
+ ${packageDir}`
715
+ );
716
+ }
717
+ if (existingWorkspace && existingWorkspace.cwd === packageDir) continue;
718
+ }
719
+ map.set(workspaceName, { cwd: packageDir, pkg: packageJson });
720
+ }
721
+ }
722
+ return map;
723
+ }
724
+ const createProjectWorkspace = async (root) => {
725
+ const map = root.pkg.workspaces ? await mapWorkspacesFromPkg({ cwd: root.cwd, pkg: root.pkg }) : /* @__PURE__ */ new Map();
726
+ const children = new Map(
727
+ [...map.entries()].map(([packageName, workspace]) => {
728
+ const w = {
729
+ ...workspace,
730
+ relativeCwd: path.relative(root.cwd, workspace.cwd)
731
+ };
732
+ return [packageName, w];
733
+ })
734
+ );
735
+ return { root, children };
736
+ };
737
+ const isAccessible = (path2) => fs.access(path2).then(
738
+ () => true,
739
+ () => false
740
+ );
741
+ const findRootWorkspace = async (cwd) => {
742
+ let currentPath = cwd;
743
+ do {
744
+ const isRootIfOneOfThesePathsExists = await Promise.all([
745
+ isAccessible(path.join(currentPath, ".yarnrc.yml")),
746
+ isAccessible(path.join(currentPath, "yarn.lock"))
747
+ ]);
748
+ if (isRootIfOneOfThesePathsExists.some(Boolean)) {
749
+ return createWorkspace(currentPath);
750
+ }
751
+ currentPath = path.dirname(currentPath);
752
+ } while (currentPath && currentPath !== "/");
753
+ return null;
754
+ };
755
+ const getPackageJsonPath = (cwd) => path.join(cwd, "package.json");
756
+ const createWorkspace = async (path2) => {
757
+ const pkg = await readPkg(path2);
758
+ return { cwd: path2, pkg };
759
+ };
760
+ async function writePkg(workspace) {
761
+ const string = `${JSON.stringify(workspace.pkg, null, 2)}
762
+ `;
763
+ await fs.writeFile(getPackageJsonPath(workspace.cwd), string, "utf8");
764
+ }
765
+ async function readPkg(cwd) {
766
+ const packagePath = getPackageJsonPath(cwd);
767
+ const pkg = await fs.readFile(packagePath, "utf8").catch((error) => {
768
+ throw new Error(
769
+ `Failed to read "${packagePath}": ${error instanceof Error ? error.message : String(error)}`
770
+ );
771
+ });
772
+ try {
773
+ return JSON.parse(pkg);
774
+ } catch (error) {
775
+ throw new Error(
776
+ `Failed to parse "${packagePath}": ${error instanceof Error ? error.message : String(error)}`
777
+ );
778
+ }
779
+ }
780
+
781
+ const COMMIT_GUIDELINE = "See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.";
782
+ const CHANGELOG_HEADER = [
783
+ "# Changelog",
784
+ "",
785
+ "All notable changes to this project will be documented in this file.",
786
+ COMMIT_GUIDELINE,
787
+ ""
788
+ ].join("\n");
789
+ const updateChangelogFile = async (versionChangelogContent, tagPrefix, file = "CHANGELOG.md") => {
790
+ const START_OF_LAST_RELEASE_PATTERN = new RegExp(
791
+ `(^#+ \\[?(?:${tagPrefix})?\\d+\\.\\d+\\.\\d+|<a name=)`,
792
+ "m"
793
+ );
794
+ let oldContent = "";
795
+ try {
796
+ oldContent = await fs.readFile(file, "utf8");
797
+ const oldContentStart = oldContent.search(START_OF_LAST_RELEASE_PATTERN);
798
+ if (oldContentStart !== -1) {
799
+ oldContent = oldContent.slice(Math.max(0, oldContentStart));
800
+ }
801
+ } catch {
802
+ }
803
+ await fs.writeFile(
804
+ file,
805
+ `${CHANGELOG_HEADER}
806
+ ${versionChangelogContent}
807
+ ${oldContent}`
808
+ );
809
+ };
810
+
811
+ const todayInYYYYMMDD = () => {
812
+ const dateFormatter = Intl.DateTimeFormat("sv-SE", {
813
+ timeZone: "UTC"
814
+ });
815
+ return dateFormatter.format(/* @__PURE__ */ new Date());
816
+ };
817
+ const versionCommandAction = async (options, { logger = new LoggerCLI("@pob/version", { json: options.json }) } = {}) => {
818
+ const rootWorkspace = await (options.cwdIsRoot ? createWorkspace(options.cwd) : findRootWorkspace(options.cwd));
819
+ if (!rootWorkspace) {
820
+ throw new UsageError("Could not find root workspace from this path.");
821
+ }
822
+ const packageManager = options.packageManager ? getPackageManager(rootWorkspace, options.packageManager) : autoDetectPackageManager(rootWorkspace);
823
+ const project = await createProjectWorkspace(rootWorkspace);
824
+ if (!options.dryRun) {
825
+ const dirtyFiles = await getDirtyFiles(project.root);
826
+ if (dirtyFiles) {
827
+ throw new Error(
828
+ `Dirty Files:
829
+ ${dirtyFiles}
830
+
831
+ There are uncommitted changes in the git repository. Please commit or stash them first.`
832
+ );
833
+ }
834
+ }
835
+ const rootWorkspaceChildren = [...project.children.values()];
836
+ let rootNewVersion = "";
837
+ let rootNewTag = "";
838
+ const isMonorepo = rootWorkspaceChildren.length > 0;
839
+ const isMonorepoVersionIndependent = isMonorepo && !rootWorkspace.pkg.version;
840
+ const workspaces = !(isMonorepo && !rootWorkspaceChildren) || options.includesRoot ? [rootWorkspace, ...rootWorkspaceChildren] : rootWorkspaceChildren;
841
+ if (options.prerelease) {
842
+ throw new UsageError("--prerelease is not supported yet.");
843
+ }
844
+ if (options.ignoreChanges) {
845
+ throw new UsageError("--ignore-changes is not supported yet.");
846
+ }
847
+ const [conventionalCommitConfig, parsedRepoUrl, gitCurrentBranch] = await Promise.all([
848
+ loadConventionalCommitConfig(rootWorkspace, options.preset),
849
+ options.createRelease ? parseGithubRepoUrl(rootWorkspace) : void 0,
850
+ getGitCurrentBranch(rootWorkspace),
851
+ // ensure gh CLI is available early to fail fast if necessary
852
+ options.createRelease ? ensureGhCliAvailable(rootWorkspace) : void 0
853
+ ]);
854
+ const rootPreviousVersionTagPromise = options.force || isMonorepoVersionIndependent ? null : getGitLatestTagVersion(rootWorkspace, {
855
+ prefix: options.tagVersionPrefix,
856
+ skipUnstable: true
857
+ });
858
+ const buildTagName = (workspace, version) => `${isMonorepo && workspace !== rootWorkspace ? `${getWorkspaceName(workspace)}@` : options.tagVersionPrefix}${version}`;
859
+ const changedWorkspaces = /* @__PURE__ */ new Map();
860
+ const dependenciesMap = isMonorepo ? buildDependenciesMaps(project) : null;
861
+ const bumpableWorkspaces = [];
862
+ logger.group(
863
+ options.force ? "Check all workspaces (force option)" : "Finding changed workspaces",
864
+ () => {
865
+ for (const workspace of workspaces) {
866
+ const workspaceName = getWorkspaceName(workspace);
867
+ const isRoot = workspace === rootWorkspace;
868
+ if (isRoot && isMonorepo && isMonorepoVersionIndependent) continue;
869
+ const version = workspace.pkg.version;
870
+ if (!version || version === "0.0.0") {
871
+ if ((isRoot || isMonorepoVersionIndependent) && (!isMonorepo || !isMonorepoVersionIndependent)) {
872
+ throw new UsageError(
873
+ 'package.json has no version in its manifest. For the first release, set to "1.0.0-pre" or "0.1.0-pre".'
874
+ );
875
+ }
876
+ logger.info(`${workspaceName}: skipped (no version)`);
877
+ continue;
878
+ }
879
+ bumpableWorkspaces.push({
880
+ workspace,
881
+ workspaceName,
882
+ isRoot,
883
+ version
884
+ });
885
+ }
886
+ if (bumpableWorkspaces.length > 0) {
887
+ logger.info("Found bumpable workspaces", {
888
+ count: bumpableWorkspaces.length
889
+ });
890
+ }
891
+ }
892
+ );
893
+ const previousTagVersionByWorkspace = new Map(
894
+ await Promise.all(
895
+ bumpableWorkspaces.map(async ({ workspace, workspaceName, isRoot }) => {
896
+ const packageOption = isMonorepo && isMonorepoVersionIndependent ? workspaceName : void 0;
897
+ const tagPrefix = packageOption ? `${packageOption}@` : options.tagVersionPrefix;
898
+ const previousTagAndVersion = await (isRoot || !isMonorepoVersionIndependent ? rootPreviousVersionTagPromise : getGitLatestTagVersion(rootWorkspace, {
899
+ path: workspace.relativeCwd,
900
+ prefix: tagPrefix,
901
+ skipUnstable: true
902
+ }));
903
+ return [workspace, previousTagAndVersion];
904
+ })
905
+ )
906
+ );
907
+ if (options.dryRun) {
908
+ logger.info("Previous tags", {
909
+ previousTagByWorkspace: Object.fromEntries(
910
+ [...previousTagVersionByWorkspace.entries()].map(
911
+ ([workspace, previousTagVersion]) => [
912
+ getWorkspaceName(workspace),
913
+ previousTagVersion?.tag
914
+ ]
915
+ )
916
+ )
917
+ });
918
+ }
919
+ const parseCommits = createCommitsParser(conventionalCommitConfig);
920
+ const commitsByWorkspace = options.force ? void 0 : new Map(
921
+ await Promise.all(
922
+ bumpableWorkspaces.map(async ({ workspace }) => {
923
+ const previousTagVersion = previousTagVersionByWorkspace.get(workspace);
924
+ const commits = await asyncIterableToArray(
925
+ parseCommits(
926
+ getGitCommits(rootWorkspace, {
927
+ path: workspace.relativeCwd,
928
+ from: previousTagVersion?.tag || void 0
929
+ })
930
+ )
931
+ );
932
+ return [workspace, commits];
933
+ })
934
+ )
935
+ );
936
+ await logger.group("Finding changed workspaces", async () => {
937
+ for (const { workspace, workspaceName } of bumpableWorkspaces) {
938
+ let bumpType = null;
939
+ let bumpReason;
940
+ if (options.force) {
941
+ bumpType = options.force;
942
+ bumpReason = "forced by --force flag";
943
+ } else {
944
+ const commits = commitsByWorkspace?.get(workspace);
945
+ if (!commits || commits.length === 0) {
946
+ logger.info(`${workspaceName}: skipped (no changes)`);
947
+ continue;
948
+ }
949
+ const { releaseType, reason } = await recommendBump(
950
+ commits,
951
+ conventionalCommitConfig
952
+ );
953
+ bumpReason = reason;
954
+ if (releaseType) {
955
+ bumpType = releaseType;
956
+ }
957
+ }
958
+ if (bumpType) {
959
+ if (isMonorepo && !workspace.pkg.name) {
960
+ throw new Error("Workspace name is required");
961
+ }
962
+ const currentVersion = workspace.pkg.version;
963
+ if (!currentVersion) {
964
+ throw new UsageError(
965
+ `Invalid "${getWorkspaceName(workspace)}" version`
966
+ );
967
+ }
968
+ changedWorkspaces.set(workspace, {
969
+ bumpType,
970
+ bumpReason
971
+ });
972
+ }
973
+ }
974
+ if (changedWorkspaces.size === 0) {
975
+ logger.info("No changed workspaces");
976
+ }
977
+ });
978
+ const bumpedWorkspaces = /* @__PURE__ */ new Map();
979
+ const noVersionToUpdateWorkspaces = /* @__PURE__ */ new Map();
980
+ const batches = dependenciesMap ? buildTopologicalOrderBatches(project, dependenciesMap) : [[rootWorkspace]];
981
+ logger.group("Preparing bumping", () => {
982
+ for (const batch of batches) {
983
+ for (const workspace of batch) {
984
+ const currentVersion = workspace.pkg.version;
985
+ if (!currentVersion && !workspace.pkg.private) {
986
+ throw new UsageError(
987
+ `Invalid "${getWorkspaceName(workspace)}" version`
988
+ );
989
+ }
990
+ const changedWorkspace = changedWorkspaces.get(workspace);
991
+ let bumpType = null;
992
+ const bumpReasons = [];
993
+ const dependenciesToBump = [];
994
+ if (changedWorkspace) {
995
+ bumpType = changedWorkspace.bumpType;
996
+ bumpReasons.push(changedWorkspace.bumpReason || "by commits");
997
+ }
998
+ const dependencies = dependenciesMap?.get(workspace);
999
+ if (dependencies) {
1000
+ for (const [
1001
+ dependencyWorkspace,
1002
+ dependencyType,
1003
+ dependencyDescriptor
1004
+ ] of dependencies) {
1005
+ const dependencyBumpedWorkspace = bumpedWorkspaces.get(dependencyWorkspace);
1006
+ if (!dependencyBumpedWorkspace) {
1007
+ continue;
1008
+ }
1009
+ if (dependencyType === "peerDependencies" && !options.alwaysBumpPeerDependencies && // skip when peerdependency with a new version satisfied by the existing range.
1010
+ satisfies(
1011
+ dependencyBumpedWorkspace.newVersion,
1012
+ dependencyDescriptor.selector,
1013
+ { includePrerelease: true }
1014
+ )) {
1015
+ continue;
1016
+ }
1017
+ const newRange = calcBumpRange(
1018
+ workspace,
1019
+ dependencyDescriptor.selector,
1020
+ dependencyBumpedWorkspace.newVersion
1021
+ );
1022
+ if (dependencyDescriptor.selector === newRange) {
1023
+ continue;
1024
+ }
1025
+ dependenciesToBump.push([
1026
+ dependencyType,
1027
+ dependencyDescriptor,
1028
+ newRange
1029
+ ]);
1030
+ bumpType = getHighestBumpType([
1031
+ bumpType ?? "patch",
1032
+ calcBumpType(
1033
+ dependencyBumpedWorkspace.bumpType,
1034
+ options.bumpDependentsHighestAs
1035
+ )
1036
+ ]);
1037
+ bumpReasons.push(
1038
+ `Version bump for dependency: ${PackageDescriptorNameUtils.stringify(dependencyDescriptor.name)}`
1039
+ );
1040
+ }
1041
+ }
1042
+ const workspaceName = getWorkspaceName(workspace);
1043
+ if (!currentVersion) {
1044
+ logger.info(`${workspaceName}: skipped (no version)`);
1045
+ if (workspace !== rootWorkspace) {
1046
+ noVersionToUpdateWorkspaces.set(workspace, {
1047
+ dependenciesToBump
1048
+ });
1049
+ }
1050
+ } else if (!bumpType) {
1051
+ logger.info(
1052
+ `${workspaceName}: skipped (${changedWorkspace ? `no bump recommended by ${options.preset}` : "no changes"})`
1053
+ );
1054
+ } else {
1055
+ const newVersion = incrementVersion(
1056
+ workspace,
1057
+ currentVersion,
1058
+ bumpType
1059
+ );
1060
+ const tagName = buildTagName(workspace, newVersion);
1061
+ if (workspace === rootWorkspace) {
1062
+ rootNewVersion = newVersion;
1063
+ rootNewTag = tagName;
1064
+ }
1065
+ if (workspace !== rootWorkspace || !isMonorepo) {
1066
+ const bumpReason = bumpReasons.join("\n");
1067
+ bumpedWorkspaces.set(workspace, {
1068
+ currentVersion,
1069
+ bumpType,
1070
+ bumpReason,
1071
+ bumpForDependenciesReasons: changedWorkspace ? bumpReasons.slice(1) : bumpReasons,
1072
+ newVersion,
1073
+ newTag: tagName,
1074
+ hasChanged: changedWorkspace !== void 0,
1075
+ dependenciesToBump
1076
+ });
1077
+ logger.info(
1078
+ `${workspaceName}: ${currentVersion} -> ${!isMonorepo || isMonorepoVersionIndependent ? newVersion : "bump"} (${bumpReason.replaceAll("\n", " ; ")})`
1079
+ );
1080
+ }
1081
+ }
1082
+ }
1083
+ }
1084
+ });
1085
+ if (isMonorepo && !isMonorepoVersionIndependent) {
1086
+ const currentVersion = rootWorkspace.pkg.version;
1087
+ const highestBumpType = getHighestBumpType(
1088
+ [...bumpedWorkspaces.values()].map(({ bumpType }) => bumpType)
1089
+ );
1090
+ const newVersion = incrementVersion(
1091
+ rootWorkspace,
1092
+ currentVersion,
1093
+ highestBumpType
1094
+ );
1095
+ rootNewVersion = newVersion;
1096
+ rootNewTag = buildTagName(rootWorkspace, newVersion);
1097
+ [...bumpedWorkspaces.entries()].forEach(([workspace, bumpedWorkspace]) => {
1098
+ const isRoot = workspace === rootWorkspace;
1099
+ if (isRoot) {
1100
+ throw new Error("Unexpected root found in bumped workspaces");
1101
+ }
1102
+ bumpedWorkspace.bumpType = highestBumpType;
1103
+ bumpedWorkspace.newVersion = newVersion;
1104
+ bumpedWorkspace.newTag = null;
1105
+ });
1106
+ [
1107
+ ...bumpedWorkspaces.entries(),
1108
+ ...noVersionToUpdateWorkspaces.entries()
1109
+ ].forEach(([workspace, { dependenciesToBump }]) => {
1110
+ dependenciesToBump.forEach((dependencyToBump) => {
1111
+ dependencyToBump[2] = calcBumpRange(
1112
+ workspace,
1113
+ dependencyToBump[1].selector,
1114
+ newVersion
1115
+ );
1116
+ });
1117
+ });
1118
+ bumpedWorkspaces.set(rootWorkspace, {
1119
+ currentVersion,
1120
+ bumpType: highestBumpType,
1121
+ hasChanged: true,
1122
+ newVersion: rootNewVersion,
1123
+ newTag: rootNewTag,
1124
+ dependenciesToBump: []
1125
+ });
1126
+ logger.info(
1127
+ `${currentVersion} -> ${newVersion}`
1128
+ // TODO Json only:
1129
+ // {
1130
+ // oldVersion: currentVersion,
1131
+ // newVersion, },
1132
+ );
1133
+ }
1134
+ await logger.group("Applying changes", async () => {
1135
+ if (!options.dryRun) {
1136
+ if (packageManager.installOnPackageContentChange) {
1137
+ logger.info(`${getWorkspaceName(rootWorkspace)}: Running install`);
1138
+ await packageManager.installOnPackageContentChange(rootWorkspace);
1139
+ }
1140
+ logger.info("Lifecycle script: preversion");
1141
+ if (isMonorepoVersionIndependent && rootWorkspace.pkg.scripts?.preversion) {
1142
+ await packageManager.runScript(rootWorkspace, "preversion");
1143
+ }
1144
+ for (const workspace of bumpedWorkspaces.keys()) {
1145
+ if (workspace.pkg.scripts?.preversion) {
1146
+ await packageManager.runScript(workspace, "preversion");
1147
+ }
1148
+ }
1149
+ logger.info("Modifying versions in package.json");
1150
+ await Promise.all(
1151
+ [...bumpedWorkspaces.entries()].map(
1152
+ ([workspace, { newVersion, dependenciesToBump }]) => {
1153
+ workspace.pkg.version = newVersion;
1154
+ for (const [
1155
+ dependencyType,
1156
+ dependencyDescriptor,
1157
+ dependencyNewRange
1158
+ ] of dependenciesToBump) {
1159
+ const newDescriptor = PackageDependencyDescriptorUtils.make(
1160
+ dependencyDescriptor,
1161
+ dependencyNewRange
1162
+ );
1163
+ const [key, newValue] = PackageDependencyDescriptorUtils.stringify(newDescriptor);
1164
+ workspace.pkg[dependencyType][key] = newValue;
1165
+ }
1166
+ return writePkg(workspace);
1167
+ }
1168
+ )
1169
+ );
1170
+ if (packageManager.installOnPackageContentChange) {
1171
+ logger.info(`${getWorkspaceName(rootWorkspace)}: Running install`);
1172
+ await packageManager.installOnPackageContentChange(rootWorkspace);
1173
+ }
1174
+ logger.info("Lifecycle script: version");
1175
+ for (const workspace of bumpedWorkspaces.keys()) {
1176
+ if (workspace.pkg.scripts?.version) {
1177
+ await packageManager.runScript(workspace, "version");
1178
+ }
1179
+ }
1180
+ }
1181
+ if (isMonorepoVersionIndependent && rootWorkspace.pkg.scripts?.version) {
1182
+ await packageManager.runScript(rootWorkspace, "version");
1183
+ }
1184
+ });
1185
+ const changelogs = /* @__PURE__ */ new Map();
1186
+ await Promise.all(
1187
+ [...bumpedWorkspaces.entries()].map(
1188
+ async ([
1189
+ workspace,
1190
+ { newTag, hasChanged, bumpReason, bumpForDependenciesReasons }
1191
+ ]) => {
1192
+ const commits = commitsByWorkspace?.get(workspace);
1193
+ let changelog = await generateChangelog(
1194
+ rootWorkspace,
1195
+ workspace.pkg,
1196
+ conventionalCommitConfig,
1197
+ previousTagVersionByWorkspace.get(workspace)?.tag || null,
1198
+ isMonorepoVersionIndependent ? newTag : rootNewTag,
1199
+ commits,
1200
+ todayInYYYYMMDD()
1201
+ // {
1202
+ // path: workspaceRelativePath,
1203
+ // previousTag: previousTagByWorkspace.get(workspace) || undefined,
1204
+ // verbose: options.verbose,
1205
+ // tagPrefix: options.tagVersionPrefix,
1206
+ // lernaPackage:
1207
+ // rootWorkspace === workspace
1208
+ // ? undefined
1209
+ // : getWorkspaceName(workspace),
1210
+ // },
1211
+ );
1212
+ if (bumpForDependenciesReasons && workspace !== rootWorkspace) {
1213
+ if (bumpForDependenciesReasons.length > 0) {
1214
+ changelog += `${!changelog.endsWith("\n\n") ? "\n" : ""}${bumpForDependenciesReasons.join("\n")}
1215
+
1216
+ `;
1217
+ }
1218
+ }
1219
+ if (changelog.slice(changelog.indexOf("\n")).trim().length === 0) {
1220
+ changelog += `${!changelog.endsWith("\n\n") ? "\n" : ""}Note: no notable changes
1221
+
1222
+ `;
1223
+ }
1224
+ if (!changelog && rootWorkspace === workspace) {
1225
+ throw new Error("No changelog found for root workspace");
1226
+ }
1227
+ changelogs.set(workspace, changelog);
1228
+ if (options.changelog) {
1229
+ if (options.dryRun) {
1230
+ logger.info(
1231
+ `${getWorkspaceName(workspace)}: ${options.changelog}
1232
+ ${changelog}`
1233
+ );
1234
+ } else {
1235
+ await updateChangelogFile(
1236
+ changelog,
1237
+ options.tagVersionPrefix,
1238
+ `${workspace.cwd}/${options.changelog}`
1239
+ );
1240
+ }
1241
+ }
1242
+ }
1243
+ )
1244
+ );
1245
+ if (!options.dryRun) {
1246
+ logger.separator();
1247
+ if (packageManager.installOnPackageContentChange) {
1248
+ logger.info(`${getWorkspaceName(rootWorkspace)}: Running install`);
1249
+ await packageManager.installOnPackageContentChange(rootWorkspace);
1250
+ }
1251
+ logger.separator();
1252
+ const changedFiles = await getDirtyFiles(project.root);
1253
+ if (changedFiles) {
1254
+ logger.info("Commit, tag and push", {
1255
+ changedFiles
1256
+ });
1257
+ const tagsSet = new Set(
1258
+ [...bumpedWorkspaces.values()].map(({ newTag }) => newTag).filter((newTag) => newTag !== null)
1259
+ );
1260
+ const tagsInCommitMessage = [...tagsSet].map((tag) => `- ${tag}`).join("\n");
1261
+ const message = options.commitMessage.replace(/\\n/g, "\n").replace(
1262
+ /%a/g,
1263
+ isMonorepoVersionIndependent ? `
1264
+
1265
+ ${tagsInCommitMessage}` : rootNewVersion
1266
+ ).replace(/%s/g, rootNewTag).replace(/%v/g, rootNewVersion).replace(/%t/g, tagsInCommitMessage);
1267
+ await createGitCommit(rootWorkspace, message);
1268
+ for (const [workspace, { newTag }] of bumpedWorkspaces.entries()) {
1269
+ if (newTag === null) continue;
1270
+ await createGitTag(workspace, newTag);
1271
+ }
1272
+ if (await isBehindRemote(rootWorkspace, options.gitRemote, gitCurrentBranch)) {
1273
+ logger.error("Remote is ahead, aborting");
1274
+ process.exit(1);
1275
+ }
1276
+ await pushCommitsAndTags(
1277
+ rootWorkspace,
1278
+ options.gitRemote,
1279
+ gitCurrentBranch
1280
+ );
1281
+ if (rootWorkspace.pkg.scripts?.postversion) {
1282
+ logger.info("Lifecycle script: postversion");
1283
+ await packageManager.runScript(rootWorkspace, "postversion");
1284
+ }
1285
+ if (options.createRelease && parsedRepoUrl) {
1286
+ logger.info("Create git release");
1287
+ await Promise.all(
1288
+ [...bumpedWorkspaces.entries()].map(([workspace, { newTag }]) => {
1289
+ if (newTag === null) return void 0;
1290
+ const changelog = changelogs.get(workspace);
1291
+ if (!changelog) {
1292
+ logger.warn(
1293
+ `No changelog found for workspace: ${getWorkspaceName(
1294
+ workspace
1295
+ )}`
1296
+ );
1297
+ return void 0;
1298
+ }
1299
+ return createGhRelease(workspace, {
1300
+ parsedRepoUrl,
1301
+ tag: newTag,
1302
+ body: changelog,
1303
+ prerelease: !!options.prerelease
1304
+ });
1305
+ })
1306
+ );
1307
+ }
1308
+ if (!options.publish) {
1309
+ return;
1310
+ }
1311
+ await logger.group("Publishing packages", async () => {
1312
+ if (packageManager.publishWorkspaces) {
1313
+ await packageManager.publishWorkspaces(rootWorkspace);
1314
+ } else {
1315
+ for (const [workspace] of bumpedWorkspaces.entries()) {
1316
+ if (workspace.pkg.private) {
1317
+ logger.info(
1318
+ `Skipping private workspace ${getWorkspaceName(workspace)}`
1319
+ );
1320
+ continue;
1321
+ }
1322
+ logger.info(`Publishing ${getWorkspaceName(workspace)}`);
1323
+ await packageManager.publish(workspace);
1324
+ }
1325
+ }
1326
+ });
1327
+ } else {
1328
+ logger.warn("No changes made. Nothing to commit.");
1329
+ }
1330
+ }
1331
+ };
1332
+ const Defaults = {
1333
+ cwd: process.cwd(),
1334
+ includesRoot: false,
1335
+ dryRun: false,
1336
+ tagVersionPrefix: "v",
1337
+ changelog: "CHANGELOG.md",
1338
+ commitMessage: "chore: release %a",
1339
+ bumpDependentsHighestAs: "major",
1340
+ alwaysBumpPeerDependencies: false,
1341
+ gitRemote: "origin",
1342
+ verbose: false};
1343
+ program.command("version", { isDefault: true }).usage("Bump package version using conventional commit").addOption(
1344
+ new Option("--includes-root", "Release root workspace [untested]").default(
1345
+ Defaults.includesRoot
1346
+ )
1347
+ ).addOption(new Option("--cwd", "working directory").default(process.cwd())).addOption(
1348
+ new Option(
1349
+ "--dry-run",
1350
+ "Print the versions without actually generating the package archive"
1351
+ ).default(Defaults.dryRun)
1352
+ ).addOption(new Option("-v,--verbose").default(Defaults.verbose)).addOption(
1353
+ new Option("--force <type>", "Specify the release type").choices([
1354
+ "major",
1355
+ "minor",
1356
+ "patch"
1357
+ ])
1358
+ ).addOption(
1359
+ new Option(
1360
+ "--prerelease [releaseType]",
1361
+ "Add a prerelease identifier to new versions"
1362
+ )
1363
+ ).addOption(
1364
+ new Option(
1365
+ "--preset <presetName>",
1366
+ "Conventional Changelog preset to require. Defaults to conventional-changelog-conventionalcommits."
1367
+ ).default("conventional-changelog-conventionalcommits")
1368
+ ).addOption(
1369
+ new Option("--tag-version-prefix <prefix>", "Tag version prefix").default(
1370
+ Defaults.tagVersionPrefix
1371
+ )
1372
+ ).addOption(
1373
+ new Option(
1374
+ "--changelog <path>",
1375
+ "Changelog path. Default to CHANGELOG.md."
1376
+ ).default(Defaults.changelog)
1377
+ ).addOption(
1378
+ new Option(
1379
+ "-m,--commit-message <message>",
1380
+ 'Commit message. Default to "chore: release %a". You can use %v for the version, %s for the version with prefix, %t to list tags, %a for auto best display.'
1381
+ ).default(Defaults.commitMessage)
1382
+ ).addOption(
1383
+ new Option("--create-release <type>", "Create a release").choices([
1384
+ "github"
1385
+ ])
1386
+ ).addOption(
1387
+ new Option(
1388
+ "--bump-dependents-highest-as <type>",
1389
+ "Bump dependents highest version as major, minor or patch"
1390
+ ).choices(["major", "minor", "patch"]).default(Defaults.bumpDependentsHighestAs)
1391
+ ).addOption(
1392
+ new Option(
1393
+ "--always-bump-peer-dependencies",
1394
+ "Always bump peer dependencies. Default to bumping only if the version doesn't satisfies the peer dependency range."
1395
+ ).default(Defaults.alwaysBumpPeerDependencies)
1396
+ ).addOption(
1397
+ new Option(
1398
+ "--git-remote <remote-name>",
1399
+ "Git remote to push commits and tags to"
1400
+ ).default(Defaults.gitRemote)
1401
+ ).addOption(
1402
+ new Option(
1403
+ "--ignore-changes <glob>",
1404
+ 'Ignore changes in files matching the glob. Example: "**/*.test.js"'
1405
+ )
1406
+ ).addOption(new Option("--json", "Logger json")).addOption(
1407
+ new Option(
1408
+ "--package-manager <manager>",
1409
+ "Specify the package manager to use (bun or yarn). Autodetected if not specified."
1410
+ ).choices(["bun", "yarn"])
1411
+ ).addOption(new Option("--publish", "Publish after versioning").default(false)).action((options) => versionCommandAction(options));
1412
+
1413
+ const pkg = JSON.parse(
1414
+ // eslint-disable-next-line unicorn/prefer-json-parse-buffer
1415
+ fs$1.readFileSync(new URL("../package.json", import.meta.url), "utf8")
1416
+ );
1417
+ program.name(pkg.name).description(pkg.description).version(pkg.version);
1418
+ program.parse();
1419
+ //# sourceMappingURL=index-node.mjs.map