verelease 0.1.2-alpha.2 → 0.1.2-alpha.4
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 +2900 -792
- package/dist/bin.js.map +1 -1
- package/dist/index.js +2880 -865
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/dist/bin.js
CHANGED
|
@@ -1,12 +1,250 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { join as join20, dirname as dirname3 } from "path";
|
|
8
|
+
|
|
3
9
|
// src/cli.ts
|
|
4
|
-
import { resolve as resolve2, join as
|
|
5
|
-
import { writeFile as
|
|
6
|
-
import { mkdtemp as
|
|
7
|
-
import { tmpdir as
|
|
10
|
+
import { resolve as resolve2, join as join19 } from "path";
|
|
11
|
+
import { writeFile as writeFile6, mkdir as mkdir6, readFile as readFile14, rm as rm6 } from "fs/promises";
|
|
12
|
+
import { mkdtemp as mkdtemp7 } from "fs/promises";
|
|
13
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
8
14
|
import { existsSync } from "fs";
|
|
9
|
-
import
|
|
15
|
+
import * as prompts3 from "@clack/prompts";
|
|
16
|
+
import colors3 from "picocolors";
|
|
17
|
+
import * as Effect4 from "effect/Effect";
|
|
18
|
+
import * as Exit2 from "effect/Exit";
|
|
19
|
+
|
|
20
|
+
// ../core/dist/errors.js
|
|
21
|
+
import * as Data from "effect/Data";
|
|
22
|
+
var NpmAuthError = class extends Data.TaggedError("NpmAuthError") {
|
|
23
|
+
};
|
|
24
|
+
var BaselineFetchError = class extends Data.TaggedError("BaselineFetchError") {
|
|
25
|
+
};
|
|
26
|
+
var TestExecutionError = class extends Data.TaggedError("TestExecutionError") {
|
|
27
|
+
};
|
|
28
|
+
var ManifestMutationError = class extends Data.TaggedError("ManifestMutationError") {
|
|
29
|
+
};
|
|
30
|
+
var LockfileSyncError = class extends Data.TaggedError("LockfileSyncError") {
|
|
31
|
+
};
|
|
32
|
+
var PublishError = class extends Data.TaggedError("PublishError") {
|
|
33
|
+
};
|
|
34
|
+
var RegistryCollisionError = class extends Data.TaggedError("RegistryCollisionError") {
|
|
35
|
+
};
|
|
36
|
+
var CargoAuthError = class extends Data.TaggedError("CargoAuthError") {
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ../core/dist/transaction.js
|
|
40
|
+
import * as Effect from "effect/Effect";
|
|
41
|
+
import * as Exit from "effect/Exit";
|
|
42
|
+
import { readFile, writeFile, unlink, stat } from "fs/promises";
|
|
43
|
+
import { join } from "path";
|
|
44
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
45
|
+
async function fileExists(path) {
|
|
46
|
+
try {
|
|
47
|
+
const s = await stat(path);
|
|
48
|
+
return s.isFile();
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function executeReleaseTransaction(options, use) {
|
|
54
|
+
const { plan, cwd, lockfileAdapter, onRollback, onStep } = options;
|
|
55
|
+
const acquire = Effect.tryPromise({
|
|
56
|
+
try: async () => {
|
|
57
|
+
onStep?.("Creating manifest backups...");
|
|
58
|
+
const filesToTouch = /* @__PURE__ */ new Set();
|
|
59
|
+
for (const pkgPlan of plan.packages) {
|
|
60
|
+
if (pkgPlan.bump !== "none") {
|
|
61
|
+
const pkgJson = join(pkgPlan.directory, "package.json");
|
|
62
|
+
const jsrJson = join(pkgPlan.directory, "jsr.json");
|
|
63
|
+
const denoJson = join(pkgPlan.directory, "deno.json");
|
|
64
|
+
const cargoToml = join(pkgPlan.directory, "Cargo.toml");
|
|
65
|
+
if (await fileExists(pkgJson))
|
|
66
|
+
filesToTouch.add(pkgJson);
|
|
67
|
+
if (await fileExists(jsrJson))
|
|
68
|
+
filesToTouch.add(jsrJson);
|
|
69
|
+
if (await fileExists(denoJson))
|
|
70
|
+
filesToTouch.add(denoJson);
|
|
71
|
+
if (await fileExists(cargoToml))
|
|
72
|
+
filesToTouch.add(cargoToml);
|
|
73
|
+
}
|
|
74
|
+
for (const update of pkgPlan.dependentUpdates) {
|
|
75
|
+
const depPkgPlan = plan.packages.find((p) => p.name === update.dependentName);
|
|
76
|
+
if (depPkgPlan) {
|
|
77
|
+
const depPkgJson = join(depPkgPlan.directory, "package.json");
|
|
78
|
+
const depCargoToml = join(depPkgPlan.directory, "Cargo.toml");
|
|
79
|
+
if (await fileExists(depPkgJson))
|
|
80
|
+
filesToTouch.add(depPkgJson);
|
|
81
|
+
if (await fileExists(depCargoToml))
|
|
82
|
+
filesToTouch.add(depCargoToml);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const backups = [];
|
|
87
|
+
for (const filePath of filesToTouch) {
|
|
88
|
+
const originalContent = await readFile(filePath, "utf8");
|
|
89
|
+
backups.push({ filePath, originalContent });
|
|
90
|
+
}
|
|
91
|
+
onStep?.("Applying version bumps to manifests...");
|
|
92
|
+
const versionMap = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const pkgPlan of plan.packages) {
|
|
94
|
+
if (pkgPlan.bump !== "none") {
|
|
95
|
+
versionMap.set(pkgPlan.name, pkgPlan.proposedVersion);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const depUpdateMap = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const pkgPlan of plan.packages) {
|
|
100
|
+
for (const depUp of pkgPlan.dependentUpdates) {
|
|
101
|
+
if (!depUpdateMap.has(depUp.dependentName)) {
|
|
102
|
+
depUpdateMap.set(depUp.dependentName, []);
|
|
103
|
+
}
|
|
104
|
+
depUpdateMap.get(depUp.dependentName).push({
|
|
105
|
+
dependencyName: depUp.dependencyName,
|
|
106
|
+
newRange: depUp.newRange,
|
|
107
|
+
type: depUp.dependencyType
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const modifiedManifests = [];
|
|
112
|
+
for (const pkgPlan of plan.packages) {
|
|
113
|
+
const pkgJsonPath = join(pkgPlan.directory, "package.json");
|
|
114
|
+
if (filesToTouch.has(pkgJsonPath)) {
|
|
115
|
+
const content = await readFile(pkgJsonPath, "utf8");
|
|
116
|
+
const parsed = JSON.parse(content);
|
|
117
|
+
if (versionMap.has(pkgPlan.name)) {
|
|
118
|
+
parsed.version = versionMap.get(pkgPlan.name);
|
|
119
|
+
}
|
|
120
|
+
const depUpdates = depUpdateMap.get(pkgPlan.name);
|
|
121
|
+
if (depUpdates) {
|
|
122
|
+
for (const up of depUpdates) {
|
|
123
|
+
const sec = parsed[up.type];
|
|
124
|
+
if (sec && sec[up.dependencyName]) {
|
|
125
|
+
sec[up.dependencyName] = up.newRange;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await writeFile(pkgJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
130
|
+
modifiedManifests.push(pkgJsonPath);
|
|
131
|
+
}
|
|
132
|
+
const jsrJsonPath = join(pkgPlan.directory, "jsr.json");
|
|
133
|
+
if (filesToTouch.has(jsrJsonPath)) {
|
|
134
|
+
const content = await readFile(jsrJsonPath, "utf8");
|
|
135
|
+
const parsed = JSON.parse(content);
|
|
136
|
+
if (versionMap.has(pkgPlan.name)) {
|
|
137
|
+
parsed.version = versionMap.get(pkgPlan.name);
|
|
138
|
+
}
|
|
139
|
+
await writeFile(jsrJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
140
|
+
modifiedManifests.push(jsrJsonPath);
|
|
141
|
+
}
|
|
142
|
+
const denoJsonPath = join(pkgPlan.directory, "deno.json");
|
|
143
|
+
if (filesToTouch.has(denoJsonPath)) {
|
|
144
|
+
const content = await readFile(denoJsonPath, "utf8");
|
|
145
|
+
const parsed = JSON.parse(content);
|
|
146
|
+
if (versionMap.has(pkgPlan.name)) {
|
|
147
|
+
parsed.version = versionMap.get(pkgPlan.name);
|
|
148
|
+
}
|
|
149
|
+
await writeFile(denoJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
150
|
+
modifiedManifests.push(denoJsonPath);
|
|
151
|
+
}
|
|
152
|
+
const cargoTomlPath = join(pkgPlan.directory, "Cargo.toml");
|
|
153
|
+
if (filesToTouch.has(cargoTomlPath)) {
|
|
154
|
+
const content = await readFile(cargoTomlPath, "utf8");
|
|
155
|
+
const parsed = parseToml(content);
|
|
156
|
+
if (versionMap.has(pkgPlan.name) && parsed.package && typeof parsed.package === "object") {
|
|
157
|
+
parsed.package.version = versionMap.get(pkgPlan.name);
|
|
158
|
+
}
|
|
159
|
+
const depUpdates = depUpdateMap.get(pkgPlan.name);
|
|
160
|
+
if (depUpdates) {
|
|
161
|
+
for (const up of depUpdates) {
|
|
162
|
+
for (const section of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
163
|
+
const dependencies = parsed[section];
|
|
164
|
+
if (!dependencies || typeof dependencies !== "object" || !(up.dependencyName in dependencies)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const dependency = dependencies[up.dependencyName];
|
|
168
|
+
if (typeof dependency === "string") {
|
|
169
|
+
dependencies[up.dependencyName] = up.newRange;
|
|
170
|
+
} else if (dependency && typeof dependency === "object") {
|
|
171
|
+
dependency.version = up.newRange;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await writeFile(cargoTomlPath, stringifyToml(parsed), "utf8");
|
|
177
|
+
modifiedManifests.push(cargoTomlPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
onStep?.(`Regenerating lockfile with ${lockfileAdapter.packageManager}...`);
|
|
181
|
+
const lockRes = await lockfileAdapter.regenerateLockfile(cwd);
|
|
182
|
+
if (!lockRes.success) {
|
|
183
|
+
for (const b of backups) {
|
|
184
|
+
await writeFile(b.filePath, b.originalContent, "utf8");
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`Lockfile generation failed: ${lockRes.output}`);
|
|
187
|
+
}
|
|
188
|
+
const changelogBackups = /* @__PURE__ */ new Map();
|
|
189
|
+
const generatedFiles = /* @__PURE__ */ new Set();
|
|
190
|
+
const scope = {
|
|
191
|
+
plan,
|
|
192
|
+
modifiedManifests,
|
|
193
|
+
changelogBackups,
|
|
194
|
+
generatedFiles,
|
|
195
|
+
backupChangelog(path, original) {
|
|
196
|
+
changelogBackups.set(path, original);
|
|
197
|
+
},
|
|
198
|
+
trackGeneratedFile(path) {
|
|
199
|
+
generatedFiles.add(path);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
return { scope, backups };
|
|
203
|
+
},
|
|
204
|
+
catch: (err) => new ManifestMutationError({
|
|
205
|
+
message: err instanceof Error ? err.message : String(err)
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
const release = (resource, exit) => Effect.promise(async () => {
|
|
209
|
+
if (Exit.isFailure(exit)) {
|
|
210
|
+
onRollback?.("Rolling back manifests to previous versions...");
|
|
211
|
+
for (const b of resource.backups) {
|
|
212
|
+
try {
|
|
213
|
+
await writeFile(b.filePath, b.originalContent, "utf8");
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
onRollback?.("Restored manifests.");
|
|
218
|
+
onRollback?.("Restoring changelog files...");
|
|
219
|
+
for (const [chPath, original] of resource.scope.changelogBackups) {
|
|
220
|
+
try {
|
|
221
|
+
if (original === null) {
|
|
222
|
+
await unlink(chPath);
|
|
223
|
+
} else {
|
|
224
|
+
await writeFile(chPath, original, "utf8");
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
onRollback?.("Restored changelog files.");
|
|
230
|
+
onRollback?.("Removing generated migration guides...");
|
|
231
|
+
for (const genPath of resource.scope.generatedFiles) {
|
|
232
|
+
try {
|
|
233
|
+
await unlink(genPath);
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
onRollback?.("Removed generated migration guides.");
|
|
238
|
+
onRollback?.("Synchronizing workspace lockfile...");
|
|
239
|
+
try {
|
|
240
|
+
await lockfileAdapter.regenerateLockfile(cwd);
|
|
241
|
+
onRollback?.("Synchronized lockfile.");
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return Effect.acquireUseRelease(acquire, ({ scope }) => use(scope), release);
|
|
247
|
+
}
|
|
10
248
|
|
|
11
249
|
// ../workspace/dist/graph.js
|
|
12
250
|
var PackageGraphImpl = class {
|
|
@@ -160,12 +398,13 @@ var PackageGraphImpl = class {
|
|
|
160
398
|
};
|
|
161
399
|
|
|
162
400
|
// ../workspace/dist/resolver.js
|
|
163
|
-
import { readFile, stat, readdir } from "fs/promises";
|
|
164
|
-
import { join, relative, resolve } from "path";
|
|
401
|
+
import { readFile as readFile2, stat as stat2, readdir } from "fs/promises";
|
|
402
|
+
import { join as join2, relative, resolve } from "path";
|
|
165
403
|
import { parse as parseYaml } from "yaml";
|
|
166
|
-
|
|
404
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
405
|
+
async function fileExists2(path) {
|
|
167
406
|
try {
|
|
168
|
-
const s = await
|
|
407
|
+
const s = await stat2(path);
|
|
169
408
|
return s.isFile();
|
|
170
409
|
} catch {
|
|
171
410
|
return false;
|
|
@@ -173,113 +412,343 @@ async function fileExists(path) {
|
|
|
173
412
|
}
|
|
174
413
|
async function dirExists(path) {
|
|
175
414
|
try {
|
|
176
|
-
const s = await
|
|
415
|
+
const s = await stat2(path);
|
|
177
416
|
return s.isDirectory();
|
|
178
417
|
} catch {
|
|
179
418
|
return false;
|
|
180
419
|
}
|
|
181
420
|
}
|
|
421
|
+
function extractCargoDeps(depSection) {
|
|
422
|
+
const result = {};
|
|
423
|
+
if (!depSection || typeof depSection !== "object")
|
|
424
|
+
return result;
|
|
425
|
+
for (const [key, value] of Object.entries(depSection)) {
|
|
426
|
+
if (typeof value === "string") {
|
|
427
|
+
result[key] = value;
|
|
428
|
+
} else if (value && typeof value === "object") {
|
|
429
|
+
const depObj = value;
|
|
430
|
+
if (typeof depObj.version === "string") {
|
|
431
|
+
result[key] = depObj.version;
|
|
432
|
+
} else if (depObj.path) {
|
|
433
|
+
result[key] = "*";
|
|
434
|
+
} else if (depObj.workspace === true) {
|
|
435
|
+
result[key] = "workspace:*";
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
182
441
|
var DefaultWorkspaceResolver = class {
|
|
183
442
|
name = "default-workspace-resolver";
|
|
184
443
|
async supports(rootDir) {
|
|
185
|
-
const pnpmWorkspace =
|
|
186
|
-
const packageJson =
|
|
187
|
-
|
|
444
|
+
const pnpmWorkspace = join2(rootDir, "pnpm-workspace.yaml");
|
|
445
|
+
const packageJson = join2(rootDir, "package.json");
|
|
446
|
+
const cargoToml = join2(rootDir, "Cargo.toml");
|
|
447
|
+
return await fileExists2(pnpmWorkspace) || await fileExists2(packageJson) || await fileExists2(cargoToml);
|
|
188
448
|
}
|
|
189
449
|
async resolve(rootDir) {
|
|
190
450
|
const absRootDir = resolve(rootDir);
|
|
191
|
-
const pnpmWorkspacePath =
|
|
192
|
-
const rootPkgJsonPath =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
451
|
+
const pnpmWorkspacePath = join2(absRootDir, "pnpm-workspace.yaml");
|
|
452
|
+
const rootPkgJsonPath = join2(absRootDir, "package.json");
|
|
453
|
+
const rootCargoTomlPath = join2(absRootDir, "Cargo.toml");
|
|
454
|
+
const allPackages = [];
|
|
455
|
+
const skipped = [];
|
|
456
|
+
const ecosystems = [];
|
|
457
|
+
let detectedPackageManager = "unknown";
|
|
458
|
+
const hasPnpmWorkspace = await fileExists2(pnpmWorkspacePath);
|
|
459
|
+
const hasRootPkgJson = await fileExists2(rootPkgJsonPath);
|
|
460
|
+
if (hasPnpmWorkspace || hasRootPkgJson) {
|
|
461
|
+
let jsPatterns = [];
|
|
462
|
+
let jsPackageManager = "unknown";
|
|
463
|
+
if (hasPnpmWorkspace) {
|
|
464
|
+
jsPackageManager = "pnpm";
|
|
465
|
+
try {
|
|
466
|
+
const content = await readFile2(pnpmWorkspacePath, "utf8");
|
|
467
|
+
const parsed = parseYaml(content);
|
|
468
|
+
if (parsed && Array.isArray(parsed.packages)) {
|
|
469
|
+
jsPatterns = parsed.packages;
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
} else if (hasRootPkgJson) {
|
|
474
|
+
try {
|
|
475
|
+
const content = await readFile2(rootPkgJsonPath, "utf8");
|
|
476
|
+
const parsed = JSON.parse(content);
|
|
477
|
+
if (Array.isArray(parsed.workspaces)) {
|
|
478
|
+
jsPatterns = parsed.workspaces;
|
|
479
|
+
jsPackageManager = "npm";
|
|
480
|
+
} else if (parsed.workspaces && Array.isArray(parsed.workspaces.packages)) {
|
|
481
|
+
jsPatterns = parsed.workspaces.packages;
|
|
482
|
+
jsPackageManager = "yarn";
|
|
483
|
+
} else {
|
|
484
|
+
const pkg = {
|
|
485
|
+
name: parsed.name || "unnamed-package",
|
|
486
|
+
version: parsed.version || "0.0.0",
|
|
487
|
+
directory: absRootDir,
|
|
488
|
+
manifestPath: rootPkgJsonPath,
|
|
489
|
+
manifest: parsed,
|
|
490
|
+
isPrivate: !!parsed.private,
|
|
491
|
+
relativeDir: ".",
|
|
492
|
+
ecosystem: "npm"
|
|
493
|
+
};
|
|
494
|
+
allPackages.push(pkg);
|
|
495
|
+
ecosystems.push("npm");
|
|
496
|
+
detectedPackageManager = "npm";
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
228
500
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (await dirExists(baseDir)) {
|
|
238
|
-
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
239
|
-
for (const entry of entries) {
|
|
240
|
-
if (entry.isDirectory()) {
|
|
241
|
-
matchedDirs.add(join(baseDir, entry.name));
|
|
242
|
-
}
|
|
501
|
+
if (jsPatterns.length > 0) {
|
|
502
|
+
detectedPackageManager = jsPackageManager;
|
|
503
|
+
ecosystems.push("npm");
|
|
504
|
+
const matchedDirs = /* @__PURE__ */ new Set();
|
|
505
|
+
const { included, excluded } = splitPatterns(jsPatterns);
|
|
506
|
+
for (const pattern of included) {
|
|
507
|
+
for (const dir of await expandPattern(absRootDir, pattern)) {
|
|
508
|
+
matchedDirs.add(dir);
|
|
243
509
|
}
|
|
244
510
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
511
|
+
for (const pattern of excluded) {
|
|
512
|
+
for (const dir of await expandPattern(absRootDir, pattern)) {
|
|
513
|
+
matchedDirs.delete(dir);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
for (const dir of matchedDirs) {
|
|
517
|
+
const manifestPath = join2(dir, "package.json");
|
|
518
|
+
if (await fileExists2(manifestPath)) {
|
|
519
|
+
try {
|
|
520
|
+
const content = await readFile2(manifestPath, "utf8");
|
|
521
|
+
const manifest = JSON.parse(content);
|
|
522
|
+
if (!manifest.name) {
|
|
523
|
+
skipped.push({ directory: dir, reason: "package.json names no package" });
|
|
524
|
+
} else if (!manifest.version) {
|
|
525
|
+
skipped.push({
|
|
526
|
+
directory: dir,
|
|
527
|
+
name: manifest.name,
|
|
528
|
+
reason: "package.json declares no version"
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (manifest.name && manifest.version) {
|
|
532
|
+
allPackages.push({
|
|
533
|
+
name: manifest.name,
|
|
534
|
+
version: manifest.version,
|
|
535
|
+
directory: dir,
|
|
536
|
+
manifestPath,
|
|
537
|
+
manifest,
|
|
538
|
+
isPrivate: !!manifest.private,
|
|
539
|
+
relativeDir: relative(absRootDir, dir),
|
|
540
|
+
ecosystem: "npm"
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
}
|
|
249
546
|
}
|
|
250
547
|
}
|
|
251
548
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
549
|
+
if (await fileExists2(rootCargoTomlPath)) {
|
|
550
|
+
try {
|
|
551
|
+
const rootCargoRaw = await readFile2(rootCargoTomlPath, "utf8");
|
|
552
|
+
const rootCargo = parseToml2(rootCargoRaw);
|
|
553
|
+
const workspaceDefaultVersion = rootCargo.workspace?.package?.version || rootCargo.package?.version || "0.1.0";
|
|
554
|
+
let cargoPatterns = [];
|
|
555
|
+
if (rootCargo.workspace && Array.isArray(rootCargo.workspace.members)) {
|
|
556
|
+
cargoPatterns = rootCargo.workspace.members;
|
|
557
|
+
}
|
|
558
|
+
if (rootCargo.package && rootCargo.package.name) {
|
|
559
|
+
const pkgName = rootCargo.package.name;
|
|
560
|
+
const pkgVersion = typeof rootCargo.package.version === "string" ? rootCargo.package.version : workspaceDefaultVersion;
|
|
561
|
+
const isPrivate = rootCargo.package.publish === false;
|
|
562
|
+
const manifest = {
|
|
563
|
+
name: pkgName,
|
|
564
|
+
version: pkgVersion,
|
|
565
|
+
private: isPrivate,
|
|
566
|
+
dependencies: extractCargoDeps(rootCargo.dependencies),
|
|
567
|
+
devDependencies: extractCargoDeps(rootCargo["dev-dependencies"])
|
|
568
|
+
};
|
|
569
|
+
allPackages.push({
|
|
570
|
+
name: pkgName,
|
|
571
|
+
version: pkgVersion,
|
|
572
|
+
directory: absRootDir,
|
|
573
|
+
manifestPath: rootCargoTomlPath,
|
|
574
|
+
manifest,
|
|
575
|
+
isPrivate,
|
|
576
|
+
relativeDir: ".",
|
|
577
|
+
ecosystem: "cargo"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (cargoPatterns.length > 0) {
|
|
581
|
+
const matchedCrateDirs = /* @__PURE__ */ new Set();
|
|
582
|
+
for (const pattern of cargoPatterns) {
|
|
583
|
+
if (pattern.endsWith("/*")) {
|
|
584
|
+
const baseDir = join2(absRootDir, pattern.slice(0, -2));
|
|
585
|
+
if (await dirExists(baseDir)) {
|
|
586
|
+
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
587
|
+
for (const entry of entries) {
|
|
588
|
+
if (entry.isDirectory()) {
|
|
589
|
+
matchedCrateDirs.add(join2(baseDir, entry.name));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
const exactDir = join2(absRootDir, pattern);
|
|
595
|
+
if (await dirExists(exactDir)) {
|
|
596
|
+
matchedCrateDirs.add(exactDir);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (const dir of matchedCrateDirs) {
|
|
601
|
+
const manifestPath = join2(dir, "Cargo.toml");
|
|
602
|
+
if (manifestPath === rootCargoTomlPath)
|
|
603
|
+
continue;
|
|
604
|
+
if (await fileExists2(manifestPath)) {
|
|
605
|
+
try {
|
|
606
|
+
const crateRaw = await readFile2(manifestPath, "utf8");
|
|
607
|
+
const crateParsed = parseToml2(crateRaw);
|
|
608
|
+
if (crateParsed.package && crateParsed.package.name) {
|
|
609
|
+
const name = crateParsed.package.name;
|
|
610
|
+
let version2 = crateParsed.package.version;
|
|
611
|
+
if (typeof version2 !== "string") {
|
|
612
|
+
version2 = workspaceDefaultVersion;
|
|
613
|
+
}
|
|
614
|
+
const isPrivate = crateParsed.package.publish === false;
|
|
615
|
+
const manifest = {
|
|
616
|
+
name,
|
|
617
|
+
version: version2,
|
|
618
|
+
private: isPrivate,
|
|
619
|
+
dependencies: extractCargoDeps(crateParsed.dependencies),
|
|
620
|
+
devDependencies: extractCargoDeps(crateParsed["dev-dependencies"])
|
|
621
|
+
};
|
|
622
|
+
allPackages.push({
|
|
623
|
+
name,
|
|
624
|
+
version: version2,
|
|
625
|
+
directory: dir,
|
|
626
|
+
manifestPath,
|
|
627
|
+
manifest,
|
|
628
|
+
isPrivate,
|
|
629
|
+
relativeDir: relative(absRootDir, dir),
|
|
630
|
+
ecosystem: "cargo"
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
}
|
|
269
636
|
}
|
|
270
|
-
} catch {
|
|
271
637
|
}
|
|
638
|
+
if (!ecosystems.includes("cargo")) {
|
|
639
|
+
ecosystems.push("cargo");
|
|
640
|
+
}
|
|
641
|
+
if (detectedPackageManager === "unknown") {
|
|
642
|
+
detectedPackageManager = "cargo";
|
|
643
|
+
}
|
|
644
|
+
} catch {
|
|
272
645
|
}
|
|
273
646
|
}
|
|
274
|
-
const graph = new PackageGraphImpl(
|
|
647
|
+
const graph = new PackageGraphImpl(allPackages);
|
|
275
648
|
return {
|
|
276
649
|
rootDirectory: absRootDir,
|
|
277
|
-
packageManager,
|
|
278
|
-
packages,
|
|
279
|
-
|
|
650
|
+
packageManager: detectedPackageManager,
|
|
651
|
+
packages: allPackages,
|
|
652
|
+
...skipped.length > 0 ? { skipped } : {},
|
|
653
|
+
graph,
|
|
654
|
+
ecosystems
|
|
280
655
|
};
|
|
281
656
|
}
|
|
282
657
|
};
|
|
658
|
+
function splitPatterns(patterns) {
|
|
659
|
+
const included = [];
|
|
660
|
+
const excluded = [];
|
|
661
|
+
for (const raw of patterns) {
|
|
662
|
+
const pattern = raw.trim();
|
|
663
|
+
if (!pattern)
|
|
664
|
+
continue;
|
|
665
|
+
if (pattern.startsWith("!"))
|
|
666
|
+
excluded.push(pattern.slice(1));
|
|
667
|
+
else
|
|
668
|
+
included.push(pattern);
|
|
669
|
+
}
|
|
670
|
+
return { included, excluded };
|
|
671
|
+
}
|
|
672
|
+
var NEVER_A_PACKAGE = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
673
|
+
async function expandPattern(root, pattern) {
|
|
674
|
+
const segments = pattern.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
675
|
+
let current = [root];
|
|
676
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
677
|
+
const segment = segments[index];
|
|
678
|
+
const next = /* @__PURE__ */ new Set();
|
|
679
|
+
if (segment === "**") {
|
|
680
|
+
const remaining = segments.slice(index + 1);
|
|
681
|
+
for (const dir of current) {
|
|
682
|
+
for (const descendant of await descendants(dir))
|
|
683
|
+
next.add(descendant);
|
|
684
|
+
}
|
|
685
|
+
current = [...next];
|
|
686
|
+
if (remaining.length === 0)
|
|
687
|
+
return current;
|
|
688
|
+
const results = /* @__PURE__ */ new Set();
|
|
689
|
+
for (const dir of current) {
|
|
690
|
+
for (const match of await expandPattern(dir, remaining.join("/")))
|
|
691
|
+
results.add(match);
|
|
692
|
+
}
|
|
693
|
+
return [...results];
|
|
694
|
+
}
|
|
695
|
+
for (const dir of current) {
|
|
696
|
+
if (segment.includes("*")) {
|
|
697
|
+
const matches = segmentMatcher(segment);
|
|
698
|
+
let entries;
|
|
699
|
+
try {
|
|
700
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
701
|
+
} catch {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
for (const entry of entries) {
|
|
705
|
+
if (!entry.isDirectory())
|
|
706
|
+
continue;
|
|
707
|
+
if (NEVER_A_PACKAGE.has(entry.name))
|
|
708
|
+
continue;
|
|
709
|
+
if (matches(entry.name))
|
|
710
|
+
next.add(join2(dir, entry.name));
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
const candidate = join2(dir, segment);
|
|
714
|
+
if (await dirExists(candidate))
|
|
715
|
+
next.add(candidate);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
current = [...next];
|
|
719
|
+
if (current.length === 0)
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
return current;
|
|
723
|
+
}
|
|
724
|
+
async function descendants(root, maxDepth = 8) {
|
|
725
|
+
const found = [root];
|
|
726
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
727
|
+
while (queue.length > 0) {
|
|
728
|
+
const { dir, depth } = queue.shift();
|
|
729
|
+
if (depth >= maxDepth)
|
|
730
|
+
continue;
|
|
731
|
+
let entries;
|
|
732
|
+
try {
|
|
733
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
734
|
+
} catch {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
for (const entry of entries) {
|
|
738
|
+
if (!entry.isDirectory() || NEVER_A_PACKAGE.has(entry.name))
|
|
739
|
+
continue;
|
|
740
|
+
const child = join2(dir, entry.name);
|
|
741
|
+
found.push(child);
|
|
742
|
+
queue.push({ dir: child, depth: depth + 1 });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return found;
|
|
746
|
+
}
|
|
747
|
+
function segmentMatcher(segment) {
|
|
748
|
+
const source = segment.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/gu, "\\$&")).join("[^/]*");
|
|
749
|
+
const expression = new RegExp(`^${source}$`, "u");
|
|
750
|
+
return (name) => expression.test(name);
|
|
751
|
+
}
|
|
283
752
|
|
|
284
753
|
// ../workspace/dist/propagator.js
|
|
285
754
|
import semver from "semver";
|
|
@@ -380,9 +849,11 @@ var DefaultSemVerDecisionEngine = class {
|
|
|
380
849
|
const reasons = [];
|
|
381
850
|
let confidenceSum = 0;
|
|
382
851
|
let confidenceCount = 0;
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
const
|
|
852
|
+
const blockingEvidence = evidence.filter((e) => e.blocking === true);
|
|
853
|
+
const gradingEvidence = evidence.filter((e) => e.blocking !== true);
|
|
854
|
+
const majorEvidence = gradingEvidence.filter((e) => e.severity === "major");
|
|
855
|
+
const minorEvidence = gradingEvidence.filter((e) => e.severity === "minor");
|
|
856
|
+
const patchEvidence = gradingEvidence.filter((e) => e.severity === "patch");
|
|
386
857
|
if (majorEvidence.length > 0) {
|
|
387
858
|
if (isZeroMajor && policy.zeroMajor?.breaking === "minor") {
|
|
388
859
|
bump = "minor";
|
|
@@ -416,10 +887,16 @@ var DefaultSemVerDecisionEngine = class {
|
|
|
416
887
|
bump = "none";
|
|
417
888
|
reasons.push("No compatibility changes detected requiring a version bump.");
|
|
418
889
|
}
|
|
890
|
+
if (blockingEvidence.length > 0) {
|
|
891
|
+
reasons.unshift(`Release blocked by ${blockingEvidence.length} finding(s); the version below is not proposed until they clear:`);
|
|
892
|
+
for (const e of blockingEvidence) {
|
|
893
|
+
reasons.splice(1, 0, ` - [${e.source}] ${e.title}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
419
896
|
const confidence = confidenceCount > 0 ? confidenceSum / confidenceCount : 1;
|
|
420
|
-
return this.createDecision(packageName, currentVersion, bump, evidence, reasons, confidence, policy, channel);
|
|
897
|
+
return this.createDecision(packageName, currentVersion, bump, evidence, reasons, confidence, policy, channel, blockingEvidence.length > 0);
|
|
421
898
|
}
|
|
422
|
-
createDecision(packageName, currentVersion, bump, evidence, reasons, confidence, policy, channel) {
|
|
899
|
+
createDecision(packageName, currentVersion, bump, evidence, reasons, confidence, policy, channel, blocked = false) {
|
|
423
900
|
let proposedVersion = currentVersion;
|
|
424
901
|
if (bump !== "none") {
|
|
425
902
|
if (channel && channel !== "stable") {
|
|
@@ -427,6 +904,9 @@ var DefaultSemVerDecisionEngine = class {
|
|
|
427
904
|
const currentPrerelease = parsed.prerelease;
|
|
428
905
|
if (currentPrerelease.length > 0 && currentPrerelease[0] === channel) {
|
|
429
906
|
proposedVersion = semver2.inc(currentVersion, "prerelease", channel) || currentVersion;
|
|
907
|
+
} else if (currentPrerelease.length > 0) {
|
|
908
|
+
const promoted = `${parsed.major}.${parsed.minor}.${parsed.patch}-${channel}.0`;
|
|
909
|
+
proposedVersion = semver2.gt(promoted, currentVersion) ? promoted : semver2.inc(currentVersion, `pre${bump}`, channel) || currentVersion;
|
|
430
910
|
} else {
|
|
431
911
|
const baseBump = `pre${bump}`;
|
|
432
912
|
proposedVersion = semver2.inc(currentVersion, baseBump, channel) || currentVersion;
|
|
@@ -444,14 +924,15 @@ var DefaultSemVerDecisionEngine = class {
|
|
|
444
924
|
confidence,
|
|
445
925
|
reasons,
|
|
446
926
|
evidence,
|
|
447
|
-
requiresApproval
|
|
927
|
+
requiresApproval,
|
|
928
|
+
...blocked ? { blocked: true } : {}
|
|
448
929
|
};
|
|
449
930
|
}
|
|
450
931
|
};
|
|
451
932
|
|
|
452
933
|
// ../contracts/dist/store.js
|
|
453
|
-
import { readFile as
|
|
454
|
-
import { join as
|
|
934
|
+
import { readFile as readFile3, writeFile as writeFile2, mkdir, readdir as readdir2 } from "fs/promises";
|
|
935
|
+
import { join as join3 } from "path";
|
|
455
936
|
import semver3 from "semver";
|
|
456
937
|
var FileContractStore = class {
|
|
457
938
|
directory;
|
|
@@ -471,9 +952,9 @@ var FileContractStore = class {
|
|
|
471
952
|
const files = await readdir2(this.directory, { recursive: true });
|
|
472
953
|
for (const file of files) {
|
|
473
954
|
if (typeof file === "string" && file.endsWith(".json")) {
|
|
474
|
-
const fullPath =
|
|
955
|
+
const fullPath = join3(this.directory, file);
|
|
475
956
|
try {
|
|
476
|
-
const content = await
|
|
957
|
+
const content = await readFile3(fullPath, "utf8");
|
|
477
958
|
const parsed = JSON.parse(content);
|
|
478
959
|
if (!packageName || parsed.package === packageName) {
|
|
479
960
|
contracts.push(parsed);
|
|
@@ -494,8 +975,8 @@ var FileContractStore = class {
|
|
|
494
975
|
await this.ensureDir();
|
|
495
976
|
const sanitizedPackage = contract.package.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
496
977
|
const sanitizedId = contract.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
497
|
-
const filePath =
|
|
498
|
-
await
|
|
978
|
+
const filePath = join3(this.directory, `${sanitizedPackage}__${sanitizedId}.json`);
|
|
979
|
+
await writeFile2(filePath, JSON.stringify(contract, null, 2), "utf8");
|
|
499
980
|
}
|
|
500
981
|
async getActiveContracts(packageName, targetVersion) {
|
|
501
982
|
const all = await this.listContracts(packageName);
|
|
@@ -519,20 +1000,20 @@ var FileContractStore = class {
|
|
|
519
1000
|
import { execFile } from "child_process";
|
|
520
1001
|
import { promisify } from "util";
|
|
521
1002
|
import { mkdtemp, rm, mkdir as mkdir2 } from "fs/promises";
|
|
522
|
-
import { join as
|
|
1003
|
+
import { join as join4 } from "path";
|
|
523
1004
|
import { tmpdir } from "os";
|
|
524
1005
|
import * as tar from "tar";
|
|
525
1006
|
var execFileAsync = promisify(execFile);
|
|
526
1007
|
var CandidatePacker = class {
|
|
527
|
-
async pack(packageDir, packageName,
|
|
528
|
-
const tempDir = await mkdtemp(
|
|
1008
|
+
async pack(packageDir, packageName, version2) {
|
|
1009
|
+
const tempDir = await mkdtemp(join4(tmpdir(), "verelease-candidate-"));
|
|
529
1010
|
const tarballDest = tempDir;
|
|
530
1011
|
const { stdout } = await execFileAsync("npm", ["pack", "--pack-destination", tarballDest], {
|
|
531
1012
|
cwd: packageDir
|
|
532
1013
|
});
|
|
533
1014
|
const tarballFilename = stdout.trim().split("\n").pop().trim();
|
|
534
|
-
const tarballPath =
|
|
535
|
-
const unpackedDir =
|
|
1015
|
+
const tarballPath = join4(tarballDest, tarballFilename);
|
|
1016
|
+
const unpackedDir = join4(tempDir, "unpacked");
|
|
536
1017
|
await mkdir2(unpackedDir, { recursive: true });
|
|
537
1018
|
await tar.x({
|
|
538
1019
|
file: tarballPath,
|
|
@@ -542,14 +1023,15 @@ var CandidatePacker = class {
|
|
|
542
1023
|
});
|
|
543
1024
|
return {
|
|
544
1025
|
packageName,
|
|
545
|
-
version,
|
|
1026
|
+
version: version2,
|
|
546
1027
|
tarballPath,
|
|
547
|
-
unpackedDir
|
|
1028
|
+
unpackedDir,
|
|
1029
|
+
sourceDir: packageDir
|
|
548
1030
|
};
|
|
549
1031
|
}
|
|
550
1032
|
async cleanup(packResult) {
|
|
551
1033
|
try {
|
|
552
|
-
const parentDir =
|
|
1034
|
+
const parentDir = join4(packResult.unpackedDir, "..");
|
|
553
1035
|
await rm(parentDir, { recursive: true, force: true });
|
|
554
1036
|
} catch {
|
|
555
1037
|
}
|
|
@@ -557,15 +1039,15 @@ var CandidatePacker = class {
|
|
|
557
1039
|
};
|
|
558
1040
|
|
|
559
1041
|
// ../contracts/dist/runner.js
|
|
560
|
-
import { mkdir as mkdir3, symlink, rm as rm2, writeFile as
|
|
561
|
-
import { join as
|
|
1042
|
+
import { access, cp, mkdir as mkdir3, symlink, rm as rm2, readFile as readFile5, writeFile as writeFile3, copyFile } from "fs/promises";
|
|
1043
|
+
import { join as join6, dirname, basename } from "path";
|
|
562
1044
|
import { tmpdir as tmpdir2 } from "os";
|
|
563
1045
|
import { mkdtemp as mkdtemp2 } from "fs/promises";
|
|
564
1046
|
|
|
565
1047
|
// ../test-adapter-node/dist/node-test.js
|
|
566
1048
|
import { spawn } from "child_process";
|
|
567
|
-
import { readFile as
|
|
568
|
-
import { join as
|
|
1049
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1050
|
+
import { join as join5 } from "path";
|
|
569
1051
|
var NodeTestAdapter = class {
|
|
570
1052
|
options;
|
|
571
1053
|
name = "node:test";
|
|
@@ -579,6 +1061,11 @@ var NodeTestAdapter = class {
|
|
|
579
1061
|
if (hasNodeTestScript) {
|
|
580
1062
|
return { supported: true, confidence: 0.95 };
|
|
581
1063
|
}
|
|
1064
|
+
const testScript = scripts.test ?? "";
|
|
1065
|
+
const otherRunner = /\b(vitest|jest|playwright|bun\s+test|deno\s+test|ava|mocha)\b/u;
|
|
1066
|
+
if (otherRunner.test(testScript)) {
|
|
1067
|
+
return { supported: false, confidence: 0 };
|
|
1068
|
+
}
|
|
582
1069
|
if (hasTestFiles) {
|
|
583
1070
|
return { supported: true, confidence: 0.7 };
|
|
584
1071
|
}
|
|
@@ -592,15 +1079,13 @@ var NodeTestAdapter = class {
|
|
|
592
1079
|
let testFiles = ctx.testFiles && ctx.testFiles.length > 0 ? ctx.testFiles : [];
|
|
593
1080
|
if (loaderArgs.length === 0 || testFiles.length === 0) {
|
|
594
1081
|
try {
|
|
595
|
-
const pkgRaw = await
|
|
1082
|
+
const pkgRaw = await readFile4(join5(ctx.packagePath, "package.json"), "utf8");
|
|
596
1083
|
const pkg = JSON.parse(pkgRaw);
|
|
597
|
-
const
|
|
598
|
-
if (loaderArgs.length === 0
|
|
599
|
-
loaderArgs =
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
testFiles = ["src/**/*.test.ts"];
|
|
603
|
-
}
|
|
1084
|
+
const parsed = parseNodeTestScript(pkg.scripts?.test ?? "");
|
|
1085
|
+
if (loaderArgs.length === 0)
|
|
1086
|
+
loaderArgs = parsed.loaderArgs;
|
|
1087
|
+
if (testFiles.length === 0)
|
|
1088
|
+
testFiles = parsed.testFiles;
|
|
604
1089
|
} catch {
|
|
605
1090
|
}
|
|
606
1091
|
}
|
|
@@ -757,6 +1242,64 @@ var NodeTestAdapter = class {
|
|
|
757
1242
|
};
|
|
758
1243
|
}
|
|
759
1244
|
};
|
|
1245
|
+
function parseNodeTestScript(script) {
|
|
1246
|
+
const empty = { loaderArgs: [], testFiles: [] };
|
|
1247
|
+
if (!script.includes("--test"))
|
|
1248
|
+
return empty;
|
|
1249
|
+
const segment = script.split(/&&|;/u).map((part) => part.trim()).find((part) => /(^|\s)node\s/u.test(part) && part.includes("--test"));
|
|
1250
|
+
if (!segment)
|
|
1251
|
+
return empty;
|
|
1252
|
+
const tokens = tokenize(segment);
|
|
1253
|
+
const start = tokens.findIndex((token) => token === "--test");
|
|
1254
|
+
if (start === -1)
|
|
1255
|
+
return empty;
|
|
1256
|
+
const loaderArgs = [];
|
|
1257
|
+
const testFiles = [];
|
|
1258
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
1259
|
+
const token = tokens[index];
|
|
1260
|
+
if ((token === "--import" || token === "--loader") && tokens[index + 1]) {
|
|
1261
|
+
loaderArgs.push(token, tokens[index + 1]);
|
|
1262
|
+
index += 1;
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (token.startsWith("--import=") || token.startsWith("--loader=")) {
|
|
1266
|
+
loaderArgs.push(token);
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (index > start && !token.startsWith("-")) {
|
|
1270
|
+
testFiles.push(token);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return { loaderArgs, testFiles };
|
|
1274
|
+
}
|
|
1275
|
+
function tokenize(command) {
|
|
1276
|
+
const tokens = [];
|
|
1277
|
+
let current = "";
|
|
1278
|
+
let quote = null;
|
|
1279
|
+
for (const character of command) {
|
|
1280
|
+
if (quote) {
|
|
1281
|
+
if (character === quote)
|
|
1282
|
+
quote = null;
|
|
1283
|
+
else
|
|
1284
|
+
current += character;
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (character === '"' || character === "'") {
|
|
1288
|
+
quote = character;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (/\s/u.test(character)) {
|
|
1292
|
+
if (current)
|
|
1293
|
+
tokens.push(current);
|
|
1294
|
+
current = "";
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
current += character;
|
|
1298
|
+
}
|
|
1299
|
+
if (current)
|
|
1300
|
+
tokens.push(current);
|
|
1301
|
+
return tokens;
|
|
1302
|
+
}
|
|
760
1303
|
|
|
761
1304
|
// ../contracts/dist/runner.js
|
|
762
1305
|
var ContractRunner = class {
|
|
@@ -764,24 +1307,74 @@ var ContractRunner = class {
|
|
|
764
1307
|
constructor(testAdapter = new NodeTestAdapter()) {
|
|
765
1308
|
this.testAdapter = testAdapter;
|
|
766
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Links the candidate's declared dependencies into the consumer.
|
|
1312
|
+
*
|
|
1313
|
+
* Resolution walks up from the source directory, so it works with pnpm's
|
|
1314
|
+
* per-package symlinks and with hoisted layouts alike. A dependency that
|
|
1315
|
+
* cannot be found is left out: the resulting import error is a more useful
|
|
1316
|
+
* signal than a silent substitution.
|
|
1317
|
+
*/
|
|
1318
|
+
async linkDependencies(candidate, nodeModulesDir) {
|
|
1319
|
+
if (!candidate.sourceDir)
|
|
1320
|
+
return;
|
|
1321
|
+
let manifest;
|
|
1322
|
+
try {
|
|
1323
|
+
manifest = JSON.parse(await readFile5(join6(candidate.unpackedDir, "package.json"), "utf8"));
|
|
1324
|
+
} catch {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const names = Object.keys({
|
|
1328
|
+
...manifest.dependencies,
|
|
1329
|
+
...manifest.optionalDependencies
|
|
1330
|
+
});
|
|
1331
|
+
for (const name of names) {
|
|
1332
|
+
const resolved = await this.resolveFrom(name, candidate.sourceDir);
|
|
1333
|
+
if (!resolved)
|
|
1334
|
+
continue;
|
|
1335
|
+
const linkPath = join6(nodeModulesDir, ...name.split("/"));
|
|
1336
|
+
await mkdir3(dirname(linkPath), { recursive: true });
|
|
1337
|
+
try {
|
|
1338
|
+
await symlink(resolved, linkPath, "junction");
|
|
1339
|
+
} catch {
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
/** Finds an installed package by walking `node_modules` up from a directory. */
|
|
1344
|
+
async resolveFrom(name, fromDir) {
|
|
1345
|
+
let dir = fromDir;
|
|
1346
|
+
for (; ; ) {
|
|
1347
|
+
const candidatePath = join6(dir, "node_modules", ...name.split("/"));
|
|
1348
|
+
try {
|
|
1349
|
+
await access(candidatePath);
|
|
1350
|
+
return candidatePath;
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
const parent = dirname(dir);
|
|
1354
|
+
if (parent === dir)
|
|
1355
|
+
return null;
|
|
1356
|
+
dir = parent;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
767
1359
|
async runContract(contract, candidate) {
|
|
768
1360
|
const startTime = Date.now();
|
|
769
|
-
const consumerEnvDir = await mkdtemp2(
|
|
770
|
-
const nodeModulesDir =
|
|
1361
|
+
const consumerEnvDir = await mkdtemp2(join6(tmpdir2(), "verelease-consumer-"));
|
|
1362
|
+
const nodeModulesDir = join6(consumerEnvDir, "node_modules");
|
|
771
1363
|
try {
|
|
772
|
-
await
|
|
773
|
-
let targetPkgLink =
|
|
1364
|
+
await writeFile3(join6(consumerEnvDir, "package.json"), JSON.stringify({ name: "consumer-fixture", type: "module" }, null, 2), "utf8");
|
|
1365
|
+
let targetPkgLink = join6(nodeModulesDir, candidate.packageName);
|
|
774
1366
|
if (candidate.packageName.startsWith("@")) {
|
|
775
1367
|
const [scope, pkgName] = candidate.packageName.split("/");
|
|
776
|
-
const scopeDir =
|
|
1368
|
+
const scopeDir = join6(nodeModulesDir, scope);
|
|
777
1369
|
await mkdir3(scopeDir, { recursive: true });
|
|
778
|
-
targetPkgLink =
|
|
1370
|
+
targetPkgLink = join6(scopeDir, pkgName);
|
|
779
1371
|
} else {
|
|
780
1372
|
await mkdir3(nodeModulesDir, { recursive: true });
|
|
781
1373
|
}
|
|
782
|
-
await
|
|
1374
|
+
await cp(candidate.unpackedDir, targetPkgLink, { recursive: true });
|
|
1375
|
+
await this.linkDependencies(candidate, nodeModulesDir);
|
|
783
1376
|
const isolatedTestName = basename(contract.testFile);
|
|
784
|
-
const isolatedTestPath =
|
|
1377
|
+
const isolatedTestPath = join6(consumerEnvDir, isolatedTestName);
|
|
785
1378
|
await copyFile(contract.testFile, isolatedTestPath);
|
|
786
1379
|
const testRun = await this.testAdapter.run({
|
|
787
1380
|
packagePath: consumerEnvDir,
|
|
@@ -862,24 +1455,122 @@ var ContractCompatibilityAnalyzer = class {
|
|
|
862
1455
|
};
|
|
863
1456
|
|
|
864
1457
|
// ../api-diff/dist/extractor.js
|
|
1458
|
+
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
1459
|
+
import { dirname as dirname2, join as join7, resolve as resolvePath } from "path";
|
|
865
1460
|
import ts from "typescript";
|
|
866
1461
|
var ApiExtractor = class {
|
|
867
|
-
|
|
868
|
-
|
|
1462
|
+
/**
|
|
1463
|
+
* The full public surface reachable from an entry point, following
|
|
1464
|
+
* `export ... from` through the package.
|
|
1465
|
+
*
|
|
1466
|
+
* Every package here is published behind a barrel — `src/index.ts` is often
|
|
1467
|
+
* nothing but `export * from "./engine.js"` — so parsing the entry point
|
|
1468
|
+
* alone reports no API at all, and a diff of nothing against nothing finds
|
|
1469
|
+
* no change however good the baseline is.
|
|
1470
|
+
*
|
|
1471
|
+
* Only relative specifiers are followed. A bare specifier is another
|
|
1472
|
+
* package's surface, and changes there are that package's business, not a
|
|
1473
|
+
* change to this one's API.
|
|
1474
|
+
*/
|
|
1475
|
+
async extractFromEntrypoint(entryPath, options = {}) {
|
|
1476
|
+
const maxDepth = options.maxDepth ?? 16;
|
|
869
1477
|
const exports = {};
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1478
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1479
|
+
const walk = async (filePath, depth) => {
|
|
1480
|
+
const key = resolvePath(filePath);
|
|
1481
|
+
if (visited.has(key) || depth > maxDepth)
|
|
1482
|
+
return;
|
|
1483
|
+
visited.add(key);
|
|
1484
|
+
let source;
|
|
1485
|
+
try {
|
|
1486
|
+
source = await readFile6(key, "utf8");
|
|
1487
|
+
} catch {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const snapshot = this.extractFromSource(source, key);
|
|
1491
|
+
for (const [name, symbol] of Object.entries(snapshot.exports)) {
|
|
1492
|
+
exports[name] ??= symbol;
|
|
1493
|
+
}
|
|
1494
|
+
for (const reExport of snapshot.reExports ?? []) {
|
|
1495
|
+
if (!reExport.specifier.startsWith("."))
|
|
1496
|
+
continue;
|
|
1497
|
+
const target = await this.resolveRelative(reExport.specifier, dirname2(key));
|
|
1498
|
+
if (!target)
|
|
1499
|
+
continue;
|
|
1500
|
+
if (reExport.namespaceAlias) {
|
|
1501
|
+
exports[reExport.namespaceAlias] ??= {
|
|
1502
|
+
kind: "variable",
|
|
1503
|
+
name: reExport.namespaceAlias,
|
|
1504
|
+
typeString: `typeof import(${JSON.stringify(reExport.specifier)})`
|
|
1505
|
+
};
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
const before = new Set(Object.keys(exports));
|
|
1509
|
+
await walk(target, depth + 1);
|
|
1510
|
+
if (reExport.names) {
|
|
1511
|
+
const wanted = new Map(reExport.names.map((n) => [n.local, n.exported]));
|
|
1512
|
+
for (const name of Object.keys(exports)) {
|
|
1513
|
+
if (before.has(name))
|
|
1514
|
+
continue;
|
|
1515
|
+
const exportedAs = wanted.get(name);
|
|
1516
|
+
if (exportedAs === void 0) {
|
|
1517
|
+
delete exports[name];
|
|
1518
|
+
} else if (exportedAs !== name) {
|
|
1519
|
+
exports[exportedAs] = { ...exports[name], name: exportedAs };
|
|
1520
|
+
delete exports[name];
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
await walk(entryPath, 0);
|
|
1527
|
+
return { exports };
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Maps an ESM specifier to a file on disk.
|
|
1531
|
+
*
|
|
1532
|
+
* TypeScript sources import with a `.js` extension that only exists after a
|
|
1533
|
+
* build, so the declared path is rarely the path to read.
|
|
1534
|
+
*/
|
|
1535
|
+
async resolveRelative(specifier, fromDir) {
|
|
1536
|
+
const base = join7(fromDir, specifier);
|
|
1537
|
+
const withoutJs = base.replace(/\.js$/u, "");
|
|
1538
|
+
const candidates = [
|
|
1539
|
+
base.endsWith(".ts") ? base : null,
|
|
1540
|
+
`${withoutJs}.ts`,
|
|
1541
|
+
`${withoutJs}.d.ts`,
|
|
1542
|
+
`${withoutJs}.tsx`,
|
|
1543
|
+
base,
|
|
1544
|
+
join7(base, "index.ts"),
|
|
1545
|
+
join7(base, "index.d.ts"),
|
|
1546
|
+
join7(withoutJs, "index.ts")
|
|
1547
|
+
].filter((c) => c !== null);
|
|
1548
|
+
for (const candidate of candidates) {
|
|
1549
|
+
try {
|
|
1550
|
+
if ((await stat3(candidate)).isFile())
|
|
1551
|
+
return candidate;
|
|
1552
|
+
} catch {
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
extractFromSource(sourceCode, fileName = "index.ts") {
|
|
1558
|
+
const sourceFile = ts.createSourceFile(fileName, sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1559
|
+
const exports = {};
|
|
1560
|
+
const reExports = [];
|
|
1561
|
+
const visit = (node) => {
|
|
1562
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
|
|
1563
|
+
const isExported = (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || !!modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
1564
|
+
if (ts.isFunctionDeclaration(node) && node.name && isExported) {
|
|
1565
|
+
const fnName = node.name.text;
|
|
1566
|
+
const parameters = node.parameters.map((param) => {
|
|
1567
|
+
return {
|
|
1568
|
+
name: param.name.getText(sourceFile),
|
|
1569
|
+
isOptional: !!param.questionToken || !!param.initializer,
|
|
1570
|
+
typeString: param.type ? param.type.getText(sourceFile) : "any"
|
|
1571
|
+
};
|
|
1572
|
+
});
|
|
1573
|
+
exports[fnName] = {
|
|
883
1574
|
kind: "function",
|
|
884
1575
|
name: fnName,
|
|
885
1576
|
parameters,
|
|
@@ -949,11 +1640,45 @@ var ApiExtractor = class {
|
|
|
949
1640
|
name: ifaceName,
|
|
950
1641
|
properties
|
|
951
1642
|
};
|
|
1643
|
+
} else if (ts.isTypeAliasDeclaration(node) && node.name && isExported) {
|
|
1644
|
+
const aliasName = node.name.text;
|
|
1645
|
+
exports[aliasName] = {
|
|
1646
|
+
kind: "type",
|
|
1647
|
+
name: aliasName,
|
|
1648
|
+
typeString: node.type.getText(sourceFile)
|
|
1649
|
+
};
|
|
1650
|
+
} else if (ts.isEnumDeclaration(node) && node.name && isExported) {
|
|
1651
|
+
const enumName = node.name.text;
|
|
1652
|
+
const members = {};
|
|
1653
|
+
for (const member of node.members) {
|
|
1654
|
+
const memberName = member.name.getText(sourceFile);
|
|
1655
|
+
members[memberName] = {
|
|
1656
|
+
name: memberName,
|
|
1657
|
+
...member.initializer ? { value: member.initializer.getText(sourceFile) } : {}
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
exports[enumName] = { kind: "enum", name: enumName, members };
|
|
1661
|
+
}
|
|
1662
|
+
if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
1663
|
+
const specifier = node.moduleSpecifier.text;
|
|
1664
|
+
if (!node.exportClause) {
|
|
1665
|
+
reExports.push({ specifier });
|
|
1666
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
1667
|
+
reExports.push({ specifier, namespaceAlias: node.exportClause.name.text });
|
|
1668
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
1669
|
+
reExports.push({
|
|
1670
|
+
specifier,
|
|
1671
|
+
names: node.exportClause.elements.map((el) => ({
|
|
1672
|
+
local: (el.propertyName ?? el.name).text,
|
|
1673
|
+
exported: el.name.text
|
|
1674
|
+
}))
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
952
1677
|
}
|
|
953
1678
|
ts.forEachChild(node, visit);
|
|
954
1679
|
};
|
|
955
1680
|
ts.forEachChild(sourceFile, visit);
|
|
956
|
-
return { exports };
|
|
1681
|
+
return reExports.length > 0 ? { exports, reExports } : { exports };
|
|
957
1682
|
}
|
|
958
1683
|
};
|
|
959
1684
|
|
|
@@ -993,6 +1718,12 @@ var ApiDiffEngine = class {
|
|
|
993
1718
|
this.diffFunction(packageName, name, baseSym, candSym, evidence);
|
|
994
1719
|
} else if (baseSym.kind === "interface" && candSym.kind === "interface") {
|
|
995
1720
|
this.diffInterface(packageName, name, baseSym, candSym, evidence);
|
|
1721
|
+
} else if (baseSym.kind === "class" && candSym.kind === "class") {
|
|
1722
|
+
this.diffClass(packageName, name, baseSym, candSym, evidence);
|
|
1723
|
+
} else if (baseSym.kind === "type" && candSym.kind === "type") {
|
|
1724
|
+
this.diffTypeAlias(packageName, name, baseSym, candSym, evidence);
|
|
1725
|
+
} else if (baseSym.kind === "enum" && candSym.kind === "enum") {
|
|
1726
|
+
this.diffEnum(packageName, name, baseSym, candSym, evidence);
|
|
996
1727
|
}
|
|
997
1728
|
}
|
|
998
1729
|
for (const [name, candSym] of Object.entries(candidateExports)) {
|
|
@@ -1075,6 +1806,112 @@ var ApiDiffEngine = class {
|
|
|
1075
1806
|
}
|
|
1076
1807
|
}
|
|
1077
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Compares the members of an exported class.
|
|
1811
|
+
*
|
|
1812
|
+
* The extractor has always captured methods and properties, but only
|
|
1813
|
+
* interfaces were compared, so a class could gain or lose public methods
|
|
1814
|
+
* without any evidence being produced — the export was still there, so the
|
|
1815
|
+
* package looked unchanged.
|
|
1816
|
+
*/
|
|
1817
|
+
diffClass(packageName, className, baseClass, candClass, evidence) {
|
|
1818
|
+
for (const memberKind of ["methods", "properties"]) {
|
|
1819
|
+
const before = baseClass[memberKind];
|
|
1820
|
+
const after = candClass[memberKind];
|
|
1821
|
+
const singular = memberKind === "methods" ? "method" : "property";
|
|
1822
|
+
for (const memberName of Object.keys(before)) {
|
|
1823
|
+
if (!after[memberName]) {
|
|
1824
|
+
evidence.push({
|
|
1825
|
+
id: `api.class.${singular}Removed:${className}:${memberName}`,
|
|
1826
|
+
package: packageName,
|
|
1827
|
+
source: "api",
|
|
1828
|
+
severity: "major",
|
|
1829
|
+
confidence: 0.95,
|
|
1830
|
+
title: `Public ${singular} removed from class: ${className}.${memberName}`,
|
|
1831
|
+
description: `Class "${className}" no longer exposes ${singular} "${memberName}". Callers of it break.`
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
for (const memberName of Object.keys(after)) {
|
|
1836
|
+
if (!before[memberName]) {
|
|
1837
|
+
evidence.push({
|
|
1838
|
+
id: `api.class.${singular}Added:${className}:${memberName}`,
|
|
1839
|
+
package: packageName,
|
|
1840
|
+
source: "api",
|
|
1841
|
+
severity: "minor",
|
|
1842
|
+
confidence: 0.9,
|
|
1843
|
+
title: `Public ${singular} added to class: ${className}.${memberName}`,
|
|
1844
|
+
description: `Class "${className}" gained ${singular} "${memberName}".`
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Compares an exported type alias.
|
|
1852
|
+
*
|
|
1853
|
+
* Reported with reduced confidence, because a definition change can go
|
|
1854
|
+
* either way and telling them apart needs the type checker rather than the
|
|
1855
|
+
* syntax: widening a union is safe for code that reads the type and breaking
|
|
1856
|
+
* for code that implements it, and narrowing breaks both. Calling it
|
|
1857
|
+
* breaking and saying why is more useful than staying silent, and the
|
|
1858
|
+
* confidence lets a policy hold it for review.
|
|
1859
|
+
*/
|
|
1860
|
+
diffTypeAlias(packageName, typeName, baseAlias, candAlias, evidence) {
|
|
1861
|
+
if (baseAlias.typeString === candAlias.typeString)
|
|
1862
|
+
return;
|
|
1863
|
+
evidence.push({
|
|
1864
|
+
id: `api.type.changed:${typeName}`,
|
|
1865
|
+
package: packageName,
|
|
1866
|
+
source: "types",
|
|
1867
|
+
severity: "major",
|
|
1868
|
+
confidence: 0.6,
|
|
1869
|
+
title: `Exported type changed: ${typeName}`,
|
|
1870
|
+
description: `Type "${typeName}" changed from "${baseAlias.typeString}" to "${candAlias.typeString}". A narrowing breaks consumers; a widening may only break those implementing it.`
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
/** Compares the members of an exported enum. */
|
|
1874
|
+
diffEnum(packageName, enumName, baseEnum, candEnum, evidence) {
|
|
1875
|
+
for (const [memberName, member] of Object.entries(baseEnum.members)) {
|
|
1876
|
+
if (!candEnum.members[memberName]) {
|
|
1877
|
+
evidence.push({
|
|
1878
|
+
id: `api.enum.memberRemoved:${enumName}:${memberName}`,
|
|
1879
|
+
package: packageName,
|
|
1880
|
+
source: "types",
|
|
1881
|
+
severity: "major",
|
|
1882
|
+
confidence: 0.95,
|
|
1883
|
+
title: `Enum member removed: ${enumName}.${memberName}`,
|
|
1884
|
+
description: `Enum "${enumName}" no longer declares "${memberName}".`
|
|
1885
|
+
});
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
const candValue = candEnum.members[memberName].value;
|
|
1889
|
+
if (member.value !== void 0 && candValue !== void 0 && member.value !== candValue) {
|
|
1890
|
+
evidence.push({
|
|
1891
|
+
id: `api.enum.valueChanged:${enumName}:${memberName}`,
|
|
1892
|
+
package: packageName,
|
|
1893
|
+
source: "types",
|
|
1894
|
+
severity: "major",
|
|
1895
|
+
confidence: 0.9,
|
|
1896
|
+
title: `Enum value changed: ${enumName}.${memberName}`,
|
|
1897
|
+
description: `"${enumName}.${memberName}" changed from ${member.value} to ${candValue}.`
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
for (const memberName of Object.keys(candEnum.members)) {
|
|
1902
|
+
if (!baseEnum.members[memberName]) {
|
|
1903
|
+
evidence.push({
|
|
1904
|
+
id: `api.enum.memberAdded:${enumName}:${memberName}`,
|
|
1905
|
+
package: packageName,
|
|
1906
|
+
source: "types",
|
|
1907
|
+
severity: "minor",
|
|
1908
|
+
confidence: 0.9,
|
|
1909
|
+
title: `Enum member added: ${enumName}.${memberName}`,
|
|
1910
|
+
description: `Enum "${enumName}" gained "${memberName}".`
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1078
1915
|
diffInterface(packageName, ifaceName, baseIface, candIface, evidence) {
|
|
1079
1916
|
for (const [propName, prop] of Object.entries(baseIface.properties)) {
|
|
1080
1917
|
if (!candIface.properties[propName]) {
|
|
@@ -1118,11 +1955,11 @@ var ApiDiffEngine = class {
|
|
|
1118
1955
|
};
|
|
1119
1956
|
|
|
1120
1957
|
// ../api-diff/dist/analyzer.js
|
|
1121
|
-
import { readFile as
|
|
1122
|
-
import { join as
|
|
1123
|
-
async function
|
|
1958
|
+
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
1959
|
+
import { join as join8 } from "path";
|
|
1960
|
+
async function fileExists3(path) {
|
|
1124
1961
|
try {
|
|
1125
|
-
const s = await
|
|
1962
|
+
const s = await stat4(path);
|
|
1126
1963
|
return s.isFile();
|
|
1127
1964
|
} catch {
|
|
1128
1965
|
return false;
|
|
@@ -1141,15 +1978,14 @@ var ApiDiffAnalyzer = class {
|
|
|
1141
1978
|
if (!candidateEntrypoint) {
|
|
1142
1979
|
return [];
|
|
1143
1980
|
}
|
|
1144
|
-
const
|
|
1145
|
-
const candidateSnapshot = this.extractor.extractFromSource(candidateSource, candidateEntrypoint);
|
|
1981
|
+
const candidateSnapshot = await this.extractor.extractFromEntrypoint(candidateEntrypoint);
|
|
1146
1982
|
let baselineSnapshot;
|
|
1147
|
-
if (context.baselinePath && await
|
|
1148
|
-
const baseSource = await
|
|
1983
|
+
if (context.baselinePath && await fileExists3(context.baselinePath)) {
|
|
1984
|
+
const baseSource = await readFile7(context.baselinePath, "utf8");
|
|
1149
1985
|
if (context.baselinePath.endsWith(".json")) {
|
|
1150
1986
|
baselineSnapshot = JSON.parse(baseSource);
|
|
1151
1987
|
} else {
|
|
1152
|
-
baselineSnapshot = this.extractor.
|
|
1988
|
+
baselineSnapshot = await this.extractor.extractFromEntrypoint(context.baselinePath);
|
|
1153
1989
|
}
|
|
1154
1990
|
}
|
|
1155
1991
|
if (!baselineSnapshot) {
|
|
@@ -1159,14 +1995,14 @@ var ApiDiffAnalyzer = class {
|
|
|
1159
1995
|
}
|
|
1160
1996
|
async findEntrypoint(pkgDir) {
|
|
1161
1997
|
const candidates = [
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1998
|
+
join8(pkgDir, "src", "index.ts"),
|
|
1999
|
+
join8(pkgDir, "src", "index.js"),
|
|
2000
|
+
join8(pkgDir, "index.ts"),
|
|
2001
|
+
join8(pkgDir, "index.js"),
|
|
2002
|
+
join8(pkgDir, "dist", "index.d.ts")
|
|
1167
2003
|
];
|
|
1168
2004
|
for (const c of candidates) {
|
|
1169
|
-
if (await
|
|
2005
|
+
if (await fileExists3(c)) {
|
|
1170
2006
|
return c;
|
|
1171
2007
|
}
|
|
1172
2008
|
}
|
|
@@ -1177,14 +2013,14 @@ var ApiDiffAnalyzer = class {
|
|
|
1177
2013
|
// ../api-diff/dist/baseline.js
|
|
1178
2014
|
import { execFile as execFile2 } from "child_process";
|
|
1179
2015
|
import { promisify as promisify2 } from "util";
|
|
1180
|
-
import { mkdtemp as mkdtemp3, mkdir as mkdir4, stat as
|
|
1181
|
-
import { join as
|
|
2016
|
+
import { mkdtemp as mkdtemp3, mkdir as mkdir4, stat as stat5 } from "fs/promises";
|
|
2017
|
+
import { join as join9 } from "path";
|
|
1182
2018
|
import { tmpdir as tmpdir3 } from "os";
|
|
1183
2019
|
import * as tar2 from "tar";
|
|
1184
2020
|
var execFileAsync2 = promisify2(execFile2);
|
|
1185
|
-
async function
|
|
2021
|
+
async function fileExists4(path) {
|
|
1186
2022
|
try {
|
|
1187
|
-
const s = await
|
|
2023
|
+
const s = await stat5(path);
|
|
1188
2024
|
return s.isFile();
|
|
1189
2025
|
} catch {
|
|
1190
2026
|
return false;
|
|
@@ -1199,20 +2035,20 @@ var BaselineFetcher = class {
|
|
|
1199
2035
|
* or the extracted package has no recognizable entry point.
|
|
1200
2036
|
*/
|
|
1201
2037
|
async fetch(packageName, parentTmpDir) {
|
|
1202
|
-
const
|
|
1203
|
-
if (!
|
|
2038
|
+
const version2 = await this.getLatestVersion(packageName);
|
|
2039
|
+
if (!version2)
|
|
1204
2040
|
return null;
|
|
1205
|
-
const tempDir = parentTmpDir ?
|
|
2041
|
+
const tempDir = parentTmpDir ? join9(parentTmpDir, packageName.replace(/\//g, "__")) : await mkdtemp3(join9(tmpdir3(), "verelease-baseline-"));
|
|
1206
2042
|
await mkdir4(tempDir, { recursive: true });
|
|
1207
2043
|
let tarballFilename;
|
|
1208
2044
|
try {
|
|
1209
|
-
const { stdout } = await execFileAsync2("npm", ["pack", `${packageName}@${
|
|
2045
|
+
const { stdout } = await execFileAsync2("npm", ["pack", `${packageName}@${version2}`, "--pack-destination", tempDir], { timeout: 3e4 });
|
|
1210
2046
|
tarballFilename = stdout.trim().split("\n").pop().trim();
|
|
1211
2047
|
} catch {
|
|
1212
2048
|
return null;
|
|
1213
2049
|
}
|
|
1214
|
-
const tarballPath =
|
|
1215
|
-
const extractedDir =
|
|
2050
|
+
const tarballPath = join9(tempDir, tarballFilename);
|
|
2051
|
+
const extractedDir = join9(tempDir, "extracted");
|
|
1216
2052
|
await mkdir4(extractedDir, { recursive: true });
|
|
1217
2053
|
try {
|
|
1218
2054
|
await tar2.x({
|
|
@@ -1227,7 +2063,7 @@ var BaselineFetcher = class {
|
|
|
1227
2063
|
const entryPointPath = await this.findEntrypoint(extractedDir);
|
|
1228
2064
|
if (!entryPointPath)
|
|
1229
2065
|
return null;
|
|
1230
|
-
return { version, entryPointPath, extractedDir };
|
|
2066
|
+
return { version: version2, entryPointPath, extractedDir };
|
|
1231
2067
|
}
|
|
1232
2068
|
async getLatestVersion(packageName) {
|
|
1233
2069
|
try {
|
|
@@ -1242,16 +2078,16 @@ var BaselineFetcher = class {
|
|
|
1242
2078
|
}
|
|
1243
2079
|
async findEntrypoint(pkgDir) {
|
|
1244
2080
|
const candidates = [
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
2081
|
+
join9(pkgDir, "dist", "index.d.ts"),
|
|
2082
|
+
join9(pkgDir, "dist", "index.d.mts"),
|
|
2083
|
+
join9(pkgDir, "src", "index.ts"),
|
|
2084
|
+
join9(pkgDir, "src", "index.js"),
|
|
2085
|
+
join9(pkgDir, "index.d.ts"),
|
|
2086
|
+
join9(pkgDir, "index.ts"),
|
|
2087
|
+
join9(pkgDir, "index.js")
|
|
1252
2088
|
];
|
|
1253
2089
|
for (const candidate of candidates) {
|
|
1254
|
-
if (await
|
|
2090
|
+
if (await fileExists4(candidate)) {
|
|
1255
2091
|
return candidate;
|
|
1256
2092
|
}
|
|
1257
2093
|
}
|
|
@@ -1259,141 +2095,381 @@ var BaselineFetcher = class {
|
|
|
1259
2095
|
}
|
|
1260
2096
|
};
|
|
1261
2097
|
|
|
1262
|
-
// ../
|
|
1263
|
-
import { readFile as
|
|
1264
|
-
import { join as
|
|
1265
|
-
|
|
2098
|
+
// ../api-diff/dist/cargo-semver.js
|
|
2099
|
+
import { readFile as readFile8, stat as stat6 } from "fs/promises";
|
|
2100
|
+
import { join as join10 } from "path";
|
|
2101
|
+
import { execFile as execFile3 } from "child_process";
|
|
2102
|
+
import { promisify as promisify3 } from "util";
|
|
2103
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
2104
|
+
async function fileExists5(path) {
|
|
1266
2105
|
try {
|
|
1267
|
-
const s = await
|
|
2106
|
+
const s = await stat6(path);
|
|
1268
2107
|
return s.isFile();
|
|
1269
2108
|
} catch {
|
|
1270
2109
|
return false;
|
|
1271
2110
|
}
|
|
1272
2111
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
2112
|
+
function extractRustApiSnapshot(sourceCode, crateName, version2) {
|
|
2113
|
+
const items = {};
|
|
2114
|
+
const stripped = sourceCode.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "");
|
|
2115
|
+
const fnRegex = /pub(?:\s+async)?(?:\s+const)?(?:\s+unsafe)?\s+fn\s+([a-zA-Z0-9_]+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)(?:\s*->\s*([^{;]+))?/g;
|
|
2116
|
+
let match;
|
|
2117
|
+
while ((match = fnRegex.exec(stripped)) !== null) {
|
|
2118
|
+
const name = match[1];
|
|
2119
|
+
const params = match[2]?.trim() ?? "";
|
|
2120
|
+
const returnType = match[3]?.trim() ?? "()";
|
|
2121
|
+
items[`fn:${name}`] = {
|
|
2122
|
+
kind: "fn",
|
|
2123
|
+
name,
|
|
2124
|
+
signature: `pub fn ${name}(${params}) -> ${returnType}`
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
const structRegex = /pub\s+struct\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?/g;
|
|
2128
|
+
while ((match = structRegex.exec(stripped)) !== null) {
|
|
2129
|
+
const name = match[1];
|
|
2130
|
+
items[`struct:${name}`] = {
|
|
2131
|
+
kind: "struct",
|
|
2132
|
+
name,
|
|
2133
|
+
signature: `pub struct ${name}`
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
const enumRegex = /pub\s+enum\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?/g;
|
|
2137
|
+
while ((match = enumRegex.exec(stripped)) !== null) {
|
|
2138
|
+
const name = match[1];
|
|
2139
|
+
items[`enum:${name}`] = {
|
|
2140
|
+
kind: "enum",
|
|
2141
|
+
name,
|
|
2142
|
+
signature: `pub enum ${name}`
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
const traitRegex = /pub\s+trait\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?/g;
|
|
2146
|
+
while ((match = traitRegex.exec(stripped)) !== null) {
|
|
2147
|
+
const name = match[1];
|
|
2148
|
+
items[`trait:${name}`] = {
|
|
2149
|
+
kind: "trait",
|
|
2150
|
+
name,
|
|
2151
|
+
signature: `pub trait ${name}`
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
const typeRegex = /pub\s+type\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?\s*=\s*([^;]+);/g;
|
|
2155
|
+
while ((match = typeRegex.exec(stripped)) !== null) {
|
|
2156
|
+
const name = match[1];
|
|
2157
|
+
const target = match[2]?.trim() ?? "";
|
|
2158
|
+
items[`type:${name}`] = {
|
|
2159
|
+
kind: "type",
|
|
2160
|
+
name,
|
|
2161
|
+
signature: `pub type ${name} = ${target}`
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
const constRegex = /pub\s+const\s+([a-zA-Z0-9_]+)\s*:\s*([a-zA-Z0-9_&<>\[\]]+)/g;
|
|
2165
|
+
while ((match = constRegex.exec(stripped)) !== null) {
|
|
2166
|
+
const name = match[1];
|
|
2167
|
+
const constType = match[2]?.trim() ?? "";
|
|
2168
|
+
items[`const:${name}`] = {
|
|
2169
|
+
kind: "const",
|
|
2170
|
+
name,
|
|
2171
|
+
signature: `pub const ${name}: ${constType}`
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
return {
|
|
2175
|
+
crateName,
|
|
2176
|
+
version: version2,
|
|
2177
|
+
items
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
function diffRustApi(crateName, baseline, candidate) {
|
|
2181
|
+
const evidence = [];
|
|
2182
|
+
for (const [key, baseItem] of Object.entries(baseline.items)) {
|
|
2183
|
+
const candItem = candidate.items[key];
|
|
2184
|
+
if (!candItem) {
|
|
2185
|
+
evidence.push({
|
|
2186
|
+
id: `rust.api.removed:${crateName}:${baseItem.kind}:${baseItem.name}`,
|
|
2187
|
+
package: crateName,
|
|
2188
|
+
source: "api",
|
|
2189
|
+
severity: "major",
|
|
2190
|
+
confidence: 0.95,
|
|
2191
|
+
title: `Removed public ${baseItem.kind}: ${baseItem.name}`,
|
|
2192
|
+
description: `Breaking API change: The public ${baseItem.kind} '${baseItem.name}' was removed from ${crateName}.`,
|
|
2193
|
+
metadata: {
|
|
2194
|
+
itemKind: baseItem.kind,
|
|
2195
|
+
itemName: baseItem.name,
|
|
2196
|
+
previousSignature: baseItem.signature
|
|
1321
2197
|
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
await writeFile3(pkgJsonPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
1341
|
-
modifiedFiles.push(pkgJsonPath);
|
|
2198
|
+
});
|
|
2199
|
+
} else if (baseItem.signature !== candItem.signature) {
|
|
2200
|
+
evidence.push({
|
|
2201
|
+
id: `rust.api.changed:${crateName}:${baseItem.kind}:${baseItem.name}`,
|
|
2202
|
+
package: crateName,
|
|
2203
|
+
source: "api",
|
|
2204
|
+
severity: "major",
|
|
2205
|
+
confidence: 0.9,
|
|
2206
|
+
title: `Changed signature of ${baseItem.kind}: ${baseItem.name}`,
|
|
2207
|
+
description: `Breaking API change: Signature changed from '${baseItem.signature}' to '${candItem.signature}'.`,
|
|
2208
|
+
metadata: {
|
|
2209
|
+
itemKind: baseItem.kind,
|
|
2210
|
+
itemName: baseItem.name,
|
|
2211
|
+
previousSignature: baseItem.signature,
|
|
2212
|
+
newSignature: candItem.signature
|
|
1342
2213
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
for (const [key, candItem] of Object.entries(candidate.items)) {
|
|
2218
|
+
if (!baseline.items[key]) {
|
|
2219
|
+
evidence.push({
|
|
2220
|
+
id: `rust.api.added:${crateName}:${candItem.kind}:${candItem.name}`,
|
|
2221
|
+
package: crateName,
|
|
2222
|
+
source: "api",
|
|
2223
|
+
severity: "minor",
|
|
2224
|
+
confidence: 0.9,
|
|
2225
|
+
title: `Added public ${candItem.kind}: ${candItem.name}`,
|
|
2226
|
+
description: `Feature: New public ${candItem.kind} '${candItem.name}' added to ${crateName}.`,
|
|
2227
|
+
metadata: {
|
|
2228
|
+
itemKind: candItem.kind,
|
|
2229
|
+
itemName: candItem.name,
|
|
2230
|
+
newSignature: candItem.signature
|
|
1352
2231
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return evidence;
|
|
2236
|
+
}
|
|
2237
|
+
function parseCargoSemVerOutput(rawOutput, crateName) {
|
|
2238
|
+
const evidence = [];
|
|
2239
|
+
const lines = rawOutput.split("\n");
|
|
2240
|
+
for (const line of lines) {
|
|
2241
|
+
const trimmed = line.trim();
|
|
2242
|
+
if (!trimmed || !trimmed.startsWith("{"))
|
|
2243
|
+
continue;
|
|
2244
|
+
try {
|
|
2245
|
+
const parsed = JSON.parse(trimmed);
|
|
2246
|
+
const ruleName = parsed.name || parsed.rule_name || "semver_check";
|
|
2247
|
+
const isBreaking = parsed.category === "breaking" || parsed.level === "error" || parsed.severity === "error" || parsed.category === "Major";
|
|
2248
|
+
const severity = isBreaking ? "major" : "minor";
|
|
2249
|
+
const description = parsed.description || parsed.message || `Diagnostic from cargo-semver-checks (${ruleName})`;
|
|
2250
|
+
evidence.push({
|
|
2251
|
+
id: `cargo.semver.diagnostic:${crateName}:${ruleName}`,
|
|
2252
|
+
package: crateName,
|
|
2253
|
+
source: "api",
|
|
2254
|
+
severity,
|
|
2255
|
+
confidence: 0.98,
|
|
2256
|
+
title: isBreaking ? `Cargo semver-checks breaking change: ${ruleName}` : `Cargo semver-checks non-breaking addition: ${ruleName}`,
|
|
2257
|
+
description,
|
|
2258
|
+
metadata: {
|
|
2259
|
+
ruleName,
|
|
2260
|
+
span: parsed.span,
|
|
2261
|
+
rawDiagnostic: parsed
|
|
1362
2262
|
}
|
|
2263
|
+
});
|
|
2264
|
+
} catch {
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
return evidence;
|
|
2268
|
+
}
|
|
2269
|
+
var CargoSemVerAnalyzer = class {
|
|
2270
|
+
name = "cargo-semver-analyzer";
|
|
2271
|
+
async analyze(context) {
|
|
2272
|
+
const { packagePath, packageName } = context;
|
|
2273
|
+
const cargoTomlPath = join10(packagePath, "Cargo.toml");
|
|
2274
|
+
if (!await fileExists5(cargoTomlPath)) {
|
|
2275
|
+
return [];
|
|
2276
|
+
}
|
|
2277
|
+
try {
|
|
2278
|
+
const { stdout } = await execFileAsync3("cargo", [
|
|
2279
|
+
"semver-checks",
|
|
2280
|
+
"check-release",
|
|
2281
|
+
"--manifest-path",
|
|
2282
|
+
cargoTomlPath,
|
|
2283
|
+
"--json",
|
|
2284
|
+
...context.baselinePath ? ["--baseline-root", context.baselinePath] : []
|
|
2285
|
+
], {
|
|
2286
|
+
cwd: packagePath,
|
|
2287
|
+
timeout: 6e4
|
|
2288
|
+
});
|
|
2289
|
+
const parsedEvidence = parseCargoSemVerOutput(stdout, packageName);
|
|
2290
|
+
if (parsedEvidence.length > 0) {
|
|
2291
|
+
return parsedEvidence;
|
|
1363
2292
|
}
|
|
1364
2293
|
} catch (err) {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const transaction = {
|
|
1371
|
-
id: `tx-${Date.now()}`,
|
|
1372
|
-
plan,
|
|
1373
|
-
backups,
|
|
1374
|
-
modifiedFiles,
|
|
1375
|
-
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1376
|
-
async rollback() {
|
|
1377
|
-
for (const b of backups) {
|
|
1378
|
-
await writeFile3(b.filePath, b.originalContent, "utf8");
|
|
2294
|
+
const execError = err;
|
|
2295
|
+
if (execError.stdout) {
|
|
2296
|
+
const diagnostics = parseCargoSemVerOutput(execError.stdout, packageName);
|
|
2297
|
+
if (diagnostics.length > 0) {
|
|
2298
|
+
return diagnostics;
|
|
1379
2299
|
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const candidateEntrypoint = await this.findRustEntrypoint(packagePath);
|
|
2303
|
+
if (!candidateEntrypoint) {
|
|
2304
|
+
return [];
|
|
2305
|
+
}
|
|
2306
|
+
const candidateSource = await readFile8(candidateEntrypoint, "utf8");
|
|
2307
|
+
const candidateSnapshot = extractRustApiSnapshot(candidateSource, packageName, context.currentVersion);
|
|
2308
|
+
let baselineSnapshot;
|
|
2309
|
+
if (context.baselinePath && await fileExists5(context.baselinePath)) {
|
|
2310
|
+
const baseSource = await readFile8(context.baselinePath, "utf8");
|
|
2311
|
+
if (context.baselinePath.endsWith(".json")) {
|
|
2312
|
+
baselineSnapshot = JSON.parse(baseSource);
|
|
2313
|
+
} else {
|
|
2314
|
+
baselineSnapshot = extractRustApiSnapshot(baseSource, packageName, context.previousVersion);
|
|
2315
|
+
}
|
|
2316
|
+
} else if (context.baselinePath) {
|
|
2317
|
+
const baseEntrypoint = await this.findRustEntrypoint(context.baselinePath);
|
|
2318
|
+
if (baseEntrypoint && await fileExists5(baseEntrypoint)) {
|
|
2319
|
+
const baseSource = await readFile8(baseEntrypoint, "utf8");
|
|
2320
|
+
baselineSnapshot = extractRustApiSnapshot(baseSource, packageName, context.previousVersion);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (!baselineSnapshot) {
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
return diffRustApi(packageName, baselineSnapshot, candidateSnapshot);
|
|
2327
|
+
}
|
|
2328
|
+
async findRustEntrypoint(dir) {
|
|
2329
|
+
const candidates = [
|
|
2330
|
+
join10(dir, "src", "lib.rs"),
|
|
2331
|
+
join10(dir, "src", "main.rs"),
|
|
2332
|
+
join10(dir, "lib.rs"),
|
|
2333
|
+
join10(dir, "main.rs")
|
|
2334
|
+
];
|
|
2335
|
+
for (const c of candidates) {
|
|
2336
|
+
if (await fileExists5(c)) {
|
|
2337
|
+
return c;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
// ../api-diff/dist/git-baseline.js
|
|
2345
|
+
import { execFile as execFile4 } from "child_process";
|
|
2346
|
+
import { mkdtemp as mkdtemp4, readFile as readFile9, readdir as readdir3, rm as rm3, stat as stat7 } from "fs/promises";
|
|
2347
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
2348
|
+
import { join as join11, relative as relative2 } from "path";
|
|
2349
|
+
import { promisify as promisify4 } from "util";
|
|
2350
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
2351
|
+
var GitBaselineResolver = class {
|
|
2352
|
+
repoRoot;
|
|
2353
|
+
constructor(repoRoot) {
|
|
2354
|
+
this.repoRoot = repoRoot;
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Checks out the last release of a package into a temporary worktree.
|
|
2358
|
+
*
|
|
2359
|
+
* Returns null when the package has never been released, which is not a
|
|
2360
|
+
* failure: a first release has nothing to be compared against.
|
|
2361
|
+
*/
|
|
2362
|
+
async resolve(packageName, packageDir) {
|
|
2363
|
+
const ref = await this.lastReleaseTag(packageName);
|
|
2364
|
+
if (!ref)
|
|
2365
|
+
return null;
|
|
2366
|
+
const worktree = await mkdtemp4(join11(tmpdir4(), "verelease-baseline-git-"));
|
|
2367
|
+
const cleanup = async () => {
|
|
2368
|
+
try {
|
|
2369
|
+
await execFileAsync4("git", ["worktree", "remove", "--force", worktree], {
|
|
2370
|
+
cwd: this.repoRoot
|
|
2371
|
+
});
|
|
2372
|
+
} catch {
|
|
2373
|
+
await rm3(worktree, { recursive: true, force: true }).catch(() => void 0);
|
|
2374
|
+
await execFileAsync4("git", ["worktree", "prune"], { cwd: this.repoRoot }).catch(() => void 0);
|
|
1382
2375
|
}
|
|
1383
2376
|
};
|
|
1384
|
-
|
|
2377
|
+
try {
|
|
2378
|
+
await execFileAsync4("git", ["worktree", "add", "--detach", worktree, ref], {
|
|
2379
|
+
cwd: this.repoRoot
|
|
2380
|
+
});
|
|
2381
|
+
} catch {
|
|
2382
|
+
await cleanup();
|
|
2383
|
+
return null;
|
|
2384
|
+
}
|
|
2385
|
+
const relativeDir = relative2(this.repoRoot, packageDir);
|
|
2386
|
+
let baselineDir = join11(worktree, relativeDir);
|
|
2387
|
+
let entryPointPath = await findEntrypoint(baselineDir);
|
|
2388
|
+
if (!entryPointPath) {
|
|
2389
|
+
baselineDir = await findPackageByName(worktree, packageName);
|
|
2390
|
+
entryPointPath = baselineDir ? await findEntrypoint(baselineDir) : null;
|
|
2391
|
+
}
|
|
2392
|
+
if (!entryPointPath) {
|
|
2393
|
+
await cleanup();
|
|
2394
|
+
return null;
|
|
2395
|
+
}
|
|
2396
|
+
return { ref, entryPointPath, cleanup };
|
|
2397
|
+
}
|
|
2398
|
+
/** The newest tag naming this package, by either supported tag format. */
|
|
2399
|
+
async lastReleaseTag(packageName) {
|
|
2400
|
+
for (const pattern of [`${packageName}@*`, "v*"]) {
|
|
2401
|
+
try {
|
|
2402
|
+
const { stdout } = await execFileAsync4("git", ["tag", "--list", pattern, "--sort=-creatordate"], { cwd: this.repoRoot });
|
|
2403
|
+
const [first] = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2404
|
+
if (first)
|
|
2405
|
+
return first;
|
|
2406
|
+
} catch {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return null;
|
|
1385
2411
|
}
|
|
1386
2412
|
};
|
|
2413
|
+
async function findEntrypoint(pkgDir) {
|
|
2414
|
+
const candidates = [
|
|
2415
|
+
join11(pkgDir, "src", "index.ts"),
|
|
2416
|
+
join11(pkgDir, "src", "index.js"),
|
|
2417
|
+
join11(pkgDir, "index.ts"),
|
|
2418
|
+
join11(pkgDir, "index.js"),
|
|
2419
|
+
join11(pkgDir, "dist", "index.d.ts")
|
|
2420
|
+
];
|
|
2421
|
+
for (const candidate of candidates) {
|
|
2422
|
+
try {
|
|
2423
|
+
if ((await stat7(candidate)).isFile())
|
|
2424
|
+
return candidate;
|
|
2425
|
+
} catch {
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
async function findPackageByName(root, packageName, maxDepth = 6) {
|
|
2431
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
2432
|
+
while (queue.length > 0) {
|
|
2433
|
+
const { dir, depth } = queue.shift();
|
|
2434
|
+
try {
|
|
2435
|
+
const manifest = JSON.parse(await readFile9(join11(dir, "package.json"), "utf8"));
|
|
2436
|
+
if (manifest.name === packageName)
|
|
2437
|
+
return dir;
|
|
2438
|
+
} catch {
|
|
2439
|
+
}
|
|
2440
|
+
if (depth >= maxDepth)
|
|
2441
|
+
continue;
|
|
2442
|
+
let entries;
|
|
2443
|
+
try {
|
|
2444
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
2445
|
+
} catch {
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
for (const entry of entries) {
|
|
2449
|
+
if (!entry.isDirectory())
|
|
2450
|
+
continue;
|
|
2451
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
2452
|
+
continue;
|
|
2453
|
+
queue.push({ dir: join11(dir, entry.name), depth: depth + 1 });
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
// ../manifest/dist/mutator.js
|
|
2460
|
+
import { readFile as readFile10, writeFile as writeFile4, stat as stat8 } from "fs/promises";
|
|
2461
|
+
import { join as join12 } from "path";
|
|
2462
|
+
import { parse as parseToml3, stringify as stringifyToml2 } from "smol-toml";
|
|
1387
2463
|
|
|
1388
2464
|
// ../manifest/dist/lockfile.js
|
|
1389
|
-
import { execFile as
|
|
1390
|
-
import { promisify as
|
|
1391
|
-
import { stat as
|
|
1392
|
-
import { join as
|
|
1393
|
-
var
|
|
1394
|
-
async function
|
|
2465
|
+
import { execFile as execFile5 } from "child_process";
|
|
2466
|
+
import { promisify as promisify5 } from "util";
|
|
2467
|
+
import { stat as stat9 } from "fs/promises";
|
|
2468
|
+
import { join as join13 } from "path";
|
|
2469
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
2470
|
+
async function fileExists6(path) {
|
|
1395
2471
|
try {
|
|
1396
|
-
const s = await
|
|
2472
|
+
const s = await stat9(path);
|
|
1397
2473
|
return s.isFile();
|
|
1398
2474
|
} catch {
|
|
1399
2475
|
return false;
|
|
@@ -1404,7 +2480,7 @@ var PnpmLockfileAdapter = class {
|
|
|
1404
2480
|
async regenerateLockfile(rootDirectory) {
|
|
1405
2481
|
const start = Date.now();
|
|
1406
2482
|
try {
|
|
1407
|
-
const { stdout, stderr } = await
|
|
2483
|
+
const { stdout, stderr } = await execFileAsync5("pnpm", ["install", "--lockfile-only"], {
|
|
1408
2484
|
cwd: rootDirectory
|
|
1409
2485
|
});
|
|
1410
2486
|
return {
|
|
@@ -1426,7 +2502,29 @@ var NpmLockfileAdapter = class {
|
|
|
1426
2502
|
async regenerateLockfile(rootDirectory) {
|
|
1427
2503
|
const start = Date.now();
|
|
1428
2504
|
try {
|
|
1429
|
-
const { stdout, stderr } = await
|
|
2505
|
+
const { stdout, stderr } = await execFileAsync5("npm", ["install", "--package-lock-only"], {
|
|
2506
|
+
cwd: rootDirectory
|
|
2507
|
+
});
|
|
2508
|
+
return {
|
|
2509
|
+
success: true,
|
|
2510
|
+
output: (stdout + "\n" + stderr).trim(),
|
|
2511
|
+
durationMs: Date.now() - start
|
|
2512
|
+
};
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
return {
|
|
2515
|
+
success: false,
|
|
2516
|
+
output: err.message || String(err),
|
|
2517
|
+
durationMs: Date.now() - start
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
var CargoLockfileAdapter = class {
|
|
2523
|
+
packageManager = "cargo";
|
|
2524
|
+
async regenerateLockfile(rootDirectory) {
|
|
2525
|
+
const start = Date.now();
|
|
2526
|
+
try {
|
|
2527
|
+
const { stdout, stderr } = await execFileAsync5("cargo", ["generate-lockfile"], {
|
|
1430
2528
|
cwd: rootDirectory
|
|
1431
2529
|
});
|
|
1432
2530
|
return {
|
|
@@ -1446,55 +2544,99 @@ var NpmLockfileAdapter = class {
|
|
|
1446
2544
|
var AutoLockfileAdapter = class {
|
|
1447
2545
|
packageManager = "npm";
|
|
1448
2546
|
async regenerateLockfile(rootDirectory) {
|
|
1449
|
-
|
|
2547
|
+
const hasPnpm = await fileExists6(join13(rootDirectory, "pnpm-lock.yaml"));
|
|
2548
|
+
const hasNpm = await fileExists6(join13(rootDirectory, "package-lock.json"));
|
|
2549
|
+
const hasCargo = await fileExists6(join13(rootDirectory, "Cargo.toml")) || await fileExists6(join13(rootDirectory, "Cargo.lock"));
|
|
2550
|
+
if (hasCargo && !hasPnpm && !hasNpm) {
|
|
2551
|
+
this.packageManager = "cargo";
|
|
2552
|
+
return new CargoLockfileAdapter().regenerateLockfile(rootDirectory);
|
|
2553
|
+
}
|
|
2554
|
+
if (hasPnpm) {
|
|
1450
2555
|
this.packageManager = "pnpm";
|
|
1451
|
-
|
|
2556
|
+
const res = await new PnpmLockfileAdapter().regenerateLockfile(rootDirectory);
|
|
2557
|
+
if (hasCargo) {
|
|
2558
|
+
await new CargoLockfileAdapter().regenerateLockfile(rootDirectory).catch(() => {
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
return res;
|
|
1452
2562
|
}
|
|
1453
|
-
if (
|
|
2563
|
+
if (hasNpm) {
|
|
1454
2564
|
this.packageManager = "npm";
|
|
1455
|
-
|
|
2565
|
+
const res = await new NpmLockfileAdapter().regenerateLockfile(rootDirectory);
|
|
2566
|
+
if (hasCargo) {
|
|
2567
|
+
await new CargoLockfileAdapter().regenerateLockfile(rootDirectory).catch(() => {
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
return res;
|
|
2571
|
+
}
|
|
2572
|
+
if (hasCargo) {
|
|
2573
|
+
this.packageManager = "cargo";
|
|
2574
|
+
return new CargoLockfileAdapter().regenerateLockfile(rootDirectory);
|
|
1456
2575
|
}
|
|
1457
2576
|
return new NpmLockfileAdapter().regenerateLockfile(rootDirectory);
|
|
1458
2577
|
}
|
|
1459
2578
|
};
|
|
1460
2579
|
|
|
1461
2580
|
// ../publisher-npm/dist/publisher.js
|
|
1462
|
-
import { execFile as
|
|
1463
|
-
import { promisify as
|
|
1464
|
-
|
|
2581
|
+
import { execFile as execFile6, spawn as spawn2 } from "child_process";
|
|
2582
|
+
import { promisify as promisify6 } from "util";
|
|
2583
|
+
import * as Effect2 from "effect/Effect";
|
|
2584
|
+
import * as Schedule from "effect/Schedule";
|
|
2585
|
+
import * as prompts from "@clack/prompts";
|
|
2586
|
+
import colors from "picocolors";
|
|
2587
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
1465
2588
|
function openBrowser(url) {
|
|
1466
2589
|
const platform = process.platform;
|
|
1467
2590
|
try {
|
|
1468
2591
|
if (platform === "darwin") {
|
|
1469
|
-
|
|
2592
|
+
execFile6("open", [url]);
|
|
1470
2593
|
} else if (platform === "win32") {
|
|
1471
|
-
|
|
2594
|
+
execFile6("cmd", ["/c", "start", '""', url]);
|
|
1472
2595
|
} else {
|
|
1473
|
-
|
|
2596
|
+
execFile6("xdg-open", [url]);
|
|
1474
2597
|
}
|
|
1475
2598
|
} catch {
|
|
1476
2599
|
}
|
|
1477
2600
|
}
|
|
1478
2601
|
async function pollForWebAuthToken(doneUrl, timeoutMs = 9e4) {
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
while (Date.now() - start < timeoutMs) {
|
|
2602
|
+
const deadline = Date.now() + timeoutMs;
|
|
2603
|
+
while (Date.now() < deadline) {
|
|
1482
2604
|
try {
|
|
1483
|
-
const
|
|
2605
|
+
const response = await fetch(doneUrl, {
|
|
1484
2606
|
headers: { "user-agent": "verelease-npm-publisher" }
|
|
1485
2607
|
});
|
|
1486
|
-
if (
|
|
1487
|
-
const
|
|
1488
|
-
if (
|
|
1489
|
-
return
|
|
1490
|
-
|
|
2608
|
+
if (response.status === 200) {
|
|
2609
|
+
const body = await response.json();
|
|
2610
|
+
if (body.token)
|
|
2611
|
+
return body.token;
|
|
2612
|
+
} else if (response.status !== 202) {
|
|
2613
|
+
return null;
|
|
1491
2614
|
}
|
|
2615
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
2616
|
+
await new Promise((resolve3) => setTimeout(resolve3, Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1e3 : 1500));
|
|
1492
2617
|
} catch {
|
|
2618
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1493
2619
|
}
|
|
1494
|
-
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
|
|
1495
2620
|
}
|
|
1496
2621
|
return null;
|
|
1497
2622
|
}
|
|
2623
|
+
function getNpmLoginUrl(authUrl) {
|
|
2624
|
+
const parsed = new URL(authUrl);
|
|
2625
|
+
return `https://www.npmjs.com/login?next=${encodeURIComponent(parsed.pathname + parsed.search)}`;
|
|
2626
|
+
}
|
|
2627
|
+
function mergePublishedAndUnpublishedVersions(versions, time) {
|
|
2628
|
+
const published = Array.isArray(versions) ? versions : typeof versions === "string" ? [versions] : [];
|
|
2629
|
+
const ghosts = [];
|
|
2630
|
+
if (time && typeof time === "object") {
|
|
2631
|
+
for (const key of Object.keys(time)) {
|
|
2632
|
+
if (key === "created" || key === "modified")
|
|
2633
|
+
continue;
|
|
2634
|
+
if (!published.includes(key))
|
|
2635
|
+
ghosts.push(key);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
return [...published, ...ghosts];
|
|
2639
|
+
}
|
|
1498
2640
|
var NpmPublishProvider = class {
|
|
1499
2641
|
options;
|
|
1500
2642
|
name = "npm";
|
|
@@ -1507,20 +2649,90 @@ var NpmPublishProvider = class {
|
|
|
1507
2649
|
}
|
|
1508
2650
|
return channel;
|
|
1509
2651
|
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Check npm authentication status without throwing.
|
|
2654
|
+
*/
|
|
2655
|
+
async checkAuth() {
|
|
2656
|
+
try {
|
|
2657
|
+
const { stdout } = await execFileAsync6("npm", ["whoami"]);
|
|
2658
|
+
const username = stdout.trim();
|
|
2659
|
+
return { authenticated: true, username };
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
return {
|
|
2662
|
+
authenticated: false,
|
|
2663
|
+
error: err.stderr || err.message || "401 Unauthorized"
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Proactively verifies npm authentication.
|
|
2669
|
+
* If unauthenticated and interactive, prompts the user to log in now,
|
|
2670
|
+
* runs `npm login` interactively, verifies the login, and continues.
|
|
2671
|
+
*/
|
|
2672
|
+
async ensureAuth(options = {}) {
|
|
2673
|
+
const isInteractive = options.interactive ?? (this.options.interactive || !process.env.CI && process.stdin.isTTY);
|
|
2674
|
+
const status = await this.checkAuth();
|
|
2675
|
+
if (status.authenticated && status.username) {
|
|
2676
|
+
return status.username;
|
|
2677
|
+
}
|
|
2678
|
+
if (!isInteractive) {
|
|
2679
|
+
throw new NpmAuthError({
|
|
2680
|
+
message: "You are not logged in to npm (npm whoami failed with 401). Run 'npm login' or configure NPM_TOKEN / NODE_AUTH_TOKEN in CI."
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
prompts.log.warn(colors.yellow("You are not currently logged in to npm."));
|
|
2684
|
+
const shouldLogin = await prompts.confirm({
|
|
2685
|
+
message: "Would you like to log in to npm now?",
|
|
2686
|
+
initialValue: true
|
|
2687
|
+
});
|
|
2688
|
+
if (prompts.isCancel(shouldLogin) || !shouldLogin) {
|
|
2689
|
+
throw new NpmAuthError({
|
|
2690
|
+
message: "npm authentication cancelled. Run 'npm login' before releasing."
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
prompts.log.info(colors.cyan("Launching npm login in your terminal..."));
|
|
2694
|
+
await new Promise((resolve3, reject) => {
|
|
2695
|
+
const child = spawn2("npm", ["login"], { stdio: "inherit" });
|
|
2696
|
+
child.on("close", (code) => {
|
|
2697
|
+
if (code === 0) {
|
|
2698
|
+
resolve3();
|
|
2699
|
+
} else {
|
|
2700
|
+
reject(new NpmAuthError({
|
|
2701
|
+
message: `npm login exited with code ${code}`,
|
|
2702
|
+
code: code ?? void 0
|
|
2703
|
+
}));
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
child.on("error", (err) => {
|
|
2707
|
+
reject(new NpmAuthError({
|
|
2708
|
+
message: `Failed to launch npm login: ${err.message}`
|
|
2709
|
+
}));
|
|
2710
|
+
});
|
|
2711
|
+
});
|
|
2712
|
+
const recheck = await this.checkAuth();
|
|
2713
|
+
if (recheck.authenticated && recheck.username) {
|
|
2714
|
+
prompts.log.success(colors.green(`Authenticated as ${colors.bold(recheck.username)} on npm.`));
|
|
2715
|
+
return recheck.username;
|
|
2716
|
+
}
|
|
2717
|
+
throw new NpmAuthError({
|
|
2718
|
+
message: "npm login completed, but npm authentication could not be verified."
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
1510
2721
|
async prepare(ctx) {
|
|
1511
2722
|
try {
|
|
1512
|
-
await
|
|
2723
|
+
await execFileAsync6("npm", ["pack", "--dry-run"], {
|
|
1513
2724
|
cwd: ctx.packagePlan.directory
|
|
1514
2725
|
});
|
|
1515
2726
|
} catch (err) {
|
|
1516
|
-
throw new
|
|
2727
|
+
throw new PublishError({
|
|
2728
|
+
packageName: ctx.packagePlan.name,
|
|
2729
|
+
message: `Failed packing verification for package ${ctx.packagePlan.name}: ${err.message || String(err)}`
|
|
2730
|
+
});
|
|
1517
2731
|
}
|
|
1518
|
-
if (!ctx.dryRun
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
}
|
|
1522
|
-
throw new Error(`You are not logged in to npm (npm whoami failed with 401). Please run 'npm login' first.`);
|
|
1523
|
-
}
|
|
2732
|
+
if (!ctx.dryRun) {
|
|
2733
|
+
await this.ensureAuth({
|
|
2734
|
+
interactive: this.options.interactive ?? (!process.env.CI && process.stdin.isTTY)
|
|
2735
|
+
});
|
|
1524
2736
|
}
|
|
1525
2737
|
}
|
|
1526
2738
|
async publish(ctx) {
|
|
@@ -1530,8 +2742,8 @@ var NpmPublishProvider = class {
|
|
|
1530
2742
|
if (ctx.dryRun) {
|
|
1531
2743
|
args.push("--dry-run");
|
|
1532
2744
|
}
|
|
1533
|
-
const
|
|
1534
|
-
args.push("--access",
|
|
2745
|
+
const access2 = ctx.access || this.options.defaultAccess || "public";
|
|
2746
|
+
args.push("--access", access2);
|
|
1535
2747
|
const hasOidc = !!(process.env.ACTIONS_ID_TOKEN_REQUEST_URL || process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN);
|
|
1536
2748
|
const useProvenance = ctx.provenance ?? this.options.provenance ?? hasOidc;
|
|
1537
2749
|
if (useProvenance) {
|
|
@@ -1545,8 +2757,7 @@ var NpmPublishProvider = class {
|
|
|
1545
2757
|
}
|
|
1546
2758
|
const isInteractive = this.options.interactive ?? (!ctx.dryRun && !process.env.CI && process.stdin.isTTY);
|
|
1547
2759
|
if (isInteractive) {
|
|
1548
|
-
|
|
1549
|
-
\u{1F510} Interactive terminal detected for npm publish:`);
|
|
2760
|
+
prompts.log.info(colors.cyan("Terminal ready for npm publish authentication:"));
|
|
1550
2761
|
console.log(` \u2022 WebAuthn / Passkey / Touch ID: press ENTER when prompted to open browser.`);
|
|
1551
2762
|
console.log(` \u2022 Authenticator App: enter your 6-digit TOTP code if prompted.
|
|
1552
2763
|
`);
|
|
@@ -1596,87 +2807,338 @@ var NpmPublishProvider = class {
|
|
|
1596
2807
|
});
|
|
1597
2808
|
});
|
|
1598
2809
|
}
|
|
1599
|
-
|
|
1600
|
-
const
|
|
1601
|
-
cwd: ctx.packagePlan.directory
|
|
2810
|
+
return new Promise((resolve3) => {
|
|
2811
|
+
const child = spawn2("npm", args, {
|
|
2812
|
+
cwd: ctx.packagePlan.directory,
|
|
2813
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1602
2814
|
});
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
${
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
`);
|
|
1628
|
-
openBrowser(authUrl);
|
|
1629
|
-
console.log(`\u23F3 Waiting for Touch ID / security key approval in your browser (up to 90s)...`);
|
|
1630
|
-
const token = await pollForWebAuthToken(doneUrl, 9e4);
|
|
1631
|
-
if (token) {
|
|
1632
|
-
console.log(`\x1B[32m\u2713 Touch ID / Passkey approved! Completing publish...\x1B[0m
|
|
1633
|
-
`);
|
|
1634
|
-
return this.publish({
|
|
1635
|
-
...ctx,
|
|
1636
|
-
extraArgs: [...ctx.extraArgs || [], "--otp", token]
|
|
1637
|
-
});
|
|
1638
|
-
} else {
|
|
1639
|
-
console.error(`\x1B[31m\u274C WebAuthn approval timed out or was cancelled.\x1B[0m
|
|
2815
|
+
let stdout = "";
|
|
2816
|
+
let stderr = "";
|
|
2817
|
+
let openedAuthUrl = false;
|
|
2818
|
+
let authUrl;
|
|
2819
|
+
let doneUrl;
|
|
2820
|
+
const handleOutput = (chunk, isError) => {
|
|
2821
|
+
const text = chunk.toString();
|
|
2822
|
+
if (isError)
|
|
2823
|
+
stderr += text;
|
|
2824
|
+
else
|
|
2825
|
+
stdout += text;
|
|
2826
|
+
const doneUrlMatch = `${stdout}
|
|
2827
|
+
${stderr}`.match(/https:\/\/registry\.npmjs\.org\/-\/v1\/done\?authId=[^\s\x1b"']+/);
|
|
2828
|
+
doneUrl ??= doneUrlMatch?.[0];
|
|
2829
|
+
if (!openedAuthUrl && !ctx.dryRun) {
|
|
2830
|
+
const authUrlMatch = `${stdout}
|
|
2831
|
+
${stderr}`.match(/https:\/\/www\.npmjs\.com\/auth\/cli\/[^\s\x1b"']+/);
|
|
2832
|
+
if (authUrlMatch) {
|
|
2833
|
+
openedAuthUrl = true;
|
|
2834
|
+
authUrl = authUrlMatch[0];
|
|
2835
|
+
const loginUrl = getNpmLoginUrl(authUrl);
|
|
2836
|
+
prompts.log.info(colors.cyan(colors.bold("\u{1F511} WebAuthn / Security Key 2FA Detected!")));
|
|
2837
|
+
console.log(" Opening npm's authentication page while publish is waiting:");
|
|
2838
|
+
console.log(` \u{1F449} ${colors.underline(loginUrl)}
|
|
1640
2839
|
`);
|
|
2840
|
+
openBrowser(loginUrl);
|
|
2841
|
+
}
|
|
1641
2842
|
}
|
|
1642
|
-
}
|
|
1643
|
-
return {
|
|
1644
|
-
provider: this.name,
|
|
1645
|
-
packageName: ctx.packagePlan.name,
|
|
1646
|
-
version: ctx.packagePlan.proposedVersion,
|
|
1647
|
-
published: false,
|
|
1648
|
-
dryRun: ctx.dryRun,
|
|
1649
|
-
distTag,
|
|
1650
|
-
durationMs: Date.now() - startTime,
|
|
1651
|
-
output: err.stdout || "",
|
|
1652
|
-
error: err.message || String(err)
|
|
1653
2843
|
};
|
|
1654
|
-
|
|
2844
|
+
child.stdout?.on("data", (chunk) => handleOutput(chunk, false));
|
|
2845
|
+
child.stderr?.on("data", (chunk) => handleOutput(chunk, true));
|
|
2846
|
+
child.on("error", (error) => {
|
|
2847
|
+
resolve3({
|
|
2848
|
+
provider: this.name,
|
|
2849
|
+
packageName: ctx.packagePlan.name,
|
|
2850
|
+
version: ctx.packagePlan.proposedVersion,
|
|
2851
|
+
published: false,
|
|
2852
|
+
dryRun: ctx.dryRun,
|
|
2853
|
+
distTag,
|
|
2854
|
+
durationMs: Date.now() - startTime,
|
|
2855
|
+
output: `${stdout}
|
|
2856
|
+
${stderr}`.trim(),
|
|
2857
|
+
error: error.message
|
|
2858
|
+
});
|
|
2859
|
+
});
|
|
2860
|
+
child.on("close", async (code) => {
|
|
2861
|
+
if (code !== 0 && doneUrl && !ctx.dryRun) {
|
|
2862
|
+
const token = await pollForWebAuthToken(doneUrl);
|
|
2863
|
+
if (token) {
|
|
2864
|
+
resolve3(await this.publish({
|
|
2865
|
+
...ctx,
|
|
2866
|
+
extraArgs: [...ctx.extraArgs || [], "--otp", token]
|
|
2867
|
+
}));
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
const output = `${stdout}
|
|
2872
|
+
${stderr}`.trim();
|
|
2873
|
+
resolve3({
|
|
2874
|
+
provider: this.name,
|
|
2875
|
+
packageName: ctx.packagePlan.name,
|
|
2876
|
+
version: ctx.packagePlan.proposedVersion,
|
|
2877
|
+
published: code === 0,
|
|
2878
|
+
dryRun: ctx.dryRun,
|
|
2879
|
+
distTag,
|
|
2880
|
+
durationMs: Date.now() - startTime,
|
|
2881
|
+
output,
|
|
2882
|
+
error: code === 0 ? void 0 : `npm publish exited with code ${code}.`
|
|
2883
|
+
});
|
|
2884
|
+
});
|
|
2885
|
+
});
|
|
1655
2886
|
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Resiliently fetches published versions using EffectTS retry schedule.
|
|
2889
|
+
*
|
|
2890
|
+
* The result also includes versions that were previously published and
|
|
2891
|
+
* then unpublished. npm permanently blocks republishing an unpublished
|
|
2892
|
+
* `name@version` (a supply-chain safeguard introduced after the left-pad
|
|
2893
|
+
* incident), so those "ghost" versions must be treated as unavailable
|
|
2894
|
+
* just like currently-published ones when proposing the next version.
|
|
2895
|
+
*/
|
|
1656
2896
|
async getPublishedVersions(packageName) {
|
|
2897
|
+
const fetchVersions = Effect2.tryPromise({
|
|
2898
|
+
try: async () => {
|
|
2899
|
+
const { stdout } = await execFileAsync6("npm", ["view", packageName, "--json", "versions", "time"]);
|
|
2900
|
+
const parsed = JSON.parse(stdout.trim());
|
|
2901
|
+
return mergePublishedAndUnpublishedVersions(parsed.versions, parsed.time);
|
|
2902
|
+
},
|
|
2903
|
+
catch: (err) => err
|
|
2904
|
+
});
|
|
2905
|
+
const withRetry = Effect2.retry(fetchVersions, {
|
|
2906
|
+
schedule: Schedule.recurs(2)
|
|
2907
|
+
});
|
|
1657
2908
|
try {
|
|
1658
|
-
|
|
1659
|
-
const parsed = JSON.parse(stdout.trim());
|
|
1660
|
-
if (Array.isArray(parsed)) {
|
|
1661
|
-
return parsed;
|
|
1662
|
-
}
|
|
1663
|
-
if (typeof parsed === "string") {
|
|
1664
|
-
return [parsed];
|
|
1665
|
-
}
|
|
1666
|
-
return [];
|
|
2909
|
+
return await Effect2.runPromise(withRetry);
|
|
1667
2910
|
} catch {
|
|
1668
2911
|
return [];
|
|
1669
2912
|
}
|
|
1670
2913
|
}
|
|
1671
2914
|
};
|
|
1672
2915
|
|
|
2916
|
+
// ../publisher-cargo/dist/publisher.js
|
|
2917
|
+
import { execFile as execFile7, spawn as spawn3 } from "child_process";
|
|
2918
|
+
import { promisify as promisify7 } from "util";
|
|
2919
|
+
import { readFile as readFile11, stat as stat10 } from "fs/promises";
|
|
2920
|
+
import { join as join14 } from "path";
|
|
2921
|
+
import { homedir } from "os";
|
|
2922
|
+
import * as Effect3 from "effect/Effect";
|
|
2923
|
+
import * as Schedule2 from "effect/Schedule";
|
|
2924
|
+
import * as prompts2 from "@clack/prompts";
|
|
2925
|
+
import colors2 from "picocolors";
|
|
2926
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
2927
|
+
var execFileAsync7 = promisify7(execFile7);
|
|
2928
|
+
async function fileExists7(path) {
|
|
2929
|
+
try {
|
|
2930
|
+
const s = await stat10(path);
|
|
2931
|
+
return s.isFile();
|
|
2932
|
+
} catch {
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
var CargoPublishProvider = class {
|
|
2937
|
+
options;
|
|
2938
|
+
name = "cargo";
|
|
2939
|
+
constructor(options = {}) {
|
|
2940
|
+
this.options = options;
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Checks whether crates.io / cargo credentials exist.
|
|
2944
|
+
* Looks in CARGO_REGISTRY_TOKEN env var, options.token,
|
|
2945
|
+
* ~/.cargo/credentials.toml, or ~/.cargo/credentials.
|
|
2946
|
+
*/
|
|
2947
|
+
async checkAuth() {
|
|
2948
|
+
if (this.options.token) {
|
|
2949
|
+
return { authenticated: true, token: this.options.token };
|
|
2950
|
+
}
|
|
2951
|
+
if (process.env.CARGO_REGISTRY_TOKEN) {
|
|
2952
|
+
return { authenticated: true, token: process.env.CARGO_REGISTRY_TOKEN };
|
|
2953
|
+
}
|
|
2954
|
+
const home = homedir();
|
|
2955
|
+
const credToml = join14(home, ".cargo", "credentials.toml");
|
|
2956
|
+
const credOld = join14(home, ".cargo", "credentials");
|
|
2957
|
+
for (const credPath of [credToml, credOld]) {
|
|
2958
|
+
if (await fileExists7(credPath)) {
|
|
2959
|
+
try {
|
|
2960
|
+
const content = await readFile11(credPath, "utf8");
|
|
2961
|
+
const parsed = parseToml4(content);
|
|
2962
|
+
const token = parsed.registry?.token || parsed.registries?.["crates-io"]?.token;
|
|
2963
|
+
if (token) {
|
|
2964
|
+
return { authenticated: true, token };
|
|
2965
|
+
}
|
|
2966
|
+
} catch {
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
return {
|
|
2971
|
+
authenticated: false,
|
|
2972
|
+
error: "No Cargo token found in environment (CARGO_REGISTRY_TOKEN) or ~/.cargo/credentials.toml"
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Ensures auth is present. If interactive and missing, prompts the user to enter a token.
|
|
2977
|
+
*/
|
|
2978
|
+
async ensureAuth(options = {}) {
|
|
2979
|
+
const isInteractive = options.interactive !== void 0 ? options.interactive : this.options.interactive !== false && Boolean(process.stdout.isTTY);
|
|
2980
|
+
const status = await this.checkAuth();
|
|
2981
|
+
if (status.authenticated && status.token) {
|
|
2982
|
+
return status.token;
|
|
2983
|
+
}
|
|
2984
|
+
if (!isInteractive) {
|
|
2985
|
+
throw new CargoAuthError({
|
|
2986
|
+
message: status.error || "Missing crates.io token in non-interactive mode"
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
prompts2.note(`Crates.io authentication is required to publish Rust crates.
|
|
2990
|
+
Generate an API token at: ${colors2.cyan("https://crates.io/settings/tokens")}`, colors2.yellow("crates.io Token Required"));
|
|
2991
|
+
const enteredToken = await prompts2.password({
|
|
2992
|
+
message: "Paste your crates.io API token:",
|
|
2993
|
+
validate(val) {
|
|
2994
|
+
if (!val || val.trim().length === 0)
|
|
2995
|
+
return "Token cannot be empty.";
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
if (prompts2.isCancel(enteredToken)) {
|
|
2999
|
+
throw new CargoAuthError({
|
|
3000
|
+
message: "Crates.io authentication cancelled by user."
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
const token = String(enteredToken).trim();
|
|
3004
|
+
process.env.CARGO_REGISTRY_TOKEN = token;
|
|
3005
|
+
return token;
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Queries crates.io API for crate existence and released versions.
|
|
3009
|
+
*/
|
|
3010
|
+
async queryCratesIo(crateName) {
|
|
3011
|
+
const runQuery = Effect3.tryPromise({
|
|
3012
|
+
try: async () => {
|
|
3013
|
+
const url = `https://crates.io/api/v1/crates/${encodeURIComponent(crateName)}`;
|
|
3014
|
+
const res = await fetch(url, {
|
|
3015
|
+
headers: {
|
|
3016
|
+
"User-Agent": "verelease-cargo-publisher (https://github.com/ivanjeremic/verelease)"
|
|
3017
|
+
}
|
|
3018
|
+
});
|
|
3019
|
+
if (res.status === 404) {
|
|
3020
|
+
return { exists: false, versions: [] };
|
|
3021
|
+
}
|
|
3022
|
+
if (!res.ok) {
|
|
3023
|
+
throw new Error(`crates.io API error: ${res.status} ${res.statusText}`);
|
|
3024
|
+
}
|
|
3025
|
+
const data = await res.json();
|
|
3026
|
+
const versions = data.versions?.map((v) => v.num) || [];
|
|
3027
|
+
return {
|
|
3028
|
+
exists: true,
|
|
3029
|
+
maxVersion: data.crate?.max_version,
|
|
3030
|
+
versions
|
|
3031
|
+
};
|
|
3032
|
+
},
|
|
3033
|
+
catch: (err) => err
|
|
3034
|
+
});
|
|
3035
|
+
const withRetry = Effect3.retry(runQuery, {
|
|
3036
|
+
schedule: Schedule2.recurs(3)
|
|
3037
|
+
});
|
|
3038
|
+
return Effect3.runPromise(withRetry);
|
|
3039
|
+
}
|
|
3040
|
+
async prepare(ctx) {
|
|
3041
|
+
if (!ctx.dryRun) {
|
|
3042
|
+
await this.ensureAuth({ interactive: this.options.interactive });
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
async publish(ctx) {
|
|
3046
|
+
const { packagePlan, dryRun } = ctx;
|
|
3047
|
+
const startTime = Date.now();
|
|
3048
|
+
const cargoBin = this.options.cargoBinary || "cargo";
|
|
3049
|
+
let token;
|
|
3050
|
+
if (!dryRun) {
|
|
3051
|
+
token = await this.ensureAuth({ interactive: this.options.interactive });
|
|
3052
|
+
}
|
|
3053
|
+
const args = ["publish"];
|
|
3054
|
+
if (dryRun) {
|
|
3055
|
+
args.push("--dry-run");
|
|
3056
|
+
}
|
|
3057
|
+
if (this.options.registry) {
|
|
3058
|
+
args.push("--registry", this.options.registry);
|
|
3059
|
+
}
|
|
3060
|
+
if (this.options.allowDirty ?? true) {
|
|
3061
|
+
args.push("--allow-dirty");
|
|
3062
|
+
}
|
|
3063
|
+
if (ctx.extraArgs) {
|
|
3064
|
+
args.push(...ctx.extraArgs);
|
|
3065
|
+
}
|
|
3066
|
+
return new Promise((resolve3, reject) => {
|
|
3067
|
+
const child = spawn3(cargoBin, args, {
|
|
3068
|
+
cwd: packagePlan.directory,
|
|
3069
|
+
env: {
|
|
3070
|
+
...process.env,
|
|
3071
|
+
...token ? { CARGO_REGISTRY_TOKEN: token } : {}
|
|
3072
|
+
},
|
|
3073
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3074
|
+
});
|
|
3075
|
+
let stdout = "";
|
|
3076
|
+
let stderr = "";
|
|
3077
|
+
child.stdout?.on("data", (chunk) => {
|
|
3078
|
+
stdout += chunk.toString();
|
|
3079
|
+
});
|
|
3080
|
+
child.stderr?.on("data", (chunk) => {
|
|
3081
|
+
stderr += chunk.toString();
|
|
3082
|
+
});
|
|
3083
|
+
child.on("close", (code) => {
|
|
3084
|
+
const durationMs = Date.now() - startTime;
|
|
3085
|
+
const combinedOutput = (stdout + "\n" + stderr).trim();
|
|
3086
|
+
if (code !== 0) {
|
|
3087
|
+
const errMsg = `cargo publish exited with code ${code}:
|
|
3088
|
+
${combinedOutput}`;
|
|
3089
|
+
const pubErr = new PublishError({
|
|
3090
|
+
message: errMsg,
|
|
3091
|
+
packageName: packagePlan.name,
|
|
3092
|
+
code: code ?? 1
|
|
3093
|
+
});
|
|
3094
|
+
return resolve3({
|
|
3095
|
+
provider: this.name,
|
|
3096
|
+
packageName: packagePlan.name,
|
|
3097
|
+
version: packagePlan.proposedVersion,
|
|
3098
|
+
published: false,
|
|
3099
|
+
dryRun,
|
|
3100
|
+
distTag: "latest",
|
|
3101
|
+
durationMs,
|
|
3102
|
+
output: combinedOutput,
|
|
3103
|
+
error: errMsg
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
resolve3({
|
|
3107
|
+
provider: this.name,
|
|
3108
|
+
packageName: packagePlan.name,
|
|
3109
|
+
version: packagePlan.proposedVersion,
|
|
3110
|
+
published: !dryRun,
|
|
3111
|
+
dryRun,
|
|
3112
|
+
distTag: "latest",
|
|
3113
|
+
durationMs,
|
|
3114
|
+
output: combinedOutput
|
|
3115
|
+
});
|
|
3116
|
+
});
|
|
3117
|
+
child.on("error", (err) => {
|
|
3118
|
+
const durationMs = Date.now() - startTime;
|
|
3119
|
+
resolve3({
|
|
3120
|
+
provider: this.name,
|
|
3121
|
+
packageName: packagePlan.name,
|
|
3122
|
+
version: packagePlan.proposedVersion,
|
|
3123
|
+
published: false,
|
|
3124
|
+
dryRun,
|
|
3125
|
+
distTag: "latest",
|
|
3126
|
+
durationMs,
|
|
3127
|
+
output: "",
|
|
3128
|
+
error: `Failed to execute ${cargoBin}: ${err.message}`
|
|
3129
|
+
});
|
|
3130
|
+
});
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
|
|
1673
3135
|
// ../config/dist/config.js
|
|
1674
|
-
import { stat as
|
|
1675
|
-
import { join as
|
|
3136
|
+
import { stat as stat11 } from "fs/promises";
|
|
3137
|
+
import { join as join15 } from "path";
|
|
1676
3138
|
import { pathToFileURL } from "url";
|
|
1677
|
-
async function
|
|
3139
|
+
async function fileExists8(path) {
|
|
1678
3140
|
try {
|
|
1679
|
-
const s = await
|
|
3141
|
+
const s = await stat11(path);
|
|
1680
3142
|
return s.isFile();
|
|
1681
3143
|
} catch {
|
|
1682
3144
|
return false;
|
|
@@ -1694,8 +3156,8 @@ async function loadConfig(rootDir) {
|
|
|
1694
3156
|
"autoversion.config.mjs"
|
|
1695
3157
|
];
|
|
1696
3158
|
for (const name of candidateNames) {
|
|
1697
|
-
const fullPath =
|
|
1698
|
-
if (await
|
|
3159
|
+
const fullPath = join15(rootDir, name);
|
|
3160
|
+
if (await fileExists8(fullPath)) {
|
|
1699
3161
|
try {
|
|
1700
3162
|
const fileUrl = pathToFileURL(fullPath).href;
|
|
1701
3163
|
const mod = await import(fileUrl);
|
|
@@ -1740,8 +3202,8 @@ var MigrationScriptSchema = z.object({
|
|
|
1740
3202
|
});
|
|
1741
3203
|
|
|
1742
3204
|
// ../ai/dist/changelog.js
|
|
1743
|
-
function renderChangelogMarkdown(
|
|
1744
|
-
const parts = [`# ${
|
|
3205
|
+
function renderChangelogMarkdown(version2, date, data) {
|
|
3206
|
+
const parts = [`# ${version2} (${date})`, ""];
|
|
1745
3207
|
if (data.breakingChanges.length > 0) {
|
|
1746
3208
|
parts.push("## \u26A0\uFE0F Breaking Changes");
|
|
1747
3209
|
for (const item of data.breakingChanges) {
|
|
@@ -1949,13 +3411,13 @@ walkAndTransform(targetDir);
|
|
|
1949
3411
|
}
|
|
1950
3412
|
|
|
1951
3413
|
// ../ai/dist/validation.js
|
|
1952
|
-
import { cp, mkdtemp as
|
|
1953
|
-
import { execFile as
|
|
1954
|
-
import { promisify as
|
|
1955
|
-
import { join as
|
|
1956
|
-
import { tmpdir as
|
|
3414
|
+
import { cp as cp2, mkdtemp as mkdtemp5, rm as rm4, writeFile as writeFile5, mkdir as mkdir5, symlink as symlink2, readdir as readdir4 } from "fs/promises";
|
|
3415
|
+
import { execFile as execFile8 } from "child_process";
|
|
3416
|
+
import { promisify as promisify8 } from "util";
|
|
3417
|
+
import { join as join16 } from "path";
|
|
3418
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
1957
3419
|
import * as tar3 from "tar";
|
|
1958
|
-
var
|
|
3420
|
+
var execFileAsync8 = promisify8(execFile8);
|
|
1959
3421
|
|
|
1960
3422
|
// ../ai/dist/provider.js
|
|
1961
3423
|
var TanStackReleaseAI = class {
|
|
@@ -2013,9 +3475,9 @@ ${JSON.stringify(input.breakingChanges, null, 2)}`;
|
|
|
2013
3475
|
};
|
|
2014
3476
|
|
|
2015
3477
|
// ../git/dist/git.js
|
|
2016
|
-
import { execFile as
|
|
2017
|
-
import { promisify as
|
|
2018
|
-
var
|
|
3478
|
+
import { execFile as execFile9 } from "child_process";
|
|
3479
|
+
import { promisify as promisify9 } from "util";
|
|
3480
|
+
var execFileAsync9 = promisify9(execFile9);
|
|
2019
3481
|
var GitManager = class {
|
|
2020
3482
|
cwd;
|
|
2021
3483
|
constructor(cwd = process.cwd()) {
|
|
@@ -2030,35 +3492,86 @@ var GitManager = class {
|
|
|
2030
3492
|
}
|
|
2031
3493
|
return `${options.packageName}@${options.version}`;
|
|
2032
3494
|
}
|
|
3495
|
+
/**
|
|
3496
|
+
* The most recent release tag for a package, or null if it has never been
|
|
3497
|
+
* released.
|
|
3498
|
+
*
|
|
3499
|
+
* Tags are matched by the same formats `formatTagName` writes, so this finds
|
|
3500
|
+
* what a previous release actually created rather than assuming a convention.
|
|
3501
|
+
*/
|
|
3502
|
+
async lastReleaseTag(packageName, strategy) {
|
|
3503
|
+
const pattern = strategy === "fixed" ? "v*" : `${packageName}@*`;
|
|
3504
|
+
try {
|
|
3505
|
+
const { stdout } = await execFileAsync9("git", ["tag", "--list", pattern, "--sort=-creatordate"], { cwd: this.cwd });
|
|
3506
|
+
const [first] = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3507
|
+
return first ?? null;
|
|
3508
|
+
} catch {
|
|
3509
|
+
return null;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Files under `directory` that changed between `ref` and the working tree.
|
|
3514
|
+
*
|
|
3515
|
+
* Used to answer whether a package changed at all since its last release. A
|
|
3516
|
+
* package nobody touched should not be republished, and asking git is exact
|
|
3517
|
+
* — far better evidence than inferring activity from a green test suite.
|
|
3518
|
+
*/
|
|
3519
|
+
async changedFilesSince(ref, directory) {
|
|
3520
|
+
try {
|
|
3521
|
+
const { stdout } = await execFileAsync9("git", ["diff", "--name-only", `${ref}...HEAD`, "--", directory], { cwd: this.cwd });
|
|
3522
|
+
const committed = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3523
|
+
const { stdout: dirty } = await execFileAsync9("git", ["status", "--porcelain", "--", directory], { cwd: this.cwd });
|
|
3524
|
+
const uncommitted = dirty.split("\n").map((line) => line.slice(3).trim()).filter(Boolean);
|
|
3525
|
+
return [.../* @__PURE__ */ new Set([...committed, ...uncommitted])];
|
|
3526
|
+
} catch {
|
|
3527
|
+
return [];
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
/**
|
|
3531
|
+
* The newest release tag in the repository, whatever package it names.
|
|
3532
|
+
*
|
|
3533
|
+
* A monorepo that tags only its root package still has a meaningful "last
|
|
3534
|
+
* release" for everything released alongside it; without this, packages that
|
|
3535
|
+
* are never tagged individually would have no reference point at all.
|
|
3536
|
+
*/
|
|
3537
|
+
async latestReleaseTag() {
|
|
3538
|
+
try {
|
|
3539
|
+
const { stdout } = await execFileAsync9("git", ["tag", "--list", "--sort=-creatordate"], { cwd: this.cwd });
|
|
3540
|
+
const [first] = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3541
|
+
return first ?? null;
|
|
3542
|
+
} catch {
|
|
3543
|
+
return null;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
2033
3546
|
async isClean() {
|
|
2034
|
-
const { stdout } = await
|
|
3547
|
+
const { stdout } = await execFileAsync9("git", ["status", "--porcelain"], {
|
|
2035
3548
|
cwd: this.cwd
|
|
2036
3549
|
});
|
|
2037
3550
|
return stdout.trim().length === 0;
|
|
2038
3551
|
}
|
|
2039
3552
|
async getHeadCommit() {
|
|
2040
|
-
const { stdout } = await
|
|
3553
|
+
const { stdout } = await execFileAsync9("git", ["rev-parse", "HEAD"], {
|
|
2041
3554
|
cwd: this.cwd
|
|
2042
3555
|
});
|
|
2043
3556
|
return stdout.trim();
|
|
2044
3557
|
}
|
|
2045
3558
|
async createTag(tagName, message) {
|
|
2046
3559
|
const args = ["tag", "-a", tagName, "-m", message || `Release ${tagName}`];
|
|
2047
|
-
await
|
|
3560
|
+
await execFileAsync9("git", args, { cwd: this.cwd });
|
|
2048
3561
|
}
|
|
2049
3562
|
async createReleaseCommit(files, message = "chore(release): publish") {
|
|
2050
3563
|
if (files.length > 0) {
|
|
2051
|
-
await
|
|
3564
|
+
await execFileAsync9("git", ["add", ...files], { cwd: this.cwd });
|
|
2052
3565
|
} else {
|
|
2053
|
-
await
|
|
3566
|
+
await execFileAsync9("git", ["add", "-A"], { cwd: this.cwd });
|
|
2054
3567
|
}
|
|
2055
|
-
await
|
|
3568
|
+
await execFileAsync9("git", ["commit", "-m", message], { cwd: this.cwd });
|
|
2056
3569
|
return this.getHeadCommit();
|
|
2057
3570
|
}
|
|
2058
3571
|
};
|
|
2059
3572
|
|
|
2060
3573
|
// ../test-adapter/dist/command.js
|
|
2061
|
-
import { spawn as
|
|
3574
|
+
import { spawn as spawn4 } from "child_process";
|
|
2062
3575
|
var CommandTestAdapter = class {
|
|
2063
3576
|
config;
|
|
2064
3577
|
name = "command";
|
|
@@ -2083,7 +3596,7 @@ var CommandTestAdapter = class {
|
|
|
2083
3596
|
const [executable, ...args] = cmd;
|
|
2084
3597
|
const finalArgs = [...args, ...ctx.extraArgs || []];
|
|
2085
3598
|
return new Promise((resolve3) => {
|
|
2086
|
-
const child =
|
|
3599
|
+
const child = spawn4(executable, finalArgs, {
|
|
2087
3600
|
cwd: ctx.packagePath,
|
|
2088
3601
|
env: { ...process.env, ...ctx.env },
|
|
2089
3602
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2154,7 +3667,173 @@ var CommandTestAdapter = class {
|
|
|
2154
3667
|
stack: err.stack
|
|
2155
3668
|
}
|
|
2156
3669
|
],
|
|
2157
|
-
totalTests: 1,
|
|
3670
|
+
totalTests: 1,
|
|
3671
|
+
passedTests: 0,
|
|
3672
|
+
failedTests: 1,
|
|
3673
|
+
skippedTests: 0
|
|
3674
|
+
});
|
|
3675
|
+
});
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
// ../test-adapter/dist/cargo.js
|
|
3681
|
+
import { spawn as spawn5 } from "child_process";
|
|
3682
|
+
function parseCargoTestOutput(stdout, stderr) {
|
|
3683
|
+
const tests = [];
|
|
3684
|
+
const lines = stdout.split("\n");
|
|
3685
|
+
let passed = 0;
|
|
3686
|
+
let failed = 0;
|
|
3687
|
+
let skipped = 0;
|
|
3688
|
+
const testLineRegex = /^\s*test\s+([^\s]+)\s+\.\.\.\s+(ok|FAILED|ignored)/;
|
|
3689
|
+
const failureSection = [];
|
|
3690
|
+
let inFailureSection = false;
|
|
3691
|
+
for (const line of lines) {
|
|
3692
|
+
if (line.startsWith("failures:")) {
|
|
3693
|
+
inFailureSection = true;
|
|
3694
|
+
continue;
|
|
3695
|
+
}
|
|
3696
|
+
if (inFailureSection) {
|
|
3697
|
+
if (line.startsWith("test result:")) {
|
|
3698
|
+
inFailureSection = false;
|
|
3699
|
+
} else if (line.trim()) {
|
|
3700
|
+
failureSection.push(line);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
const match = line.match(testLineRegex);
|
|
3704
|
+
if (match) {
|
|
3705
|
+
const [, testName, statusStr] = match;
|
|
3706
|
+
if (statusStr === "ok") {
|
|
3707
|
+
passed++;
|
|
3708
|
+
tests.push({
|
|
3709
|
+
name: testName,
|
|
3710
|
+
status: "passed"
|
|
3711
|
+
});
|
|
3712
|
+
} else if (statusStr === "FAILED") {
|
|
3713
|
+
failed++;
|
|
3714
|
+
tests.push({
|
|
3715
|
+
name: testName,
|
|
3716
|
+
status: "failed",
|
|
3717
|
+
failure: {
|
|
3718
|
+
message: `Cargo test assertion failed: ${testName}`
|
|
3719
|
+
}
|
|
3720
|
+
});
|
|
3721
|
+
} else if (statusStr === "ignored") {
|
|
3722
|
+
skipped++;
|
|
3723
|
+
tests.push({
|
|
3724
|
+
name: testName,
|
|
3725
|
+
status: "skipped"
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
const summaryRegex = /test result:\s+(ok|FAILED)\.\s+(\d+)\s+passed;\s+(\d+)\s+failed;\s+(\d+)\s+ignored/;
|
|
3731
|
+
const summaryMatch = stdout.match(summaryRegex);
|
|
3732
|
+
if (summaryMatch) {
|
|
3733
|
+
const [, resultStr, pStr, fStr, iStr] = summaryMatch;
|
|
3734
|
+
passed = parseInt(pStr, 10);
|
|
3735
|
+
failed = parseInt(fStr, 10);
|
|
3736
|
+
skipped = parseInt(iStr, 10);
|
|
3737
|
+
}
|
|
3738
|
+
const total = passed + failed + skipped;
|
|
3739
|
+
const success = failed === 0;
|
|
3740
|
+
return {
|
|
3741
|
+
success,
|
|
3742
|
+
totalTests: total,
|
|
3743
|
+
passedTests: passed,
|
|
3744
|
+
failedTests: failed,
|
|
3745
|
+
skippedTests: skipped,
|
|
3746
|
+
tests,
|
|
3747
|
+
failures: failureSection
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
var CargoTestAdapter = class {
|
|
3751
|
+
options;
|
|
3752
|
+
name = "cargo:test";
|
|
3753
|
+
constructor(options = {}) {
|
|
3754
|
+
this.options = options;
|
|
3755
|
+
}
|
|
3756
|
+
async detect(ctx) {
|
|
3757
|
+
const isCargo = ctx.files.includes("Cargo.toml") || Boolean(ctx.manifest && ("package" in ctx.manifest || "workspace" in ctx.manifest));
|
|
3758
|
+
if (isCargo) {
|
|
3759
|
+
return {
|
|
3760
|
+
supported: true,
|
|
3761
|
+
confidence: 0.95,
|
|
3762
|
+
suggestedConfig: { command: ["cargo", "test"] }
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
return { supported: false, confidence: 0 };
|
|
3766
|
+
}
|
|
3767
|
+
async run(ctx) {
|
|
3768
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3769
|
+
const startTime = Date.now();
|
|
3770
|
+
const cargoBin = this.options.cargoBinary || "cargo";
|
|
3771
|
+
const baseArgs = ["test", ...this.options.extraArgs || []];
|
|
3772
|
+
const finalArgs = [...baseArgs, ...ctx.extraArgs || []];
|
|
3773
|
+
return new Promise((resolve3) => {
|
|
3774
|
+
const child = spawn5(cargoBin, finalArgs, {
|
|
3775
|
+
cwd: ctx.packagePath,
|
|
3776
|
+
env: { ...process.env, ...ctx.env },
|
|
3777
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3778
|
+
});
|
|
3779
|
+
let stdout = "";
|
|
3780
|
+
let stderr = "";
|
|
3781
|
+
child.stdout?.on("data", (chunk) => {
|
|
3782
|
+
stdout += chunk.toString();
|
|
3783
|
+
});
|
|
3784
|
+
child.stderr?.on("data", (chunk) => {
|
|
3785
|
+
stderr += chunk.toString();
|
|
3786
|
+
});
|
|
3787
|
+
child.on("close", (code) => {
|
|
3788
|
+
const durationMs = Date.now() - startTime;
|
|
3789
|
+
const parsed = parseCargoTestOutput(stdout, stderr);
|
|
3790
|
+
const suites = [
|
|
3791
|
+
{
|
|
3792
|
+
name: "cargo-test-suite",
|
|
3793
|
+
status: parsed.success && code === 0 ? "passed" : "failed",
|
|
3794
|
+
durationMs,
|
|
3795
|
+
tests: parsed.tests
|
|
3796
|
+
}
|
|
3797
|
+
];
|
|
3798
|
+
const diagnostics = [];
|
|
3799
|
+
if (code !== 0 || !parsed.success) {
|
|
3800
|
+
diagnostics.push({
|
|
3801
|
+
severity: "error",
|
|
3802
|
+
message: parsed.failures.join("\n") || stderr.trim() || `cargo test exited with code ${code}`
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
resolve3({
|
|
3806
|
+
adapter: this.name,
|
|
3807
|
+
runtime: "rust",
|
|
3808
|
+
command: [cargoBin, ...finalArgs],
|
|
3809
|
+
startedAt,
|
|
3810
|
+
durationMs,
|
|
3811
|
+
success: code === 0 && parsed.success,
|
|
3812
|
+
suites,
|
|
3813
|
+
diagnostics,
|
|
3814
|
+
totalTests: parsed.totalTests,
|
|
3815
|
+
passedTests: parsed.passedTests,
|
|
3816
|
+
failedTests: parsed.failedTests,
|
|
3817
|
+
skippedTests: parsed.skippedTests
|
|
3818
|
+
});
|
|
3819
|
+
});
|
|
3820
|
+
child.on("error", (err) => {
|
|
3821
|
+
const durationMs = Date.now() - startTime;
|
|
3822
|
+
resolve3({
|
|
3823
|
+
adapter: this.name,
|
|
3824
|
+
runtime: "rust",
|
|
3825
|
+
command: [cargoBin, ...finalArgs],
|
|
3826
|
+
startedAt,
|
|
3827
|
+
durationMs,
|
|
3828
|
+
success: false,
|
|
3829
|
+
suites: [],
|
|
3830
|
+
diagnostics: [
|
|
3831
|
+
{
|
|
3832
|
+
severity: "error",
|
|
3833
|
+
message: `Failed to spawn ${cargoBin}: ${err.message}`
|
|
3834
|
+
}
|
|
3835
|
+
],
|
|
3836
|
+
totalTests: 0,
|
|
2158
3837
|
passedTests: 0,
|
|
2159
3838
|
failedTests: 1,
|
|
2160
3839
|
skippedTests: 0
|
|
@@ -2165,8 +3844,9 @@ var CommandTestAdapter = class {
|
|
|
2165
3844
|
};
|
|
2166
3845
|
|
|
2167
3846
|
// ../test-adapter/dist/test-suite-analyzer.js
|
|
2168
|
-
import { readFile as
|
|
2169
|
-
import { join as
|
|
3847
|
+
import { readFile as readFile12, readdir as readdir5 } from "fs/promises";
|
|
3848
|
+
import { join as join17 } from "path";
|
|
3849
|
+
import { parse as parseToml5 } from "smol-toml";
|
|
2170
3850
|
var TestSuiteAnalyzer = class {
|
|
2171
3851
|
name = "test-suite-analyzer";
|
|
2172
3852
|
adapters;
|
|
@@ -2174,6 +3854,7 @@ var TestSuiteAnalyzer = class {
|
|
|
2174
3854
|
constructor(options) {
|
|
2175
3855
|
this.adapters = [
|
|
2176
3856
|
...options?.adapters ?? [],
|
|
3857
|
+
new CargoTestAdapter(),
|
|
2177
3858
|
new CommandTestAdapter()
|
|
2178
3859
|
// always available as lowest-confidence fallback
|
|
2179
3860
|
];
|
|
@@ -2181,24 +3862,27 @@ var TestSuiteAnalyzer = class {
|
|
|
2181
3862
|
}
|
|
2182
3863
|
async analyze(context) {
|
|
2183
3864
|
const { packageName, packagePath } = context;
|
|
2184
|
-
let manifest;
|
|
3865
|
+
let manifest = {};
|
|
3866
|
+
let isCargo = false;
|
|
2185
3867
|
try {
|
|
2186
|
-
const raw = await
|
|
3868
|
+
const raw = await readFile12(join17(packagePath, "package.json"), "utf8");
|
|
2187
3869
|
manifest = JSON.parse(raw);
|
|
2188
3870
|
} catch {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
3871
|
+
try {
|
|
3872
|
+
const cargoRaw = await readFile12(join17(packagePath, "Cargo.toml"), "utf8");
|
|
3873
|
+
manifest = parseToml5(cargoRaw);
|
|
3874
|
+
isCargo = true;
|
|
3875
|
+
} catch {
|
|
3876
|
+
return [];
|
|
3877
|
+
}
|
|
2194
3878
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
files = [];
|
|
3879
|
+
if (!isCargo) {
|
|
3880
|
+
const scripts = manifest.scripts || {};
|
|
3881
|
+
if (!scripts.test) {
|
|
3882
|
+
return [];
|
|
3883
|
+
}
|
|
2201
3884
|
}
|
|
3885
|
+
const files = await listProjectFiles(packagePath);
|
|
2202
3886
|
const detectionCtx = { packagePath, manifest, files };
|
|
2203
3887
|
const best = await this.detectBestAdapter(detectionCtx);
|
|
2204
3888
|
if (!best) {
|
|
@@ -2215,6 +3899,7 @@ var TestSuiteAnalyzer = class {
|
|
|
2215
3899
|
return [
|
|
2216
3900
|
{
|
|
2217
3901
|
id: `test.suite.error:${packageName}`,
|
|
3902
|
+
blocking: true,
|
|
2218
3903
|
package: packageName,
|
|
2219
3904
|
source: "test",
|
|
2220
3905
|
severity: "major",
|
|
@@ -2247,25 +3932,7 @@ var TestSuiteAnalyzer = class {
|
|
|
2247
3932
|
return [];
|
|
2248
3933
|
}
|
|
2249
3934
|
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
|
-
];
|
|
3935
|
+
return [];
|
|
2269
3936
|
}
|
|
2270
3937
|
const failedSuites = testRun.suites.filter((s) => s.status === "failed");
|
|
2271
3938
|
const failedTests = failedSuites.flatMap((s) => s.tests.filter((t) => t.status === "failed"));
|
|
@@ -2276,6 +3943,7 @@ var TestSuiteAnalyzer = class {
|
|
|
2276
3943
|
package: packageName,
|
|
2277
3944
|
source: "test",
|
|
2278
3945
|
severity: "major",
|
|
3946
|
+
blocking: true,
|
|
2279
3947
|
confidence: 0.85,
|
|
2280
3948
|
title: `Test suite failed (${testRun.failedTests}/${testRun.totalTests} tests failed)`,
|
|
2281
3949
|
description: `${testRun.failedTests} tests failed via ${adapterName}:
|
|
@@ -2292,9 +3960,53 @@ ${failureMessages}`,
|
|
|
2292
3960
|
];
|
|
2293
3961
|
}
|
|
2294
3962
|
};
|
|
3963
|
+
var UNINTERESTING_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
3964
|
+
"node_modules",
|
|
3965
|
+
"dist",
|
|
3966
|
+
"build",
|
|
3967
|
+
"out",
|
|
3968
|
+
"coverage",
|
|
3969
|
+
"target",
|
|
3970
|
+
"tmp",
|
|
3971
|
+
"test-results",
|
|
3972
|
+
"playwright-report"
|
|
3973
|
+
]);
|
|
3974
|
+
async function listProjectFiles(root, { maxDepth = 3, maxEntries = 5e3 } = {}) {
|
|
3975
|
+
const files = [];
|
|
3976
|
+
const queue = [
|
|
3977
|
+
{ dir: root, depth: 0, prefix: "" }
|
|
3978
|
+
];
|
|
3979
|
+
while (queue.length > 0 && files.length < maxEntries) {
|
|
3980
|
+
const { dir, depth, prefix } = queue.shift();
|
|
3981
|
+
let entries;
|
|
3982
|
+
try {
|
|
3983
|
+
entries = await readdir5(dir, { withFileTypes: true });
|
|
3984
|
+
} catch {
|
|
3985
|
+
continue;
|
|
3986
|
+
}
|
|
3987
|
+
for (const entry of entries) {
|
|
3988
|
+
if (files.length >= maxEntries)
|
|
3989
|
+
break;
|
|
3990
|
+
const relative3 = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
3991
|
+
if (entry.isDirectory()) {
|
|
3992
|
+
if (UNINTERESTING_DIRECTORIES.has(entry.name) || entry.name.startsWith("."))
|
|
3993
|
+
continue;
|
|
3994
|
+
if (depth < maxDepth) {
|
|
3995
|
+
queue.push({ dir: join17(dir, entry.name), depth: depth + 1, prefix: relative3 });
|
|
3996
|
+
}
|
|
3997
|
+
} else {
|
|
3998
|
+
files.push(relative3);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
return files;
|
|
4003
|
+
}
|
|
2295
4004
|
|
|
2296
4005
|
// ../test-adapter-vitest/dist/vitest.js
|
|
2297
|
-
import { spawn as
|
|
4006
|
+
import { spawn as spawn6 } from "child_process";
|
|
4007
|
+
import { mkdtemp as mkdtemp6, readFile as readFile13, rm as rm5 } from "fs/promises";
|
|
4008
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
4009
|
+
import { join as join18 } from "path";
|
|
2298
4010
|
var VitestTestAdapter = class {
|
|
2299
4011
|
name = "vitest";
|
|
2300
4012
|
async detect(ctx) {
|
|
@@ -2315,9 +4027,20 @@ var VitestTestAdapter = class {
|
|
|
2315
4027
|
async run(ctx) {
|
|
2316
4028
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2317
4029
|
const startTime = Date.now();
|
|
2318
|
-
const
|
|
4030
|
+
const reportDirectory = await mkdtemp6(join18(tmpdir6(), "verelease-vitest-"));
|
|
4031
|
+
const reportFile = join18(reportDirectory, "report.json");
|
|
4032
|
+
const declaredArgs = await readVitestScriptArgs(ctx.packagePath);
|
|
4033
|
+
const command = [
|
|
4034
|
+
"npx",
|
|
4035
|
+
"vitest",
|
|
4036
|
+
"run",
|
|
4037
|
+
"--reporter=json",
|
|
4038
|
+
`--outputFile=${reportFile}`,
|
|
4039
|
+
...declaredArgs,
|
|
4040
|
+
...ctx.testFiles || []
|
|
4041
|
+
];
|
|
2319
4042
|
return new Promise((resolve3) => {
|
|
2320
|
-
const child =
|
|
4043
|
+
const child = spawn6(command[0], command.slice(1), {
|
|
2321
4044
|
cwd: ctx.packagePath,
|
|
2322
4045
|
env: {
|
|
2323
4046
|
...process.env,
|
|
@@ -2334,12 +4057,19 @@ var VitestTestAdapter = class {
|
|
|
2334
4057
|
child.stderr?.on("data", (chunk) => {
|
|
2335
4058
|
stderr += chunk.toString();
|
|
2336
4059
|
});
|
|
2337
|
-
child.on("close", (code) => {
|
|
4060
|
+
child.on("close", async (code) => {
|
|
2338
4061
|
const durationMs = Date.now() - startTime;
|
|
2339
|
-
|
|
2340
|
-
|
|
4062
|
+
let jsonOutput = stdout;
|
|
4063
|
+
try {
|
|
4064
|
+
jsonOutput = await readFile13(reportFile, "utf8");
|
|
4065
|
+
} catch {
|
|
4066
|
+
} finally {
|
|
4067
|
+
await rm5(reportDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
4068
|
+
}
|
|
4069
|
+
resolve3(this.parseJsonOutput(jsonOutput, stderr, code === 0, durationMs, startedAt, command));
|
|
2341
4070
|
});
|
|
2342
4071
|
child.on("error", (err) => {
|
|
4072
|
+
void rm5(reportDirectory, { recursive: true, force: true });
|
|
2343
4073
|
resolve3({
|
|
2344
4074
|
adapter: this.name,
|
|
2345
4075
|
runtime: process.version,
|
|
@@ -2432,9 +4162,74 @@ var VitestTestAdapter = class {
|
|
|
2432
4162
|
};
|
|
2433
4163
|
}
|
|
2434
4164
|
};
|
|
4165
|
+
async function readVitestScriptArgs(packagePath) {
|
|
4166
|
+
let script = "";
|
|
4167
|
+
try {
|
|
4168
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
4169
|
+
const { join: join21 } = await import("path");
|
|
4170
|
+
const manifest = JSON.parse(await readFile16(join21(packagePath, "package.json"), "utf8"));
|
|
4171
|
+
script = manifest.scripts?.test ?? "";
|
|
4172
|
+
} catch {
|
|
4173
|
+
return [];
|
|
4174
|
+
}
|
|
4175
|
+
if (!script.includes("vitest"))
|
|
4176
|
+
return [];
|
|
4177
|
+
const tokens = tokenizeCommand(script);
|
|
4178
|
+
const start = tokens.indexOf("vitest");
|
|
4179
|
+
if (start === -1)
|
|
4180
|
+
return [];
|
|
4181
|
+
const args = [];
|
|
4182
|
+
for (let index = start + 1; index < tokens.length; index += 1) {
|
|
4183
|
+
const token = tokens[index];
|
|
4184
|
+
if (token === "run")
|
|
4185
|
+
continue;
|
|
4186
|
+
if (token.startsWith("--reporter")) {
|
|
4187
|
+
if (!token.includes("="))
|
|
4188
|
+
index += 1;
|
|
4189
|
+
continue;
|
|
4190
|
+
}
|
|
4191
|
+
if (token.startsWith("--outputFile")) {
|
|
4192
|
+
if (!token.includes("="))
|
|
4193
|
+
index += 1;
|
|
4194
|
+
continue;
|
|
4195
|
+
}
|
|
4196
|
+
if (token === "&&" || token === ";")
|
|
4197
|
+
break;
|
|
4198
|
+
args.push(token);
|
|
4199
|
+
}
|
|
4200
|
+
return args;
|
|
4201
|
+
}
|
|
4202
|
+
function tokenizeCommand(command) {
|
|
4203
|
+
const tokens = [];
|
|
4204
|
+
let current = "";
|
|
4205
|
+
let quote = null;
|
|
4206
|
+
for (const character of command) {
|
|
4207
|
+
if (quote) {
|
|
4208
|
+
if (character === quote)
|
|
4209
|
+
quote = null;
|
|
4210
|
+
else
|
|
4211
|
+
current += character;
|
|
4212
|
+
continue;
|
|
4213
|
+
}
|
|
4214
|
+
if (character === '"' || character === "'") {
|
|
4215
|
+
quote = character;
|
|
4216
|
+
continue;
|
|
4217
|
+
}
|
|
4218
|
+
if (/\s/u.test(character)) {
|
|
4219
|
+
if (current)
|
|
4220
|
+
tokens.push(current);
|
|
4221
|
+
current = "";
|
|
4222
|
+
continue;
|
|
4223
|
+
}
|
|
4224
|
+
current += character;
|
|
4225
|
+
}
|
|
4226
|
+
if (current)
|
|
4227
|
+
tokens.push(current);
|
|
4228
|
+
return tokens;
|
|
4229
|
+
}
|
|
2435
4230
|
|
|
2436
4231
|
// ../test-adapter-jest/dist/jest.js
|
|
2437
|
-
import { spawn as
|
|
4232
|
+
import { spawn as spawn7 } from "child_process";
|
|
2438
4233
|
var JestTestAdapter = class {
|
|
2439
4234
|
name = "jest";
|
|
2440
4235
|
async detect(ctx) {
|
|
@@ -2458,7 +4253,7 @@ var JestTestAdapter = class {
|
|
|
2458
4253
|
const startTime = Date.now();
|
|
2459
4254
|
const command = ["npx", "jest", "--json", ...ctx.testFiles || []];
|
|
2460
4255
|
return new Promise((resolve3) => {
|
|
2461
|
-
const child =
|
|
4256
|
+
const child = spawn7(command[0], command.slice(1), {
|
|
2462
4257
|
cwd: ctx.packagePath,
|
|
2463
4258
|
env: {
|
|
2464
4259
|
...process.env,
|
|
@@ -2574,7 +4369,7 @@ var JestTestAdapter = class {
|
|
|
2574
4369
|
};
|
|
2575
4370
|
|
|
2576
4371
|
// ../test-adapter-bun/dist/bun.js
|
|
2577
|
-
import { spawn as
|
|
4372
|
+
import { spawn as spawn8 } from "child_process";
|
|
2578
4373
|
var BunTestAdapter = class {
|
|
2579
4374
|
name = "bun:test";
|
|
2580
4375
|
async detect(ctx) {
|
|
@@ -2591,7 +4386,7 @@ var BunTestAdapter = class {
|
|
|
2591
4386
|
const startTime = Date.now();
|
|
2592
4387
|
const command = ["bun", "test", ...ctx.testFiles || []];
|
|
2593
4388
|
return new Promise((resolve3) => {
|
|
2594
|
-
const child =
|
|
4389
|
+
const child = spawn8(command[0], command.slice(1), {
|
|
2595
4390
|
cwd: ctx.packagePath,
|
|
2596
4391
|
env: {
|
|
2597
4392
|
...process.env,
|
|
@@ -2679,7 +4474,7 @@ var BunTestAdapter = class {
|
|
|
2679
4474
|
};
|
|
2680
4475
|
|
|
2681
4476
|
// ../test-adapter-deno/dist/deno.js
|
|
2682
|
-
import { spawn as
|
|
4477
|
+
import { spawn as spawn9 } from "child_process";
|
|
2683
4478
|
var DenoTestAdapter = class {
|
|
2684
4479
|
name = "deno:test";
|
|
2685
4480
|
async detect(ctx) {
|
|
@@ -2696,7 +4491,7 @@ var DenoTestAdapter = class {
|
|
|
2696
4491
|
const startTime = Date.now();
|
|
2697
4492
|
const command = ["deno", "test", "--allow-all", ...ctx.testFiles || []];
|
|
2698
4493
|
return new Promise((resolve3) => {
|
|
2699
|
-
const child =
|
|
4494
|
+
const child = spawn9(command[0], command.slice(1), {
|
|
2700
4495
|
cwd: ctx.packagePath,
|
|
2701
4496
|
env: {
|
|
2702
4497
|
...process.env,
|
|
@@ -2784,18 +4579,29 @@ var DenoTestAdapter = class {
|
|
|
2784
4579
|
|
|
2785
4580
|
// src/cli.ts
|
|
2786
4581
|
import semver4 from "semver";
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
4582
|
+
var RELEASE_CHANNELS = /* @__PURE__ */ new Set(["stable", "alpha", "beta", "rc", "canary"]);
|
|
4583
|
+
function resolveReleaseChannel(requestedChannel, configuredChannel, packageVersions) {
|
|
4584
|
+
const explicitChannel = requestedChannel || configuredChannel;
|
|
4585
|
+
if (explicitChannel) {
|
|
4586
|
+
if (!RELEASE_CHANNELS.has(explicitChannel)) {
|
|
4587
|
+
throw new Error(
|
|
4588
|
+
`Unsupported release channel "${explicitChannel}". Choose stable, alpha, beta, rc, or canary.`
|
|
4589
|
+
);
|
|
4590
|
+
}
|
|
4591
|
+
return explicitChannel;
|
|
4592
|
+
}
|
|
4593
|
+
const prereleaseChannels = new Set(
|
|
4594
|
+
packageVersions.map((version2) => semver4.parse(version2)?.prerelease[0]).filter((channel) => typeof channel === "string")
|
|
4595
|
+
);
|
|
4596
|
+
if (prereleaseChannels.size === 1) {
|
|
4597
|
+
return [...prereleaseChannels][0];
|
|
4598
|
+
}
|
|
4599
|
+
if (prereleaseChannels.size > 1) {
|
|
4600
|
+
throw new Error(
|
|
4601
|
+
`Packages are on multiple prerelease channels (${[...prereleaseChannels].join(", ")}). Choose an explicit channel with --channel.`
|
|
4602
|
+
);
|
|
2798
4603
|
}
|
|
4604
|
+
return "stable";
|
|
2799
4605
|
}
|
|
2800
4606
|
var VereleaseCli = class {
|
|
2801
4607
|
cwd;
|
|
@@ -2803,15 +4609,30 @@ var VereleaseCli = class {
|
|
|
2803
4609
|
this.cwd = resolve2(options.cwd || process.cwd());
|
|
2804
4610
|
}
|
|
2805
4611
|
async createPlan(options = {}) {
|
|
4612
|
+
const isInteractive = process.stdout.isTTY && !options.json;
|
|
4613
|
+
const spin = isInteractive ? prompts3.spinner() : void 0;
|
|
4614
|
+
spin?.start("Resolving workspace package graph");
|
|
2806
4615
|
const config = await loadConfig(this.cwd);
|
|
2807
4616
|
const resolver = new DefaultWorkspaceResolver();
|
|
2808
4617
|
const discovery = await resolver.resolve(this.cwd);
|
|
4618
|
+
const channel = resolveReleaseChannel(
|
|
4619
|
+
options.channel,
|
|
4620
|
+
config.versioning?.channel,
|
|
4621
|
+
discovery.packages.map((pkg) => pkg.version)
|
|
4622
|
+
);
|
|
4623
|
+
spin?.stop(`Discovered ${colors3.bold(discovery.packages.length)} workspace package${discovery.packages.length === 1 ? "" : "s"}`);
|
|
4624
|
+
for (const skippedPackage of discovery.skipped ?? []) {
|
|
4625
|
+
console.log(
|
|
4626
|
+
`${colors3.dim("skipped")} ${skippedPackage.name ?? skippedPackage.directory}: ${skippedPackage.reason}`
|
|
4627
|
+
);
|
|
4628
|
+
}
|
|
2809
4629
|
const contractStore = new FileContractStore(
|
|
2810
|
-
|
|
4630
|
+
join19(this.cwd, config.contracts?.directory || ".verelease/contracts")
|
|
2811
4631
|
);
|
|
2812
4632
|
const contractAnalyzer = new ContractCompatibilityAnalyzer(contractStore);
|
|
2813
4633
|
const apiDiffAnalyzer = new ApiDiffAnalyzer();
|
|
2814
4634
|
const baselineFetcher = new BaselineFetcher();
|
|
4635
|
+
const gitBaselineResolver = new GitBaselineResolver(this.cwd);
|
|
2815
4636
|
const testSuiteAnalyzer = config.tests?.skip ? null : new TestSuiteAnalyzer({
|
|
2816
4637
|
adapters: [
|
|
2817
4638
|
new VitestTestAdapter(),
|
|
@@ -2824,12 +4645,32 @@ var VereleaseCli = class {
|
|
|
2824
4645
|
});
|
|
2825
4646
|
const semverEngine = new DefaultSemVerDecisionEngine();
|
|
2826
4647
|
const npmPublisher = new NpmPublishProvider();
|
|
4648
|
+
const cargoPublisher = new CargoPublishProvider();
|
|
4649
|
+
const cargoSemVerAnalyzer = new CargoSemVerAnalyzer();
|
|
2827
4650
|
const evidenceMap = /* @__PURE__ */ new Map();
|
|
4651
|
+
const comparedAgainst = /* @__PURE__ */ new Map();
|
|
2828
4652
|
const initialBumps = /* @__PURE__ */ new Map();
|
|
2829
|
-
const baselineTmpDir = await
|
|
4653
|
+
const baselineTmpDir = await mkdtemp7(join19(tmpdir7(), "verelease-baseline-"));
|
|
2830
4654
|
try {
|
|
4655
|
+
spin?.start("Evaluating compatibility evidence (API diffs, tests & contracts)");
|
|
4656
|
+
const planGit = new GitManager(this.cwd);
|
|
2831
4657
|
for (const pkg of discovery.packages) {
|
|
2832
4658
|
const evidence = [];
|
|
4659
|
+
const releaseRef = await planGit.lastReleaseTag(pkg.name) ?? await planGit.latestReleaseTag();
|
|
4660
|
+
if (releaseRef) {
|
|
4661
|
+
const changed = await planGit.changedFilesSince(releaseRef, pkg.directory);
|
|
4662
|
+
if (changed.length === 0) {
|
|
4663
|
+
evidenceMap.set(pkg.name, []);
|
|
4664
|
+
initialBumps.set(pkg.name, {
|
|
4665
|
+
name: pkg.name,
|
|
4666
|
+
currentVersion: pkg.version,
|
|
4667
|
+
proposedVersion: pkg.version,
|
|
4668
|
+
bump: "none",
|
|
4669
|
+
reasons: [`Unchanged since ${releaseRef}.`]
|
|
4670
|
+
});
|
|
4671
|
+
continue;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
2833
4674
|
try {
|
|
2834
4675
|
const contractEv = await contractAnalyzer.analyze({
|
|
2835
4676
|
packageName: pkg.name,
|
|
@@ -2839,23 +4680,58 @@ var VereleaseCli = class {
|
|
|
2839
4680
|
evidence.push(...contractEv);
|
|
2840
4681
|
} catch {
|
|
2841
4682
|
}
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
4683
|
+
if (pkg.ecosystem === "cargo") {
|
|
4684
|
+
try {
|
|
4685
|
+
const cargoEv = await cargoSemVerAnalyzer.analyze({
|
|
4686
|
+
packageName: pkg.name,
|
|
4687
|
+
packagePath: pkg.directory,
|
|
4688
|
+
currentVersion: pkg.version
|
|
4689
|
+
});
|
|
4690
|
+
evidence.push(...cargoEv);
|
|
4691
|
+
} catch {
|
|
4692
|
+
}
|
|
4693
|
+
} else {
|
|
4694
|
+
let baselinePath;
|
|
4695
|
+
let previousVersion;
|
|
4696
|
+
let releaseBaselineCleanup;
|
|
4697
|
+
let baselineOrigin;
|
|
4698
|
+
try {
|
|
4699
|
+
const fromHistory = await gitBaselineResolver.resolve(pkg.name, pkg.directory);
|
|
4700
|
+
if (fromHistory) {
|
|
4701
|
+
baselinePath = fromHistory.entryPointPath;
|
|
4702
|
+
previousVersion = fromHistory.ref;
|
|
4703
|
+
releaseBaselineCleanup = fromHistory.cleanup;
|
|
4704
|
+
baselineOrigin = `git tag ${fromHistory.ref}`;
|
|
4705
|
+
}
|
|
4706
|
+
} catch {
|
|
4707
|
+
}
|
|
4708
|
+
if (!baselinePath && !pkg.isPrivate) {
|
|
4709
|
+
try {
|
|
4710
|
+
const fromRegistry = await baselineFetcher.fetch(pkg.name, baselineTmpDir);
|
|
4711
|
+
if (fromRegistry) {
|
|
4712
|
+
baselinePath = fromRegistry.entryPointPath;
|
|
4713
|
+
previousVersion = fromRegistry.version;
|
|
4714
|
+
baselineOrigin = `npm ${pkg.name}@${fromRegistry.version}`;
|
|
4715
|
+
}
|
|
4716
|
+
} catch {
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
try {
|
|
4720
|
+
const apiEv = await apiDiffAnalyzer.analyze({
|
|
4721
|
+
packageName: pkg.name,
|
|
4722
|
+
packagePath: pkg.directory,
|
|
4723
|
+
currentVersion: pkg.version,
|
|
4724
|
+
baselinePath,
|
|
4725
|
+
previousVersion
|
|
4726
|
+
});
|
|
4727
|
+
evidence.push(...apiEv);
|
|
4728
|
+
} catch {
|
|
4729
|
+
} finally {
|
|
4730
|
+
await releaseBaselineCleanup?.().catch(() => void 0);
|
|
4731
|
+
}
|
|
4732
|
+
if (baselineOrigin) {
|
|
4733
|
+
comparedAgainst.set(pkg.name, baselineOrigin);
|
|
2846
4734
|
}
|
|
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
4735
|
}
|
|
2860
4736
|
if (testSuiteAnalyzer) {
|
|
2861
4737
|
try {
|
|
@@ -2874,14 +4750,13 @@ var VereleaseCli = class {
|
|
|
2874
4750
|
pkg.version,
|
|
2875
4751
|
evidence,
|
|
2876
4752
|
config.versioning,
|
|
2877
|
-
|
|
4753
|
+
channel
|
|
2878
4754
|
);
|
|
2879
4755
|
const forced = options.forceBump?.[pkg.name] || options.defaultBump;
|
|
2880
4756
|
let bump = forced || decision.bump;
|
|
2881
4757
|
let proposedVersion = decision.proposedVersion;
|
|
2882
4758
|
const reasons = [...decision.reasons];
|
|
2883
4759
|
if (forced && decision.bump === "none") {
|
|
2884
|
-
const channel = options.channel || config.versioning?.channel || "stable";
|
|
2885
4760
|
if (channel !== "stable") {
|
|
2886
4761
|
const baseBump = `pre${forced}`;
|
|
2887
4762
|
proposedVersion = semver4.inc(pkg.version, baseBump, channel) || pkg.version;
|
|
@@ -2892,10 +4767,20 @@ var VereleaseCli = class {
|
|
|
2892
4767
|
}
|
|
2893
4768
|
if (!pkg.isPrivate) {
|
|
2894
4769
|
try {
|
|
2895
|
-
|
|
2896
|
-
const
|
|
2897
|
-
if (
|
|
2898
|
-
|
|
4770
|
+
let publishedVersions = [];
|
|
4771
|
+
const registryName = pkg.ecosystem === "cargo" ? "crates.io" : "npm registry";
|
|
4772
|
+
if (pkg.ecosystem === "cargo") {
|
|
4773
|
+
const res = await cargoPublisher.queryCratesIo(pkg.name);
|
|
4774
|
+
publishedVersions = res.versions;
|
|
4775
|
+
} else {
|
|
4776
|
+
publishedVersions = await npmPublisher.getPublishedVersions(pkg.name);
|
|
4777
|
+
}
|
|
4778
|
+
const proposedCollides = publishedVersions.includes(proposedVersion);
|
|
4779
|
+
const currentCollidesWithoutBump = bump === "none" && publishedVersions.includes(pkg.version);
|
|
4780
|
+
if (proposedCollides || currentCollidesWithoutBump) {
|
|
4781
|
+
reasons.push(
|
|
4782
|
+
proposedCollides ? `Version ${proposedVersion} is already published on ${registryName}.` : `Current version ${pkg.version} is already published on ${registryName}; a bump is required.`
|
|
4783
|
+
);
|
|
2899
4784
|
let nextCandidate = proposedVersion;
|
|
2900
4785
|
if (channel !== "stable") {
|
|
2901
4786
|
const baseBump = bump === "none" ? "patch" : bump;
|
|
@@ -2922,6 +4807,14 @@ var VereleaseCli = class {
|
|
|
2922
4807
|
} catch {
|
|
2923
4808
|
}
|
|
2924
4809
|
}
|
|
4810
|
+
const origin = comparedAgainst.get(pkg.name);
|
|
4811
|
+
if (origin) {
|
|
4812
|
+
reasons.push(`Compared against ${origin}.`);
|
|
4813
|
+
} else if (pkg.ecosystem !== "cargo") {
|
|
4814
|
+
reasons.push(
|
|
4815
|
+
"No previous release to compare against: this package has no release tag and is not published. Its first release cannot be inferred from an API diff."
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
2925
4818
|
initialBumps.set(pkg.name, {
|
|
2926
4819
|
name: pkg.name,
|
|
2927
4820
|
currentVersion: pkg.version,
|
|
@@ -2930,10 +4823,12 @@ var VereleaseCli = class {
|
|
|
2930
4823
|
reasons
|
|
2931
4824
|
});
|
|
2932
4825
|
}
|
|
4826
|
+
spin?.stop("Compatibility evidence evaluated");
|
|
2933
4827
|
} finally {
|
|
2934
|
-
await
|
|
4828
|
+
await rm6(baselineTmpDir, { recursive: true, force: true }).catch(() => {
|
|
2935
4829
|
});
|
|
2936
4830
|
}
|
|
4831
|
+
spin?.start("Propagating dependency updates across workspace graph");
|
|
2937
4832
|
const propagator = new VersionPropagator(discovery.graph);
|
|
2938
4833
|
const { bumps: finalBumps, dependencyUpdates } = propagator.propagate(initialBumps);
|
|
2939
4834
|
const publishOrder = discovery.graph.getTopologicalOrder();
|
|
@@ -2949,7 +4844,7 @@ var VereleaseCli = class {
|
|
|
2949
4844
|
pkg.version,
|
|
2950
4845
|
evidence,
|
|
2951
4846
|
config.versioning,
|
|
2952
|
-
|
|
4847
|
+
channel
|
|
2953
4848
|
);
|
|
2954
4849
|
packagePlans.push({
|
|
2955
4850
|
name: pkg.name,
|
|
@@ -2957,11 +4852,12 @@ var VereleaseCli = class {
|
|
|
2957
4852
|
currentVersion: pkg.version,
|
|
2958
4853
|
proposedVersion: bumpTarget.proposedVersion,
|
|
2959
4854
|
bump: bumpTarget.bump,
|
|
2960
|
-
channel
|
|
4855
|
+
channel,
|
|
2961
4856
|
evidence,
|
|
2962
4857
|
reasons: bumpTarget.reasons,
|
|
2963
4858
|
confidence: decision.confidence,
|
|
2964
4859
|
requiresApproval: decision.requiresApproval,
|
|
4860
|
+
...decision.blocked ? { blocked: true } : {},
|
|
2965
4861
|
dependentUpdates: depUpdates.map((d) => ({
|
|
2966
4862
|
dependentName: pkgName,
|
|
2967
4863
|
dependencyName: d.dependencyName,
|
|
@@ -2970,15 +4866,17 @@ var VereleaseCli = class {
|
|
|
2970
4866
|
newRange: d.newRange
|
|
2971
4867
|
})),
|
|
2972
4868
|
filesToUpdate: [pkg.manifestPath],
|
|
2973
|
-
publish: !pkg.isPrivate && bumpTarget.bump !== "none"
|
|
4869
|
+
publish: !pkg.isPrivate && bumpTarget.bump !== "none",
|
|
4870
|
+
ecosystem: pkg.ecosystem
|
|
2974
4871
|
});
|
|
2975
4872
|
}
|
|
4873
|
+
spin?.stop(`Calculated release plan [Channel: ${colors3.cyan(channel)}]`);
|
|
2976
4874
|
const plan = {
|
|
2977
4875
|
schemaVersion: 1,
|
|
2978
4876
|
id: `plan-${Date.now()}`,
|
|
2979
4877
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2980
4878
|
rootDirectory: this.cwd,
|
|
2981
|
-
channel
|
|
4879
|
+
channel,
|
|
2982
4880
|
packages: packagePlans,
|
|
2983
4881
|
publishOrder,
|
|
2984
4882
|
summary: {
|
|
@@ -2993,6 +4891,11 @@ var VereleaseCli = class {
|
|
|
2993
4891
|
return { plan, evidenceMap };
|
|
2994
4892
|
}
|
|
2995
4893
|
async runPlanCommand(options = {}) {
|
|
4894
|
+
if (!options.json) {
|
|
4895
|
+
prompts3.intro(
|
|
4896
|
+
colors3.cyan(colors3.bold("\u25C9 Verelease")) + colors3.dim(" evidence-based release plan")
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
2996
4899
|
const { plan } = await this.createPlan(options);
|
|
2997
4900
|
if (options.json) {
|
|
2998
4901
|
console.log(JSON.stringify(plan, null, 2));
|
|
@@ -3000,198 +4903,269 @@ var VereleaseCli = class {
|
|
|
3000
4903
|
}
|
|
3001
4904
|
this.printPlan(plan);
|
|
3002
4905
|
try {
|
|
3003
|
-
const verelDir =
|
|
4906
|
+
const verelDir = join19(this.cwd, ".verelease");
|
|
3004
4907
|
await mkdir6(verelDir, { recursive: true });
|
|
3005
|
-
await
|
|
3006
|
-
|
|
4908
|
+
await writeFile6(
|
|
4909
|
+
join19(verelDir, "release-plan.json"),
|
|
3007
4910
|
JSON.stringify(plan, null, 2),
|
|
3008
4911
|
"utf8"
|
|
3009
4912
|
);
|
|
3010
4913
|
} catch {
|
|
3011
4914
|
}
|
|
4915
|
+
prompts3.outro(
|
|
4916
|
+
plan.summary.bumpedPackages === 0 ? colors3.green("\u2728 All packages are up to date.") : colors3.cyan(
|
|
4917
|
+
`Plan ready: run ${colors3.bold(`verelease release --channel ${plan.channel}`)} to execute.`
|
|
4918
|
+
)
|
|
4919
|
+
);
|
|
3012
4920
|
return plan;
|
|
3013
4921
|
}
|
|
3014
4922
|
async runReleaseCommand(options = {}) {
|
|
4923
|
+
if (!options.dryRun) {
|
|
4924
|
+
const git2 = new GitManager(this.cwd);
|
|
4925
|
+
let isClean;
|
|
4926
|
+
try {
|
|
4927
|
+
isClean = await git2.isClean();
|
|
4928
|
+
} catch (error) {
|
|
4929
|
+
throw new Error(
|
|
4930
|
+
"Cannot verify that the release working tree is clean. Run verelease from a Git repository before publishing.",
|
|
4931
|
+
{ cause: error }
|
|
4932
|
+
);
|
|
4933
|
+
}
|
|
4934
|
+
if (!isClean) {
|
|
4935
|
+
throw new Error(
|
|
4936
|
+
"Refusing to release from a dirty working tree. Commit, stash, or remove all changes before publishing."
|
|
4937
|
+
);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
if (!options.json) {
|
|
4941
|
+
prompts3.intro(
|
|
4942
|
+
colors3.cyan(colors3.bold("\u25C9 Verelease")) + colors3.dim(
|
|
4943
|
+
` release pipeline [${colors3.bold(options.channel || "auto")}]` + (options.dryRun ? colors3.yellow(" (DRY RUN)") : "")
|
|
4944
|
+
)
|
|
4945
|
+
);
|
|
4946
|
+
}
|
|
3015
4947
|
const { plan, evidenceMap } = await this.createPlan(options);
|
|
3016
|
-
console.log(`
|
|
3017
|
-
\u{1F680} Verelease Pipeline [Channel: ${plan.channel}, Dry-run: ${!!options.dryRun}]
|
|
3018
|
-
`);
|
|
3019
4948
|
this.printPlan(plan);
|
|
3020
4949
|
if (plan.summary.bumpedPackages === 0) {
|
|
3021
|
-
|
|
4950
|
+
prompts3.outro(colors3.green("\u2728 No packages require release."));
|
|
3022
4951
|
return;
|
|
3023
4952
|
}
|
|
3024
4953
|
const publishable = plan.packages.filter((p) => p.publish && p.bump !== "none");
|
|
3025
4954
|
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
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
if (
|
|
3034
|
-
|
|
4955
|
+
const promptDescription = publishable.length > 0 ? `publishing ${publishable.map((p) => colors3.cyan(`${p.name}@${p.proposedVersion}`)).join(", ")}` + (internalBumps.length > 0 ? ` and bumping ${internalBumps.length} internal package(s)` : "") : `bumping ${internalBumps.length} internal package(s)`;
|
|
4956
|
+
const isInteractive = !options.dryRun && !options.yes && !process.env.CI && process.stdin.isTTY;
|
|
4957
|
+
const npmPublisher = new NpmPublishProvider();
|
|
4958
|
+
const cargoPublisher = new CargoPublishProvider();
|
|
4959
|
+
const hasNpmPackages = publishable.some((p) => p.ecosystem !== "cargo");
|
|
4960
|
+
const hasCargoPackages = publishable.some((p) => p.ecosystem === "cargo");
|
|
4961
|
+
if (publishable.length > 0 && !options.dryRun) {
|
|
4962
|
+
if (hasNpmPackages) {
|
|
4963
|
+
const authStatus = await npmPublisher.checkAuth();
|
|
4964
|
+
if (!authStatus.authenticated) {
|
|
4965
|
+
if (isInteractive) {
|
|
4966
|
+
await npmPublisher.ensureAuth({ interactive: true });
|
|
4967
|
+
} else {
|
|
4968
|
+
throw new NpmAuthError({
|
|
4969
|
+
message: "You are not logged in to npm. Run 'npm login' or configure NPM_TOKEN / NODE_AUTH_TOKEN in CI."
|
|
4970
|
+
});
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
if (hasCargoPackages) {
|
|
4975
|
+
const authStatus = await cargoPublisher.checkAuth();
|
|
4976
|
+
if (!authStatus.authenticated) {
|
|
4977
|
+
if (isInteractive) {
|
|
4978
|
+
await cargoPublisher.ensureAuth({ interactive: true });
|
|
4979
|
+
} else {
|
|
4980
|
+
throw new CargoAuthError({
|
|
4981
|
+
message: "Missing crates.io token. Set CARGO_REGISTRY_TOKEN or configure credentials."
|
|
4982
|
+
});
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
if (isInteractive) {
|
|
4988
|
+
const confirmed = await prompts3.confirm({
|
|
4989
|
+
message: `Proceed with ${promptDescription}?`,
|
|
4990
|
+
initialValue: true
|
|
4991
|
+
});
|
|
4992
|
+
if (prompts3.isCancel(confirmed) || !confirmed) {
|
|
4993
|
+
prompts3.cancel("Release cancelled by user. No manifests, lockfiles, or packages were modified.");
|
|
3035
4994
|
return;
|
|
3036
4995
|
}
|
|
3037
4996
|
}
|
|
3038
|
-
|
|
4997
|
+
if (options.dryRun) {
|
|
4998
|
+
prompts3.log.info(colors3.yellow("DRY RUN: Manifest mutations, changelogs, git tags, and registry publishing skipped."));
|
|
4999
|
+
prompts3.outro(colors3.green("Dry run completed successfully."));
|
|
5000
|
+
return;
|
|
5001
|
+
}
|
|
3039
5002
|
const lockfileAdapter = new AutoLockfileAdapter();
|
|
3040
|
-
const npmPublisher = new NpmPublishProvider();
|
|
3041
5003
|
const ai = new TanStackReleaseAI();
|
|
3042
5004
|
const git = new GitManager(this.cwd);
|
|
3043
|
-
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
await transaction.rollback();
|
|
3055
|
-
console.log("\u21A9 Rolled back manifest updates");
|
|
3056
|
-
process.exit(1);
|
|
3057
|
-
}
|
|
3058
|
-
console.log("\u2713 Lockfile regenerated successfully");
|
|
3059
|
-
console.log("\n\u{1F916} Generating changelogs & migration documentation via AI layer...");
|
|
3060
|
-
for (const pkgPlan of plan.packages) {
|
|
3061
|
-
if (pkgPlan.bump === "none") continue;
|
|
3062
|
-
const evidence = evidenceMap.get(pkgPlan.name) || [];
|
|
3063
|
-
const changelog = await ai.generateChangelog({
|
|
3064
|
-
packageName: pkgPlan.name,
|
|
3065
|
-
previousVersion: pkgPlan.currentVersion,
|
|
3066
|
-
newVersion: pkgPlan.proposedVersion,
|
|
3067
|
-
evidence
|
|
3068
|
-
});
|
|
3069
|
-
const changelogPath = join13(pkgPlan.directory, "CHANGELOG.md");
|
|
3070
|
-
let existingChangelog = null;
|
|
3071
|
-
try {
|
|
3072
|
-
existingChangelog = await readFile7(changelogPath, "utf8");
|
|
3073
|
-
} catch {
|
|
3074
|
-
existingChangelog = null;
|
|
5005
|
+
const spin = process.stdout.isTTY ? prompts3.spinner() : void 0;
|
|
5006
|
+
const releaseProgram = executeReleaseTransaction(
|
|
5007
|
+
{
|
|
5008
|
+
plan,
|
|
5009
|
+
cwd: this.cwd,
|
|
5010
|
+
lockfileAdapter,
|
|
5011
|
+
onStep: (msg) => {
|
|
5012
|
+
spin?.message(msg);
|
|
5013
|
+
},
|
|
5014
|
+
onRollback: (msg) => {
|
|
5015
|
+
prompts3.log.warn(colors3.yellow(`\u21A9 ${msg}`));
|
|
3075
5016
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
5017
|
+
},
|
|
5018
|
+
(scope) => Effect4.gen(function* () {
|
|
5019
|
+
spin?.start("Generating changelogs & migration documentation via AI layer");
|
|
5020
|
+
for (const pkgPlan of plan.packages) {
|
|
5021
|
+
if (pkgPlan.bump === "none") continue;
|
|
5022
|
+
const evidence = evidenceMap.get(pkgPlan.name) || [];
|
|
5023
|
+
const changelog = yield* Effect4.promise(
|
|
5024
|
+
() => ai.generateChangelog({
|
|
5025
|
+
packageName: pkgPlan.name,
|
|
5026
|
+
previousVersion: pkgPlan.currentVersion,
|
|
5027
|
+
newVersion: pkgPlan.proposedVersion,
|
|
5028
|
+
evidence
|
|
5029
|
+
})
|
|
5030
|
+
);
|
|
5031
|
+
const changelogPath = join19(pkgPlan.directory, "CHANGELOG.md");
|
|
5032
|
+
let existingChangelog = null;
|
|
5033
|
+
try {
|
|
5034
|
+
existingChangelog = yield* Effect4.promise(() => readFile14(changelogPath, "utf8"));
|
|
5035
|
+
} catch {
|
|
5036
|
+
existingChangelog = null;
|
|
5037
|
+
}
|
|
5038
|
+
scope.backupChangelog(changelogPath, existingChangelog);
|
|
5039
|
+
yield* Effect4.promise(
|
|
5040
|
+
() => writeFile6(
|
|
5041
|
+
changelogPath,
|
|
5042
|
+
`${changelog.markdown}
|
|
3080
5043
|
|
|
3081
5044
|
${existingChangelog || ""}`.trim() + "\n",
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
console.log(` \u2713 Updated ${pkgPlan.name} CHANGELOG.md`);
|
|
3085
|
-
if (pkgPlan.bump === "major") {
|
|
3086
|
-
const breakingEvidence = evidence.filter((e) => e.severity === "major");
|
|
3087
|
-
const guide = await ai.generateMigrationGuide({
|
|
3088
|
-
packageName: pkgPlan.name,
|
|
3089
|
-
previousVersion: pkgPlan.currentVersion,
|
|
3090
|
-
newVersion: pkgPlan.proposedVersion,
|
|
3091
|
-
breakingChanges: breakingEvidence
|
|
3092
|
-
});
|
|
3093
|
-
const migrationsDir = join13(pkgPlan.directory, "docs", "migrations");
|
|
3094
|
-
await mkdir6(migrationsDir, { recursive: true });
|
|
3095
|
-
const guidePath = join13(
|
|
3096
|
-
migrationsDir,
|
|
3097
|
-
`${pkgPlan.currentVersion.split(".")[0]}-to-${pkgPlan.proposedVersion.split(".")[0]}.md`
|
|
5045
|
+
"utf8"
|
|
5046
|
+
)
|
|
3098
5047
|
);
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
if (options.tag) {
|
|
3104
|
-
try {
|
|
3105
|
-
console.log("\n\u{1F3F7}\uFE0F Creating Git release commit and tags...");
|
|
3106
|
-
await git.createReleaseCommit([], `chore(release): publish version ${plan.id}`);
|
|
3107
|
-
for (const pkgPlan of plan.packages) {
|
|
3108
|
-
if (pkgPlan.publish) {
|
|
3109
|
-
const tagName = git.formatTagName({
|
|
5048
|
+
if (pkgPlan.bump === "major") {
|
|
5049
|
+
const breakingEvidence = evidence.filter((e) => e.severity === "major");
|
|
5050
|
+
const guide = yield* Effect4.promise(
|
|
5051
|
+
() => ai.generateMigrationGuide({
|
|
3110
5052
|
packageName: pkgPlan.name,
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
5053
|
+
previousVersion: pkgPlan.currentVersion,
|
|
5054
|
+
newVersion: pkgPlan.proposedVersion,
|
|
5055
|
+
breakingChanges: breakingEvidence
|
|
5056
|
+
})
|
|
5057
|
+
);
|
|
5058
|
+
const migrationsDir = join19(pkgPlan.directory, "docs", "migrations");
|
|
5059
|
+
yield* Effect4.promise(async () => {
|
|
5060
|
+
await mkdir6(migrationsDir, { recursive: true });
|
|
5061
|
+
const guidePath = join19(
|
|
5062
|
+
migrationsDir,
|
|
5063
|
+
`${pkgPlan.currentVersion.split(".")[0]}-to-${pkgPlan.proposedVersion.split(".")[0]}.md`
|
|
5064
|
+
);
|
|
5065
|
+
await writeFile6(guidePath, guide.markdown, "utf8");
|
|
5066
|
+
scope.trackGeneratedFile(guidePath);
|
|
5067
|
+
});
|
|
3116
5068
|
}
|
|
3117
|
-
} catch (err) {
|
|
3118
|
-
console.warn(` \u26A0\uFE0F Git tagging warning: ${err.message || String(err)}`);
|
|
3119
5069
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
5070
|
+
spin?.stop("Changelogs and documentation generated");
|
|
5071
|
+
spin?.start("Publishing packages to registries");
|
|
5072
|
+
for (const pkgName of plan.publishOrder) {
|
|
5073
|
+
const pkgPlan = plan.packages.find((p) => p.name === pkgName);
|
|
5074
|
+
if (!pkgPlan || !pkgPlan.publish) continue;
|
|
5075
|
+
const isCargo = pkgPlan.ecosystem === "cargo";
|
|
5076
|
+
const registryLabel = isCargo ? "crates.io" : "npm registry";
|
|
5077
|
+
spin?.message(`Publishing ${colors3.bold(pkgPlan.name)}@${pkgPlan.proposedVersion} to ${registryLabel}`);
|
|
5078
|
+
const result = yield* Effect4.promise(
|
|
5079
|
+
() => isCargo ? cargoPublisher.publish({
|
|
5080
|
+
packagePlan: pkgPlan,
|
|
5081
|
+
fullPlan: plan,
|
|
5082
|
+
dryRun: !!options.dryRun
|
|
5083
|
+
}) : npmPublisher.publish({
|
|
5084
|
+
packagePlan: pkgPlan,
|
|
5085
|
+
fullPlan: plan,
|
|
5086
|
+
dryRun: !!options.dryRun,
|
|
5087
|
+
extraArgs: options.otp ? ["--otp", options.otp] : void 0
|
|
5088
|
+
})
|
|
5089
|
+
);
|
|
5090
|
+
if (!result.published) {
|
|
5091
|
+
return yield* Effect4.fail(
|
|
5092
|
+
new PublishError({
|
|
5093
|
+
packageName: pkgPlan.name,
|
|
5094
|
+
message: result.error || `${registryLabel} publish failed`
|
|
5095
|
+
})
|
|
5096
|
+
);
|
|
3145
5097
|
}
|
|
3146
|
-
|
|
5098
|
+
prompts3.log.success(
|
|
5099
|
+
colors3.green(
|
|
5100
|
+
`Published ${colors3.bold(pkgPlan.name)}@${pkgPlan.proposedVersion} [${registryLabel}]`
|
|
5101
|
+
)
|
|
5102
|
+
);
|
|
5103
|
+
}
|
|
5104
|
+
spin?.stop("All packages published successfully");
|
|
5105
|
+
if (options.tag) {
|
|
5106
|
+
spin?.start("Creating Git release commit and tags");
|
|
5107
|
+
yield* Effect4.promise(async () => {
|
|
3147
5108
|
try {
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
5109
|
+
await git.createReleaseCommit([], `chore(release): publish version ${plan.id}`);
|
|
5110
|
+
for (const pkgPlan of plan.packages) {
|
|
5111
|
+
if (pkgPlan.publish) {
|
|
5112
|
+
const tagName = git.formatTagName({
|
|
5113
|
+
packageName: pkgPlan.name,
|
|
5114
|
+
version: pkgPlan.proposedVersion
|
|
5115
|
+
});
|
|
5116
|
+
await git.createTag(tagName);
|
|
5117
|
+
}
|
|
3152
5118
|
}
|
|
3153
|
-
} catch {
|
|
5119
|
+
} catch (err) {
|
|
5120
|
+
prompts3.log.warn(`Git tagging warning: ${err.message || String(err)}`);
|
|
3154
5121
|
}
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
await lockfileAdapter.regenerateLockfile(this.cwd);
|
|
3158
|
-
console.log(" \u2713 Synchronized lockfile\n");
|
|
3159
|
-
process.exit(1);
|
|
5122
|
+
});
|
|
5123
|
+
spin?.stop("Git commit and tags created");
|
|
3160
5124
|
}
|
|
3161
|
-
}
|
|
5125
|
+
})
|
|
5126
|
+
);
|
|
5127
|
+
const exit = await Effect4.runPromiseExit(releaseProgram);
|
|
5128
|
+
if (Exit2.isFailure(exit)) {
|
|
5129
|
+
prompts3.log.error(
|
|
5130
|
+
colors3.red("Publish failed. Transactional rollback has restored all files to their clean state.")
|
|
5131
|
+
);
|
|
5132
|
+
process.exit(1);
|
|
3162
5133
|
}
|
|
3163
|
-
|
|
5134
|
+
prompts3.outro(colors3.green(colors3.bold("\u{1F389} Release completed successfully!")));
|
|
3164
5135
|
}
|
|
3165
5136
|
async runExplainCommand(packageName) {
|
|
5137
|
+
prompts3.intro(
|
|
5138
|
+
colors3.cyan(colors3.bold("\u25C9 Verelease")) + colors3.dim(` explanation for ${colors3.bold(packageName)}`)
|
|
5139
|
+
);
|
|
3166
5140
|
const { plan, evidenceMap } = await this.createPlan();
|
|
3167
5141
|
const pkgPlan = plan.packages.find((p) => p.name === packageName);
|
|
3168
5142
|
if (!pkgPlan) {
|
|
3169
|
-
|
|
5143
|
+
prompts3.log.error(`Package "${packageName}" not found in workspace.`);
|
|
3170
5144
|
return;
|
|
3171
5145
|
}
|
|
3172
5146
|
const evidence = evidenceMap.get(packageName) || [];
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
`);
|
|
3183
|
-
console.log(`Evidence items (${evidence.length}):`);
|
|
5147
|
+
const badge = pkgPlan.bump === "major" ? colors3.red("\u{1F534} MAJOR") : pkgPlan.bump === "minor" ? colors3.yellow("\u{1F7E1} MINOR") : pkgPlan.bump === "patch" ? colors3.green("\u{1F7E2} PATCH") : colors3.dim("\u26AA NONE");
|
|
5148
|
+
const lines = [
|
|
5149
|
+
`${colors3.bold("Current Version:")} ${pkgPlan.currentVersion}`,
|
|
5150
|
+
`${colors3.bold("Proposed Version:")} ${colors3.cyan(colors3.bold(pkgPlan.proposedVersion))} [${badge}]`,
|
|
5151
|
+
`${colors3.bold("Confidence:")} ${(pkgPlan.confidence * 100).toFixed(0)}%`,
|
|
5152
|
+
`${colors3.bold("Requires Review:")} ${pkgPlan.requiresApproval ? colors3.yellow("YES") : colors3.green("NO")}`,
|
|
5153
|
+
"",
|
|
5154
|
+
`${colors3.bold(`Evidence Items (${evidence.length}):`)}`
|
|
5155
|
+
];
|
|
3184
5156
|
for (const ev of evidence) {
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
`);
|
|
5157
|
+
lines.push(` \u2022 [${colors3.cyan(ev.source.toUpperCase())}] (${colors3.bold(ev.severity.toUpperCase())}) ${ev.title}`);
|
|
5158
|
+
lines.push(` ${colors3.dim(ev.description)}`);
|
|
3188
5159
|
}
|
|
3189
5160
|
if (pkgPlan.reasons.length > 0) {
|
|
3190
|
-
|
|
5161
|
+
lines.push("");
|
|
5162
|
+
lines.push(colors3.bold("Decision Reasons:"));
|
|
3191
5163
|
for (const reason of pkgPlan.reasons) {
|
|
3192
|
-
|
|
5164
|
+
lines.push(` \u2022 ${reason}`);
|
|
3193
5165
|
}
|
|
3194
5166
|
}
|
|
5167
|
+
prompts3.note(lines.join("\n"), `Evidence Breakdown`);
|
|
5168
|
+
prompts3.outro("Explanation complete.");
|
|
3195
5169
|
}
|
|
3196
5170
|
async runGuideCommand(options = {}) {
|
|
3197
5171
|
if (options.json) {
|
|
@@ -3199,18 +5173,19 @@ ${existingChangelog || ""}`.trim() + "\n",
|
|
|
3199
5173
|
JSON.stringify(
|
|
3200
5174
|
{
|
|
3201
5175
|
name: "verelease",
|
|
3202
|
-
description: "
|
|
5176
|
+
description: "Universal compatibility-aware SemVer release engine",
|
|
5177
|
+
supportedEcosystems: ["npm", "cargo"],
|
|
3203
5178
|
rules: [
|
|
3204
|
-
"Never manually edit 'version' in package.json",
|
|
5179
|
+
"Never manually edit 'version' in package.json or Cargo.toml",
|
|
3205
5180
|
"SemVer bumps are calculated from contract tests, API diffs, and dependency ranges",
|
|
3206
5181
|
"Pass --yes to bypass interactive prompts in CI"
|
|
3207
5182
|
],
|
|
3208
5183
|
commands: {
|
|
3209
|
-
plan: "
|
|
3210
|
-
release: "
|
|
3211
|
-
explain: "
|
|
3212
|
-
guide: "
|
|
3213
|
-
init: "
|
|
5184
|
+
plan: "verelease plan [--channel <channel>] [--json]",
|
|
5185
|
+
release: "verelease release [--channel <channel>] [-y, --yes]",
|
|
5186
|
+
explain: "verelease explain <pkg>",
|
|
5187
|
+
guide: "verelease guide [--json]",
|
|
5188
|
+
init: "verelease init"
|
|
3214
5189
|
},
|
|
3215
5190
|
configuration: "verelease.config.ts",
|
|
3216
5191
|
contractsDirectory: ".verelease/contracts",
|
|
@@ -3222,37 +5197,33 @@ ${existingChangelog || ""}`.trim() + "\n",
|
|
|
3222
5197
|
);
|
|
3223
5198
|
return;
|
|
3224
5199
|
}
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
## Configuration
|
|
3246
|
-
Config is loaded from \`verelease.config.ts\` or \`verelease.config.js\`.
|
|
3247
|
-
Contract test suites live in \`.verelease/contracts/\`.
|
|
3248
|
-
`);
|
|
5200
|
+
prompts3.intro(colors3.cyan(colors3.bold("\u25C9 Verelease")) + colors3.dim(" agent guide"));
|
|
5201
|
+
const content = [
|
|
5202
|
+
colors3.bold("Core Rules for AI Coding Agents:"),
|
|
5203
|
+
`1. ${colors3.bold("NEVER manually edit 'version' in package.json or Cargo.toml")}: Always let Verelease compute and bump versions based on compatibility evidence.`,
|
|
5204
|
+
`2. ${colors3.bold("Inspect first")}: Run ${colors3.cyan("verelease plan")} to preview proposed version bumps across npm and Cargo packages.`,
|
|
5205
|
+
`3. ${colors3.bold("Diagnose bumps")}: Run ${colors3.cyan("verelease explain <pkg>")} to inspect compatibility evidence (TypeScript AST diffs, Rust semver-checks, test suites).`,
|
|
5206
|
+
`4. ${colors3.bold("Automated releases")}: In CI or agents, run ${colors3.cyan("verelease release --yes")} to bypass prompts.`,
|
|
5207
|
+
`5. ${colors3.bold("Interactive releases")}: Locally, Verelease prompts [y/N] and supports npm 2FA & crates.io tokens.`,
|
|
5208
|
+
"",
|
|
5209
|
+
colors3.bold("Command Reference:"),
|
|
5210
|
+
`\u2022 ${colors3.cyan("verelease plan")} Preview release plan for stable channel`,
|
|
5211
|
+
`\u2022 ${colors3.cyan("verelease plan --channel alpha")} Preview pre-release bumps for alpha channel`,
|
|
5212
|
+
`\u2022 ${colors3.cyan("verelease release")} Interactive release pipeline`,
|
|
5213
|
+
`\u2022 ${colors3.cyan("verelease release -y")} Non-interactive release (for agents/CI)`,
|
|
5214
|
+
`\u2022 ${colors3.cyan("verelease explain <package>")} Inspect evidence and decision reasons`,
|
|
5215
|
+
`\u2022 ${colors3.cyan("verelease init")} Initialize config, contracts, and AGENTS.md`
|
|
5216
|
+
].join("\n");
|
|
5217
|
+
prompts3.note(content, "Universal Agent Guide & Workflow");
|
|
5218
|
+
prompts3.outro("Guide complete.");
|
|
3249
5219
|
}
|
|
3250
5220
|
async runInitCommand(options = {}) {
|
|
3251
|
-
const configPath =
|
|
3252
|
-
const contractsDir =
|
|
3253
|
-
const agentsPath =
|
|
3254
|
-
const
|
|
3255
|
-
|
|
5221
|
+
const configPath = join19(this.cwd, "verelease.config.ts");
|
|
5222
|
+
const contractsDir = join19(this.cwd, ".verelease", "contracts");
|
|
5223
|
+
const agentsPath = join19(this.cwd, "AGENTS.md");
|
|
5224
|
+
const gitignorePath = join19(this.cwd, ".gitignore");
|
|
5225
|
+
const pkgJsonPath = join19(this.cwd, "package.json");
|
|
5226
|
+
prompts3.intro(colors3.cyan(colors3.bold("\u25C9 Verelease")) + colors3.dim(" project initialization"));
|
|
3256
5227
|
if (!existsSync(configPath)) {
|
|
3257
5228
|
const sampleConfig = `import { defineConfig } from "verelease/config";
|
|
3258
5229
|
|
|
@@ -3275,21 +5246,48 @@ export default defineConfig({
|
|
|
3275
5246
|
},
|
|
3276
5247
|
});
|
|
3277
5248
|
`;
|
|
3278
|
-
await
|
|
3279
|
-
|
|
5249
|
+
await writeFile6(configPath, sampleConfig, "utf8");
|
|
5250
|
+
prompts3.log.success(colors3.green("Created verelease.config.ts"));
|
|
3280
5251
|
} else {
|
|
3281
|
-
|
|
5252
|
+
prompts3.log.info("verelease.config.ts already exists");
|
|
3282
5253
|
}
|
|
3283
5254
|
if (!existsSync(contractsDir)) {
|
|
3284
5255
|
await mkdir6(contractsDir, { recursive: true });
|
|
3285
|
-
await
|
|
3286
|
-
|
|
5256
|
+
await writeFile6(
|
|
5257
|
+
join19(contractsDir, "README.md"),
|
|
3287
5258
|
"# Verelease Contracts\n\nPlace consumer-driven contract definitions (.json or .yaml) here.\n",
|
|
3288
5259
|
"utf8"
|
|
3289
5260
|
);
|
|
3290
|
-
|
|
5261
|
+
prompts3.log.success(colors3.green("Created .verelease/contracts/"));
|
|
3291
5262
|
} else {
|
|
3292
|
-
|
|
5263
|
+
prompts3.log.info(".verelease/contracts/ already exists");
|
|
5264
|
+
}
|
|
5265
|
+
const generatedIgnoreEntries = [
|
|
5266
|
+
".verelease/*",
|
|
5267
|
+
"!.verelease/contracts/",
|
|
5268
|
+
".vitest/"
|
|
5269
|
+
];
|
|
5270
|
+
const existingGitignore = existsSync(gitignorePath) ? await readFile14(gitignorePath, "utf8") : "";
|
|
5271
|
+
const existingIgnoreLines = new Set(
|
|
5272
|
+
existingGitignore.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
5273
|
+
);
|
|
5274
|
+
const missingIgnoreEntries = generatedIgnoreEntries.filter(
|
|
5275
|
+
(entry) => !existingIgnoreLines.has(entry)
|
|
5276
|
+
);
|
|
5277
|
+
if (missingIgnoreEntries.length > 0) {
|
|
5278
|
+
const separator = existingGitignore.length === 0 ? "" : existingGitignore.endsWith("\n") ? "\n" : "\n\n";
|
|
5279
|
+
await writeFile6(
|
|
5280
|
+
gitignorePath,
|
|
5281
|
+
`${existingGitignore}${separator}# Verelease generated files
|
|
5282
|
+
${missingIgnoreEntries.join("\n")}
|
|
5283
|
+
`,
|
|
5284
|
+
"utf8"
|
|
5285
|
+
);
|
|
5286
|
+
prompts3.log.success(
|
|
5287
|
+
colors3.green("Ignored generated Verelease and Vitest state in .gitignore")
|
|
5288
|
+
);
|
|
5289
|
+
} else {
|
|
5290
|
+
prompts3.log.info(".gitignore already excludes generated Verelease and Vitest state");
|
|
3293
5291
|
}
|
|
3294
5292
|
const agentGuidance = `
|
|
3295
5293
|
## Releases & Versioning (Verelease)
|
|
@@ -3301,24 +5299,24 @@ This repository uses [Verelease](https://github.com/ivanjeremic/verelease) for a
|
|
|
3301
5299
|
- **NEVER manually edit "version" in package.json.**
|
|
3302
5300
|
`;
|
|
3303
5301
|
if (!existsSync(agentsPath)) {
|
|
3304
|
-
await
|
|
5302
|
+
await writeFile6(agentsPath, `# Agent Guidelines
|
|
3305
5303
|
${agentGuidance}
|
|
3306
5304
|
`, "utf8");
|
|
3307
|
-
|
|
5305
|
+
prompts3.log.success(colors3.green("Created AGENTS.md with Verelease instructions"));
|
|
3308
5306
|
} else {
|
|
3309
|
-
const currentContent = await
|
|
5307
|
+
const currentContent = await readFile14(agentsPath, "utf8");
|
|
3310
5308
|
if (!currentContent.includes("Verelease")) {
|
|
3311
|
-
await
|
|
5309
|
+
await writeFile6(agentsPath, `${currentContent.trim()}
|
|
3312
5310
|
${agentGuidance}
|
|
3313
5311
|
`, "utf8");
|
|
3314
|
-
|
|
5312
|
+
prompts3.log.success(colors3.green("Appended Verelease instructions to AGENTS.md"));
|
|
3315
5313
|
} else {
|
|
3316
|
-
|
|
5314
|
+
prompts3.log.info("AGENTS.md already contains Verelease instructions");
|
|
3317
5315
|
}
|
|
3318
5316
|
}
|
|
3319
5317
|
if (existsSync(pkgJsonPath)) {
|
|
3320
5318
|
try {
|
|
3321
|
-
const pkg = JSON.parse(await
|
|
5319
|
+
const pkg = JSON.parse(await readFile14(pkgJsonPath, "utf8"));
|
|
3322
5320
|
pkg.scripts = pkg.scripts || {};
|
|
3323
5321
|
let modified = false;
|
|
3324
5322
|
if (!pkg.scripts.plan) {
|
|
@@ -3330,127 +5328,237 @@ ${agentGuidance}
|
|
|
3330
5328
|
modified = true;
|
|
3331
5329
|
}
|
|
3332
5330
|
if (modified) {
|
|
3333
|
-
await
|
|
3334
|
-
|
|
5331
|
+
await writeFile6(pkgJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5332
|
+
prompts3.log.success(colors3.green("Added 'plan' and 'release' scripts to package.json"));
|
|
3335
5333
|
}
|
|
3336
5334
|
} catch {
|
|
3337
5335
|
}
|
|
3338
5336
|
}
|
|
3339
|
-
|
|
5337
|
+
prompts3.outro(colors3.green(colors3.bold("\u2728 Verelease successfully initialized!")));
|
|
3340
5338
|
}
|
|
3341
5339
|
printPlan(plan) {
|
|
3342
|
-
|
|
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`);
|
|
3344
|
-
console.log(` VERELEASE RELEASE PLAN`);
|
|
3345
|
-
console.log(` Channel: ${plan.channel} | Packages: ${plan.summary.totalPackages} | Bumps: ${plan.summary.bumpedPackages}`);
|
|
3346
|
-
console.log(`\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
|
|
3347
|
-
`);
|
|
5340
|
+
const lines = [];
|
|
3348
5341
|
for (const pkg of plan.packages) {
|
|
3349
|
-
const
|
|
3350
|
-
|
|
3351
|
-
|
|
5342
|
+
const badge = pkg.bump === "major" ? colors3.red("\u{1F534} MAJOR") : pkg.bump === "minor" ? colors3.yellow("\u{1F7E1} MINOR") : pkg.bump === "patch" ? colors3.green("\u{1F7E2} PATCH") : colors3.dim("\u26AA NONE");
|
|
5343
|
+
const registryName = pkg.ecosystem === "cargo" ? "crates.io" : "npm";
|
|
5344
|
+
const pubTag = pkg.publish ? colors3.cyan(` [${registryName} publish]`) : colors3.dim(" [internal]");
|
|
5345
|
+
if (pkg.blocked) {
|
|
5346
|
+
lines.push(
|
|
5347
|
+
`${colors3.red("\u26D4 BLOCKED")} ${colors3.bold(pkg.name)} ${colors3.dim(
|
|
5348
|
+
`would be ${pkg.bump}`
|
|
5349
|
+
)} ${colors3.dim(`(${pkg.currentVersion} \u2192 ${pkg.proposedVersion})`)}${pubTag}`
|
|
5350
|
+
);
|
|
5351
|
+
} else {
|
|
5352
|
+
lines.push(
|
|
5353
|
+
`${badge} ${colors3.bold(pkg.name)} ${colors3.dim(pkg.currentVersion)} \u2192 ${colors3.cyan(colors3.bold(pkg.proposedVersion))}${pubTag}`
|
|
5354
|
+
);
|
|
5355
|
+
}
|
|
3352
5356
|
if (pkg.reasons.length > 0) {
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
console.log(` ${reason}`);
|
|
5357
|
+
for (const reason of pkg.reasons.slice(0, 2)) {
|
|
5358
|
+
lines.push(` ${colors3.dim("\u2022")} ${colors3.dim(reason)}`);
|
|
3356
5359
|
}
|
|
3357
5360
|
}
|
|
3358
5361
|
if (pkg.dependentUpdates.length > 0) {
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
5362
|
+
for (const dep of pkg.dependentUpdates.slice(0, 2)) {
|
|
5363
|
+
lines.push(
|
|
5364
|
+
` ${colors3.dim("\u21B3")} ${colors3.dim(`${dep.dependentName} depends on ${dep.dependencyName} (${dep.previousRange} \u2192 ${dep.newRange})`)}`
|
|
5365
|
+
);
|
|
3362
5366
|
}
|
|
3363
5367
|
}
|
|
3364
|
-
|
|
5368
|
+
lines.push("");
|
|
3365
5369
|
}
|
|
5370
|
+
const summaryLine = [
|
|
5371
|
+
`${colors3.bold(plan.summary.totalPackages)} packages`,
|
|
5372
|
+
`${colors3.green(colors3.bold(plan.summary.bumpedPackages))} bumps`,
|
|
5373
|
+
`(${colors3.green(`${plan.summary.patchBumps} patch`)}, ${colors3.yellow(`${plan.summary.minorBumps} minor`)}, ${colors3.red(`${plan.summary.majorBumps} major`)})`,
|
|
5374
|
+
`Channel: ${colors3.cyan(plan.channel)}`
|
|
5375
|
+
].join(" | ");
|
|
5376
|
+
lines.push(summaryLine);
|
|
5377
|
+
prompts3.note(lines.join("\n"), `Release Plan Summary`);
|
|
3366
5378
|
}
|
|
3367
5379
|
};
|
|
3368
5380
|
|
|
3369
5381
|
// src/bin.ts
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
5382
|
+
var __dirname2 = dirname3(fileURLToPath(import.meta.url));
|
|
5383
|
+
async function getCliVersion() {
|
|
5384
|
+
try {
|
|
5385
|
+
const pkgJsonPath = join20(__dirname2, "../package.json");
|
|
5386
|
+
const content = await readFile15(pkgJsonPath, "utf8");
|
|
5387
|
+
const parsed = JSON.parse(content);
|
|
5388
|
+
return parsed.version || "0.1.0";
|
|
5389
|
+
} catch {
|
|
5390
|
+
return "0.1.0";
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
var commonArgs = {
|
|
5394
|
+
channel: {
|
|
5395
|
+
type: "string",
|
|
5396
|
+
description: "Release channel (e.g. stable, alpha, beta, rc)"
|
|
5397
|
+
},
|
|
5398
|
+
bump: {
|
|
5399
|
+
type: "string",
|
|
5400
|
+
description: "Manual bump override (patch | minor | major)"
|
|
5401
|
+
},
|
|
5402
|
+
dryRun: {
|
|
5403
|
+
type: "boolean",
|
|
5404
|
+
description: "Simulate pipeline without modifying manifests or publishing"
|
|
5405
|
+
},
|
|
5406
|
+
yes: {
|
|
5407
|
+
type: "boolean",
|
|
5408
|
+
alias: "y",
|
|
5409
|
+
description: "Skip interactive confirmation prompt"
|
|
5410
|
+
},
|
|
5411
|
+
json: {
|
|
5412
|
+
type: "boolean",
|
|
5413
|
+
description: "Output machine-readable JSON"
|
|
5414
|
+
},
|
|
5415
|
+
tag: {
|
|
5416
|
+
type: "boolean",
|
|
5417
|
+
description: "Create git commit and tags for release"
|
|
5418
|
+
},
|
|
5419
|
+
otp: {
|
|
5420
|
+
type: "string",
|
|
5421
|
+
description: "One-time password for npm 2FA"
|
|
5422
|
+
},
|
|
5423
|
+
cwd: {
|
|
5424
|
+
type: "string",
|
|
5425
|
+
description: "Working directory path"
|
|
3395
5426
|
}
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
5427
|
+
};
|
|
5428
|
+
function toCliOptions(args) {
|
|
5429
|
+
return {
|
|
5430
|
+
channel: args.channel,
|
|
5431
|
+
defaultBump: args.bump,
|
|
5432
|
+
dryRun: Boolean(args.dryRun),
|
|
5433
|
+
yes: Boolean(args.yes),
|
|
5434
|
+
json: Boolean(args.json),
|
|
5435
|
+
tag: Boolean(args.tag),
|
|
5436
|
+
otp: args.otp,
|
|
5437
|
+
cwd: args.cwd
|
|
3401
5438
|
};
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
5439
|
+
}
|
|
5440
|
+
var subcommandExecuted = false;
|
|
5441
|
+
var planCommand = defineCommand({
|
|
5442
|
+
meta: {
|
|
5443
|
+
name: "plan",
|
|
5444
|
+
description: "Preview the release plan without making changes"
|
|
5445
|
+
},
|
|
5446
|
+
args: commonArgs,
|
|
5447
|
+
async run({ args }) {
|
|
5448
|
+
subcommandExecuted = true;
|
|
5449
|
+
const opts = toCliOptions(args);
|
|
5450
|
+
const cli = new VereleaseCli(opts);
|
|
5451
|
+
await cli.runPlanCommand(opts);
|
|
3405
5452
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
5453
|
+
});
|
|
5454
|
+
var releaseCommand = defineCommand({
|
|
5455
|
+
meta: {
|
|
5456
|
+
name: "release",
|
|
5457
|
+
description: "Apply version bumps, generate changelogs, and publish"
|
|
5458
|
+
},
|
|
5459
|
+
args: commonArgs,
|
|
5460
|
+
async run({ args }) {
|
|
5461
|
+
subcommandExecuted = true;
|
|
5462
|
+
const opts = toCliOptions(args);
|
|
5463
|
+
const cli = new VereleaseCli(opts);
|
|
5464
|
+
await cli.runReleaseCommand(opts);
|
|
3409
5465
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
5466
|
+
});
|
|
5467
|
+
var versionCommand = defineCommand({
|
|
5468
|
+
meta: {
|
|
5469
|
+
name: "version",
|
|
5470
|
+
description: "Print the verelease version"
|
|
5471
|
+
},
|
|
5472
|
+
async run() {
|
|
5473
|
+
subcommandExecuted = true;
|
|
5474
|
+
console.log(version);
|
|
3413
5475
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
5476
|
+
});
|
|
5477
|
+
var explainCommand = defineCommand({
|
|
5478
|
+
meta: {
|
|
5479
|
+
name: "explain",
|
|
5480
|
+
description: "Explain compatibility evidence and bump decision for a package"
|
|
5481
|
+
},
|
|
5482
|
+
args: {
|
|
5483
|
+
package: {
|
|
5484
|
+
type: "positional",
|
|
5485
|
+
description: "Package name to explain (e.g. @verelease/core)",
|
|
5486
|
+
required: false
|
|
5487
|
+
},
|
|
5488
|
+
...commonArgs
|
|
5489
|
+
},
|
|
5490
|
+
async run({ args }) {
|
|
5491
|
+
subcommandExecuted = true;
|
|
5492
|
+
const opts = toCliOptions(args);
|
|
5493
|
+
const cli = new VereleaseCli(opts);
|
|
5494
|
+
const targetPkg = args.package || args._ && args._[0];
|
|
5495
|
+
if (!targetPkg) {
|
|
5496
|
+
console.error("\n\u274C Please specify a package name to explain, e.g.: verelease explain @verelease/core\n");
|
|
5497
|
+
process.exit(1);
|
|
5498
|
+
}
|
|
5499
|
+
await cli.runExplainCommand(targetPkg);
|
|
3417
5500
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
break;
|
|
3433
|
-
case "init":
|
|
3434
|
-
await cli.runInitCommand(options);
|
|
3435
|
-
break;
|
|
3436
|
-
case "explain":
|
|
3437
|
-
const targetPkg = args[1];
|
|
3438
|
-
if (!targetPkg) {
|
|
3439
|
-
console.error("Please specify a package name to explain, e.g.: verel explain @acme/core");
|
|
3440
|
-
process.exit(1);
|
|
3441
|
-
}
|
|
3442
|
-
await cli.runExplainCommand(targetPkg);
|
|
3443
|
-
break;
|
|
3444
|
-
default:
|
|
3445
|
-
console.log(`Unknown command: ${command}`);
|
|
3446
|
-
console.log(`Run 'verelease --help' for available commands and options.`);
|
|
3447
|
-
process.exit(1);
|
|
5501
|
+
});
|
|
5502
|
+
var guideCommand = defineCommand({
|
|
5503
|
+
meta: {
|
|
5504
|
+
name: "guide",
|
|
5505
|
+
description: "Show agent-optimized cheat sheet and machine-readable instructions"
|
|
5506
|
+
},
|
|
5507
|
+
args: {
|
|
5508
|
+
json: {
|
|
5509
|
+
type: "boolean",
|
|
5510
|
+
description: "Output machine-readable JSON"
|
|
5511
|
+
},
|
|
5512
|
+
cwd: {
|
|
5513
|
+
type: "string",
|
|
5514
|
+
description: "Working directory path"
|
|
3448
5515
|
}
|
|
3449
|
-
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
5516
|
+
},
|
|
5517
|
+
async run({ args }) {
|
|
5518
|
+
subcommandExecuted = true;
|
|
5519
|
+
const cli = new VereleaseCli({ cwd: args.cwd });
|
|
5520
|
+
await cli.runGuideCommand({ json: Boolean(args.json) });
|
|
3453
5521
|
}
|
|
3454
|
-
}
|
|
3455
|
-
|
|
5522
|
+
});
|
|
5523
|
+
var initCommand = defineCommand({
|
|
5524
|
+
meta: {
|
|
5525
|
+
name: "init",
|
|
5526
|
+
description: "Initialize verelease.config.ts, contracts, and AGENTS.md"
|
|
5527
|
+
},
|
|
5528
|
+
args: {
|
|
5529
|
+
cwd: {
|
|
5530
|
+
type: "string",
|
|
5531
|
+
description: "Working directory path"
|
|
5532
|
+
}
|
|
5533
|
+
},
|
|
5534
|
+
async run({ args }) {
|
|
5535
|
+
subcommandExecuted = true;
|
|
5536
|
+
const cli = new VereleaseCli({ cwd: args.cwd });
|
|
5537
|
+
await cli.runInitCommand({ cwd: args.cwd });
|
|
5538
|
+
}
|
|
5539
|
+
});
|
|
5540
|
+
var version = await getCliVersion();
|
|
5541
|
+
var mainCommand = defineCommand({
|
|
5542
|
+
meta: {
|
|
5543
|
+
name: "verelease",
|
|
5544
|
+
version,
|
|
5545
|
+
description: "Evidence-based semantic versioning release engine"
|
|
5546
|
+
},
|
|
5547
|
+
args: commonArgs,
|
|
5548
|
+
subCommands: {
|
|
5549
|
+
plan: planCommand,
|
|
5550
|
+
release: releaseCommand,
|
|
5551
|
+
version: versionCommand,
|
|
5552
|
+
explain: explainCommand,
|
|
5553
|
+
guide: guideCommand,
|
|
5554
|
+
init: initCommand
|
|
5555
|
+
},
|
|
5556
|
+
async run({ args }) {
|
|
5557
|
+
if (subcommandExecuted) return;
|
|
5558
|
+
const opts = toCliOptions(args);
|
|
5559
|
+
const cli = new VereleaseCli(opts);
|
|
5560
|
+
await cli.runPlanCommand(opts);
|
|
5561
|
+
}
|
|
5562
|
+
});
|
|
5563
|
+
void runMain(mainCommand);
|
|
3456
5564
|
//# sourceMappingURL=bin.js.map
|