verelease 0.1.2-alpha.0 → 0.1.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve as resolve2, join as join10 } from "path";
5
- import { writeFile as writeFile5, mkdir as mkdir5, readFile as readFile5, unlink } from "fs/promises";
4
+ import { resolve as resolve2, join as join13 } from "path";
5
+ import { writeFile as writeFile5, mkdir as mkdir6, readFile as readFile7, unlink, rm as rm4 } from "fs/promises";
6
+ import { mkdtemp as mkdtemp5 } from "fs/promises";
7
+ import { tmpdir as tmpdir5 } from "os";
8
+ import { existsSync } from "fs";
9
+ import { createInterface } from "readline/promises";
6
10
 
7
11
  // ../workspace/dist/graph.js
8
12
  var PackageGraphImpl = class {
@@ -554,12 +558,14 @@ var CandidatePacker = class {
554
558
 
555
559
  // ../contracts/dist/runner.js
556
560
  import { mkdir as mkdir3, symlink, rm as rm2, writeFile as writeFile2, copyFile } from "fs/promises";
557
- import { join as join4, basename } from "path";
561
+ import { join as join5, basename } from "path";
558
562
  import { tmpdir as tmpdir2 } from "os";
559
563
  import { mkdtemp as mkdtemp2 } from "fs/promises";
560
564
 
561
565
  // ../test-adapter-node/dist/node-test.js
562
566
  import { spawn } from "child_process";
567
+ import { readFile as readFile3 } from "fs/promises";
568
+ import { join as join4 } from "path";
563
569
  var NodeTestAdapter = class {
564
570
  options;
565
571
  name = "node:test";
@@ -582,8 +588,22 @@ var NodeTestAdapter = class {
582
588
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
583
589
  const startTime = Date.now();
584
590
  const reporter = this.options.reporter || "tap";
585
- const loaderArgs = this.options.loaderArgs || [];
586
- const testFiles = ctx.testFiles && ctx.testFiles.length > 0 ? ctx.testFiles : [];
591
+ let loaderArgs = this.options.loaderArgs || [];
592
+ let testFiles = ctx.testFiles && ctx.testFiles.length > 0 ? ctx.testFiles : [];
593
+ if (loaderArgs.length === 0 || testFiles.length === 0) {
594
+ try {
595
+ const pkgRaw = await readFile3(join4(ctx.packagePath, "package.json"), "utf8");
596
+ const pkg = JSON.parse(pkgRaw);
597
+ const testScript = pkg.scripts?.test || "";
598
+ if (loaderArgs.length === 0 && (testScript.includes("--import tsx") || testScript.includes("tsx"))) {
599
+ loaderArgs = ["--import", "tsx"];
600
+ }
601
+ if (testFiles.length === 0 && testScript.includes("src/**/*.test.ts")) {
602
+ testFiles = ["src/**/*.test.ts"];
603
+ }
604
+ } catch {
605
+ }
606
+ }
587
607
  const command = [
588
608
  "node",
589
609
  ...loaderArgs,
@@ -746,22 +766,22 @@ var ContractRunner = class {
746
766
  }
747
767
  async runContract(contract, candidate) {
748
768
  const startTime = Date.now();
749
- const consumerEnvDir = await mkdtemp2(join4(tmpdir2(), "verelease-consumer-"));
750
- const nodeModulesDir = join4(consumerEnvDir, "node_modules");
769
+ const consumerEnvDir = await mkdtemp2(join5(tmpdir2(), "verelease-consumer-"));
770
+ const nodeModulesDir = join5(consumerEnvDir, "node_modules");
751
771
  try {
752
- await writeFile2(join4(consumerEnvDir, "package.json"), JSON.stringify({ name: "consumer-fixture", type: "module" }, null, 2), "utf8");
753
- let targetPkgLink = join4(nodeModulesDir, candidate.packageName);
772
+ await writeFile2(join5(consumerEnvDir, "package.json"), JSON.stringify({ name: "consumer-fixture", type: "module" }, null, 2), "utf8");
773
+ let targetPkgLink = join5(nodeModulesDir, candidate.packageName);
754
774
  if (candidate.packageName.startsWith("@")) {
755
775
  const [scope, pkgName] = candidate.packageName.split("/");
756
- const scopeDir = join4(nodeModulesDir, scope);
776
+ const scopeDir = join5(nodeModulesDir, scope);
757
777
  await mkdir3(scopeDir, { recursive: true });
758
- targetPkgLink = join4(scopeDir, pkgName);
778
+ targetPkgLink = join5(scopeDir, pkgName);
759
779
  } else {
760
780
  await mkdir3(nodeModulesDir, { recursive: true });
761
781
  }
762
782
  await symlink(candidate.unpackedDir, targetPkgLink, "junction");
763
783
  const isolatedTestName = basename(contract.testFile);
764
- const isolatedTestPath = join4(consumerEnvDir, isolatedTestName);
784
+ const isolatedTestPath = join5(consumerEnvDir, isolatedTestName);
765
785
  await copyFile(contract.testFile, isolatedTestPath);
766
786
  const testRun = await this.testAdapter.run({
767
787
  packagePath: consumerEnvDir,
@@ -1098,8 +1118,8 @@ var ApiDiffEngine = class {
1098
1118
  };
1099
1119
 
1100
1120
  // ../api-diff/dist/analyzer.js
1101
- import { readFile as readFile3, stat as stat2 } from "fs/promises";
1102
- import { join as join5 } from "path";
1121
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
1122
+ import { join as join6 } from "path";
1103
1123
  async function fileExists2(path) {
1104
1124
  try {
1105
1125
  const s = await stat2(path);
@@ -1121,11 +1141,11 @@ var ApiDiffAnalyzer = class {
1121
1141
  if (!candidateEntrypoint) {
1122
1142
  return [];
1123
1143
  }
1124
- const candidateSource = await readFile3(candidateEntrypoint, "utf8");
1144
+ const candidateSource = await readFile4(candidateEntrypoint, "utf8");
1125
1145
  const candidateSnapshot = this.extractor.extractFromSource(candidateSource, candidateEntrypoint);
1126
1146
  let baselineSnapshot;
1127
1147
  if (context.baselinePath && await fileExists2(context.baselinePath)) {
1128
- const baseSource = await readFile3(context.baselinePath, "utf8");
1148
+ const baseSource = await readFile4(context.baselinePath, "utf8");
1129
1149
  if (context.baselinePath.endsWith(".json")) {
1130
1150
  baselineSnapshot = JSON.parse(baseSource);
1131
1151
  } else {
@@ -1139,11 +1159,11 @@ var ApiDiffAnalyzer = class {
1139
1159
  }
1140
1160
  async findEntrypoint(pkgDir) {
1141
1161
  const candidates = [
1142
- join5(pkgDir, "src", "index.ts"),
1143
- join5(pkgDir, "src", "index.js"),
1144
- join5(pkgDir, "index.ts"),
1145
- join5(pkgDir, "index.js"),
1146
- join5(pkgDir, "dist", "index.d.ts")
1162
+ join6(pkgDir, "src", "index.ts"),
1163
+ join6(pkgDir, "src", "index.js"),
1164
+ join6(pkgDir, "index.ts"),
1165
+ join6(pkgDir, "index.js"),
1166
+ join6(pkgDir, "dist", "index.d.ts")
1147
1167
  ];
1148
1168
  for (const c of candidates) {
1149
1169
  if (await fileExists2(c)) {
@@ -1154,9 +1174,14 @@ var ApiDiffAnalyzer = class {
1154
1174
  }
1155
1175
  };
1156
1176
 
1157
- // ../manifest/dist/mutator.js
1158
- import { readFile as readFile4, writeFile as writeFile3, stat as stat3 } from "fs/promises";
1159
- import { join as join6 } from "path";
1177
+ // ../api-diff/dist/baseline.js
1178
+ import { execFile as execFile2 } from "child_process";
1179
+ import { promisify as promisify2 } from "util";
1180
+ import { mkdtemp as mkdtemp3, mkdir as mkdir4, stat as stat3 } from "fs/promises";
1181
+ import { join as join7 } from "path";
1182
+ import { tmpdir as tmpdir3 } from "os";
1183
+ import * as tar2 from "tar";
1184
+ var execFileAsync2 = promisify2(execFile2);
1160
1185
  async function fileExists3(path) {
1161
1186
  try {
1162
1187
  const s = await stat3(path);
@@ -1165,6 +1190,86 @@ async function fileExists3(path) {
1165
1190
  return false;
1166
1191
  }
1167
1192
  }
1193
+ var BaselineFetcher = class {
1194
+ /**
1195
+ * Fetch the latest published version of a package from npm,
1196
+ * download its tarball, extract it, and locate its entry point.
1197
+ *
1198
+ * Returns `null` if the package has never been published, is offline,
1199
+ * or the extracted package has no recognizable entry point.
1200
+ */
1201
+ async fetch(packageName, parentTmpDir) {
1202
+ const version = await this.getLatestVersion(packageName);
1203
+ if (!version)
1204
+ return null;
1205
+ const tempDir = parentTmpDir ? join7(parentTmpDir, packageName.replace(/\//g, "__")) : await mkdtemp3(join7(tmpdir3(), "verelease-baseline-"));
1206
+ await mkdir4(tempDir, { recursive: true });
1207
+ let tarballFilename;
1208
+ try {
1209
+ const { stdout } = await execFileAsync2("npm", ["pack", `${packageName}@${version}`, "--pack-destination", tempDir], { timeout: 3e4 });
1210
+ tarballFilename = stdout.trim().split("\n").pop().trim();
1211
+ } catch {
1212
+ return null;
1213
+ }
1214
+ const tarballPath = join7(tempDir, tarballFilename);
1215
+ const extractedDir = join7(tempDir, "extracted");
1216
+ await mkdir4(extractedDir, { recursive: true });
1217
+ try {
1218
+ await tar2.x({
1219
+ file: tarballPath,
1220
+ cwd: extractedDir,
1221
+ strip: 1
1222
+ // npm tarballs contain a top-level `package/` directory
1223
+ });
1224
+ } catch {
1225
+ return null;
1226
+ }
1227
+ const entryPointPath = await this.findEntrypoint(extractedDir);
1228
+ if (!entryPointPath)
1229
+ return null;
1230
+ return { version, entryPointPath, extractedDir };
1231
+ }
1232
+ async getLatestVersion(packageName) {
1233
+ try {
1234
+ const { stdout } = await execFileAsync2("npm", ["view", packageName, "version", "--json"], { timeout: 15e3 });
1235
+ const parsed = JSON.parse(stdout.trim());
1236
+ if (typeof parsed === "string")
1237
+ return parsed;
1238
+ return null;
1239
+ } catch {
1240
+ return null;
1241
+ }
1242
+ }
1243
+ async findEntrypoint(pkgDir) {
1244
+ const candidates = [
1245
+ join7(pkgDir, "dist", "index.d.ts"),
1246
+ join7(pkgDir, "dist", "index.d.mts"),
1247
+ join7(pkgDir, "src", "index.ts"),
1248
+ join7(pkgDir, "src", "index.js"),
1249
+ join7(pkgDir, "index.d.ts"),
1250
+ join7(pkgDir, "index.ts"),
1251
+ join7(pkgDir, "index.js")
1252
+ ];
1253
+ for (const candidate of candidates) {
1254
+ if (await fileExists3(candidate)) {
1255
+ return candidate;
1256
+ }
1257
+ }
1258
+ return null;
1259
+ }
1260
+ };
1261
+
1262
+ // ../manifest/dist/mutator.js
1263
+ import { readFile as readFile5, writeFile as writeFile3, stat as stat4 } from "fs/promises";
1264
+ import { join as join8 } from "path";
1265
+ async function fileExists4(path) {
1266
+ try {
1267
+ const s = await stat4(path);
1268
+ return s.isFile();
1269
+ } catch {
1270
+ return false;
1271
+ }
1272
+ }
1168
1273
  var TransactionalManifestMutator = class {
1169
1274
  async applyPlan(plan) {
1170
1275
  const backups = [];
@@ -1172,27 +1277,27 @@ var TransactionalManifestMutator = class {
1172
1277
  const filesToTouch = /* @__PURE__ */ new Set();
1173
1278
  for (const pkgPlan of plan.packages) {
1174
1279
  if (pkgPlan.bump !== "none") {
1175
- const pkgJson = join6(pkgPlan.directory, "package.json");
1176
- const jsrJson = join6(pkgPlan.directory, "jsr.json");
1177
- const denoJson = join6(pkgPlan.directory, "deno.json");
1178
- if (await fileExists3(pkgJson))
1280
+ const pkgJson = join8(pkgPlan.directory, "package.json");
1281
+ const jsrJson = join8(pkgPlan.directory, "jsr.json");
1282
+ const denoJson = join8(pkgPlan.directory, "deno.json");
1283
+ if (await fileExists4(pkgJson))
1179
1284
  filesToTouch.add(pkgJson);
1180
- if (await fileExists3(jsrJson))
1285
+ if (await fileExists4(jsrJson))
1181
1286
  filesToTouch.add(jsrJson);
1182
- if (await fileExists3(denoJson))
1287
+ if (await fileExists4(denoJson))
1183
1288
  filesToTouch.add(denoJson);
1184
1289
  }
1185
1290
  for (const update of pkgPlan.dependentUpdates) {
1186
1291
  const depPkgPlan = plan.packages.find((p) => p.name === update.dependentName);
1187
1292
  if (depPkgPlan) {
1188
- const depPkgJson = join6(depPkgPlan.directory, "package.json");
1189
- if (await fileExists3(depPkgJson))
1293
+ const depPkgJson = join8(depPkgPlan.directory, "package.json");
1294
+ if (await fileExists4(depPkgJson))
1190
1295
  filesToTouch.add(depPkgJson);
1191
1296
  }
1192
1297
  }
1193
1298
  }
1194
1299
  for (const filePath of filesToTouch) {
1195
- const originalContent = await readFile4(filePath, "utf8");
1300
+ const originalContent = await readFile5(filePath, "utf8");
1196
1301
  backups.push({ filePath, originalContent });
1197
1302
  }
1198
1303
  try {
@@ -1216,9 +1321,9 @@ var TransactionalManifestMutator = class {
1216
1321
  }
1217
1322
  }
1218
1323
  for (const pkgPlan of plan.packages) {
1219
- const pkgJsonPath = join6(pkgPlan.directory, "package.json");
1324
+ const pkgJsonPath = join8(pkgPlan.directory, "package.json");
1220
1325
  if (filesToTouch.has(pkgJsonPath)) {
1221
- const content = await readFile4(pkgJsonPath, "utf8");
1326
+ const content = await readFile5(pkgJsonPath, "utf8");
1222
1327
  const parsed = JSON.parse(content);
1223
1328
  if (versionMap.has(pkgPlan.name)) {
1224
1329
  parsed.version = versionMap.get(pkgPlan.name);
@@ -1235,9 +1340,9 @@ var TransactionalManifestMutator = class {
1235
1340
  await writeFile3(pkgJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
1236
1341
  modifiedFiles.push(pkgJsonPath);
1237
1342
  }
1238
- const jsrJsonPath = join6(pkgPlan.directory, "jsr.json");
1343
+ const jsrJsonPath = join8(pkgPlan.directory, "jsr.json");
1239
1344
  if (filesToTouch.has(jsrJsonPath)) {
1240
- const content = await readFile4(jsrJsonPath, "utf8");
1345
+ const content = await readFile5(jsrJsonPath, "utf8");
1241
1346
  const parsed = JSON.parse(content);
1242
1347
  if (versionMap.has(pkgPlan.name)) {
1243
1348
  parsed.version = versionMap.get(pkgPlan.name);
@@ -1245,9 +1350,9 @@ var TransactionalManifestMutator = class {
1245
1350
  await writeFile3(jsrJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
1246
1351
  modifiedFiles.push(jsrJsonPath);
1247
1352
  }
1248
- const denoJsonPath = join6(pkgPlan.directory, "deno.json");
1353
+ const denoJsonPath = join8(pkgPlan.directory, "deno.json");
1249
1354
  if (filesToTouch.has(denoJsonPath)) {
1250
- const content = await readFile4(denoJsonPath, "utf8");
1355
+ const content = await readFile5(denoJsonPath, "utf8");
1251
1356
  const parsed = JSON.parse(content);
1252
1357
  if (versionMap.has(pkgPlan.name)) {
1253
1358
  parsed.version = versionMap.get(pkgPlan.name);
@@ -1281,14 +1386,14 @@ var TransactionalManifestMutator = class {
1281
1386
  };
1282
1387
 
1283
1388
  // ../manifest/dist/lockfile.js
1284
- import { execFile as execFile2 } from "child_process";
1285
- import { promisify as promisify2 } from "util";
1286
- import { stat as stat4 } from "fs/promises";
1287
- import { join as join7 } from "path";
1288
- var execFileAsync2 = promisify2(execFile2);
1289
- async function fileExists4(path) {
1389
+ import { execFile as execFile3 } from "child_process";
1390
+ import { promisify as promisify3 } from "util";
1391
+ import { stat as stat5 } from "fs/promises";
1392
+ import { join as join9 } from "path";
1393
+ var execFileAsync3 = promisify3(execFile3);
1394
+ async function fileExists5(path) {
1290
1395
  try {
1291
- const s = await stat4(path);
1396
+ const s = await stat5(path);
1292
1397
  return s.isFile();
1293
1398
  } catch {
1294
1399
  return false;
@@ -1299,7 +1404,7 @@ var PnpmLockfileAdapter = class {
1299
1404
  async regenerateLockfile(rootDirectory) {
1300
1405
  const start = Date.now();
1301
1406
  try {
1302
- const { stdout, stderr } = await execFileAsync2("pnpm", ["install", "--lockfile-only"], {
1407
+ const { stdout, stderr } = await execFileAsync3("pnpm", ["install", "--lockfile-only"], {
1303
1408
  cwd: rootDirectory
1304
1409
  });
1305
1410
  return {
@@ -1321,7 +1426,7 @@ var NpmLockfileAdapter = class {
1321
1426
  async regenerateLockfile(rootDirectory) {
1322
1427
  const start = Date.now();
1323
1428
  try {
1324
- const { stdout, stderr } = await execFileAsync2("npm", ["install", "--package-lock-only"], {
1429
+ const { stdout, stderr } = await execFileAsync3("npm", ["install", "--package-lock-only"], {
1325
1430
  cwd: rootDirectory
1326
1431
  });
1327
1432
  return {
@@ -1341,11 +1446,11 @@ var NpmLockfileAdapter = class {
1341
1446
  var AutoLockfileAdapter = class {
1342
1447
  packageManager = "npm";
1343
1448
  async regenerateLockfile(rootDirectory) {
1344
- if (await fileExists4(join7(rootDirectory, "pnpm-lock.yaml"))) {
1449
+ if (await fileExists5(join9(rootDirectory, "pnpm-lock.yaml"))) {
1345
1450
  this.packageManager = "pnpm";
1346
1451
  return new PnpmLockfileAdapter().regenerateLockfile(rootDirectory);
1347
1452
  }
1348
- if (await fileExists4(join7(rootDirectory, "package-lock.json"))) {
1453
+ if (await fileExists5(join9(rootDirectory, "package-lock.json"))) {
1349
1454
  this.packageManager = "npm";
1350
1455
  return new NpmLockfileAdapter().regenerateLockfile(rootDirectory);
1351
1456
  }
@@ -1354,18 +1459,18 @@ var AutoLockfileAdapter = class {
1354
1459
  };
1355
1460
 
1356
1461
  // ../publisher-npm/dist/publisher.js
1357
- import { execFile as execFile3, spawn as spawn2 } from "child_process";
1358
- import { promisify as promisify3 } from "util";
1359
- var execFileAsync3 = promisify3(execFile3);
1462
+ import { execFile as execFile4, spawn as spawn2 } from "child_process";
1463
+ import { promisify as promisify4 } from "util";
1464
+ var execFileAsync4 = promisify4(execFile4);
1360
1465
  function openBrowser(url) {
1361
1466
  const platform = process.platform;
1362
1467
  try {
1363
1468
  if (platform === "darwin") {
1364
- execFile3("open", [url]);
1469
+ execFile4("open", [url]);
1365
1470
  } else if (platform === "win32") {
1366
- execFile3("cmd", ["/c", "start", '""', url]);
1471
+ execFile4("cmd", ["/c", "start", '""', url]);
1367
1472
  } else {
1368
- execFile3("xdg-open", [url]);
1473
+ execFile4("xdg-open", [url]);
1369
1474
  }
1370
1475
  } catch {
1371
1476
  }
@@ -1404,12 +1509,19 @@ var NpmPublishProvider = class {
1404
1509
  }
1405
1510
  async prepare(ctx) {
1406
1511
  try {
1407
- await execFileAsync3("npm", ["pack", "--dry-run"], {
1512
+ await execFileAsync4("npm", ["pack", "--dry-run"], {
1408
1513
  cwd: ctx.packagePlan.directory
1409
1514
  });
1410
1515
  } catch (err) {
1411
1516
  throw new Error(`Failed packing verification for package ${ctx.packagePlan.name}: ${err.message || String(err)}`);
1412
1517
  }
1518
+ if (!ctx.dryRun && !process.env.CI) {
1519
+ try {
1520
+ await execFileAsync4("npm", ["whoami"]);
1521
+ } catch {
1522
+ throw new Error(`You are not logged in to npm (npm whoami failed with 401). Please run 'npm login' first.`);
1523
+ }
1524
+ }
1413
1525
  }
1414
1526
  async publish(ctx) {
1415
1527
  const startTime = Date.now();
@@ -1465,7 +1577,7 @@ var NpmPublishProvider = class {
1465
1577
  distTag,
1466
1578
  durationMs: Date.now() - startTime,
1467
1579
  output: "",
1468
- error: `npm publish exited with code ${code} (2FA authentication failed, timed out, or was aborted)`
1580
+ error: `npm publish exited with code ${code}. Check the npm error log above (e.g. run 'npm login' if unauthenticated, or check package permissions).`
1469
1581
  });
1470
1582
  }
1471
1583
  });
@@ -1485,7 +1597,7 @@ var NpmPublishProvider = class {
1485
1597
  });
1486
1598
  }
1487
1599
  try {
1488
- const { stdout, stderr } = await execFileAsync3("npm", args, {
1600
+ const { stdout, stderr } = await execFileAsync4("npm", args, {
1489
1601
  cwd: ctx.packagePlan.directory
1490
1602
  });
1491
1603
  return {
@@ -1543,7 +1655,7 @@ ${err.message || ""}`;
1543
1655
  }
1544
1656
  async getPublishedVersions(packageName) {
1545
1657
  try {
1546
- const { stdout } = await execFileAsync3("npm", ["view", packageName, "versions", "--json"]);
1658
+ const { stdout } = await execFileAsync4("npm", ["view", packageName, "versions", "--json"]);
1547
1659
  const parsed = JSON.parse(stdout.trim());
1548
1660
  if (Array.isArray(parsed)) {
1549
1661
  return parsed;
@@ -1559,12 +1671,12 @@ ${err.message || ""}`;
1559
1671
  };
1560
1672
 
1561
1673
  // ../config/dist/config.js
1562
- import { stat as stat5 } from "fs/promises";
1563
- import { join as join8 } from "path";
1674
+ import { stat as stat6 } from "fs/promises";
1675
+ import { join as join10 } from "path";
1564
1676
  import { pathToFileURL } from "url";
1565
- async function fileExists5(path) {
1677
+ async function fileExists6(path) {
1566
1678
  try {
1567
- const s = await stat5(path);
1679
+ const s = await stat6(path);
1568
1680
  return s.isFile();
1569
1681
  } catch {
1570
1682
  return false;
@@ -1582,8 +1694,8 @@ async function loadConfig(rootDir) {
1582
1694
  "autoversion.config.mjs"
1583
1695
  ];
1584
1696
  for (const name of candidateNames) {
1585
- const fullPath = join8(rootDir, name);
1586
- if (await fileExists5(fullPath)) {
1697
+ const fullPath = join10(rootDir, name);
1698
+ if (await fileExists6(fullPath)) {
1587
1699
  try {
1588
1700
  const fileUrl = pathToFileURL(fullPath).href;
1589
1701
  const mod = await import(fileUrl);
@@ -1837,13 +1949,13 @@ walkAndTransform(targetDir);
1837
1949
  }
1838
1950
 
1839
1951
  // ../ai/dist/validation.js
1840
- import { cp, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile4, mkdir as mkdir4, symlink as symlink2, readdir as readdir3 } from "fs/promises";
1841
- import { execFile as execFile4 } from "child_process";
1842
- import { promisify as promisify4 } from "util";
1843
- import { join as join9 } from "path";
1844
- import { tmpdir as tmpdir3 } from "os";
1845
- import * as tar2 from "tar";
1846
- var execFileAsync4 = promisify4(execFile4);
1952
+ import { cp, mkdtemp as mkdtemp4, rm as rm3, writeFile as writeFile4, mkdir as mkdir5, symlink as symlink2, readdir as readdir3 } from "fs/promises";
1953
+ import { execFile as execFile5 } from "child_process";
1954
+ import { promisify as promisify5 } from "util";
1955
+ import { join as join11 } from "path";
1956
+ import { tmpdir as tmpdir4 } from "os";
1957
+ import * as tar3 from "tar";
1958
+ var execFileAsync5 = promisify5(execFile5);
1847
1959
 
1848
1960
  // ../ai/dist/provider.js
1849
1961
  var TanStackReleaseAI = class {
@@ -1901,9 +2013,9 @@ ${JSON.stringify(input.breakingChanges, null, 2)}`;
1901
2013
  };
1902
2014
 
1903
2015
  // ../git/dist/git.js
1904
- import { execFile as execFile5 } from "child_process";
1905
- import { promisify as promisify5 } from "util";
1906
- var execFileAsync5 = promisify5(execFile5);
2016
+ import { execFile as execFile6 } from "child_process";
2017
+ import { promisify as promisify6 } from "util";
2018
+ var execFileAsync6 = promisify6(execFile6);
1907
2019
  var GitManager = class {
1908
2020
  cwd;
1909
2021
  constructor(cwd = process.cwd()) {
@@ -1919,207 +2031,982 @@ var GitManager = class {
1919
2031
  return `${options.packageName}@${options.version}`;
1920
2032
  }
1921
2033
  async isClean() {
1922
- const { stdout } = await execFileAsync5("git", ["status", "--porcelain"], {
2034
+ const { stdout } = await execFileAsync6("git", ["status", "--porcelain"], {
1923
2035
  cwd: this.cwd
1924
2036
  });
1925
2037
  return stdout.trim().length === 0;
1926
2038
  }
1927
2039
  async getHeadCommit() {
1928
- const { stdout } = await execFileAsync5("git", ["rev-parse", "HEAD"], {
2040
+ const { stdout } = await execFileAsync6("git", ["rev-parse", "HEAD"], {
1929
2041
  cwd: this.cwd
1930
2042
  });
1931
2043
  return stdout.trim();
1932
2044
  }
1933
2045
  async createTag(tagName, message) {
1934
2046
  const args = ["tag", "-a", tagName, "-m", message || `Release ${tagName}`];
1935
- await execFileAsync5("git", args, { cwd: this.cwd });
2047
+ await execFileAsync6("git", args, { cwd: this.cwd });
1936
2048
  }
1937
2049
  async createReleaseCommit(files, message = "chore(release): publish") {
1938
2050
  if (files.length > 0) {
1939
- await execFileAsync5("git", ["add", ...files], { cwd: this.cwd });
2051
+ await execFileAsync6("git", ["add", ...files], { cwd: this.cwd });
1940
2052
  } else {
1941
- await execFileAsync5("git", ["add", "-A"], { cwd: this.cwd });
2053
+ await execFileAsync6("git", ["add", "-A"], { cwd: this.cwd });
1942
2054
  }
1943
- await execFileAsync5("git", ["commit", "-m", message], { cwd: this.cwd });
2055
+ await execFileAsync6("git", ["commit", "-m", message], { cwd: this.cwd });
1944
2056
  return this.getHeadCommit();
1945
2057
  }
1946
2058
  };
1947
2059
 
1948
- // src/cli.ts
1949
- import semver4 from "semver";
1950
- var VereleaseCli = class {
1951
- cwd;
1952
- constructor(options = {}) {
1953
- this.cwd = resolve2(options.cwd || process.cwd());
2060
+ // ../test-adapter/dist/command.js
2061
+ import { spawn as spawn3 } from "child_process";
2062
+ var CommandTestAdapter = class {
2063
+ config;
2064
+ name = "command";
2065
+ constructor(config) {
2066
+ this.config = config;
1954
2067
  }
1955
- async createPlan(options = {}) {
1956
- const config = await loadConfig(this.cwd);
1957
- const resolver = new DefaultWorkspaceResolver();
1958
- const discovery = await resolver.resolve(this.cwd);
1959
- const contractStore = new FileContractStore(
1960
- join10(this.cwd, config.contracts?.directory || ".verelease/contracts")
1961
- );
1962
- const contractAnalyzer = new ContractCompatibilityAnalyzer(contractStore);
1963
- const apiDiffAnalyzer = new ApiDiffAnalyzer();
1964
- const semverEngine = new DefaultSemVerDecisionEngine();
1965
- const npmPublisher = new NpmPublishProvider();
1966
- const evidenceMap = /* @__PURE__ */ new Map();
1967
- const initialBumps = /* @__PURE__ */ new Map();
1968
- for (const pkg of discovery.packages) {
1969
- const evidence = [];
1970
- try {
1971
- const contractEv = await contractAnalyzer.analyze({
1972
- packageName: pkg.name,
1973
- packagePath: pkg.directory,
1974
- currentVersion: pkg.version
1975
- });
1976
- evidence.push(...contractEv);
1977
- } catch {
1978
- }
1979
- try {
1980
- const apiEv = await apiDiffAnalyzer.analyze({
1981
- packageName: pkg.name,
1982
- packagePath: pkg.directory,
1983
- currentVersion: pkg.version
1984
- });
1985
- evidence.push(...apiEv);
1986
- } catch {
1987
- }
1988
- evidenceMap.set(pkg.name, evidence);
1989
- const decision = semverEngine.evaluate(
1990
- pkg.name,
1991
- pkg.version,
1992
- evidence,
1993
- config.versioning,
1994
- options.channel || "stable"
1995
- );
1996
- const forced = options.forceBump?.[pkg.name] || options.defaultBump;
1997
- let bump = forced || decision.bump;
1998
- let proposedVersion = decision.proposedVersion;
1999
- const reasons = [...decision.reasons];
2000
- if (forced && decision.bump === "none") {
2001
- const channel = options.channel || config.versioning?.channel || "stable";
2002
- if (channel !== "stable") {
2003
- const baseBump = `pre${forced}`;
2004
- proposedVersion = semver4.inc(pkg.version, baseBump, channel) || pkg.version;
2005
- } else {
2006
- proposedVersion = semver4.inc(pkg.version, forced) || pkg.version;
2007
- }
2008
- reasons.push(`Bump manually requested (${forced}) for channel ${channel}`);
2009
- }
2010
- if (!pkg.isPrivate) {
2011
- try {
2012
- const publishedVersions = await npmPublisher.getPublishedVersions(pkg.name);
2013
- const channel = options.channel || config.versioning?.channel || "stable";
2014
- if (publishedVersions.includes(proposedVersion) || bump !== "none" && publishedVersions.includes(pkg.version)) {
2015
- reasons.push(`Version ${proposedVersion} is already published on npm registry.`);
2016
- let nextCandidate = proposedVersion;
2017
- if (channel !== "stable") {
2018
- const baseBump = bump === "none" ? "patch" : bump;
2019
- const parsed = semver4.parse(pkg.version);
2020
- if (parsed.prerelease.length > 0 && parsed.prerelease[0] === channel) {
2021
- nextCandidate = semver4.inc(pkg.version, "prerelease", channel) || pkg.version;
2022
- } else {
2023
- nextCandidate = semver4.inc(pkg.version, `pre${baseBump}`, channel) || pkg.version;
2024
- }
2025
- while (publishedVersions.includes(nextCandidate)) {
2026
- nextCandidate = semver4.inc(nextCandidate, "prerelease", channel) || nextCandidate;
2027
- }
2028
- } else {
2029
- const baseBump = bump === "none" ? "patch" : bump;
2030
- nextCandidate = semver4.inc(pkg.version, baseBump) || pkg.version;
2031
- while (publishedVersions.includes(nextCandidate)) {
2032
- nextCandidate = semver4.inc(nextCandidate, "patch") || nextCandidate;
2033
- }
2068
+ async detect(ctx) {
2069
+ const scripts = ctx.manifest.scripts || {};
2070
+ if (scripts.test) {
2071
+ return {
2072
+ supported: true,
2073
+ confidence: 0.5,
2074
+ suggestedConfig: { command: ["npm", "test"] }
2075
+ };
2076
+ }
2077
+ return { supported: false, confidence: 0 };
2078
+ }
2079
+ async run(ctx) {
2080
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2081
+ const startTime = Date.now();
2082
+ const cmd = this.config?.command || ["npm", "test"];
2083
+ const [executable, ...args] = cmd;
2084
+ const finalArgs = [...args, ...ctx.extraArgs || []];
2085
+ return new Promise((resolve3) => {
2086
+ const child = spawn3(executable, finalArgs, {
2087
+ cwd: ctx.packagePath,
2088
+ env: { ...process.env, ...ctx.env },
2089
+ stdio: ["ignore", "pipe", "pipe"]
2090
+ });
2091
+ let stdout = "";
2092
+ let stderr = "";
2093
+ child.stdout?.on("data", (chunk) => {
2094
+ stdout += chunk.toString();
2095
+ });
2096
+ child.stderr?.on("data", (chunk) => {
2097
+ stderr += chunk.toString();
2098
+ });
2099
+ child.on("close", (code) => {
2100
+ const durationMs = Date.now() - startTime;
2101
+ const success = code === 0;
2102
+ const testCases = [
2103
+ {
2104
+ name: `${executable} ${finalArgs.join(" ")}`,
2105
+ status: success ? "passed" : "failed",
2106
+ durationMs,
2107
+ failure: success ? void 0 : {
2108
+ message: `Process exited with code ${code}`,
2109
+ stack: stderr || stdout
2034
2110
  }
2035
- proposedVersion = nextCandidate;
2036
- bump = bump === "none" ? "patch" : bump;
2037
- reasons.push(`Automatically resolved next available registry version: ${proposedVersion}`);
2038
2111
  }
2039
- } catch {
2040
- }
2041
- }
2042
- initialBumps.set(pkg.name, {
2043
- name: pkg.name,
2044
- currentVersion: pkg.version,
2045
- proposedVersion,
2046
- bump,
2047
- reasons
2112
+ ];
2113
+ resolve3({
2114
+ adapter: this.name,
2115
+ runtime: process.version,
2116
+ command: [executable, ...finalArgs],
2117
+ startedAt,
2118
+ durationMs,
2119
+ success,
2120
+ suites: [
2121
+ {
2122
+ name: "command-suite",
2123
+ status: success ? "passed" : "failed",
2124
+ durationMs,
2125
+ tests: testCases
2126
+ }
2127
+ ],
2128
+ diagnostics: success ? [] : [
2129
+ {
2130
+ severity: "error",
2131
+ message: stderr.trim() || `Exit code ${code}`
2132
+ }
2133
+ ],
2134
+ totalTests: 1,
2135
+ passedTests: success ? 1 : 0,
2136
+ failedTests: success ? 0 : 1,
2137
+ skippedTests: 0
2138
+ });
2048
2139
  });
2049
- }
2050
- const propagator = new VersionPropagator(discovery.graph);
2051
- const { bumps: finalBumps, dependencyUpdates } = propagator.propagate(initialBumps);
2052
- const publishOrder = discovery.graph.getTopologicalOrder();
2053
- const packagePlans = [];
2054
- for (const pkgName of publishOrder) {
2055
- const pkg = discovery.packages.find((p) => p.name === pkgName);
2056
- if (!pkg) continue;
2057
- const bumpTarget = finalBumps.get(pkgName);
2058
- const evidence = evidenceMap.get(pkgName) || [];
2059
- const depUpdates = dependencyUpdates.get(pkgName) || [];
2060
- const decision = semverEngine.evaluate(
2061
- pkg.name,
2062
- pkg.version,
2063
- evidence,
2064
- config.versioning,
2065
- options.channel || "stable"
2066
- );
2067
- packagePlans.push({
2068
- name: pkg.name,
2069
- directory: pkg.directory,
2070
- currentVersion: pkg.version,
2071
- proposedVersion: bumpTarget.proposedVersion,
2072
- bump: bumpTarget.bump,
2073
- channel: options.channel || "stable",
2074
- evidence,
2075
- reasons: bumpTarget.reasons,
2076
- confidence: decision.confidence,
2077
- requiresApproval: decision.requiresApproval,
2078
- dependentUpdates: depUpdates.map((d) => ({
2079
- dependentName: pkgName,
2080
- dependencyName: d.dependencyName,
2081
- dependencyType: d.type,
2082
- previousRange: d.previousRange,
2083
- newRange: d.newRange
2084
- })),
2085
- filesToUpdate: [pkg.manifestPath],
2086
- publish: !pkg.isPrivate && bumpTarget.bump !== "none"
2140
+ child.on("error", (err) => {
2141
+ const durationMs = Date.now() - startTime;
2142
+ resolve3({
2143
+ adapter: this.name,
2144
+ runtime: process.version,
2145
+ command: [executable, ...finalArgs],
2146
+ startedAt,
2147
+ durationMs,
2148
+ success: false,
2149
+ suites: [],
2150
+ diagnostics: [
2151
+ {
2152
+ severity: "error",
2153
+ message: err.message,
2154
+ stack: err.stack
2155
+ }
2156
+ ],
2157
+ totalTests: 1,
2158
+ passedTests: 0,
2159
+ failedTests: 1,
2160
+ skippedTests: 0
2161
+ });
2087
2162
  });
2088
- }
2089
- const plan = {
2090
- schemaVersion: 1,
2091
- id: `plan-${Date.now()}`,
2092
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2093
- rootDirectory: this.cwd,
2094
- channel: options.channel || "stable",
2095
- packages: packagePlans,
2096
- publishOrder,
2097
- summary: {
2098
- totalPackages: packagePlans.length,
2099
- bumpedPackages: packagePlans.filter((p) => p.bump !== "none").length,
2100
- majorBumps: packagePlans.filter((p) => p.bump === "major").length,
2101
- minorBumps: packagePlans.filter((p) => p.bump === "minor").length,
2102
- patchBumps: packagePlans.filter((p) => p.bump === "patch").length,
2103
- noneBumps: packagePlans.filter((p) => p.bump === "none").length
2104
- }
2105
- };
2106
- return { plan, evidenceMap };
2163
+ });
2107
2164
  }
2108
- async runPlanCommand(options = {}) {
2109
- const { plan } = await this.createPlan(options);
2110
- if (options.json) {
2111
- console.log(JSON.stringify(plan, null, 2));
2112
- return plan;
2113
- }
2114
- this.printPlan(plan);
2165
+ };
2166
+
2167
+ // ../test-adapter/dist/test-suite-analyzer.js
2168
+ import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
2169
+ import { join as join12 } from "path";
2170
+ var TestSuiteAnalyzer = class {
2171
+ name = "test-suite-analyzer";
2172
+ adapters;
2173
+ timeoutMs;
2174
+ constructor(options) {
2175
+ this.adapters = [
2176
+ ...options?.adapters ?? [],
2177
+ new CommandTestAdapter()
2178
+ // always available as lowest-confidence fallback
2179
+ ];
2180
+ this.timeoutMs = options?.timeout ?? 12e4;
2181
+ }
2182
+ async analyze(context) {
2183
+ const { packageName, packagePath } = context;
2184
+ let manifest;
2115
2185
  try {
2116
- const verelDir = join10(this.cwd, ".verelease");
2117
- await mkdir5(verelDir, { recursive: true });
2118
- await writeFile5(
2119
- join10(verelDir, "release-plan.json"),
2120
- JSON.stringify(plan, null, 2),
2121
- "utf8"
2122
- );
2186
+ const raw = await readFile6(join12(packagePath, "package.json"), "utf8");
2187
+ manifest = JSON.parse(raw);
2188
+ } catch {
2189
+ return [];
2190
+ }
2191
+ const scripts = manifest.scripts || {};
2192
+ if (!scripts.test) {
2193
+ return [];
2194
+ }
2195
+ let files;
2196
+ try {
2197
+ const entries = await readdir4(packagePath, { recursive: true });
2198
+ files = entries.map(String);
2199
+ } catch {
2200
+ files = [];
2201
+ }
2202
+ const detectionCtx = { packagePath, manifest, files };
2203
+ const best = await this.detectBestAdapter(detectionCtx);
2204
+ if (!best) {
2205
+ return [];
2206
+ }
2207
+ let testRun;
2208
+ try {
2209
+ testRun = await best.adapter.run({
2210
+ packagePath,
2211
+ targetPackageName: packageName,
2212
+ timeoutMs: this.timeoutMs
2213
+ });
2214
+ } catch (error) {
2215
+ return [
2216
+ {
2217
+ id: `test.suite.error:${packageName}`,
2218
+ package: packageName,
2219
+ source: "test",
2220
+ severity: "major",
2221
+ confidence: 0.7,
2222
+ title: "Test suite failed to execute",
2223
+ description: `Test runner "${best.adapter.name}" crashed: ${error instanceof Error ? error.message : String(error)}`
2224
+ }
2225
+ ];
2226
+ }
2227
+ return this.convertToEvidence(packageName, testRun, best.adapter.name);
2228
+ }
2229
+ async detectBestAdapter(ctx) {
2230
+ const candidates = [];
2231
+ for (const adapter of this.adapters) {
2232
+ try {
2233
+ const detection = await adapter.detect(ctx);
2234
+ if (detection.supported) {
2235
+ candidates.push({ adapter, detection });
2236
+ }
2237
+ } catch {
2238
+ }
2239
+ }
2240
+ if (candidates.length === 0)
2241
+ return null;
2242
+ candidates.sort((a, b) => b.detection.confidence - a.detection.confidence);
2243
+ return candidates[0];
2244
+ }
2245
+ convertToEvidence(packageName, testRun, adapterName) {
2246
+ if (testRun.totalTests === 0) {
2247
+ return [];
2248
+ }
2249
+ if (testRun.success) {
2250
+ return [
2251
+ {
2252
+ id: `test.suite.pass:${packageName}`,
2253
+ package: packageName,
2254
+ source: "test",
2255
+ severity: "patch",
2256
+ confidence: 0.9,
2257
+ title: `Test suite passed (${testRun.passedTests}/${testRun.totalTests} tests)`,
2258
+ description: `All ${testRun.totalTests} tests passed via ${adapterName} in ${testRun.durationMs}ms. Code changes are confirmed compatible.`,
2259
+ metadata: {
2260
+ adapter: adapterName,
2261
+ total: testRun.totalTests,
2262
+ passed: testRun.passedTests,
2263
+ failed: testRun.failedTests,
2264
+ skipped: testRun.skippedTests,
2265
+ durationMs: testRun.durationMs
2266
+ }
2267
+ }
2268
+ ];
2269
+ }
2270
+ const failedSuites = testRun.suites.filter((s) => s.status === "failed");
2271
+ const failedTests = failedSuites.flatMap((s) => s.tests.filter((t) => t.status === "failed"));
2272
+ const failureMessages = failedTests.slice(0, 5).map((t) => ` - ${t.name}: ${t.failure?.message ?? "unknown failure"}`).join("\n");
2273
+ return [
2274
+ {
2275
+ id: `test.suite.fail:${packageName}`,
2276
+ package: packageName,
2277
+ source: "test",
2278
+ severity: "major",
2279
+ confidence: 0.85,
2280
+ title: `Test suite failed (${testRun.failedTests}/${testRun.totalTests} tests failed)`,
2281
+ description: `${testRun.failedTests} tests failed via ${adapterName}:
2282
+ ${failureMessages}`,
2283
+ metadata: {
2284
+ adapter: adapterName,
2285
+ total: testRun.totalTests,
2286
+ passed: testRun.passedTests,
2287
+ failed: testRun.failedTests,
2288
+ skipped: testRun.skippedTests,
2289
+ durationMs: testRun.durationMs
2290
+ }
2291
+ }
2292
+ ];
2293
+ }
2294
+ };
2295
+
2296
+ // ../test-adapter-vitest/dist/vitest.js
2297
+ import { spawn as spawn4 } from "child_process";
2298
+ var VitestTestAdapter = class {
2299
+ name = "vitest";
2300
+ async detect(ctx) {
2301
+ const scripts = ctx.manifest.scripts || {};
2302
+ const devDeps = ctx.manifest.devDependencies || {};
2303
+ const deps = ctx.manifest.dependencies || {};
2304
+ const hasVitestDep = !!devDeps.vitest || !!deps.vitest;
2305
+ const hasVitestScript = Object.values(scripts).some((s) => s.includes("vitest"));
2306
+ const hasVitestConfig = ctx.files.some((f) => f.includes("vitest.config"));
2307
+ if (hasVitestConfig || hasVitestScript) {
2308
+ return { supported: true, confidence: 0.95 };
2309
+ }
2310
+ if (hasVitestDep) {
2311
+ return { supported: true, confidence: 0.8 };
2312
+ }
2313
+ return { supported: false, confidence: 0 };
2314
+ }
2315
+ async run(ctx) {
2316
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2317
+ const startTime = Date.now();
2318
+ const command = ["npx", "vitest", "run", "--reporter=json", ...ctx.testFiles || []];
2319
+ return new Promise((resolve3) => {
2320
+ const child = spawn4(command[0], command.slice(1), {
2321
+ cwd: ctx.packagePath,
2322
+ env: {
2323
+ ...process.env,
2324
+ ...ctx.env,
2325
+ ...ctx.isolatedNodeModulesPath ? { NODE_PATH: ctx.isolatedNodeModulesPath } : {}
2326
+ },
2327
+ stdio: ["ignore", "pipe", "pipe"]
2328
+ });
2329
+ let stdout = "";
2330
+ let stderr = "";
2331
+ child.stdout?.on("data", (chunk) => {
2332
+ stdout += chunk.toString();
2333
+ });
2334
+ child.stderr?.on("data", (chunk) => {
2335
+ stderr += chunk.toString();
2336
+ });
2337
+ child.on("close", (code) => {
2338
+ const durationMs = Date.now() - startTime;
2339
+ const normalized = this.parseJsonOutput(stdout, stderr, code === 0, durationMs, startedAt, command);
2340
+ resolve3(normalized);
2341
+ });
2342
+ child.on("error", (err) => {
2343
+ resolve3({
2344
+ adapter: this.name,
2345
+ runtime: process.version,
2346
+ command,
2347
+ startedAt,
2348
+ durationMs: Date.now() - startTime,
2349
+ success: false,
2350
+ suites: [],
2351
+ diagnostics: [{ severity: "error", message: err.message, stack: err.stack }],
2352
+ totalTests: 1,
2353
+ passedTests: 0,
2354
+ failedTests: 1,
2355
+ skippedTests: 0
2356
+ });
2357
+ });
2358
+ });
2359
+ }
2360
+ parseJsonOutput(stdout, stderr, overallSuccess, durationMs, startedAt, command) {
2361
+ try {
2362
+ const jsonStart = stdout.indexOf("{");
2363
+ const jsonEnd = stdout.lastIndexOf("}");
2364
+ if (jsonStart !== -1 && jsonEnd !== -1) {
2365
+ const jsonStr = stdout.slice(jsonStart, jsonEnd + 1);
2366
+ const parsed = JSON.parse(jsonStr);
2367
+ const suites = (parsed.testResults || []).map((tr) => {
2368
+ const tests = (tr.assertionResults || []).map((ar) => ({
2369
+ name: ar.title || ar.fullName,
2370
+ status: ar.status === "passed" ? "passed" : ar.status === "pending" ? "skipped" : "failed",
2371
+ durationMs: ar.duration,
2372
+ failure: ar.status === "failed" ? {
2373
+ message: ar.failureMessages?.join("\n") || "Assertion failed"
2374
+ } : void 0
2375
+ }));
2376
+ return {
2377
+ name: tr.name,
2378
+ file: tr.name,
2379
+ status: tr.status === "passed" ? "passed" : "failed",
2380
+ durationMs: tr.endTime - tr.startTime,
2381
+ tests
2382
+ };
2383
+ });
2384
+ const totalTests = parsed.numTotalTests || suites.reduce((acc, s) => acc + s.tests.length, 0);
2385
+ const passedTests = parsed.numPassedTests || suites.reduce((acc, s) => acc + s.tests.filter((t) => t.status === "passed").length, 0);
2386
+ const failedTests = parsed.numFailedTests || suites.reduce((acc, s) => acc + s.tests.filter((t) => t.status === "failed").length, 0);
2387
+ const skippedTests = parsed.numPendingTests || 0;
2388
+ return {
2389
+ adapter: this.name,
2390
+ runtime: process.version,
2391
+ command,
2392
+ startedAt,
2393
+ durationMs,
2394
+ success: overallSuccess && failedTests === 0,
2395
+ suites,
2396
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2397
+ totalTests,
2398
+ passedTests,
2399
+ failedTests,
2400
+ skippedTests
2401
+ };
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ return {
2406
+ adapter: this.name,
2407
+ runtime: process.version,
2408
+ command,
2409
+ startedAt,
2410
+ durationMs,
2411
+ success: overallSuccess,
2412
+ suites: [
2413
+ {
2414
+ name: "vitest-suite",
2415
+ status: overallSuccess ? "passed" : "failed",
2416
+ durationMs,
2417
+ tests: [
2418
+ {
2419
+ name: "vitest execution",
2420
+ status: overallSuccess ? "passed" : "failed",
2421
+ durationMs,
2422
+ failure: overallSuccess ? void 0 : { message: stderr || stdout || "Vitest run failed" }
2423
+ }
2424
+ ]
2425
+ }
2426
+ ],
2427
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2428
+ totalTests: 1,
2429
+ passedTests: overallSuccess ? 1 : 0,
2430
+ failedTests: overallSuccess ? 0 : 1,
2431
+ skippedTests: 0
2432
+ };
2433
+ }
2434
+ };
2435
+
2436
+ // ../test-adapter-jest/dist/jest.js
2437
+ import { spawn as spawn5 } from "child_process";
2438
+ var JestTestAdapter = class {
2439
+ name = "jest";
2440
+ async detect(ctx) {
2441
+ const scripts = ctx.manifest.scripts || {};
2442
+ const devDeps = ctx.manifest.devDependencies || {};
2443
+ const deps = ctx.manifest.dependencies || {};
2444
+ const hasJest = !!devDeps.jest || !!deps.jest || !!devDeps["@types/jest"];
2445
+ const hasRtl = !!devDeps["@testing-library/react"] || !!deps["@testing-library/react"];
2446
+ const hasJestScript = Object.values(scripts).some((s) => s.includes("jest"));
2447
+ const hasJestConfig = ctx.files.some((f) => f.includes("jest.config"));
2448
+ if (hasJestConfig || hasJestScript) {
2449
+ return { supported: true, confidence: 0.95 };
2450
+ }
2451
+ if (hasJest || hasRtl) {
2452
+ return { supported: true, confidence: 0.85 };
2453
+ }
2454
+ return { supported: false, confidence: 0 };
2455
+ }
2456
+ async run(ctx) {
2457
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2458
+ const startTime = Date.now();
2459
+ const command = ["npx", "jest", "--json", ...ctx.testFiles || []];
2460
+ return new Promise((resolve3) => {
2461
+ const child = spawn5(command[0], command.slice(1), {
2462
+ cwd: ctx.packagePath,
2463
+ env: {
2464
+ ...process.env,
2465
+ ...ctx.env,
2466
+ ...ctx.isolatedNodeModulesPath ? { NODE_PATH: ctx.isolatedNodeModulesPath } : {}
2467
+ },
2468
+ stdio: ["ignore", "pipe", "pipe"]
2469
+ });
2470
+ let stdout = "";
2471
+ let stderr = "";
2472
+ child.stdout?.on("data", (chunk) => {
2473
+ stdout += chunk.toString();
2474
+ });
2475
+ child.stderr?.on("data", (chunk) => {
2476
+ stderr += chunk.toString();
2477
+ });
2478
+ child.on("close", (code) => {
2479
+ const durationMs = Date.now() - startTime;
2480
+ const normalized = this.parseJsonOutput(stdout, stderr, code === 0, durationMs, startedAt, command);
2481
+ resolve3(normalized);
2482
+ });
2483
+ child.on("error", (err) => {
2484
+ resolve3({
2485
+ adapter: this.name,
2486
+ runtime: process.version,
2487
+ command,
2488
+ startedAt,
2489
+ durationMs: Date.now() - startTime,
2490
+ success: false,
2491
+ suites: [],
2492
+ diagnostics: [{ severity: "error", message: err.message, stack: err.stack }],
2493
+ totalTests: 1,
2494
+ passedTests: 0,
2495
+ failedTests: 1,
2496
+ skippedTests: 0
2497
+ });
2498
+ });
2499
+ });
2500
+ }
2501
+ parseJsonOutput(stdout, stderr, overallSuccess, durationMs, startedAt, command) {
2502
+ try {
2503
+ const jsonStart = stdout.indexOf("{");
2504
+ const jsonEnd = stdout.lastIndexOf("}");
2505
+ if (jsonStart !== -1 && jsonEnd !== -1) {
2506
+ const parsed = JSON.parse(stdout.slice(jsonStart, jsonEnd + 1));
2507
+ const suites = (parsed.testResults || []).map((tr) => {
2508
+ const tests = (tr.assertionResults || []).map((ar) => ({
2509
+ name: ar.title || ar.fullName,
2510
+ status: ar.status === "passed" ? "passed" : ar.status === "pending" ? "skipped" : "failed",
2511
+ durationMs: ar.duration,
2512
+ failure: ar.status === "failed" ? {
2513
+ message: ar.failureMessages?.join("\n") || "Assertion failed"
2514
+ } : void 0
2515
+ }));
2516
+ return {
2517
+ name: tr.name,
2518
+ file: tr.name,
2519
+ status: tr.status === "passed" ? "passed" : "failed",
2520
+ durationMs: tr.endTime - tr.startTime,
2521
+ tests
2522
+ };
2523
+ });
2524
+ const totalTests = parsed.numTotalTests || suites.reduce((acc, s) => acc + s.tests.length, 0);
2525
+ const passedTests = parsed.numPassedTests || 0;
2526
+ const failedTests = parsed.numFailedTests || 0;
2527
+ const skippedTests = parsed.numPendingTests || 0;
2528
+ return {
2529
+ adapter: this.name,
2530
+ runtime: process.version,
2531
+ command,
2532
+ startedAt,
2533
+ durationMs,
2534
+ success: overallSuccess && failedTests === 0,
2535
+ suites,
2536
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2537
+ totalTests,
2538
+ passedTests,
2539
+ failedTests,
2540
+ skippedTests
2541
+ };
2542
+ }
2543
+ } catch {
2544
+ }
2545
+ return {
2546
+ adapter: this.name,
2547
+ runtime: process.version,
2548
+ command,
2549
+ startedAt,
2550
+ durationMs,
2551
+ success: overallSuccess,
2552
+ suites: [
2553
+ {
2554
+ name: "jest-suite",
2555
+ status: overallSuccess ? "passed" : "failed",
2556
+ durationMs,
2557
+ tests: [
2558
+ {
2559
+ name: "jest execution",
2560
+ status: overallSuccess ? "passed" : "failed",
2561
+ durationMs,
2562
+ failure: overallSuccess ? void 0 : { message: stderr || stdout || "Jest execution failed" }
2563
+ }
2564
+ ]
2565
+ }
2566
+ ],
2567
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2568
+ totalTests: 1,
2569
+ passedTests: overallSuccess ? 1 : 0,
2570
+ failedTests: overallSuccess ? 0 : 1,
2571
+ skippedTests: 0
2572
+ };
2573
+ }
2574
+ };
2575
+
2576
+ // ../test-adapter-bun/dist/bun.js
2577
+ import { spawn as spawn6 } from "child_process";
2578
+ var BunTestAdapter = class {
2579
+ name = "bun:test";
2580
+ async detect(ctx) {
2581
+ const scripts = ctx.manifest.scripts || {};
2582
+ const hasBunLock = ctx.files.some((f) => f === "bun.lock" || f === "bun.lockb");
2583
+ const hasBunScript = Object.values(scripts).some((s) => s.includes("bun test"));
2584
+ if (hasBunLock || hasBunScript) {
2585
+ return { supported: true, confidence: 0.9 };
2586
+ }
2587
+ return { supported: false, confidence: 0 };
2588
+ }
2589
+ async run(ctx) {
2590
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2591
+ const startTime = Date.now();
2592
+ const command = ["bun", "test", ...ctx.testFiles || []];
2593
+ return new Promise((resolve3) => {
2594
+ const child = spawn6(command[0], command.slice(1), {
2595
+ cwd: ctx.packagePath,
2596
+ env: {
2597
+ ...process.env,
2598
+ ...ctx.env,
2599
+ ...ctx.isolatedNodeModulesPath ? { NODE_PATH: ctx.isolatedNodeModulesPath } : {}
2600
+ },
2601
+ stdio: ["ignore", "pipe", "pipe"]
2602
+ });
2603
+ let stdout = "";
2604
+ let stderr = "";
2605
+ child.stdout?.on("data", (chunk) => {
2606
+ stdout += chunk.toString();
2607
+ });
2608
+ child.stderr?.on("data", (chunk) => {
2609
+ stderr += chunk.toString();
2610
+ });
2611
+ child.on("close", (code) => {
2612
+ const durationMs = Date.now() - startTime;
2613
+ const normalized = this.parseOutput(stdout, stderr, code === 0, durationMs, startedAt, command);
2614
+ resolve3(normalized);
2615
+ });
2616
+ child.on("error", (err) => {
2617
+ resolve3({
2618
+ adapter: this.name,
2619
+ runtime: "bun",
2620
+ command,
2621
+ startedAt,
2622
+ durationMs: Date.now() - startTime,
2623
+ success: false,
2624
+ suites: [],
2625
+ diagnostics: [{ severity: "error", message: err.message, stack: err.stack }],
2626
+ totalTests: 1,
2627
+ passedTests: 0,
2628
+ failedTests: 1,
2629
+ skippedTests: 0
2630
+ });
2631
+ });
2632
+ });
2633
+ }
2634
+ parseOutput(stdout, stderr, overallSuccess, durationMs, startedAt, command) {
2635
+ const combined = stdout + "\n" + stderr;
2636
+ const lines = combined.split("\n");
2637
+ const testCases = [];
2638
+ for (const line of lines) {
2639
+ const trimmed = line.trim();
2640
+ if (trimmed.startsWith("\u2713 ") || trimmed.startsWith("(pass)")) {
2641
+ const name = trimmed.replace(/^(✓|\(pass\))\s*/, "").replace(/\[.*?\]$/, "").trim();
2642
+ testCases.push({ name, status: "passed" });
2643
+ } else if (trimmed.startsWith("\u2717 ") || trimmed.startsWith("(fail)")) {
2644
+ const name = trimmed.replace(/^(✗|\(fail\))\s*/, "").replace(/\[.*?\]$/, "").trim();
2645
+ testCases.push({ name, status: "failed", failure: { message: "Bun test failure" } });
2646
+ }
2647
+ }
2648
+ if (testCases.length === 0) {
2649
+ testCases.push({
2650
+ name: "bun test execution",
2651
+ status: overallSuccess ? "passed" : "failed",
2652
+ durationMs,
2653
+ failure: overallSuccess ? void 0 : { message: stderr || stdout || "Bun execution failed" }
2654
+ });
2655
+ }
2656
+ const passed = testCases.filter((t) => t.status === "passed").length;
2657
+ const failed = testCases.filter((t) => t.status === "failed").length;
2658
+ const suite = {
2659
+ name: "bun:test",
2660
+ status: failed === 0 && overallSuccess ? "passed" : "failed",
2661
+ durationMs,
2662
+ tests: testCases
2663
+ };
2664
+ return {
2665
+ adapter: this.name,
2666
+ runtime: "bun",
2667
+ command,
2668
+ startedAt,
2669
+ durationMs,
2670
+ success: overallSuccess && failed === 0,
2671
+ suites: [suite],
2672
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2673
+ totalTests: testCases.length,
2674
+ passedTests: passed,
2675
+ failedTests: failed,
2676
+ skippedTests: 0
2677
+ };
2678
+ }
2679
+ };
2680
+
2681
+ // ../test-adapter-deno/dist/deno.js
2682
+ import { spawn as spawn7 } from "child_process";
2683
+ var DenoTestAdapter = class {
2684
+ name = "deno:test";
2685
+ async detect(ctx) {
2686
+ const scripts = ctx.manifest.scripts || {};
2687
+ const hasDenoJson = ctx.files.some((f) => f === "deno.json" || f === "deno.jsonc");
2688
+ const hasDenoScript = Object.values(scripts).some((s) => s.includes("deno test"));
2689
+ if (hasDenoJson || hasDenoScript) {
2690
+ return { supported: true, confidence: 0.95 };
2691
+ }
2692
+ return { supported: false, confidence: 0 };
2693
+ }
2694
+ async run(ctx) {
2695
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2696
+ const startTime = Date.now();
2697
+ const command = ["deno", "test", "--allow-all", ...ctx.testFiles || []];
2698
+ return new Promise((resolve3) => {
2699
+ const child = spawn7(command[0], command.slice(1), {
2700
+ cwd: ctx.packagePath,
2701
+ env: {
2702
+ ...process.env,
2703
+ ...ctx.env
2704
+ },
2705
+ stdio: ["ignore", "pipe", "pipe"]
2706
+ });
2707
+ let stdout = "";
2708
+ let stderr = "";
2709
+ child.stdout?.on("data", (chunk) => {
2710
+ stdout += chunk.toString();
2711
+ });
2712
+ child.stderr?.on("data", (chunk) => {
2713
+ stderr += chunk.toString();
2714
+ });
2715
+ child.on("close", (code) => {
2716
+ const durationMs = Date.now() - startTime;
2717
+ const normalized = this.parseOutput(stdout, stderr, code === 0, durationMs, startedAt, command);
2718
+ resolve3(normalized);
2719
+ });
2720
+ child.on("error", (err) => {
2721
+ resolve3({
2722
+ adapter: this.name,
2723
+ runtime: "deno",
2724
+ command,
2725
+ startedAt,
2726
+ durationMs: Date.now() - startTime,
2727
+ success: false,
2728
+ suites: [],
2729
+ diagnostics: [{ severity: "error", message: err.message, stack: err.stack }],
2730
+ totalTests: 1,
2731
+ passedTests: 0,
2732
+ failedTests: 1,
2733
+ skippedTests: 0
2734
+ });
2735
+ });
2736
+ });
2737
+ }
2738
+ parseOutput(stdout, stderr, overallSuccess, durationMs, startedAt, command) {
2739
+ const combined = stdout + "\n" + stderr;
2740
+ const lines = combined.split("\n");
2741
+ const testCases = [];
2742
+ for (const line of lines) {
2743
+ const trimmed = line.trim();
2744
+ if (trimmed.startsWith("test ") && (trimmed.endsWith("ok") || trimmed.includes("... ok"))) {
2745
+ const name = trimmed.replace(/^test\s+/, "").replace(/\s*\.\.\.\s*ok.*$/, "").trim();
2746
+ testCases.push({ name, status: "passed" });
2747
+ } else if (trimmed.startsWith("test ") && (trimmed.includes("FAILED") || trimmed.endsWith("FAILED"))) {
2748
+ const name = trimmed.replace(/^test\s+/, "").replace(/\s*\.\.\.\s*FAILED.*$/, "").trim();
2749
+ testCases.push({ name, status: "failed", failure: { message: "Deno test failure" } });
2750
+ }
2751
+ }
2752
+ if (testCases.length === 0) {
2753
+ testCases.push({
2754
+ name: "deno test execution",
2755
+ status: overallSuccess ? "passed" : "failed",
2756
+ durationMs,
2757
+ failure: overallSuccess ? void 0 : { message: stderr || stdout || "Deno execution failed" }
2758
+ });
2759
+ }
2760
+ const passed = testCases.filter((t) => t.status === "passed").length;
2761
+ const failed = testCases.filter((t) => t.status === "failed").length;
2762
+ const suite = {
2763
+ name: "deno:test",
2764
+ status: failed === 0 && overallSuccess ? "passed" : "failed",
2765
+ durationMs,
2766
+ tests: testCases
2767
+ };
2768
+ return {
2769
+ adapter: this.name,
2770
+ runtime: "deno",
2771
+ command,
2772
+ startedAt,
2773
+ durationMs,
2774
+ success: overallSuccess && failed === 0,
2775
+ suites: [suite],
2776
+ diagnostics: stderr.trim() ? [{ severity: "warning", message: stderr.trim() }] : [],
2777
+ totalTests: testCases.length,
2778
+ passedTests: passed,
2779
+ failedTests: failed,
2780
+ skippedTests: 0
2781
+ };
2782
+ }
2783
+ };
2784
+
2785
+ // src/cli.ts
2786
+ import semver4 from "semver";
2787
+ async function promptConfirm(question, defaultYes = false) {
2788
+ const rl = createInterface({
2789
+ input: process.stdin,
2790
+ output: process.stdout
2791
+ });
2792
+ try {
2793
+ const answer = (await rl.question(question)).trim().toLowerCase();
2794
+ if (!answer) return defaultYes;
2795
+ return answer === "y" || answer === "yes";
2796
+ } finally {
2797
+ rl.close();
2798
+ }
2799
+ }
2800
+ var VereleaseCli = class {
2801
+ cwd;
2802
+ constructor(options = {}) {
2803
+ this.cwd = resolve2(options.cwd || process.cwd());
2804
+ }
2805
+ async createPlan(options = {}) {
2806
+ const config = await loadConfig(this.cwd);
2807
+ const resolver = new DefaultWorkspaceResolver();
2808
+ const discovery = await resolver.resolve(this.cwd);
2809
+ const contractStore = new FileContractStore(
2810
+ join13(this.cwd, config.contracts?.directory || ".verelease/contracts")
2811
+ );
2812
+ const contractAnalyzer = new ContractCompatibilityAnalyzer(contractStore);
2813
+ const apiDiffAnalyzer = new ApiDiffAnalyzer();
2814
+ const baselineFetcher = new BaselineFetcher();
2815
+ const testSuiteAnalyzer = config.tests?.skip ? null : new TestSuiteAnalyzer({
2816
+ adapters: [
2817
+ new VitestTestAdapter(),
2818
+ new JestTestAdapter(),
2819
+ new NodeTestAdapter(),
2820
+ new BunTestAdapter(),
2821
+ new DenoTestAdapter()
2822
+ ],
2823
+ timeout: config.tests?.timeout ?? 12e4
2824
+ });
2825
+ const semverEngine = new DefaultSemVerDecisionEngine();
2826
+ const npmPublisher = new NpmPublishProvider();
2827
+ const evidenceMap = /* @__PURE__ */ new Map();
2828
+ const initialBumps = /* @__PURE__ */ new Map();
2829
+ const baselineTmpDir = await mkdtemp5(join13(tmpdir5(), "verelease-baseline-"));
2830
+ try {
2831
+ for (const pkg of discovery.packages) {
2832
+ const evidence = [];
2833
+ try {
2834
+ const contractEv = await contractAnalyzer.analyze({
2835
+ packageName: pkg.name,
2836
+ packagePath: pkg.directory,
2837
+ currentVersion: pkg.version
2838
+ });
2839
+ evidence.push(...contractEv);
2840
+ } catch {
2841
+ }
2842
+ let baselineResult = null;
2843
+ try {
2844
+ if (!pkg.isPrivate) {
2845
+ baselineResult = await baselineFetcher.fetch(pkg.name, baselineTmpDir);
2846
+ }
2847
+ } catch {
2848
+ }
2849
+ try {
2850
+ const apiEv = await apiDiffAnalyzer.analyze({
2851
+ packageName: pkg.name,
2852
+ packagePath: pkg.directory,
2853
+ currentVersion: pkg.version,
2854
+ baselinePath: baselineResult?.entryPointPath,
2855
+ previousVersion: baselineResult?.version
2856
+ });
2857
+ evidence.push(...apiEv);
2858
+ } catch {
2859
+ }
2860
+ if (testSuiteAnalyzer) {
2861
+ try {
2862
+ const testEv = await testSuiteAnalyzer.analyze({
2863
+ packageName: pkg.name,
2864
+ packagePath: pkg.directory,
2865
+ currentVersion: pkg.version
2866
+ });
2867
+ evidence.push(...testEv);
2868
+ } catch {
2869
+ }
2870
+ }
2871
+ evidenceMap.set(pkg.name, evidence);
2872
+ const decision = semverEngine.evaluate(
2873
+ pkg.name,
2874
+ pkg.version,
2875
+ evidence,
2876
+ config.versioning,
2877
+ options.channel || "stable"
2878
+ );
2879
+ const forced = options.forceBump?.[pkg.name] || options.defaultBump;
2880
+ let bump = forced || decision.bump;
2881
+ let proposedVersion = decision.proposedVersion;
2882
+ const reasons = [...decision.reasons];
2883
+ if (forced && decision.bump === "none") {
2884
+ const channel = options.channel || config.versioning?.channel || "stable";
2885
+ if (channel !== "stable") {
2886
+ const baseBump = `pre${forced}`;
2887
+ proposedVersion = semver4.inc(pkg.version, baseBump, channel) || pkg.version;
2888
+ } else {
2889
+ proposedVersion = semver4.inc(pkg.version, forced) || pkg.version;
2890
+ }
2891
+ reasons.push(`Bump manually requested (${forced}) for channel ${channel}`);
2892
+ }
2893
+ if (!pkg.isPrivate) {
2894
+ try {
2895
+ const publishedVersions = await npmPublisher.getPublishedVersions(pkg.name);
2896
+ const channel = options.channel || config.versioning?.channel || "stable";
2897
+ if (publishedVersions.includes(proposedVersion) || bump !== "none" && publishedVersions.includes(pkg.version)) {
2898
+ reasons.push(`Version ${proposedVersion} is already published on npm registry.`);
2899
+ let nextCandidate = proposedVersion;
2900
+ if (channel !== "stable") {
2901
+ const baseBump = bump === "none" ? "patch" : bump;
2902
+ const parsed = semver4.parse(pkg.version);
2903
+ if (parsed.prerelease.length > 0 && parsed.prerelease[0] === channel) {
2904
+ nextCandidate = semver4.inc(pkg.version, "prerelease", channel) || pkg.version;
2905
+ } else {
2906
+ nextCandidate = semver4.inc(pkg.version, `pre${baseBump}`, channel) || pkg.version;
2907
+ }
2908
+ while (publishedVersions.includes(nextCandidate)) {
2909
+ nextCandidate = semver4.inc(nextCandidate, "prerelease", channel) || nextCandidate;
2910
+ }
2911
+ } else {
2912
+ const baseBump = bump === "none" ? "patch" : bump;
2913
+ nextCandidate = semver4.inc(pkg.version, baseBump) || pkg.version;
2914
+ while (publishedVersions.includes(nextCandidate)) {
2915
+ nextCandidate = semver4.inc(nextCandidate, "patch") || nextCandidate;
2916
+ }
2917
+ }
2918
+ proposedVersion = nextCandidate;
2919
+ bump = bump === "none" ? "patch" : bump;
2920
+ reasons.push(`Automatically resolved next available registry version: ${proposedVersion}`);
2921
+ }
2922
+ } catch {
2923
+ }
2924
+ }
2925
+ initialBumps.set(pkg.name, {
2926
+ name: pkg.name,
2927
+ currentVersion: pkg.version,
2928
+ proposedVersion,
2929
+ bump,
2930
+ reasons
2931
+ });
2932
+ }
2933
+ } finally {
2934
+ await rm4(baselineTmpDir, { recursive: true, force: true }).catch(() => {
2935
+ });
2936
+ }
2937
+ const propagator = new VersionPropagator(discovery.graph);
2938
+ const { bumps: finalBumps, dependencyUpdates } = propagator.propagate(initialBumps);
2939
+ const publishOrder = discovery.graph.getTopologicalOrder();
2940
+ const packagePlans = [];
2941
+ for (const pkgName of publishOrder) {
2942
+ const pkg = discovery.packages.find((p) => p.name === pkgName);
2943
+ if (!pkg) continue;
2944
+ const bumpTarget = finalBumps.get(pkgName);
2945
+ const evidence = evidenceMap.get(pkgName) || [];
2946
+ const depUpdates = dependencyUpdates.get(pkgName) || [];
2947
+ const decision = semverEngine.evaluate(
2948
+ pkg.name,
2949
+ pkg.version,
2950
+ evidence,
2951
+ config.versioning,
2952
+ options.channel || "stable"
2953
+ );
2954
+ packagePlans.push({
2955
+ name: pkg.name,
2956
+ directory: pkg.directory,
2957
+ currentVersion: pkg.version,
2958
+ proposedVersion: bumpTarget.proposedVersion,
2959
+ bump: bumpTarget.bump,
2960
+ channel: options.channel || "stable",
2961
+ evidence,
2962
+ reasons: bumpTarget.reasons,
2963
+ confidence: decision.confidence,
2964
+ requiresApproval: decision.requiresApproval,
2965
+ dependentUpdates: depUpdates.map((d) => ({
2966
+ dependentName: pkgName,
2967
+ dependencyName: d.dependencyName,
2968
+ dependencyType: d.type,
2969
+ previousRange: d.previousRange,
2970
+ newRange: d.newRange
2971
+ })),
2972
+ filesToUpdate: [pkg.manifestPath],
2973
+ publish: !pkg.isPrivate && bumpTarget.bump !== "none"
2974
+ });
2975
+ }
2976
+ const plan = {
2977
+ schemaVersion: 1,
2978
+ id: `plan-${Date.now()}`,
2979
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2980
+ rootDirectory: this.cwd,
2981
+ channel: options.channel || "stable",
2982
+ packages: packagePlans,
2983
+ publishOrder,
2984
+ summary: {
2985
+ totalPackages: packagePlans.length,
2986
+ bumpedPackages: packagePlans.filter((p) => p.bump !== "none").length,
2987
+ majorBumps: packagePlans.filter((p) => p.bump === "major").length,
2988
+ minorBumps: packagePlans.filter((p) => p.bump === "minor").length,
2989
+ patchBumps: packagePlans.filter((p) => p.bump === "patch").length,
2990
+ noneBumps: packagePlans.filter((p) => p.bump === "none").length
2991
+ }
2992
+ };
2993
+ return { plan, evidenceMap };
2994
+ }
2995
+ async runPlanCommand(options = {}) {
2996
+ const { plan } = await this.createPlan(options);
2997
+ if (options.json) {
2998
+ console.log(JSON.stringify(plan, null, 2));
2999
+ return plan;
3000
+ }
3001
+ this.printPlan(plan);
3002
+ try {
3003
+ const verelDir = join13(this.cwd, ".verelease");
3004
+ await mkdir6(verelDir, { recursive: true });
3005
+ await writeFile5(
3006
+ join13(verelDir, "release-plan.json"),
3007
+ JSON.stringify(plan, null, 2),
3008
+ "utf8"
3009
+ );
2123
3010
  } catch {
2124
3011
  }
2125
3012
  return plan;
@@ -2134,6 +3021,20 @@ var VereleaseCli = class {
2134
3021
  console.log("\n\u2728 No packages require release.");
2135
3022
  return;
2136
3023
  }
3024
+ const publishable = plan.packages.filter((p) => p.publish && p.bump !== "none");
3025
+ const internalBumps = plan.packages.filter((p) => !p.publish && p.bump !== "none");
3026
+ const promptDescription = publishable.length > 0 ? `publishing ${publishable.map((p) => `${p.name}@${p.proposedVersion}`).join(", ")}` + (internalBumps.length > 0 ? ` and bumping ${internalBumps.length} internal package(s)` : "") : `bumping ${internalBumps.length} internal package(s)`;
3027
+ const shouldPrompt = !options.dryRun && !options.yes && !process.env.CI && process.stdin.isTTY;
3028
+ if (shouldPrompt) {
3029
+ const confirmed = await promptConfirm(
3030
+ `
3031
+ ? Proceed with ${promptDescription}? [y/N] `
3032
+ );
3033
+ if (!confirmed) {
3034
+ console.log("\n\u274C Release cancelled by user. No manifests, lockfiles, or packages were modified.\n");
3035
+ return;
3036
+ }
3037
+ }
2137
3038
  const mutator = new TransactionalManifestMutator();
2138
3039
  const lockfileAdapter = new AutoLockfileAdapter();
2139
3040
  const npmPublisher = new NpmPublishProvider();
@@ -2165,10 +3066,10 @@ var VereleaseCli = class {
2165
3066
  newVersion: pkgPlan.proposedVersion,
2166
3067
  evidence
2167
3068
  });
2168
- const changelogPath = join10(pkgPlan.directory, "CHANGELOG.md");
3069
+ const changelogPath = join13(pkgPlan.directory, "CHANGELOG.md");
2169
3070
  let existingChangelog = null;
2170
3071
  try {
2171
- existingChangelog = await readFile5(changelogPath, "utf8");
3072
+ existingChangelog = await readFile7(changelogPath, "utf8");
2172
3073
  } catch {
2173
3074
  existingChangelog = null;
2174
3075
  }
@@ -2189,9 +3090,9 @@ ${existingChangelog || ""}`.trim() + "\n",
2189
3090
  newVersion: pkgPlan.proposedVersion,
2190
3091
  breakingChanges: breakingEvidence
2191
3092
  });
2192
- const migrationsDir = join10(pkgPlan.directory, "docs", "migrations");
2193
- await mkdir5(migrationsDir, { recursive: true });
2194
- const guidePath = join10(
3093
+ const migrationsDir = join13(pkgPlan.directory, "docs", "migrations");
3094
+ await mkdir6(migrationsDir, { recursive: true });
3095
+ const guidePath = join13(
2195
3096
  migrationsDir,
2196
3097
  `${pkgPlan.currentVersion.split(".")[0]}-to-${pkgPlan.proposedVersion.split(".")[0]}.md`
2197
3098
  );
@@ -2292,6 +3193,151 @@ ${existingChangelog || ""}`.trim() + "\n",
2292
3193
  }
2293
3194
  }
2294
3195
  }
3196
+ async runGuideCommand(options = {}) {
3197
+ if (options.json) {
3198
+ console.log(
3199
+ JSON.stringify(
3200
+ {
3201
+ name: "verelease",
3202
+ description: "Compatibility-aware SemVer release engine",
3203
+ rules: [
3204
+ "Never manually edit 'version' in package.json",
3205
+ "SemVer bumps are calculated from contract tests, API diffs, and dependency ranges",
3206
+ "Pass --yes to bypass interactive prompts in CI"
3207
+ ],
3208
+ commands: {
3209
+ plan: "verel plan [--channel <channel>] [--json]",
3210
+ release: "verel release [--channel <channel>] [-y, --yes]",
3211
+ explain: "verel explain <pkg>",
3212
+ guide: "verel guide [--json]",
3213
+ init: "verel init"
3214
+ },
3215
+ configuration: "verelease.config.ts",
3216
+ contractsDirectory: ".verelease/contracts",
3217
+ channels: ["stable", "alpha", "beta", "rc"]
3218
+ },
3219
+ null,
3220
+ 2
3221
+ )
3222
+ );
3223
+ return;
3224
+ }
3225
+ console.log(`
3226
+ # Verelease Agent Guide
3227
+
3228
+ Verelease is an evidence-based SemVer release engine for JavaScript & TypeScript.
3229
+
3230
+ ## Core Rules for AI Coding Agents
3231
+ 1. **NEVER manually edit "version" in package.json**: Always let Verelease determine versions.
3232
+ 2. **Inspect first**: Run \`pnpm verel plan\` to preview proposed version bumps without modifying files.
3233
+ 3. **Diagnose bumps**: Run \`pnpm verel explain <pkg>\` to understand the evidence behind any bump.
3234
+ 4. **Automated releases**: In CI or automated workflows, run \`pnpm verel release --yes\` to bypass prompts.
3235
+ 5. **Interactive releases**: When run locally, Verelease asks for [y/N] confirmation and supports Touch ID / WebAuthn.
3236
+
3237
+ ## Command Reference
3238
+ - \`pnpm verel plan\` Preview release plan for stable channel
3239
+ - \`pnpm verel plan --channel alpha\` Preview pre-release bumps for alpha channel
3240
+ - \`pnpm verel release\` Interactive release pipeline
3241
+ - \`pnpm verel release -y\` Non-interactive release (for agents/CI)
3242
+ - \`pnpm verel explain <package>\` Inspect compatibility evidence and decision reasons
3243
+ - \`pnpm verel init\` Initialize config, contracts, and AGENTS.md
3244
+
3245
+ ## Configuration
3246
+ Config is loaded from \`verelease.config.ts\` or \`verelease.config.js\`.
3247
+ Contract test suites live in \`.verelease/contracts/\`.
3248
+ `);
3249
+ }
3250
+ async runInitCommand(options = {}) {
3251
+ const configPath = join13(this.cwd, "verelease.config.ts");
3252
+ const contractsDir = join13(this.cwd, ".verelease", "contracts");
3253
+ const agentsPath = join13(this.cwd, "AGENTS.md");
3254
+ const pkgJsonPath = join13(this.cwd, "package.json");
3255
+ console.log("\n\u{1F680} Initializing Verelease in current project...\n");
3256
+ if (!existsSync(configPath)) {
3257
+ const sampleConfig = `import { defineConfig } from "verelease/config";
3258
+
3259
+ export default defineConfig({
3260
+ policy: {
3261
+ defaultChannel: "stable",
3262
+ channels: {
3263
+ stable: { distTag: "latest", prerelease: false },
3264
+ alpha: { distTag: "alpha", prerelease: true },
3265
+ beta: { distTag: "beta", prerelease: true },
3266
+ rc: { distTag: "rc", prerelease: true },
3267
+ },
3268
+ },
3269
+ contracts: {
3270
+ directory: ".verelease/contracts",
3271
+ failOnBreaking: true,
3272
+ },
3273
+ publish: {
3274
+ access: "public",
3275
+ },
3276
+ });
3277
+ `;
3278
+ await writeFile5(configPath, sampleConfig, "utf8");
3279
+ console.log(" \u2713 Created verelease.config.ts");
3280
+ } else {
3281
+ console.log(" \u2022 verelease.config.ts already exists");
3282
+ }
3283
+ if (!existsSync(contractsDir)) {
3284
+ await mkdir6(contractsDir, { recursive: true });
3285
+ await writeFile5(
3286
+ join13(contractsDir, "README.md"),
3287
+ "# Verelease Contracts\n\nPlace consumer-driven contract definitions (.json or .yaml) here.\n",
3288
+ "utf8"
3289
+ );
3290
+ console.log(" \u2713 Created .verelease/contracts/");
3291
+ } else {
3292
+ console.log(" \u2022 .verelease/contracts/ already exists");
3293
+ }
3294
+ const agentGuidance = `
3295
+ ## Releases & Versioning (Verelease)
3296
+ This repository uses [Verelease](https://github.com/ivanjeremic/verelease) for automated, evidence-based SemVer releases.
3297
+ - Run \`pnpm verel plan\` to inspect proposed version bumps without modifying files.
3298
+ - Run \`pnpm verel explain <pkg>\` to understand why a specific bump was determined.
3299
+ - Run \`pnpm verel release --channel <alpha|stable>\` to release.
3300
+ - In CI or automated agent tasks, use \`pnpm verel release --yes\`.
3301
+ - **NEVER manually edit "version" in package.json.**
3302
+ `;
3303
+ if (!existsSync(agentsPath)) {
3304
+ await writeFile5(agentsPath, `# Agent Guidelines
3305
+ ${agentGuidance}
3306
+ `, "utf8");
3307
+ console.log(" \u2713 Created AGENTS.md with Verelease instructions");
3308
+ } else {
3309
+ const currentContent = await readFile7(agentsPath, "utf8");
3310
+ if (!currentContent.includes("Verelease")) {
3311
+ await writeFile5(agentsPath, `${currentContent.trim()}
3312
+ ${agentGuidance}
3313
+ `, "utf8");
3314
+ console.log(" \u2713 Appended Verelease instructions to AGENTS.md");
3315
+ } else {
3316
+ console.log(" \u2022 AGENTS.md already contains Verelease instructions");
3317
+ }
3318
+ }
3319
+ if (existsSync(pkgJsonPath)) {
3320
+ try {
3321
+ const pkg = JSON.parse(await readFile7(pkgJsonPath, "utf8"));
3322
+ pkg.scripts = pkg.scripts || {};
3323
+ let modified = false;
3324
+ if (!pkg.scripts.plan) {
3325
+ pkg.scripts.plan = "verelease plan";
3326
+ modified = true;
3327
+ }
3328
+ if (!pkg.scripts.release) {
3329
+ pkg.scripts.release = "verelease release";
3330
+ modified = true;
3331
+ }
3332
+ if (modified) {
3333
+ await writeFile5(pkgJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
3334
+ console.log(" \u2713 Added 'plan' and 'release' scripts to package.json");
3335
+ }
3336
+ } catch {
3337
+ }
3338
+ }
3339
+ console.log("\n\u2728 Verelease successfully initialized!\n");
3340
+ }
2295
3341
  printPlan(plan) {
2296
3342
  console.log(`
2297
3343
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
@@ -2324,10 +3370,34 @@ ${existingChangelog || ""}`.trim() + "\n",
2324
3370
  async function main() {
2325
3371
  const args = process.argv.slice(2);
2326
3372
  const command = args[0] || "plan";
3373
+ if (args.includes("--help") || args.includes("-h") || command === "help") {
3374
+ console.log(`
3375
+ Usage: verelease [command] [options]
3376
+
3377
+ Commands:
3378
+ plan Preview the release plan without making changes (default)
3379
+ release Apply version bumps, generate changelogs, and publish
3380
+ explain <pkg> Explain compatibility evidence and bump decision for a package
3381
+ guide Show agent-optimized cheat sheet and machine-readable instructions
3382
+ init Initialize verelease.config.ts, contracts, and AGENTS.md
3383
+
3384
+ Options:
3385
+ --channel <name> Release channel (e.g. stable, alpha, beta, rc) [default: stable]
3386
+ --bump <type> Manual bump override (patch | minor | major)
3387
+ --dry-run Simulate pipeline without modifying manifests or publishing
3388
+ -y, --yes Skip interactive confirmation prompt
3389
+ --otp <code> One-time password for npm 2FA
3390
+ --tag Create git commit and tags for release
3391
+ --json Output machine-readable JSON
3392
+ -h, --help Show this help message
3393
+ `);
3394
+ return;
3395
+ }
2327
3396
  const options = {
2328
3397
  dryRun: args.includes("--dry-run"),
2329
3398
  json: args.includes("--json"),
2330
- tag: args.includes("--tag")
3399
+ tag: args.includes("--tag"),
3400
+ yes: args.includes("--yes") || args.includes("-y")
2331
3401
  };
2332
3402
  const channelIdx = args.indexOf("--channel");
2333
3403
  if (channelIdx !== -1 && args[channelIdx + 1]) {
@@ -2357,6 +3427,12 @@ async function main() {
2357
3427
  case "version":
2358
3428
  await cli.runReleaseCommand({ ...options, dryRun: false });
2359
3429
  break;
3430
+ case "guide":
3431
+ await cli.runGuideCommand(options);
3432
+ break;
3433
+ case "init":
3434
+ await cli.runInitCommand(options);
3435
+ break;
2360
3436
  case "explain":
2361
3437
  const targetPkg = args[1];
2362
3438
  if (!targetPkg) {
@@ -2367,7 +3443,7 @@ async function main() {
2367
3443
  break;
2368
3444
  default:
2369
3445
  console.log(`Unknown command: ${command}`);
2370
- console.log(`Available commands: plan, release, version, explain`);
3446
+ console.log(`Run 'verelease --help' for available commands and options.`);
2371
3447
  process.exit(1);
2372
3448
  }
2373
3449
  } catch (err) {