@ucdjs/release-scripts 0.1.0-beta.59 → 0.1.0-beta.60

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 (2) hide show
  1. package/dist/index.mjs +185 -116
  2. package/package.json +1 -2
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { t as Eta } from "./eta-DAZlmVBQ.mjs";
2
2
  import process from "node:process";
3
3
  import readline from "node:readline";
4
+ import { parseArgs } from "node:util";
4
5
  import farver from "farver";
5
- import mri from "mri";
6
6
  import { exec } from "tinyexec";
7
7
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
8
8
  import { join, relative } from "node:path";
@@ -12,12 +12,45 @@ import semver, { gt } from "semver";
12
12
  import prompts from "prompts";
13
13
 
14
14
  //#region src/shared/utils.ts
15
- const args = mri(process.argv.slice(2));
16
- const isDryRun = !!args.dry;
17
- const isVerbose$1 = !!args.verbose;
18
- const isForce = !!args.force;
19
15
  const ucdjsReleaseOverridesPath = ".github/ucdjs-release.overrides.json";
20
- const isCI = typeof process.env.CI === "string" && process.env.CI !== "" && process.env.CI.toLowerCase() !== "false";
16
+ function parseCLIFlags() {
17
+ const { values } = parseArgs({
18
+ args: process.argv.slice(2),
19
+ options: {
20
+ dry: {
21
+ type: "boolean",
22
+ short: "d",
23
+ default: false
24
+ },
25
+ verbose: {
26
+ type: "boolean",
27
+ short: "v",
28
+ default: false
29
+ },
30
+ force: {
31
+ type: "boolean",
32
+ short: "f",
33
+ default: false
34
+ }
35
+ },
36
+ strict: false
37
+ });
38
+ return {
39
+ dry: !!values.dry,
40
+ verbose: !!values.verbose,
41
+ force: !!values.force
42
+ };
43
+ }
44
+ function getIsDryRun() {
45
+ return parseCLIFlags().dry;
46
+ }
47
+ function getIsVerbose() {
48
+ return parseCLIFlags().verbose;
49
+ }
50
+ function getIsCI() {
51
+ const ci = process.env.CI;
52
+ return typeof ci === "string" && ci !== "" && ci.toLowerCase() !== "false";
53
+ }
21
54
  const logger = {
22
55
  info: (...args) => {
23
56
  console.info(...args);
@@ -29,7 +62,7 @@ const logger = {
29
62
  console.error(` ${farver.red("✖")}`, ...args);
30
63
  },
31
64
  verbose: (...args) => {
32
- if (!isVerbose$1) return;
65
+ if (!getIsVerbose()) return;
33
66
  if (args.length === 0) {
34
67
  console.log();
35
68
  return;
@@ -78,64 +111,12 @@ async function run(bin, args, opts = {}) {
78
111
  async function dryRun(bin, args, opts) {
79
112
  return logger.verbose(farver.blue(`[dryrun] ${bin} ${args.join(" ")}`), opts || "");
80
113
  }
81
- const runIfNotDry = isDryRun ? dryRun : run;
82
- if (isDryRun || isVerbose$1 || isForce) {
83
- logger.verbose(farver.inverse(farver.yellow(" Running with special flags ")));
84
- logger.verbose({
85
- isDryRun,
86
- isVerbose: isVerbose$1,
87
- isForce
88
- });
89
- logger.verbose();
90
- }
91
-
92
- //#endregion
93
- //#region src/operations/changelog-format.ts
94
- function formatCommitLine({ commit, owner, repo, authors }) {
95
- const commitUrl = `https://github.com/${owner}/${repo}/commit/${commit.hash}`;
96
- let line = `${commit.description}`;
97
- const references = commit.references ?? [];
98
- for (const ref of references) {
99
- if (!ref.value) continue;
100
- const number = Number.parseInt(ref.value.replace(/^#/, ""), 10);
101
- if (Number.isNaN(number)) continue;
102
- if (ref.type === "issue") {
103
- line += ` ([Issue ${ref.value}](https://github.com/${owner}/${repo}/issues/${number}))`;
104
- continue;
105
- }
106
- line += ` ([PR ${ref.value}](https://github.com/${owner}/${repo}/pull/${number}))`;
107
- }
108
- line += ` ([${commit.shortHash}](${commitUrl}))`;
109
- if (authors.length > 0) {
110
- const authorList = authors.map((author) => author.login ? `[@${author.login}](https://github.com/${author.login})` : author.name).join(", ");
111
- line += ` (by ${authorList})`;
112
- }
113
- return line;
114
- }
115
- function buildTemplateGroups(options) {
116
- const { commits, owner, repo, types, commitAuthors } = options;
117
- const grouped = groupByType(commits, {
118
- includeNonConventional: false,
119
- mergeKeys: Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.types ?? [key]]))
120
- });
121
- return Object.entries(types).map(([key, value]) => {
122
- const formattedCommits = (grouped.get(key) ?? []).map((commit) => ({ line: formatCommitLine({
123
- commit,
124
- owner,
125
- repo,
126
- authors: commitAuthors.get(commit.hash) ?? []
127
- }) }));
128
- return {
129
- name: key,
130
- title: value.title,
131
- commits: formattedCommits
132
- };
133
- });
114
+ async function runIfNotDry(bin, args, opts) {
115
+ return getIsDryRun() ? dryRun(bin, args, opts) : run(bin, args, opts);
134
116
  }
135
117
 
136
118
  //#endregion
137
119
  //#region src/shared/errors.ts
138
- const isVerbose = !!mri(process.argv.slice(2)).verbose;
139
120
  function isRecord(value) {
140
121
  return typeof value === "object" && value !== null;
141
122
  }
@@ -208,24 +189,79 @@ function formatUnknownError(error) {
208
189
  }
209
190
  return { message: String(error) };
210
191
  }
211
- function exitWithError(message, hint, cause) {
212
- console.error(` ${farver.red("✖")} ${farver.bold(message)}`);
213
- if (cause !== void 0) {
214
- const formatted = formatUnknownError(cause);
215
- if (formatted.message && formatted.message !== message) console.error(farver.gray(` Cause: ${formatted.message}`));
192
+ var ReleaseError = class extends Error {
193
+ hint;
194
+ constructor(message, hint, cause) {
195
+ super(message);
196
+ this.name = "ReleaseError";
197
+ this.hint = hint;
198
+ this.cause = cause;
199
+ }
200
+ };
201
+ function printReleaseError(error) {
202
+ console.error(` ${farver.red("✖")} ${farver.bold(error.message)}`);
203
+ if (error.cause !== void 0) {
204
+ const formatted = formatUnknownError(error.cause);
205
+ if (formatted.message && formatted.message !== error.message) console.error(farver.gray(` Cause: ${formatted.message}`));
216
206
  if (formatted.code) console.error(farver.gray(` Code: ${formatted.code}`));
217
207
  if (typeof formatted.status === "number") console.error(farver.gray(` Status: ${formatted.status}`));
218
208
  if (formatted.stderr) {
219
209
  console.error(farver.gray(" Stderr:"));
220
210
  console.error(farver.gray(` ${formatted.stderr}`));
221
211
  }
222
- if (isVerbose && formatted.stack) {
212
+ if (getIsVerbose() && formatted.stack) {
223
213
  console.error(farver.gray(" Stack:"));
224
214
  console.error(farver.gray(` ${formatted.stack}`));
225
215
  }
226
216
  }
227
- if (hint) console.error(farver.gray(` ${hint}`));
228
- process.exit(1);
217
+ if (error.hint) console.error(farver.gray(` ${error.hint}`));
218
+ }
219
+ function exitWithError(message, hint, cause) {
220
+ throw new ReleaseError(message, hint, cause);
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/operations/changelog-format.ts
225
+ function formatCommitLine({ commit, owner, repo, authors }) {
226
+ const commitUrl = `https://github.com/${owner}/${repo}/commit/${commit.hash}`;
227
+ let line = `${commit.description}`;
228
+ const references = commit.references ?? [];
229
+ for (const ref of references) {
230
+ if (!ref.value) continue;
231
+ const number = Number.parseInt(ref.value.replace(/^#/, ""), 10);
232
+ if (Number.isNaN(number)) continue;
233
+ if (ref.type === "issue") {
234
+ line += ` ([Issue ${ref.value}](https://github.com/${owner}/${repo}/issues/${number}))`;
235
+ continue;
236
+ }
237
+ line += ` ([PR ${ref.value}](https://github.com/${owner}/${repo}/pull/${number}))`;
238
+ }
239
+ line += ` ([${commit.shortHash}](${commitUrl}))`;
240
+ if (authors.length > 0) {
241
+ const authorList = authors.map((author) => author.login ? `[@${author.login}](https://github.com/${author.login})` : author.name).join(", ");
242
+ line += ` (by ${authorList})`;
243
+ }
244
+ return line;
245
+ }
246
+ function buildTemplateGroups(options) {
247
+ const { commits, owner, repo, types, commitAuthors } = options;
248
+ const grouped = groupByType(commits, {
249
+ includeNonConventional: false,
250
+ mergeKeys: Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.types ?? [key]]))
251
+ });
252
+ return Object.entries(types).map(([key, value]) => {
253
+ const formattedCommits = (grouped.get(key) ?? []).map((commit) => ({ line: formatCommitLine({
254
+ commit,
255
+ owner,
256
+ repo,
257
+ authors: commitAuthors.get(commit.hash) ?? []
258
+ }) }));
259
+ return {
260
+ name: key,
261
+ title: value.title,
262
+ commits: formattedCommits
263
+ };
264
+ });
229
265
  }
230
266
 
231
267
  //#endregion
@@ -720,7 +756,7 @@ async function checkoutBranch(branch, workspaceRoot) {
720
756
  logger.info(`Successfully switched to branch: ${farver.green(branch)}`);
721
757
  return ok(true);
722
758
  }
723
- console.warn(`Unexpected git checkout output: ${output}`);
759
+ logger.warn(`Unexpected git checkout output: ${output}`);
724
760
  return ok(false);
725
761
  } catch (error) {
726
762
  const gitError = toGitError("checkoutBranch", error);
@@ -1414,18 +1450,18 @@ async function discoverWorkspacePackages(workspaceRoot, options) {
1414
1450
  if (explicitPackages) {
1415
1451
  const foundNames = new Set(workspacePackages.map((p) => p.name));
1416
1452
  const missing = explicitPackages.filter((p) => !foundNames.has(p));
1417
- if (missing.length > 0) exitWithError(`Package${missing.length > 1 ? "s" : ""} not found in workspace: ${missing.join(", ")}`, "Check your package names or run 'pnpm ls' to see available packages");
1453
+ if (missing.length > 0) return err(toWorkspaceError("discoverWorkspacePackages", `Package${missing.length > 1 ? "s" : ""} not found in workspace: ${missing.join(", ")}. Check your package names or run 'pnpm ls' to see available packages`));
1418
1454
  }
1419
1455
  const isPackagePromptEnabled = options.prompts?.packages !== false;
1420
1456
  logger.verbose("Package prompt gating", {
1421
- isCI,
1457
+ isCI: getIsCI(),
1422
1458
  isPackagePromptEnabled,
1423
1459
  hasExplicitPackages: Boolean(explicitPackages),
1424
1460
  include: workspaceOptions.include ?? [],
1425
1461
  exclude: workspaceOptions.exclude ?? [],
1426
1462
  excludePrivate: workspaceOptions.excludePrivate ?? false
1427
1463
  });
1428
- if (!isCI && isPackagePromptEnabled && !explicitPackages) {
1464
+ if (!getIsCI() && isPackagePromptEnabled && !explicitPackages) {
1429
1465
  const selectedNames = await selectPackagePrompt(workspacePackages);
1430
1466
  workspacePackages = workspacePackages.filter((pkg) => selectedNames.includes(pkg.name));
1431
1467
  }
@@ -1618,6 +1654,22 @@ function findCommitRange(packageCommits) {
1618
1654
  };
1619
1655
  }
1620
1656
  /**
1657
+ * Filters commits to find global commits (those not touching any package folder),
1658
+ * optionally further filtered to only dependency-related files.
1659
+ */
1660
+ function filterGlobalCommits(commits, commitFilesMap, packagePaths, workspaceRoot, mode) {
1661
+ const globalCommits = commits.filter((commit) => {
1662
+ const files = commitFilesMap.get(commit.shortHash);
1663
+ return files ? isGlobalCommit(workspaceRoot, files, packagePaths) : false;
1664
+ });
1665
+ if (mode === "all") return globalCommits;
1666
+ return globalCommits.filter((commit) => {
1667
+ const files = commitFilesMap.get(commit.shortHash);
1668
+ if (!files) return false;
1669
+ return files.some((file) => DEPENDENCY_FILES.includes(file.startsWith("./") ? file.slice(2) : file));
1670
+ });
1671
+ }
1672
+ /**
1621
1673
  * Get global commits for each package based on their individual commit timelines.
1622
1674
  * This solves the problem where packages with different release histories need different global commits.
1623
1675
  *
@@ -1653,29 +1705,10 @@ async function getGlobalCommitsPerPackage(workspaceRoot, packageCommits, allPack
1653
1705
  logger.verbose("Got file lists for commits", `${farver.cyan(commitFilesMap.value.size)} commits in ONE git call`);
1654
1706
  const packagePaths = new Set(allPackages.map((p) => p.path));
1655
1707
  for (const [pkgName, commits] of packageCommits) {
1656
- const globalCommitsAffectingPackage = [];
1657
1708
  logger.verbose("Filtering global commits for package", `${farver.bold(pkgName)} from ${farver.cyan(commits.length)} commits`);
1658
- for (const commit of commits) {
1659
- const files = commitFilesMap.value.get(commit.shortHash);
1660
- if (!files) continue;
1661
- if (isGlobalCommit(workspaceRoot, files, packagePaths)) globalCommitsAffectingPackage.push(commit);
1662
- }
1663
- logger.verbose("Package global commits found", `${farver.bold(pkgName)}: ${farver.cyan(globalCommitsAffectingPackage.length)} global commits`);
1664
- if (mode === "all") {
1665
- result.set(pkgName, globalCommitsAffectingPackage);
1666
- continue;
1667
- }
1668
- const dependencyCommits = [];
1669
- for (const commit of globalCommitsAffectingPackage) {
1670
- const files = commitFilesMap.value.get(commit.shortHash);
1671
- if (!files) continue;
1672
- if (files.some((file) => DEPENDENCY_FILES.includes(file.startsWith("./") ? file.slice(2) : file))) {
1673
- logger.verbose("Global commit affects dependencies", `${farver.bold(pkgName)}: commit ${farver.cyan(commit.shortHash)} affects dependencies`);
1674
- dependencyCommits.push(commit);
1675
- }
1676
- }
1677
- logger.verbose("Global commits affect dependencies", `${farver.bold(pkgName)}: ${farver.cyan(dependencyCommits.length)} global commits affect dependencies`);
1678
- result.set(pkgName, dependencyCommits);
1709
+ const filtered = filterGlobalCommits(commits, commitFilesMap.value, packagePaths, workspaceRoot, mode);
1710
+ logger.verbose("Package global commits found", `${farver.bold(pkgName)}: ${farver.cyan(filtered.length)} global commits`);
1711
+ result.set(pkgName, filtered);
1679
1712
  }
1680
1713
  return result;
1681
1714
  }
@@ -1890,6 +1923,33 @@ function formatCommitsForDisplay(commits) {
1890
1923
  if (hasMore) return `${formattedCommits}\n ${farver.dim(`... and ${commits.length - maxCommitsToShow} more commits`)}`;
1891
1924
  return formattedCommits;
1892
1925
  }
1926
+ /**
1927
+ * Pure function that resolves version bump from commits and overrides.
1928
+ * No IO, no prompts - fully testable in isolation.
1929
+ */
1930
+ function resolveAutoVersion(pkg, packageCommits, globalCommits, override) {
1931
+ const determinedBump = determineHighestBump([...packageCommits, ...globalCommits]);
1932
+ const effectiveBump = override?.type || determinedBump;
1933
+ const autoVersion = getNextVersion(pkg.version, determinedBump);
1934
+ return {
1935
+ determinedBump,
1936
+ effectiveBump,
1937
+ autoVersion,
1938
+ resolvedVersion: override?.version || autoVersion
1939
+ };
1940
+ }
1941
+ /**
1942
+ * Pure function that computes the new dependency range.
1943
+ * Returns null if the dependency should not be updated (e.g. workspace:*).
1944
+ */
1945
+ function computeDependencyRange(currentRange, newVersion, isPeerDependency) {
1946
+ if (currentRange === "workspace:*") return null;
1947
+ if (isPeerDependency) {
1948
+ const majorVersion = newVersion.split(".")[0];
1949
+ return `>=${newVersion} <${Number(majorVersion) + 1}.0.0`;
1950
+ }
1951
+ return `^${newVersion}`;
1952
+ }
1893
1953
  async function calculateVersionUpdates({ workspacePackages, packageCommits, workspaceRoot, showPrompt, globalCommitsPerPackage, overrides: initialOverrides = {} }) {
1894
1954
  const versionUpdates = [];
1895
1955
  const processedPackages = /* @__PURE__ */ new Set();
@@ -1905,13 +1965,11 @@ async function calculateVersionUpdates({ workspacePackages, packageCommits, work
1905
1965
  processedPackages.add(pkgName);
1906
1966
  const globalCommits = globalCommitsPerPackage.get(pkgName) || [];
1907
1967
  const allCommitsForPackage = [...pkgCommits, ...globalCommits];
1908
- const determinedBump = determineHighestBump(allCommitsForPackage);
1909
1968
  const override = newOverrides[pkgName];
1910
- const effectiveBump = override?.type || determinedBump;
1911
- const canPrompt = !isCI && showPrompt;
1969
+ const { determinedBump, effectiveBump, autoVersion, resolvedVersion } = resolveAutoVersion(pkg, pkgCommits, globalCommits, override);
1970
+ const canPrompt = !getIsCI() && showPrompt;
1912
1971
  if (effectiveBump === "none" && !canPrompt) continue;
1913
- const autoVersion = getNextVersion(pkg.version, determinedBump);
1914
- let newVersion = override?.version || autoVersion;
1972
+ let newVersion = resolvedVersion;
1915
1973
  let finalBumpType = effectiveBump;
1916
1974
  if (canPrompt) {
1917
1975
  logger.clearScreen();
@@ -1980,7 +2038,7 @@ async function calculateVersionUpdates({ workspacePackages, packageCommits, work
1980
2038
  changeKind: canPrompt ? "manual" : "auto"
1981
2039
  });
1982
2040
  }
1983
- if (!isCI && showPrompt) for (const pkg of workspacePackages) {
2041
+ if (!getIsCI() && showPrompt) for (const pkg of workspacePackages) {
1984
2042
  if (processedPackages.has(pkg.name)) continue;
1985
2043
  logger.clearScreen();
1986
2044
  logger.section(`📦 Package: ${pkg.name}`);
@@ -2058,15 +2116,13 @@ async function updatePackageJson(pkg, newVersion, dependencyUpdates) {
2058
2116
  if (!deps) return;
2059
2117
  const oldVersion = deps[depName];
2060
2118
  if (!oldVersion) return;
2061
- if (oldVersion === "workspace:*") {
2119
+ const newRange = computeDependencyRange(oldVersion, depVersion, isPeerDependency);
2120
+ if (newRange === null) {
2062
2121
  logger.verbose(` - Skipping workspace:* dependency: ${depName}`);
2063
2122
  return;
2064
2123
  }
2065
- if (isPeerDependency) {
2066
- const majorVersion = depVersion.split(".")[0];
2067
- deps[depName] = `>=${depVersion} <${Number(majorVersion) + 1}.0.0`;
2068
- } else deps[depName] = `^${depVersion}`;
2069
- logger.verbose(` - Updated dependency ${depName}: ${oldVersion} → ${deps[depName]}`);
2124
+ deps[depName] = newRange;
2125
+ logger.verbose(` - Updated dependency ${depName}: ${oldVersion} → ${newRange}`);
2070
2126
  }
2071
2127
  for (const [depName, depVersion] of dependencyUpdates) {
2072
2128
  updateDependency(packageJson.dependencies, depName, depVersion);
@@ -2720,6 +2776,15 @@ async function verifyWorkflow(options) {
2720
2776
 
2721
2777
  //#endregion
2722
2778
  //#region src/index.ts
2779
+ function withErrorBoundary(fn) {
2780
+ return fn().catch((e) => {
2781
+ if (e instanceof ReleaseError) {
2782
+ printReleaseError(e);
2783
+ process.exit(1);
2784
+ }
2785
+ throw e;
2786
+ });
2787
+ }
2723
2788
  async function createReleaseScripts(options) {
2724
2789
  const normalizedOptions = normalizeReleaseScriptsOptions(options);
2725
2790
  logger.verbose("Release scripts config", {
@@ -2740,24 +2805,28 @@ async function createReleaseScripts(options) {
2740
2805
  });
2741
2806
  return {
2742
2807
  async verify() {
2743
- return verifyWorkflow(normalizedOptions);
2808
+ return withErrorBoundary(() => verifyWorkflow(normalizedOptions));
2744
2809
  },
2745
2810
  async prepare() {
2746
- return prepareWorkflow(normalizedOptions);
2811
+ return withErrorBoundary(() => prepareWorkflow(normalizedOptions));
2747
2812
  },
2748
2813
  async publish() {
2749
- return publishWorkflow(normalizedOptions);
2814
+ return withErrorBoundary(() => publishWorkflow(normalizedOptions));
2750
2815
  },
2751
2816
  packages: {
2752
2817
  async list() {
2753
- const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
2754
- if (!result.ok) throw new Error(result.error.message);
2755
- return result.value;
2818
+ return withErrorBoundary(async () => {
2819
+ const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
2820
+ if (!result.ok) throw new Error(result.error.message);
2821
+ return result.value;
2822
+ });
2756
2823
  },
2757
2824
  async get(packageName) {
2758
- const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
2759
- if (!result.ok) throw new Error(result.error.message);
2760
- return result.value.find((p) => p.name === packageName);
2825
+ return withErrorBoundary(async () => {
2826
+ const result = await discoverWorkspacePackages(normalizedOptions.workspaceRoot, normalizedOptions);
2827
+ if (!result.ok) throw new Error(result.error.message);
2828
+ return result.value.find((p) => p.name === packageName);
2829
+ });
2761
2830
  }
2762
2831
  }
2763
2832
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ucdjs/release-scripts",
3
- "version": "0.1.0-beta.59",
3
+ "version": "0.1.0-beta.60",
4
4
  "description": "@ucdjs release scripts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,7 +28,6 @@
28
28
  "@luxass/utils": "2.7.3",
29
29
  "commit-parser": "1.3.0",
30
30
  "farver": "1.0.0-beta.1",
31
- "mri": "1.2.0",
32
31
  "prompts": "2.4.2",
33
32
  "semver": "7.7.4",
34
33
  "tinyexec": "1.0.2"