@ucdjs/release-scripts 0.1.0-beta.30 → 0.1.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -35,6 +35,9 @@ interface ReleaseScriptsOptionsInput {
35
35
  otp?: string;
36
36
  provenance?: boolean;
37
37
  };
38
+ prompts?: {
39
+ versions?: boolean;
40
+ };
38
41
  }
39
42
  //#endregion
40
43
  //#region src/services/workspace.service.d.ts
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import * as CommitParser from "commit-parser";
7
7
  import process from "node:process";
8
8
  import semver from "semver";
9
9
  import fs from "node:fs/promises";
10
+ import prompts from "prompts";
10
11
 
11
12
  //#region src/utils/changelog-formatters.ts
12
13
  const eta$1 = new Eta();
@@ -243,23 +244,25 @@ const DEFAULT_TYPES = {
243
244
  }
244
245
  };
245
246
  function normalizeReleaseScriptsOptions(options) {
246
- const { workspaceRoot = process.cwd(), githubToken = "", repo: fullRepo, packages = true, branch = {}, globalCommitMode = "dependencies", pullRequest = {}, changelog = {}, types = {}, dryRun = false, npm = {} } = options;
247
+ const { workspaceRoot = process.cwd(), githubToken = "", repo: fullRepo, packages = true, branch = {}, globalCommitMode = "dependencies", pullRequest = {}, changelog = {}, types = {}, dryRun = false, npm = {}, prompts = {} } = options;
247
248
  const token = githubToken.trim();
248
249
  if (!token) throw new Error("GitHub token is required. Pass it in via options.");
249
250
  if (!fullRepo || !fullRepo.trim() || !fullRepo.includes("/")) throw new Error("Repository (repo) is required. Specify in 'owner/repo' format (e.g., 'octocat/hello-world').");
250
251
  const [owner, repo] = fullRepo.split("/");
251
252
  if (!owner || !repo) throw new Error(`Invalid repo format: "${fullRepo}". Expected format: "owner/repo" (e.g., "octocat/hello-world").`);
253
+ const normalizedPackages = typeof packages === "object" && !Array.isArray(packages) ? {
254
+ exclude: packages.exclude ?? [],
255
+ include: packages.include ?? [],
256
+ excludePrivate: packages.excludePrivate ?? false
257
+ } : packages;
258
+ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
252
259
  return {
253
260
  dryRun,
254
261
  workspaceRoot,
255
262
  githubToken: token,
256
263
  owner,
257
264
  repo,
258
- packages: typeof packages === "object" && !Array.isArray(packages) ? {
259
- exclude: packages.exclude ?? [],
260
- include: packages.include ?? [],
261
- excludePrivate: packages.excludePrivate ?? false
262
- } : packages,
265
+ packages: normalizedPackages,
263
266
  branch: {
264
267
  release: branch.release ?? "release/next",
265
268
  default: branch.default ?? "main"
@@ -281,7 +284,8 @@ function normalizeReleaseScriptsOptions(options) {
281
284
  npm: {
282
285
  otp: npm.otp,
283
286
  provenance: npm.provenance ?? true
284
- }
287
+ },
288
+ prompts: { versions: prompts.versions ?? !isCI }
285
289
  };
286
290
  }
287
291
  var ReleaseScriptsOptions = class extends Context.Tag("@ucdjs/release-scripts/ReleaseScriptsOptions")() {};
@@ -714,17 +718,26 @@ var NPMService = class extends Effect.Service()("@ucdjs/release-scripts/NPMServi
714
718
 
715
719
  //#endregion
716
720
  //#region src/services/workspace.service.ts
717
- const DependencyObjectSchema = Schema.Record({
721
+ const DependencyRecordSchema = Schema.Record({
718
722
  key: Schema.String,
719
723
  value: Schema.String
720
724
  });
725
+ const PnpmDependencySchema = Schema.Record({
726
+ key: Schema.String,
727
+ value: Schema.Struct({
728
+ from: Schema.String,
729
+ version: Schema.String,
730
+ resolved: Schema.optional(Schema.String),
731
+ path: Schema.optional(Schema.String)
732
+ })
733
+ });
721
734
  const PackageJsonSchema = Schema.Struct({
722
735
  name: Schema.String,
723
736
  private: Schema.optional(Schema.Boolean),
724
737
  version: Schema.optional(Schema.String),
725
- dependencies: Schema.optional(DependencyObjectSchema),
726
- devDependencies: Schema.optional(DependencyObjectSchema),
727
- peerDependencies: Schema.optional(DependencyObjectSchema)
738
+ dependencies: Schema.optional(DependencyRecordSchema),
739
+ devDependencies: Schema.optional(DependencyRecordSchema),
740
+ peerDependencies: Schema.optional(DependencyRecordSchema)
728
741
  });
729
742
  const WorkspacePackageSchema = Schema.Struct({
730
743
  name: Schema.String,
@@ -739,9 +752,9 @@ const WorkspaceListSchema = Schema.Array(Schema.Struct({
739
752
  path: Schema.String,
740
753
  version: Schema.optional(Schema.String),
741
754
  private: Schema.optional(Schema.Boolean),
742
- dependencies: Schema.optional(DependencyObjectSchema),
743
- devDependencies: Schema.optional(DependencyObjectSchema),
744
- peerDependencies: Schema.optional(DependencyObjectSchema)
755
+ dependencies: Schema.optional(PnpmDependencySchema),
756
+ devDependencies: Schema.optional(PnpmDependencySchema),
757
+ peerDependencies: Schema.optional(PnpmDependencySchema)
745
758
  }));
746
759
  var WorkspaceService = class extends Effect.Service()("@ucdjs/release-scripts/WorkspaceService", {
747
760
  effect: Effect.gen(function* () {
@@ -813,9 +826,11 @@ var WorkspaceService = class extends Effect.Service()("@ucdjs/release-scripts/Wo
813
826
  const allPackageNames = new Set(rawProjects.map((p) => p.name));
814
827
  return Effect.all(rawProjects.map((rawProject) => readPackageJson(rawProject.path).pipe(Effect.flatMap((packageJson) => {
815
828
  if (!shouldIncludePackage(packageJson, options)) return Effect.succeed(null);
829
+ const version = packageJson.version ?? rawProject.version;
830
+ if (!version) return Effect.logWarning(`Skipping package ${rawProject.name} without version`).pipe(Effect.as(null));
816
831
  const pkg = {
817
832
  name: rawProject.name,
818
- version: rawProject.version,
833
+ version,
819
834
  path: rawProject.path,
820
835
  packageJson,
821
836
  workspaceDependencies: Object.keys(rawProject.dependencies || {}).filter((dep) => allPackageNames.has(dep)),
@@ -992,6 +1007,275 @@ var VersionCalculatorService = class extends Effect.Service()("@ucdjs/release-sc
992
1007
  dependencies: []
993
1008
  }) {};
994
1009
 
1010
+ //#endregion
1011
+ //#region src/services/version-prompt.service.ts
1012
+ function formatCommit(commit) {
1013
+ const typeEmoji = getTypeEmoji(commit.type);
1014
+ const scope = commit.scope ? `(${commit.scope})` : "";
1015
+ const breaking = commit.isBreaking ? "!" : "";
1016
+ const header = commit.isConventional ? `${typeEmoji} ${commit.type}${scope}${breaking}: ${commit.description}` : commit.message.split("\n")[0] ?? commit.message;
1017
+ const refs = commit.references.map((r) => r.type === "pull-request" ? `#${r.value}` : `#${r.value}`).join(" ");
1018
+ return refs ? `${header} (${refs})` : header;
1019
+ }
1020
+ function getTypeEmoji(type) {
1021
+ return {
1022
+ feat: "✨",
1023
+ fix: "šŸ›",
1024
+ docs: "šŸ“š",
1025
+ style: "šŸ’Ž",
1026
+ refactor: "šŸ”§",
1027
+ perf: "šŸŽļø",
1028
+ test: "🧪",
1029
+ build: "šŸ“¦",
1030
+ ci: "šŸ‘·",
1031
+ chore: "šŸ”§",
1032
+ revert: "āŖ"
1033
+ }[type] || "šŸ“";
1034
+ }
1035
+ function formatCommits(commits) {
1036
+ if (commits.length === 0) return " No commits since the last version";
1037
+ return commits.slice(0, 10).map((c) => ` ${formatCommit(c)}`).join("\n") + (commits.length > 10 ? `\n ... and ${commits.length - 10} more` : "");
1038
+ }
1039
+ function getPrereleaseInfo(version) {
1040
+ const parsed = semver.parse(version);
1041
+ if (!parsed) return null;
1042
+ if (parsed.prerelease.length === 0) return null;
1043
+ return {
1044
+ identifier: String(parsed.prerelease[0]),
1045
+ baseVersion: `${parsed.major}.${parsed.minor}.${parsed.patch}`
1046
+ };
1047
+ }
1048
+ function generateVersionOptions(currentVersion, conventionalBump, prereleaseInfo) {
1049
+ const options = [];
1050
+ const majorVersion = semver.inc(currentVersion, "major");
1051
+ const minorVersion = semver.inc(currentVersion, "minor");
1052
+ const patchVersion = semver.inc(currentVersion, "patch");
1053
+ if (majorVersion) options.push({
1054
+ title: `major ${majorVersion}`,
1055
+ value: {
1056
+ version: majorVersion,
1057
+ bumpType: "major"
1058
+ }
1059
+ });
1060
+ if (minorVersion) options.push({
1061
+ title: `minor ${minorVersion}`,
1062
+ value: {
1063
+ version: minorVersion,
1064
+ bumpType: "minor"
1065
+ }
1066
+ });
1067
+ if (patchVersion) options.push({
1068
+ title: `patch ${patchVersion}`,
1069
+ value: {
1070
+ version: patchVersion,
1071
+ bumpType: "patch"
1072
+ }
1073
+ });
1074
+ if (prereleaseInfo) {
1075
+ const nextPrerelease = semver.inc(currentVersion, "prerelease", prereleaseInfo.identifier);
1076
+ if (nextPrerelease) options.push({
1077
+ title: `next ${nextPrerelease}`,
1078
+ value: {
1079
+ version: nextPrerelease,
1080
+ bumpType: "patch"
1081
+ }
1082
+ });
1083
+ }
1084
+ const conventionalVersion = conventionalBump !== "none" ? semver.inc(currentVersion, conventionalBump) : currentVersion;
1085
+ if (conventionalVersion && conventionalVersion !== currentVersion) options.push({
1086
+ title: `conventional ${conventionalVersion}`,
1087
+ value: {
1088
+ version: conventionalVersion,
1089
+ bumpType: conventionalBump
1090
+ }
1091
+ });
1092
+ if (prereleaseInfo) {
1093
+ const prePatch = semver.inc(currentVersion, "prepatch", prereleaseInfo.identifier);
1094
+ const preMinor = semver.inc(currentVersion, "preminor", prereleaseInfo.identifier);
1095
+ const preMajor = semver.inc(currentVersion, "premajor", prereleaseInfo.identifier);
1096
+ if (prePatch) options.push({
1097
+ title: `pre-patch ${prePatch}`,
1098
+ value: {
1099
+ version: prePatch,
1100
+ bumpType: "patch"
1101
+ }
1102
+ });
1103
+ if (preMinor) options.push({
1104
+ title: `pre-minor ${preMinor}`,
1105
+ value: {
1106
+ version: preMinor,
1107
+ bumpType: "minor"
1108
+ }
1109
+ });
1110
+ if (preMajor) options.push({
1111
+ title: `pre-major ${preMajor}`,
1112
+ value: {
1113
+ version: preMajor,
1114
+ bumpType: "major"
1115
+ }
1116
+ });
1117
+ } else {
1118
+ const betaPatch = semver.inc(currentVersion, "prepatch", "beta");
1119
+ const betaMinor = semver.inc(currentVersion, "preminor", "beta");
1120
+ const betaMajor = semver.inc(currentVersion, "premajor", "beta");
1121
+ if (betaPatch) options.push({
1122
+ title: `pre-patch ${betaPatch}`,
1123
+ value: {
1124
+ version: betaPatch,
1125
+ bumpType: "patch"
1126
+ }
1127
+ });
1128
+ if (betaMinor) options.push({
1129
+ title: `pre-minor ${betaMinor}`,
1130
+ value: {
1131
+ version: betaMinor,
1132
+ bumpType: "minor"
1133
+ }
1134
+ });
1135
+ if (betaMajor) options.push({
1136
+ title: `pre-major ${betaMajor}`,
1137
+ value: {
1138
+ version: betaMajor,
1139
+ bumpType: "major"
1140
+ }
1141
+ });
1142
+ }
1143
+ options.push({
1144
+ title: `as-is ${currentVersion}`,
1145
+ value: {
1146
+ version: currentVersion,
1147
+ bumpType: "none"
1148
+ }
1149
+ });
1150
+ options.push({
1151
+ title: "custom ...",
1152
+ value: {
1153
+ version: "custom",
1154
+ bumpType: "none"
1155
+ }
1156
+ });
1157
+ return options;
1158
+ }
1159
+ async function promptForCustomVersion(currentVersion) {
1160
+ return (await prompts({
1161
+ type: "text",
1162
+ name: "version",
1163
+ message: `Enter custom version (current: ${currentVersion})`,
1164
+ validate: (input) => {
1165
+ if (!semver.valid(input)) return "Please enter a valid semver version (e.g., 1.2.3)";
1166
+ return true;
1167
+ }
1168
+ })).version || null;
1169
+ }
1170
+ var VersionPromptService = class extends Effect.Service()("@ucdjs/release-scripts/VersionPromptService", {
1171
+ effect: Effect.gen(function* () {
1172
+ const config = yield* ReleaseScriptsOptions;
1173
+ let applyToAllRemainingChoice = null;
1174
+ function promptForVersion(pkg, conventionalBump, remainingCount) {
1175
+ return Effect.async((resume) => {
1176
+ const allCommits = [...pkg.commits, ...pkg.globalCommits];
1177
+ const prereleaseInfo = getPrereleaseInfo(pkg.version);
1178
+ console.log("");
1179
+ console.log(`\x1B[1m${pkg.name}\x1B[0m`);
1180
+ console.log(`Current version: ${pkg.version}`);
1181
+ console.log("");
1182
+ console.log("Commits:");
1183
+ console.log(formatCommits(allCommits));
1184
+ console.log("");
1185
+ if (applyToAllRemainingChoice) {
1186
+ const result = {
1187
+ newVersion: applyToAllRemainingChoice.version === "custom" ? pkg.version : applyToAllRemainingChoice.version,
1188
+ bumpType: applyToAllRemainingChoice.bumpType,
1189
+ applyToAllRemaining: false
1190
+ };
1191
+ resume(Effect.succeed(result));
1192
+ return;
1193
+ }
1194
+ const options = generateVersionOptions(pkg.version, conventionalBump, prereleaseInfo);
1195
+ if (remainingCount > 1) options.push({
1196
+ title: "apply-to-all ›",
1197
+ value: {
1198
+ version: "apply-to-all",
1199
+ bumpType: "none"
1200
+ }
1201
+ });
1202
+ prompts({
1203
+ type: "select",
1204
+ name: "choice",
1205
+ message: `Select version`,
1206
+ choices: options.map((o) => ({
1207
+ title: o.title,
1208
+ value: o.value
1209
+ })),
1210
+ hint: "Use arrow keys to navigate, enter to select"
1211
+ }).then(async (response) => {
1212
+ if (!response.choice) {
1213
+ const result = {
1214
+ newVersion: pkg.version,
1215
+ bumpType: "none",
1216
+ applyToAllRemaining: false
1217
+ };
1218
+ resume(Effect.succeed(result));
1219
+ return;
1220
+ }
1221
+ if (response.choice.version === "apply-to-all") {
1222
+ const applyOptions = options.filter((o) => o.value.version !== "custom" && o.value.version !== "apply-to-all");
1223
+ const applyResponse = await prompts({
1224
+ type: "select",
1225
+ name: "choice",
1226
+ message: `Apply to all ${remainingCount} remaining packages`,
1227
+ choices: applyOptions.map((o) => ({
1228
+ title: o.title,
1229
+ value: o.value
1230
+ }))
1231
+ });
1232
+ if (applyResponse.choice) {
1233
+ if (applyResponse.choice.version === "custom") {
1234
+ const customVersion = await promptForCustomVersion(pkg.version);
1235
+ if (customVersion) applyToAllRemainingChoice = {
1236
+ version: customVersion,
1237
+ bumpType: applyResponse.choice.bumpType
1238
+ };
1239
+ } else applyToAllRemainingChoice = applyResponse.choice;
1240
+ const result = {
1241
+ newVersion: applyToAllRemainingChoice?.version || pkg.version,
1242
+ bumpType: applyToAllRemainingChoice?.bumpType || "none",
1243
+ applyToAllRemaining: true
1244
+ };
1245
+ resume(Effect.succeed(result));
1246
+ } else promptForVersion(pkg, conventionalBump, remainingCount).pipe(Effect.runPromise).then((r) => resume(Effect.succeed(r)));
1247
+ return;
1248
+ }
1249
+ let selectedVersion = response.choice.version;
1250
+ let selectedBumpType = response.choice.bumpType;
1251
+ if (selectedVersion === "custom") {
1252
+ const customVersion = await promptForCustomVersion(pkg.version);
1253
+ if (customVersion) selectedVersion = customVersion;
1254
+ else {
1255
+ selectedVersion = pkg.version;
1256
+ selectedBumpType = "none";
1257
+ }
1258
+ }
1259
+ const result = {
1260
+ newVersion: selectedVersion,
1261
+ bumpType: selectedBumpType,
1262
+ applyToAllRemaining: false
1263
+ };
1264
+ resume(Effect.succeed(result));
1265
+ });
1266
+ });
1267
+ }
1268
+ return {
1269
+ promptForVersion,
1270
+ isEnabled: config.prompts.versions,
1271
+ resetApplyToAll: () => {
1272
+ applyToAllRemainingChoice = null;
1273
+ }
1274
+ };
1275
+ }),
1276
+ dependencies: []
1277
+ }) {};
1278
+
995
1279
  //#endregion
996
1280
  //#region src/utils/helpers.ts
997
1281
  function loadOverrides(options) {
@@ -1186,6 +1470,7 @@ function constructPrepareProgram(config) {
1186
1470
  const dependencyGraph = yield* DependencyGraphService;
1187
1471
  const packageUpdater = yield* PackageUpdaterService;
1188
1472
  const versionCalculator = yield* VersionCalculatorService;
1473
+ const versionPrompt = yield* VersionPromptService;
1189
1474
  const workspace = yield* WorkspaceService;
1190
1475
  yield* git.workspace.assertWorkspaceReady;
1191
1476
  let releasePullRequest = yield* github.getPullRequestByBranch(config.branch.release);
@@ -1214,10 +1499,56 @@ function constructPrepareProgram(config) {
1214
1499
  yield* git.branches.checkout(config.branch.default);
1215
1500
  const packages = yield* workspace.discoverWorkspacePackages.pipe(Effect.flatMap(mergePackageCommitsIntoPackages), Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)));
1216
1501
  yield* Console.log(`šŸ“¦ Discovered ${packages.length} packages with commits.`);
1217
- const releases = yield* versionCalculator.calculateBumps(packages, overrides);
1218
1502
  yield* dependencyGraph.topologicalOrder(packages);
1503
+ const releases = [];
1504
+ if (versionPrompt.isEnabled) {
1505
+ yield* Console.log("\nšŸŽÆ Interactive version selection enabled.\n");
1506
+ versionPrompt.resetApplyToAll();
1507
+ for (let i = 0; i < packages.length; i++) {
1508
+ const pkg = packages[i];
1509
+ const conventionalBump = determineBump([...pkg.commits, ...pkg.globalCommits]);
1510
+ const remainingCount = packages.length - i;
1511
+ const override = overrides[pkg.name];
1512
+ if (override) {
1513
+ if (!semver.valid(override)) return yield* Effect.fail(/* @__PURE__ */ new Error(`Invalid override version for ${pkg.name}: ${override}`));
1514
+ releases.push({
1515
+ package: {
1516
+ name: pkg.name,
1517
+ version: pkg.version,
1518
+ path: pkg.path,
1519
+ packageJson: pkg.packageJson,
1520
+ workspaceDependencies: pkg.workspaceDependencies,
1521
+ workspaceDevDependencies: pkg.workspaceDevDependencies
1522
+ },
1523
+ currentVersion: pkg.version,
1524
+ newVersion: override,
1525
+ bumpType: "none",
1526
+ hasDirectChanges: pkg.commits.length > 0
1527
+ });
1528
+ continue;
1529
+ }
1530
+ const result = yield* versionPrompt.promptForVersion(pkg, conventionalBump, remainingCount);
1531
+ releases.push({
1532
+ package: {
1533
+ name: pkg.name,
1534
+ version: pkg.version,
1535
+ path: pkg.path,
1536
+ packageJson: pkg.packageJson,
1537
+ workspaceDependencies: pkg.workspaceDependencies,
1538
+ workspaceDevDependencies: pkg.workspaceDevDependencies
1539
+ },
1540
+ currentVersion: pkg.version,
1541
+ newVersion: result.newVersion,
1542
+ bumpType: result.bumpType,
1543
+ hasDirectChanges: pkg.commits.length > 0
1544
+ });
1545
+ }
1546
+ } else {
1547
+ const calculatedReleases = yield* versionCalculator.calculateBumps(packages, overrides);
1548
+ releases.push(...calculatedReleases);
1549
+ }
1219
1550
  const releasesCount = releases.length;
1220
- yield* Console.log(`šŸ“Š ${releasesCount} package${releasesCount === 1 ? "" : "s"} will be released.`);
1551
+ yield* Console.log(`\nšŸ“Š ${releasesCount} package${releasesCount === 1 ? "" : "s"} will be released.`);
1221
1552
  yield* git.branches.checkout(originalBranch);
1222
1553
  yield* Console.log("āœļø Updating package.json files...");
1223
1554
  yield* packageUpdater.applyReleases(packages, releases);
@@ -1272,6 +1603,8 @@ ${releases.map((r) => ` - ${r.package.name}@${r.newVersion}`).join("\n")}`;
1272
1603
  yield* Console.log("āœ… Pull request updated.");
1273
1604
  }
1274
1605
  yield* Console.log(`\nšŸŽ‰ Release preparation complete! View PR: #${releasePullRequest.number}`);
1606
+ yield* git.branches.checkout(config.branch.default);
1607
+ yield* Console.log(`āœ… Switched back to "${config.branch.default}".`);
1275
1608
  });
1276
1609
  }
1277
1610
 
@@ -1500,7 +1833,7 @@ function constructVerifyProgram(config) {
1500
1833
  //#region src/index.ts
1501
1834
  async function createReleaseScripts(options) {
1502
1835
  const config = normalizeReleaseScriptsOptions(options);
1503
- const AppLayer = Layer.mergeAll(ChangelogService.Default, GitService.Default, GitHubService.Default, DependencyGraphService.Default, NPMService.Default, PackageUpdaterService.Default, VersionCalculatorService.Default, WorkspaceService.Default).pipe(Layer.provide(Layer.succeed(ReleaseScriptsOptions, config)), Layer.provide(NodeCommandExecutor.layer), Layer.provide(NodeFileSystem.layer));
1836
+ const AppLayer = Layer.mergeAll(ChangelogService.Default, GitService.Default, GitHubService.Default, DependencyGraphService.Default, NPMService.Default, PackageUpdaterService.Default, VersionCalculatorService.Default, VersionPromptService.Default, WorkspaceService.Default).pipe(Layer.provide(Layer.succeed(ReleaseScriptsOptions, config)), Layer.provide(NodeCommandExecutor.layer), Layer.provide(NodeFileSystem.layer));
1504
1837
  const runProgram = (program) => {
1505
1838
  const provided = program.pipe(Effect.provide(AppLayer));
1506
1839
  return Effect.runPromise(provided);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ucdjs/release-scripts",
3
- "version": "0.1.0-beta.30",
3
+ "version": "0.1.0-beta.32",
4
4
  "description": "@ucdjs release scripts",
5
5
  "type": "module",
6
6
  "license": "MIT",