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 +1333 -257
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1260 -210
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
5
|
-
import { writeFile as writeFile5, mkdir as
|
|
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
|
|
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
|
-
|
|
586
|
-
|
|
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(
|
|
750
|
-
const nodeModulesDir =
|
|
769
|
+
const consumerEnvDir = await mkdtemp2(join5(tmpdir2(), "verelease-consumer-"));
|
|
770
|
+
const nodeModulesDir = join5(consumerEnvDir, "node_modules");
|
|
751
771
|
try {
|
|
752
|
-
await writeFile2(
|
|
753
|
-
let targetPkgLink =
|
|
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 =
|
|
776
|
+
const scopeDir = join5(nodeModulesDir, scope);
|
|
757
777
|
await mkdir3(scopeDir, { recursive: true });
|
|
758
|
-
targetPkgLink =
|
|
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 =
|
|
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
|
|
1102
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
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
|
-
// ../
|
|
1158
|
-
import {
|
|
1159
|
-
import {
|
|
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 =
|
|
1176
|
-
const jsrJson =
|
|
1177
|
-
const denoJson =
|
|
1178
|
-
if (await
|
|
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
|
|
1285
|
+
if (await fileExists4(jsrJson))
|
|
1181
1286
|
filesToTouch.add(jsrJson);
|
|
1182
|
-
if (await
|
|
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 =
|
|
1189
|
-
if (await
|
|
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
|
|
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 =
|
|
1324
|
+
const pkgJsonPath = join8(pkgPlan.directory, "package.json");
|
|
1220
1325
|
if (filesToTouch.has(pkgJsonPath)) {
|
|
1221
|
-
const content = await
|
|
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 =
|
|
1343
|
+
const jsrJsonPath = join8(pkgPlan.directory, "jsr.json");
|
|
1239
1344
|
if (filesToTouch.has(jsrJsonPath)) {
|
|
1240
|
-
const content = await
|
|
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 =
|
|
1353
|
+
const denoJsonPath = join8(pkgPlan.directory, "deno.json");
|
|
1249
1354
|
if (filesToTouch.has(denoJsonPath)) {
|
|
1250
|
-
const content = await
|
|
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
|
|
1285
|
-
import { promisify as
|
|
1286
|
-
import { stat as
|
|
1287
|
-
import { join as
|
|
1288
|
-
var
|
|
1289
|
-
async function
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1358
|
-
import { promisify as
|
|
1359
|
-
var
|
|
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
|
-
|
|
1469
|
+
execFile4("open", [url]);
|
|
1365
1470
|
} else if (platform === "win32") {
|
|
1366
|
-
|
|
1471
|
+
execFile4("cmd", ["/c", "start", '""', url]);
|
|
1367
1472
|
} else {
|
|
1368
|
-
|
|
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
|
|
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} (
|
|
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
|
|
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
|
|
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
|
|
1563
|
-
import { join as
|
|
1674
|
+
import { stat as stat6 } from "fs/promises";
|
|
1675
|
+
import { join as join10 } from "path";
|
|
1564
1676
|
import { pathToFileURL } from "url";
|
|
1565
|
-
async function
|
|
1677
|
+
async function fileExists6(path) {
|
|
1566
1678
|
try {
|
|
1567
|
-
const s = await
|
|
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 =
|
|
1586
|
-
if (await
|
|
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
|
|
1841
|
-
import { execFile as
|
|
1842
|
-
import { promisify as
|
|
1843
|
-
import { join as
|
|
1844
|
-
import { tmpdir as
|
|
1845
|
-
import * as
|
|
1846
|
-
var
|
|
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
|
|
1905
|
-
import { promisify as
|
|
1906
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2051
|
+
await execFileAsync6("git", ["add", ...files], { cwd: this.cwd });
|
|
1940
2052
|
} else {
|
|
1941
|
-
await
|
|
2053
|
+
await execFileAsync6("git", ["add", "-A"], { cwd: this.cwd });
|
|
1942
2054
|
}
|
|
1943
|
-
await
|
|
2055
|
+
await execFileAsync6("git", ["commit", "-m", message], { cwd: this.cwd });
|
|
1944
2056
|
return this.getHeadCommit();
|
|
1945
2057
|
}
|
|
1946
2058
|
};
|
|
1947
2059
|
|
|
1948
|
-
//
|
|
1949
|
-
import
|
|
1950
|
-
var
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
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
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
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
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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 =
|
|
3069
|
+
const changelogPath = join13(pkgPlan.directory, "CHANGELOG.md");
|
|
2169
3070
|
let existingChangelog = null;
|
|
2170
3071
|
try {
|
|
2171
|
-
existingChangelog = await
|
|
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 =
|
|
2193
|
-
await
|
|
2194
|
-
const guidePath =
|
|
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(`
|
|
3446
|
+
console.log(`Run 'verelease --help' for available commands and options.`);
|
|
2371
3447
|
process.exit(1);
|
|
2372
3448
|
}
|
|
2373
3449
|
} catch (err) {
|