@kb-labs/release-manager-core 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1457 @@
1
+ import { readFile, writeFile, mkdir, rm, rename, cp } from 'fs/promises';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, rmSync, statSync } from 'fs';
3
+ import { join, relative, resolve, dirname } from 'path';
4
+ import simpleGit from 'simple-git';
5
+ import semver2 from 'semver';
6
+ import globby from 'globby';
7
+ import { discoverSubRepoPaths } from '@kb-labs/sdk';
8
+ import { execa } from 'execa';
9
+ import { spawn, execSync } from 'child_process';
10
+ import { tmpdir } from 'os';
11
+ import { randomBytes } from 'crypto';
12
+
13
+ // src/planner.ts
14
+ function applyVersionStrategy(packages, options) {
15
+ if (options.strategy === "lockstep") {
16
+ return applyLockstep(packages);
17
+ }
18
+ if (options.strategy === "adaptive") {
19
+ return applyAdaptive(packages);
20
+ }
21
+ return packages;
22
+ }
23
+ function applyLockstep(packages) {
24
+ if (packages.length === 0) {
25
+ return packages;
26
+ }
27
+ const maxBump = getMaxBump(packages);
28
+ const maxVersion = packages.reduce((max, pkg) => {
29
+ return semver2.gt(pkg.currentVersion, max) ? pkg.currentVersion : max;
30
+ }, packages[0].currentVersion);
31
+ const releaseType = maxBump === "auto" ? "patch" : maxBump;
32
+ const nextVersion = semver2.inc(maxVersion, releaseType) || maxVersion;
33
+ return packages.map((pkg) => ({
34
+ ...pkg,
35
+ bump: maxBump,
36
+ nextVersion
37
+ }));
38
+ }
39
+ function applyAdaptive(packages) {
40
+ const hasBreaking = packages.some((pkg) => pkg.bump === "major");
41
+ if (hasBreaking) {
42
+ return applyLockstep(packages);
43
+ }
44
+ return packages;
45
+ }
46
+ function getMaxBump(packages) {
47
+ let maxBump = "patch";
48
+ for (const pkg of packages) {
49
+ if (pkg.bump === "major") {
50
+ return "major";
51
+ }
52
+ if (pkg.bump === "minor") {
53
+ maxBump = "minor";
54
+ }
55
+ }
56
+ return maxBump;
57
+ }
58
+
59
+ // src/planner.ts
60
+ async function planRelease(options) {
61
+ const { cwd, config, scope, bumpOverride } = options;
62
+ const allPackages = await discoverPackages(cwd, config);
63
+ let packages;
64
+ if (scope && scope !== "root") {
65
+ const isWorkspace = existsSync(join(cwd, ".gitmodules"));
66
+ if (isWorkspace) {
67
+ const matchedRoots = filterByScope(allPackages, scope, config);
68
+ const innerPackages = [];
69
+ for (const root of matchedRoots) {
70
+ innerPackages.push(root);
71
+ const inner = await discoverPackages(root.path, config);
72
+ for (const pkg of inner) {
73
+ if (!innerPackages.some((p) => p.name === pkg.name)) {
74
+ innerPackages.push(pkg);
75
+ }
76
+ }
77
+ }
78
+ packages = innerPackages;
79
+ } else {
80
+ packages = filterByScope(allPackages, scope, config);
81
+ }
82
+ } else {
83
+ packages = allPackages;
84
+ }
85
+ const isWorkspaceRoot = existsSync(join(cwd, ".gitmodules")) && !scope;
86
+ let modifiedPackages;
87
+ if (isWorkspaceRoot) {
88
+ modifiedPackages = packages;
89
+ } else {
90
+ const git = simpleGit(cwd, { timeout: { block: 6e4 } });
91
+ modifiedPackages = await detectModifiedPackages(git, packages);
92
+ }
93
+ let planPackages = [];
94
+ for (const pkg of modifiedPackages) {
95
+ const bump = bumpOverride || config.bump || "auto";
96
+ const git = isWorkspaceRoot ? simpleGit(pkg.path, { timeout: { block: 6e4 } }) : simpleGit(cwd, { timeout: { block: 6e4 } });
97
+ const nextVersion = await computeNextVersion(
98
+ pkg.path,
99
+ pkg.currentVersion,
100
+ bump,
101
+ git
102
+ );
103
+ planPackages.push({
104
+ ...pkg,
105
+ nextVersion,
106
+ bump: bump === "auto" ? detectBumpType(pkg.currentVersion, nextVersion) : bump
107
+ });
108
+ }
109
+ const bumpStrategy = config.versioningStrategy || config.changelog?.bumpStrategy || "independent";
110
+ const versionStrategy = mapBumpStrategyToVersionStrategy(bumpStrategy);
111
+ planPackages = applyVersionStrategy(planPackages, {
112
+ strategy: versionStrategy});
113
+ return {
114
+ packages: planPackages,
115
+ strategy: config.strategy || "semver",
116
+ registry: config.registry || "https://registry.npmjs.org",
117
+ rollbackEnabled: config.rollback?.enabled ?? true
118
+ };
119
+ }
120
+ function mapBumpStrategyToVersionStrategy(bumpStrategy) {
121
+ if (bumpStrategy === "lockstep") {
122
+ return "lockstep";
123
+ }
124
+ if (bumpStrategy === "ripple" || bumpStrategy === "adaptive") {
125
+ return "adaptive";
126
+ }
127
+ return "independent";
128
+ }
129
+ async function discoverPackages(cwd, config) {
130
+ const isWorkspaceRoot = existsSync(join(cwd, ".gitmodules"));
131
+ if (isWorkspaceRoot) {
132
+ return discoverSubRepoPackages(cwd, config);
133
+ }
134
+ const packages = [];
135
+ const configPaths = config?.packages?.paths;
136
+ const pattern = configPaths?.length ? configPaths.map((p) => `${p}/package.json`) : "**/package.json";
137
+ const packageJsonPaths = await globby(pattern, {
138
+ cwd,
139
+ absolute: true,
140
+ onlyFiles: true,
141
+ ignore: [
142
+ "**/node_modules/**",
143
+ "**/dist/**",
144
+ "**/build/**",
145
+ "**/.git/**",
146
+ "**/.*/**"
147
+ ]
148
+ });
149
+ for (let i = 0; i < packageJsonPaths.length; i++) {
150
+ if (i % 10 === 0) {
151
+ await new Promise((resolve2) => {
152
+ setImmediate(resolve2);
153
+ });
154
+ }
155
+ const packageJsonPath = packageJsonPaths[i];
156
+ const packagePath = join(packageJsonPath, "..");
157
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
158
+ const isRootPackageJson = packageJsonPath === join(cwd, "package.json");
159
+ if (packageJson.private) {
160
+ continue;
161
+ }
162
+ if (isRootPackageJson && existsSync(join(packagePath, "pnpm-workspace.yaml"))) {
163
+ continue;
164
+ }
165
+ if (isRootPackageJson && !packageJson.name) {
166
+ continue;
167
+ }
168
+ const globalFilter = config?.packages;
169
+ if (globalFilter?.include?.length || globalFilter?.exclude?.length) {
170
+ const rel = relative(cwd, packagePath);
171
+ if (globalFilter.include?.length && !matchesPackagePattern(packageJson.name, rel, globalFilter.include)) {
172
+ continue;
173
+ }
174
+ if (globalFilter.exclude?.length && matchesPackagePattern(packageJson.name, rel, globalFilter.exclude)) {
175
+ continue;
176
+ }
177
+ }
178
+ packages.push({
179
+ name: packageJson.name,
180
+ path: packagePath,
181
+ currentVersion: packageJson.version,
182
+ nextVersion: packageJson.version,
183
+ bump: "auto",
184
+ isPublished: false
185
+ });
186
+ }
187
+ return packages;
188
+ }
189
+ function filterByScope(packages, scope, config) {
190
+ const scopeFilter = config?.scopes?.[scope]?.packages;
191
+ const globalFilter = config?.packages;
192
+ const mergedInclude = [...globalFilter?.include ?? [], ...scopeFilter?.include ?? []];
193
+ const mergedExclude = [...globalFilter?.exclude ?? [], ...scopeFilter?.exclude ?? []];
194
+ const isExactName = !scope.includes("*") && (scope.startsWith("@") || !scope.includes("/"));
195
+ const scopeRegex = new RegExp(
196
+ "^" + scope.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*") + "$"
197
+ );
198
+ const result = [];
199
+ for (const pkg of packages) {
200
+ const isPathPattern2 = scope.includes("/") && !scope.startsWith("@");
201
+ const matchTarget = isPathPattern2 ? pkg.path : pkg.name;
202
+ const matches = isExactName ? pkg.name === scope : scopeRegex.test(matchTarget);
203
+ if (!matches) {
204
+ continue;
205
+ }
206
+ if (mergedInclude.length && !matchesPackagePattern(pkg.name, pkg.path, mergedInclude)) {
207
+ continue;
208
+ }
209
+ if (mergedExclude.length && matchesPackagePattern(pkg.name, pkg.path, mergedExclude)) {
210
+ continue;
211
+ }
212
+ result.push(pkg);
213
+ }
214
+ if (result.length === 0) {
215
+ const wouldMatchExcluded = packages.some((p) => {
216
+ const matchTarget = isPathPattern(scope) ? p.path : p.name;
217
+ const scopeMatches = isExactName ? p.name === scope : scopeRegex.test(matchTarget);
218
+ if (!scopeMatches) {
219
+ return false;
220
+ }
221
+ return mergedExclude.length > 0 && matchesPackagePattern(p.name, p.path, mergedExclude);
222
+ });
223
+ if (wouldMatchExcluded) {
224
+ throw new Error(`Scope "${scope}" matches packages that are excluded by configuration`);
225
+ }
226
+ const globalInclude = globalFilter?.include;
227
+ if (globalInclude?.length) {
228
+ throw new Error(`Scope "${scope}" did not match any packages. Note: packages.include restricts discovery to: ${globalInclude.join(", ")}`);
229
+ }
230
+ throw new Error(`Scope "${scope}" did not match any discovered packages`);
231
+ }
232
+ return result;
233
+ }
234
+ function isPathPattern(scope) {
235
+ return scope.includes("/") && !scope.startsWith("@");
236
+ }
237
+ async function discoverSubRepoPackages(workspaceRoot, config) {
238
+ const subRepoPaths = discoverSubRepoPaths(workspaceRoot);
239
+ const packages = [];
240
+ const globalFilter = config?.packages;
241
+ for (const subRepoPath of subRepoPaths) {
242
+ try {
243
+ const pkgJson = JSON.parse(
244
+ await readFile(join(subRepoPath, "package.json"), "utf-8")
245
+ );
246
+ if (!pkgJson.name) {
247
+ continue;
248
+ }
249
+ const rel = relative(workspaceRoot, subRepoPath);
250
+ if (globalFilter?.include?.length && !matchesPackagePattern(pkgJson.name, rel, globalFilter.include)) {
251
+ continue;
252
+ }
253
+ if (globalFilter?.exclude?.length && matchesPackagePattern(pkgJson.name, rel, globalFilter.exclude)) {
254
+ continue;
255
+ }
256
+ packages.push({
257
+ name: pkgJson.name,
258
+ path: subRepoPath,
259
+ currentVersion: pkgJson.version || "0.0.0",
260
+ nextVersion: pkgJson.version || "0.0.0",
261
+ bump: "auto",
262
+ isPublished: false
263
+ });
264
+ } catch {
265
+ }
266
+ }
267
+ return packages;
268
+ }
269
+ function shouldIgnoreFile(file) {
270
+ const ignoredPaths = [
271
+ "node_modules/",
272
+ ".git/",
273
+ "dist/",
274
+ "build/",
275
+ ".next/",
276
+ ".nuxt/",
277
+ "coverage/",
278
+ ".cache/",
279
+ "tmp/"
280
+ ];
281
+ return ignoredPaths.some((path) => file.includes(path));
282
+ }
283
+ async function detectModifiedPackages(git, packages) {
284
+ const status = await git.status();
285
+ const diffSummary = await git.diffSummary(["HEAD"]);
286
+ const modifiedPaths = [
287
+ ...status.files.map((f) => f.path),
288
+ ...diffSummary.files.map((f) => f.file)
289
+ ].filter((path) => !shouldIgnoreFile(path));
290
+ const modified = [];
291
+ for (let i = 0; i < packages.length; i++) {
292
+ if (i % 10 === 0) {
293
+ await new Promise((resolve2) => {
294
+ setImmediate(resolve2);
295
+ });
296
+ }
297
+ const pkg = packages[i];
298
+ const packageModified = modifiedPaths.some(
299
+ (path) => path.startsWith(pkg.path) || path.includes(pkg.name)
300
+ );
301
+ if (packageModified) {
302
+ modified.push(pkg);
303
+ }
304
+ }
305
+ return modified.length > 0 ? modified : packages;
306
+ }
307
+ async function computeNextVersion(packagePath, currentVersion, bump, git) {
308
+ if (bump === "auto") {
309
+ const detectedBump = await detectVersionFromCommits(git, packagePath);
310
+ return semver2.inc(currentVersion, detectedBump) || currentVersion;
311
+ }
312
+ return semver2.inc(currentVersion, bump) || currentVersion;
313
+ }
314
+ async function detectVersionFromCommits(git, packagePath) {
315
+ try {
316
+ const log = await git.log({
317
+ maxCount: 50,
318
+ file: packagePath
319
+ });
320
+ let hasMinor = false;
321
+ let hasBreaking = false;
322
+ for (const commit of log.all) {
323
+ const message = commit.message.toLowerCase();
324
+ if (message.includes("!:")) {
325
+ hasBreaking = true;
326
+ } else if (message.startsWith("feat") || message.startsWith("feature")) {
327
+ hasMinor = true;
328
+ }
329
+ }
330
+ if (hasBreaking) {
331
+ return "major";
332
+ }
333
+ if (hasMinor) {
334
+ return "minor";
335
+ }
336
+ return "patch";
337
+ } catch (error) {
338
+ return "patch";
339
+ }
340
+ }
341
+ function detectBumpType(currentVersion, nextVersion) {
342
+ if (semver2.major(currentVersion) < semver2.major(nextVersion)) {
343
+ return "major";
344
+ }
345
+ if (semver2.minor(currentVersion) < semver2.minor(nextVersion)) {
346
+ return "minor";
347
+ }
348
+ return "patch";
349
+ }
350
+ function matchesPackagePattern(pkgName, relativePath, patterns) {
351
+ for (const pattern of patterns) {
352
+ const isPathPattern2 = pattern.includes("/") && !pattern.startsWith("@");
353
+ const target = isPathPattern2 ? relativePath : pkgName;
354
+ const regex = new RegExp(
355
+ "^" + pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*") + "$"
356
+ );
357
+ if (regex.test(target)) {
358
+ return true;
359
+ }
360
+ }
361
+ return false;
362
+ }
363
+ function createExecaShellAdapter() {
364
+ return {
365
+ async exec(command, args, options) {
366
+ try {
367
+ const result = await execa(command, args || [], {
368
+ cwd: options?.cwd,
369
+ timeout: options?.timeout,
370
+ preferLocal: true,
371
+ env: options?.env || process.env
372
+ });
373
+ return {
374
+ ok: result.exitCode === 0,
375
+ code: result.exitCode,
376
+ stdout: result.stdout || "",
377
+ stderr: result.stderr || ""
378
+ };
379
+ } catch (error) {
380
+ return {
381
+ ok: false,
382
+ code: error.exitCode || 1,
383
+ stdout: error.stdout || "",
384
+ stderr: error.stderr || error.message || ""
385
+ };
386
+ }
387
+ }
388
+ };
389
+ }
390
+
391
+ // src/publisher.ts
392
+ async function publishPackages(options) {
393
+ const { plan, dryRun, shell } = options;
394
+ const shellApi = shell || createExecaShellAdapter();
395
+ const result = {
396
+ published: [],
397
+ skipped: [],
398
+ errors: [],
399
+ versionUpdates: []
400
+ };
401
+ if (dryRun) {
402
+ for (const pkg of plan.packages) {
403
+ result.skipped.push(`${pkg.name}@${pkg.nextVersion} (dry-run)`);
404
+ result.versionUpdates.push({
405
+ package: pkg.name,
406
+ from: pkg.currentVersion || "unknown",
407
+ to: pkg.nextVersion || "unknown",
408
+ updated: false
409
+ });
410
+ }
411
+ return result;
412
+ }
413
+ for (const pkg of plan.packages) {
414
+ try {
415
+ const registry = plan.registry || "https://registry.npmjs.org";
416
+ try {
417
+ await updatePackageVersion(pkg);
418
+ result.versionUpdates.push({
419
+ package: pkg.name,
420
+ from: pkg.currentVersion || "unknown",
421
+ to: pkg.nextVersion || "unknown",
422
+ updated: true
423
+ });
424
+ } catch (versionError) {
425
+ const msg = `Failed to update version for ${pkg.name}: ${versionError instanceof Error ? versionError.message : String(versionError)}`;
426
+ result.errors.push(msg);
427
+ result.versionUpdates.push({
428
+ package: pkg.name,
429
+ from: pkg.currentVersion || "unknown",
430
+ to: pkg.nextVersion || "unknown",
431
+ updated: false
432
+ });
433
+ continue;
434
+ }
435
+ const pm = options.config?.publish?.packageManager ?? "pnpm";
436
+ const access = options.config?.publish?.access ?? "public";
437
+ const publishResult = await shellApi.exec(
438
+ pm,
439
+ ["publish", "--access", access, "--registry", registry],
440
+ {
441
+ cwd: pkg.path,
442
+ timeout: 6e4
443
+ }
444
+ );
445
+ if (publishResult.ok) {
446
+ result.published.push(`${pkg.name}@${pkg.nextVersion}`);
447
+ } else {
448
+ const errorDetails = publishResult.stderr || publishResult.stdout || "Unknown error";
449
+ result.errors.push(`Failed to publish ${pkg.name}: ${errorDetails}`);
450
+ }
451
+ } catch (error) {
452
+ const msg = `Failed to publish ${pkg.name}: ${error instanceof Error ? error.message : String(error)}`;
453
+ result.errors.push(msg);
454
+ }
455
+ }
456
+ return result;
457
+ }
458
+ async function updatePackageVersion(pkg) {
459
+ const packageJsonPath = join(pkg.path, "package.json");
460
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
461
+ packageJson.version = pkg.nextVersion;
462
+ await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
463
+ }
464
+ async function updatePackageVersions(plan) {
465
+ const results = [];
466
+ for (const pkg of plan.packages) {
467
+ try {
468
+ await updatePackageVersion(pkg);
469
+ results.push({
470
+ package: pkg.name,
471
+ from: pkg.currentVersion || "unknown",
472
+ to: pkg.nextVersion || "unknown",
473
+ updated: true
474
+ });
475
+ } catch (error) {
476
+ console.warn(`Failed to update version for ${pkg.name}: ${error instanceof Error ? error.message : String(error)}`);
477
+ results.push({
478
+ package: pkg.name,
479
+ from: pkg.currentVersion || "unknown",
480
+ to: pkg.nextVersion || "unknown",
481
+ updated: false
482
+ });
483
+ }
484
+ }
485
+ return results;
486
+ }
487
+ async function generateChangelog(options) {
488
+ const { cwd, plan } = options;
489
+ const changelogPath = join(cwd, "CHANGELOG.md");
490
+ let existingChangelog = "";
491
+ try {
492
+ existingChangelog = await readFile(changelogPath, "utf-8");
493
+ } catch {
494
+ }
495
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
496
+ const header = `## [${date}] Release
497
+
498
+ `;
499
+ const entries = [];
500
+ for (const pkg of plan.packages) {
501
+ entries.push(`- **${pkg.name}**: ${pkg.currentVersion} \u2192 ${pkg.nextVersion}`);
502
+ }
503
+ const newEntry = header + entries.join("\n") + "\n\n";
504
+ const updatedChangelog = newEntry + existingChangelog;
505
+ try {
506
+ await mkdir(join(cwd, ".kb", "release"), { recursive: true });
507
+ await writeFile(changelogPath, updatedChangelog, "utf-8");
508
+ } catch (error) {
509
+ console.warn(`Failed to write changelog: ${error instanceof Error ? error.message : String(error)}`);
510
+ }
511
+ return newEntry;
512
+ }
513
+ async function generateEnhancedChangelog(options) {
514
+ const simpleChangelog = await generateChangelog({
515
+ cwd: options.cwd,
516
+ plan: options.plan
517
+ });
518
+ return {
519
+ changelog: simpleChangelog,
520
+ manifest: null
521
+ };
522
+ }
523
+ async function copyChangelogToPackages(options) {
524
+ const { plan, changelog } = options;
525
+ for (const pkg of plan.packages) {
526
+ try {
527
+ let packageChangelog;
528
+ if (plan.packages.length === 1) {
529
+ packageChangelog = changelog;
530
+ } else {
531
+ packageChangelog = createPackageChangelog(pkg, changelog);
532
+ }
533
+ if (!packageChangelog || packageChangelog.trim().length === 0) {
534
+ console.warn(`No changelog content for ${pkg.name}, skipping`);
535
+ continue;
536
+ }
537
+ const changelogPath = join(pkg.path, "CHANGELOG.md");
538
+ let existingChangelog = "";
539
+ try {
540
+ existingChangelog = await readFile(changelogPath, "utf-8");
541
+ } catch {
542
+ }
543
+ const versionPattern = new RegExp(
544
+ `^##\\s+${pkg.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+${pkg.nextVersion.replace(/\./g, "\\.")}`,
545
+ "m"
546
+ );
547
+ let updatedChangelog;
548
+ if (existingChangelog && versionPattern.test(existingChangelog)) {
549
+ const lines = existingChangelog.split("\n");
550
+ let startIdx = -1;
551
+ let endIdx = lines.length;
552
+ for (let i = 0; i < lines.length; i++) {
553
+ const line = lines[i];
554
+ if (line && versionPattern.test(line)) {
555
+ startIdx = i;
556
+ } else if (startIdx !== -1 && line && /^##\s+@?[\w-]+/.test(line)) {
557
+ endIdx = i;
558
+ break;
559
+ }
560
+ }
561
+ if (startIdx !== -1) {
562
+ const before = lines.slice(0, startIdx).join("\n");
563
+ const after = lines.slice(endIdx).join("\n");
564
+ updatedChangelog = (before ? before + "\n" : "") + packageChangelog + (after ? "\n" + after : "");
565
+ } else {
566
+ updatedChangelog = packageChangelog;
567
+ }
568
+ } else {
569
+ updatedChangelog = packageChangelog + (existingChangelog ? "\n" + existingChangelog : "");
570
+ }
571
+ await writeFile(changelogPath, updatedChangelog.trim() + "\n", "utf-8");
572
+ } catch (error) {
573
+ console.warn(`Failed to write changelog for ${pkg.name}: ${error instanceof Error ? error.message : String(error)}`);
574
+ }
575
+ }
576
+ }
577
+ function createPackageChangelog(pkg, changelog) {
578
+ const packageHeaderPattern = new RegExp(
579
+ `^##\\s+${pkg.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+\\d+\\.\\d+\\.\\d+`,
580
+ "gm"
581
+ );
582
+ const allHeaders = Array.from(changelog.matchAll(/^##\s+(@[\w-]+\/)?[\w-]+\s+\d+\.\d+\.\d+/gm));
583
+ let startIdx = -1;
584
+ let endIdx = changelog.length;
585
+ for (let i = 0; i < allHeaders.length; i++) {
586
+ const match = allHeaders[i];
587
+ if (!match || !match.index) {
588
+ continue;
589
+ }
590
+ if (packageHeaderPattern.test(match[0])) {
591
+ startIdx = match.index;
592
+ if (i + 1 < allHeaders.length) {
593
+ endIdx = allHeaders[i + 1].index;
594
+ }
595
+ break;
596
+ }
597
+ }
598
+ if (startIdx === -1) {
599
+ return "";
600
+ }
601
+ return changelog.substring(startIdx, endIdx).trim();
602
+ }
603
+ async function commitAndTagRelease(options) {
604
+ const { cwd, plan, dryRun } = options;
605
+ const simpleGit2 = (await import('simple-git')).default;
606
+ const result = {
607
+ committed: false,
608
+ tagged: [],
609
+ pushed: false
610
+ };
611
+ if (dryRun) {
612
+ return result;
613
+ }
614
+ try {
615
+ const commitMessage = createCommitMessage(plan);
616
+ for (const pkg of plan.packages) {
617
+ const pkgGit = simpleGit2(pkg.path);
618
+ const filesToStage = ["package.json"];
619
+ const changelogPath = join(pkg.path, "CHANGELOG.md");
620
+ if (existsSync(changelogPath)) {
621
+ filesToStage.push("CHANGELOG.md");
622
+ }
623
+ await pkgGit.add(filesToStage);
624
+ try {
625
+ await pkgGit.commit(commitMessage);
626
+ result.committed = true;
627
+ } catch (commitError) {
628
+ const msg = commitError instanceof Error ? commitError.message : String(commitError);
629
+ if (!msg.includes("nothing to commit") && !msg.includes("nothing added to commit")) {
630
+ throw commitError;
631
+ }
632
+ }
633
+ }
634
+ const git = simpleGit2(cwd);
635
+ const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
636
+ const isLockstep = plan.packages.length > 1 && uniqueVersions.size === 1;
637
+ if (isLockstep) {
638
+ const version = plan.packages[0].nextVersion;
639
+ const tagName = `v${version}`;
640
+ await git.addTag(tagName);
641
+ result.tagged.push(tagName);
642
+ } else {
643
+ for (const pkg of plan.packages) {
644
+ const pkgGit = simpleGit2(pkg.path);
645
+ const tagName = `${pkg.name}@${pkg.nextVersion}`;
646
+ await pkgGit.addTag(tagName);
647
+ result.tagged.push(tagName);
648
+ }
649
+ }
650
+ for (const pkg of plan.packages) {
651
+ const pkgGit = simpleGit2(pkg.path);
652
+ if (result.committed) {
653
+ await pkgGit.push(["--no-verify"]);
654
+ }
655
+ await pkgGit.pushTags("--no-verify");
656
+ }
657
+ result.pushed = true;
658
+ } catch (error) {
659
+ console.error(`Git operations failed: ${error instanceof Error ? error.message : String(error)}`);
660
+ throw error;
661
+ }
662
+ return result;
663
+ }
664
+ function createCommitMessage(plan) {
665
+ const lines = [];
666
+ if (plan.packages.length === 1 && plan.packages[0]) {
667
+ const pkg = plan.packages[0];
668
+ lines.push(`chore(release): publish ${pkg.name}@${pkg.nextVersion}`);
669
+ } else {
670
+ lines.push(`chore(release): publish ${plan.packages.length} packages`);
671
+ }
672
+ lines.push("");
673
+ for (const pkg of plan.packages) {
674
+ lines.push(`- ${pkg.name}@${pkg.nextVersion}`);
675
+ }
676
+ return lines.join("\n");
677
+ }
678
+ async function saveSnapshot(options) {
679
+ const { cwd, plan } = options;
680
+ const snapshot = {
681
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
682
+ packages: plan.packages.map((pkg) => ({
683
+ ...pkg,
684
+ nextVersion: pkg.currentVersion
685
+ // Store current before update
686
+ }))
687
+ };
688
+ const snapshotDir = join(cwd, ".kb", "release");
689
+ await mkdir(snapshotDir, { recursive: true });
690
+ const snapshotPath = join(snapshotDir, "backup.json");
691
+ await writeFile(snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
692
+ await cleanupOldSnapshots();
693
+ }
694
+ async function restoreSnapshot(cwd) {
695
+ const snapshotPath = join(cwd, ".kb", "release", "backup.json");
696
+ if (!existsSync(snapshotPath)) {
697
+ throw new Error("No backup snapshot found");
698
+ }
699
+ const snapshotContent = await readFile(snapshotPath, "utf-8");
700
+ const snapshot = JSON.parse(snapshotContent);
701
+ for (const pkg of snapshot.packages) {
702
+ const packageJsonPath = join(pkg.path, "package.json");
703
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
704
+ packageJson.version = pkg.currentVersion;
705
+ await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
706
+ }
707
+ }
708
+ async function cleanupOldSnapshots(_snapshotDir) {
709
+ }
710
+
711
+ // src/runner.ts
712
+ async function runRelease(options) {
713
+ const {
714
+ config,
715
+ runChecks,
716
+ executePlan,
717
+ onStageChange
718
+ } = options;
719
+ const startTime = Date.now();
720
+ const errors = [];
721
+ let checks;
722
+ try {
723
+ onStageChange?.("planning");
724
+ if (config.verify && config.verify.length > 0 && runChecks) {
725
+ onStageChange?.("checking");
726
+ checks = await runChecks("checking");
727
+ const failedChecks = Object.entries(checks).filter(([_, result]) => result && !result.ok).map(([id]) => id);
728
+ if (failedChecks.length > 0) {
729
+ errors.push(`Pre-release checks failed: ${failedChecks.join(", ")}`);
730
+ if (config.strict) {
731
+ return {
732
+ ok: false,
733
+ timingMs: Date.now() - startTime,
734
+ errors,
735
+ checks
736
+ };
737
+ }
738
+ }
739
+ }
740
+ onStageChange?.("publishing");
741
+ if (executePlan) {
742
+ await executePlan();
743
+ }
744
+ onStageChange?.("verifying");
745
+ return {
746
+ ok: errors.length === 0,
747
+ timingMs: Date.now() - startTime,
748
+ errors: errors.length > 0 ? errors : void 0,
749
+ checks
750
+ };
751
+ } catch (error) {
752
+ onStageChange?.("rollback");
753
+ errors.push(error instanceof Error ? error.message : String(error));
754
+ return {
755
+ ok: false,
756
+ timingMs: Date.now() - startTime,
757
+ errors,
758
+ checks
759
+ };
760
+ }
761
+ }
762
+
763
+ // src/reporters/json.ts
764
+ function renderJson(report) {
765
+ return JSON.stringify(report, null, 2);
766
+ }
767
+
768
+ // src/reporters/markdown.ts
769
+ function renderMarkdown(report) {
770
+ const lines = [];
771
+ lines.push("# \u{1F9E9} KB Labs Release Summary");
772
+ lines.push("");
773
+ lines.push(`**Timestamp**: ${report.ts}`);
774
+ lines.push(`**Stage**: ${report.stage}`);
775
+ lines.push("");
776
+ if (report.result.ok) {
777
+ lines.push("## \u2705 Release: SUCCESS");
778
+ } else {
779
+ lines.push("## \u274C Release: FAILED");
780
+ }
781
+ lines.push("");
782
+ if (report.result.checks) {
783
+ lines.push("## Quality Checks");
784
+ lines.push("");
785
+ for (const [id, result] of Object.entries(report.result.checks)) {
786
+ if (!result) {
787
+ continue;
788
+ }
789
+ const icon = result.ok ? "\u2705" : "\u274C";
790
+ lines.push(`- ${icon} **${id}**: ${result.ok ? "PASSED" : "FAILED"}`);
791
+ if (result.hint && !result.ok) {
792
+ lines.push(` - ${result.hint}`);
793
+ }
794
+ }
795
+ lines.push("");
796
+ }
797
+ if (report.result.checksPerPackage) {
798
+ lines.push("## Package Quality Checks");
799
+ lines.push("");
800
+ for (const [pkgName, pkgChecks] of Object.entries(report.result.checksPerPackage)) {
801
+ lines.push(`### ${pkgName}`);
802
+ lines.push("");
803
+ for (const [id, result] of Object.entries(pkgChecks)) {
804
+ if (!result) {
805
+ continue;
806
+ }
807
+ const icon = result.ok ? "\u2705" : "\u274C";
808
+ lines.push(`- ${icon} **${id}**: ${result.ok ? "PASSED" : "FAILED"}`);
809
+ if (result.hint && !result.ok) {
810
+ lines.push(` - ${result.hint}`);
811
+ }
812
+ }
813
+ lines.push("");
814
+ }
815
+ }
816
+ if (report.result.published && report.result.published.length > 0) {
817
+ lines.push("## \u{1F680} Published Packages");
818
+ lines.push("");
819
+ for (const pkg of report.result.published) {
820
+ lines.push(`- ${pkg}`);
821
+ }
822
+ lines.push("");
823
+ }
824
+ if (report.result.errors && report.result.errors.length > 0) {
825
+ lines.push("## \u274C Errors");
826
+ lines.push("");
827
+ for (const error of report.result.errors) {
828
+ lines.push(`- ${error}`);
829
+ }
830
+ lines.push("");
831
+ }
832
+ lines.push(`**Duration**: ${formatTiming(report.result.timingMs)}`);
833
+ lines.push("");
834
+ return lines.join("\n");
835
+ }
836
+ function formatTiming(ms) {
837
+ if (ms < 1e3) {
838
+ return `${ms}ms`;
839
+ }
840
+ if (ms < 6e4) {
841
+ return `${(ms / 1e3).toFixed(1)}s`;
842
+ }
843
+ return `${(ms / 6e4).toFixed(1)}m`;
844
+ }
845
+
846
+ // src/reporters/text.ts
847
+ function renderText(report) {
848
+ const lines = [];
849
+ lines.push("[release] " + (report.result.ok ? "OK" : "FAILED"));
850
+ lines.push("");
851
+ if (report.result.checks) {
852
+ for (const [id, result] of Object.entries(report.result.checks)) {
853
+ if (!result) {
854
+ continue;
855
+ }
856
+ lines.push(`[${id}] ${result.ok ? "pass" : "fail"}`);
857
+ if (result.hint && !result.ok) {
858
+ lines.push(` ${result.hint}`);
859
+ }
860
+ }
861
+ lines.push("");
862
+ }
863
+ if (report.result.checksPerPackage) {
864
+ for (const [pkgName, pkgChecks] of Object.entries(report.result.checksPerPackage)) {
865
+ lines.push(`[${pkgName}]`);
866
+ for (const [id, result] of Object.entries(pkgChecks)) {
867
+ if (!result) {
868
+ continue;
869
+ }
870
+ lines.push(` [${id}] ${result.ok ? "pass" : "fail"}`);
871
+ if (result.hint && !result.ok) {
872
+ lines.push(` ${result.hint}`);
873
+ }
874
+ }
875
+ }
876
+ lines.push("");
877
+ }
878
+ if (report.result.published && report.result.published.length > 0) {
879
+ lines.push("[published] " + report.result.published.length + " package(s)");
880
+ for (const pkg of report.result.published) {
881
+ lines.push(` ${pkg}`);
882
+ }
883
+ lines.push("");
884
+ }
885
+ if (report.result.errors && report.result.errors.length > 0) {
886
+ lines.push("[errors]");
887
+ for (const error of report.result.errors) {
888
+ lines.push(` ${error}`);
889
+ }
890
+ lines.push("");
891
+ }
892
+ lines.push(`[timing] ${formatTiming2(report.result.timingMs)}`);
893
+ return lines.join("\n");
894
+ }
895
+ function formatTiming2(ms) {
896
+ if (ms < 1e3) {
897
+ return `${ms}ms`;
898
+ }
899
+ if (ms < 6e4) {
900
+ return `${(ms / 1e3).toFixed(1)}s`;
901
+ }
902
+ return `${(ms / 6e4).toFixed(1)}m`;
903
+ }
904
+ async function buildPackages(packages, options) {
905
+ const results = [];
906
+ for (const pkg of packages) {
907
+ options?.logger?.info?.(`Building ${pkg.name}...`);
908
+ const result = await runSafeBuild(pkg.path, pkg.name);
909
+ results.push({ ...result, name: pkg.name });
910
+ options?.onProgress?.(pkg.name, { ...result, name: pkg.name });
911
+ if (!result.success) {
912
+ options?.logger?.error?.(`Build failed for ${pkg.name}: ${result.error}`);
913
+ break;
914
+ }
915
+ options?.logger?.info?.(`Built ${pkg.name} in ${result.durationMs}ms`);
916
+ }
917
+ return results;
918
+ }
919
+ async function runSafeBuild(packagePath, packageName) {
920
+ const usesTsup = existsSync(join(packagePath, "tsup.config.ts")) || existsSync(join(packagePath, "tsup.config.js"));
921
+ if (usesTsup) {
922
+ return runTsupSafeBuild(packagePath, packageName);
923
+ }
924
+ return runDirectBuild(packagePath, packageName);
925
+ }
926
+ function isBuildCommand(command, args) {
927
+ const full = [command, ...args ?? []].join(" ").trim();
928
+ return /\b(pnpm|npm|yarn)\s+(run\s+)?build\b/.test(full);
929
+ }
930
+ async function runTsupSafeBuild(packagePath, packageName) {
931
+ const startTime = Date.now();
932
+ const buildId = randomBytes(6).toString("hex");
933
+ const tempDir = join(tmpdir(), `kb-release-build-${buildId}`);
934
+ const distDir = join(packagePath, "dist");
935
+ const backupDir = join(packagePath, `dist.bak-${buildId}`);
936
+ try {
937
+ const buildResult = await spawnCommand(`npx tsup -d ${tempDir}`, packagePath);
938
+ if (!buildResult.success) {
939
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
940
+ });
941
+ return { ...buildResult, name: packageName, durationMs: Date.now() - startTime };
942
+ }
943
+ if (existsSync(distDir)) {
944
+ await rename(distDir, backupDir);
945
+ }
946
+ try {
947
+ await rename(tempDir, distDir);
948
+ } catch {
949
+ await cp(tempDir, distDir, { recursive: true });
950
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
951
+ });
952
+ }
953
+ await rm(backupDir, { recursive: true, force: true }).catch(() => {
954
+ });
955
+ return { success: true, name: packageName, durationMs: Date.now() - startTime };
956
+ } catch (err) {
957
+ if (existsSync(backupDir) && !existsSync(distDir)) {
958
+ await rename(backupDir, distDir).catch(() => {
959
+ });
960
+ }
961
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
962
+ });
963
+ return {
964
+ success: false,
965
+ name: packageName,
966
+ error: err instanceof Error ? err.message : String(err),
967
+ durationMs: Date.now() - startTime
968
+ };
969
+ }
970
+ }
971
+ async function runDirectBuild(packagePath, packageName) {
972
+ const result = await spawnCommand("pnpm run build", packagePath);
973
+ return { ...result, name: packageName };
974
+ }
975
+ function spawnCommand(command, cwd, timeoutMs = 5 * 60 * 1e3) {
976
+ const startTime = Date.now();
977
+ return new Promise((resolve2) => {
978
+ const child = spawn(command, [], {
979
+ cwd,
980
+ stdio: "pipe",
981
+ shell: true,
982
+ env: { ...process.env }
983
+ });
984
+ let stdout = "";
985
+ let stderr = "";
986
+ child.stdout?.on("data", (data) => {
987
+ stdout += data.toString();
988
+ });
989
+ child.stderr?.on("data", (data) => {
990
+ stderr += data.toString();
991
+ });
992
+ child.on("close", (code) => {
993
+ const exitCode = code ?? 1;
994
+ const durationMs = Date.now() - startTime;
995
+ if (exitCode === 0) {
996
+ resolve2({ success: true, durationMs, stdout, stderr, exitCode });
997
+ return;
998
+ }
999
+ const combined = (stderr || stdout).trim();
1000
+ const tail = combined.split("\n").slice(-30).join("\n");
1001
+ resolve2({
1002
+ success: false,
1003
+ error: tail || `Build failed with exit code ${exitCode}`,
1004
+ durationMs,
1005
+ stdout,
1006
+ stderr,
1007
+ exitCode
1008
+ });
1009
+ });
1010
+ child.on("error", (err) => {
1011
+ resolve2({ success: false, error: err.message, durationMs: Date.now() - startTime, stdout: "", stderr: "", exitCode: 1 });
1012
+ });
1013
+ setTimeout(() => {
1014
+ child.kill();
1015
+ resolve2({ success: false, error: `Timed out after ${timeoutMs / 1e3}s`, durationMs: Date.now() - startTime, stdout: "", stderr: "", exitCode: 1 });
1016
+ }, timeoutMs);
1017
+ });
1018
+ }
1019
+ async function runReleaseChecks(checks, options) {
1020
+ const results = [];
1021
+ for (const check of checks) {
1022
+ const result = await runSingleCheck(check, options);
1023
+ results.push(result);
1024
+ options.logger?.info?.(`Check ${check.id}: ${result.ok ? "passed" : "failed"} (${result.timingMs}ms)`);
1025
+ if (!result.ok && !check.optional) {
1026
+ break;
1027
+ }
1028
+ }
1029
+ return results;
1030
+ }
1031
+ async function runSingleCheck(check, options) {
1032
+ const runIn = check.runIn ?? "perPackage";
1033
+ let pathsToRun;
1034
+ if (runIn === "repoRoot") {
1035
+ pathsToRun = [options.repoRoot];
1036
+ } else if (runIn === "scopePath") {
1037
+ pathsToRun = [options.scopePath ?? options.repoRoot];
1038
+ } else {
1039
+ pathsToRun = options.packagePaths.length > 0 ? options.packagePaths : [options.repoRoot];
1040
+ }
1041
+ let checkOk = true;
1042
+ let checkError;
1043
+ let totalDurationMs = 0;
1044
+ for (const pkgPath of pathsToRun) {
1045
+ const resolvedArgs = (check.args ?? []).map(
1046
+ (arg) => arg.match(/\.(sh|js|ts|mjs|cjs)$/) ? join(options.repoRoot, arg) : arg
1047
+ );
1048
+ const fullCommand = [check.command, ...resolvedArgs].join(" ");
1049
+ const timeoutMs = check.timeoutMs ?? 12e4;
1050
+ const result = await spawnCommand(fullCommand, pkgPath, timeoutMs);
1051
+ totalDurationMs += result.durationMs;
1052
+ const ok = evaluateParser(check, result.stdout, result.stderr, result.exitCode);
1053
+ if (!ok) {
1054
+ checkOk = false;
1055
+ checkError = result.error ?? (result.stderr || result.stdout || `exit code ${result.exitCode}`);
1056
+ break;
1057
+ }
1058
+ }
1059
+ return {
1060
+ id: check.id,
1061
+ ok: checkOk,
1062
+ details: checkError ? { error: checkError } : void 0,
1063
+ hint: check.optional ? "optional" : void 0,
1064
+ timingMs: totalDurationMs
1065
+ };
1066
+ }
1067
+ function evaluateParser(check, stdout, stderr, exitCode) {
1068
+ const parser = check.parser ?? "exitcode";
1069
+ if (parser === "exitcode") {
1070
+ return exitCode === 0;
1071
+ }
1072
+ if (parser === "json") {
1073
+ try {
1074
+ const parsed = JSON.parse(stdout);
1075
+ return parsed.ok === true || parsed.success === true || parsed.status === "ok";
1076
+ } catch {
1077
+ return false;
1078
+ }
1079
+ }
1080
+ if (typeof parser === "function") {
1081
+ return parser(stdout, stderr, exitCode);
1082
+ }
1083
+ return exitCode === 0;
1084
+ }
1085
+ async function verifyPackages(packages, options) {
1086
+ const results = [];
1087
+ for (const pkg of packages) {
1088
+ const result = verifyPackage(pkg.path, pkg.name);
1089
+ results.push(result);
1090
+ options?.onProgress?.(pkg.name, result);
1091
+ }
1092
+ return results;
1093
+ }
1094
+ function verifyPackage(packagePath, packageName) {
1095
+ const pkgJsonPath = join(packagePath, "package.json");
1096
+ if (!existsSync(pkgJsonPath)) {
1097
+ return { name: packageName ?? packagePath, success: true, issues: [] };
1098
+ }
1099
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
1100
+ const name = packageName ?? pkg.name ?? packagePath;
1101
+ if (pkg.private) {
1102
+ return { name, success: true, issues: [] };
1103
+ }
1104
+ if (!existsSync(join(packagePath, "dist"))) {
1105
+ return { name, success: true, issues: [] };
1106
+ }
1107
+ const issues = [];
1108
+ const tmpDir = join(tmpdir(), `kb-verify-${randomBytes(6).toString("hex")}`);
1109
+ try {
1110
+ mkdirSync(tmpDir, { recursive: true });
1111
+ const origPkg = readFileSync(pkgJsonPath, "utf-8");
1112
+ const modPkg = JSON.parse(origPkg);
1113
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
1114
+ const deps = modPkg[section];
1115
+ if (!deps) {
1116
+ continue;
1117
+ }
1118
+ for (const [k, v] of Object.entries(deps)) {
1119
+ if (typeof v === "string" && v.startsWith("link:")) {
1120
+ deps[k] = "*";
1121
+ }
1122
+ }
1123
+ }
1124
+ writeFileSync(pkgJsonPath, JSON.stringify(modPkg, null, 2) + "\n");
1125
+ let tgzFile;
1126
+ try {
1127
+ execSync(`npm pack --pack-destination ${tmpDir}`, { cwd: packagePath, stdio: "pipe", timeout: 3e4 });
1128
+ const files = readdirSync(tmpDir).filter((f) => f.endsWith(".tgz"));
1129
+ tgzFile = files[0] ? join(tmpDir, files[0]) : void 0;
1130
+ } finally {
1131
+ writeFileSync(pkgJsonPath, origPkg);
1132
+ }
1133
+ if (!tgzFile) {
1134
+ issues.push("npm pack produced no tarball");
1135
+ return { name, success: false, issues };
1136
+ }
1137
+ execSync(`tar xzf ${tgzFile}`, { cwd: tmpDir, stdio: "pipe" });
1138
+ const extractedDir = join(tmpDir, "package");
1139
+ const testFiles = findFiles(
1140
+ join(extractedDir, "dist"),
1141
+ (f) => f.includes(".spec.") || f.includes(".test.") || f.includes("__tests__")
1142
+ );
1143
+ if (testFiles.length > 0) {
1144
+ issues.push(`Test files in dist/: ${testFiles.slice(0, 3).join(", ")}`);
1145
+ }
1146
+ const extractedPkg = JSON.parse(readFileSync(join(extractedDir, "package.json"), "utf-8"));
1147
+ for (const field of ["main", "module", "types"]) {
1148
+ const val = extractedPkg[field];
1149
+ if (val && !existsSync(join(extractedDir, val))) {
1150
+ issues.push(`${field}: ${val} does not exist in published package`);
1151
+ }
1152
+ }
1153
+ if (extractedPkg.exports) {
1154
+ checkExportsExist(extractedPkg.exports, extractedDir, "exports", issues);
1155
+ }
1156
+ const esmEntry = resolveEsmEntry(extractedPkg);
1157
+ if (esmEntry) {
1158
+ const esmPath = join(extractedDir, esmEntry);
1159
+ if (existsSync(esmPath)) {
1160
+ checkDirectoryImports(esmPath, join(extractedDir, "dist"), issues);
1161
+ try {
1162
+ execSync(`node --check ${esmPath}`, { stdio: "pipe", timeout: 1e4 });
1163
+ } catch {
1164
+ issues.push(`ESM syntax error in ${esmEntry}`);
1165
+ }
1166
+ }
1167
+ }
1168
+ const cjsEntry = resolveCjsEntry(extractedPkg);
1169
+ if (cjsEntry) {
1170
+ const cjsPath = join(extractedDir, cjsEntry);
1171
+ if (existsSync(cjsPath)) {
1172
+ try {
1173
+ execSync(`node --check ${cjsPath}`, { stdio: "pipe", timeout: 1e4 });
1174
+ } catch {
1175
+ issues.push(`CJS syntax error in ${cjsEntry}`);
1176
+ }
1177
+ }
1178
+ }
1179
+ } catch (err) {
1180
+ issues.push(`Verification error: ${err instanceof Error ? err.message : String(err)}`);
1181
+ } finally {
1182
+ rmSync(tmpDir, { recursive: true, force: true });
1183
+ }
1184
+ return { name, success: issues.length === 0, issues };
1185
+ }
1186
+ function resolveEsmEntry(pkg) {
1187
+ return pkg.exports?.["."]?.import ?? pkg.module ?? pkg.main;
1188
+ }
1189
+ function resolveCjsEntry(pkg) {
1190
+ const req = pkg.exports?.["."]?.require;
1191
+ if (req) {
1192
+ return req;
1193
+ }
1194
+ if (pkg.main?.endsWith(".cjs")) {
1195
+ return pkg.main;
1196
+ }
1197
+ return void 0;
1198
+ }
1199
+ function checkExportsExist(exports$1, baseDir, prefix, issues) {
1200
+ if (typeof exports$1 === "string") {
1201
+ if (exports$1.includes("*")) {
1202
+ return;
1203
+ }
1204
+ if (!existsSync(join(baseDir, exports$1))) {
1205
+ issues.push(`${prefix}: ${exports$1} missing`);
1206
+ }
1207
+ } else if (exports$1 && typeof exports$1 === "object") {
1208
+ for (const [k, v] of Object.entries(exports$1)) {
1209
+ if (k.includes("*")) {
1210
+ continue;
1211
+ }
1212
+ checkExportsExist(v, baseDir, `${prefix}.${k}`, issues);
1213
+ }
1214
+ }
1215
+ }
1216
+ function checkDirectoryImports(filePath, distDir, issues) {
1217
+ const content = readFileSync(filePath, "utf-8");
1218
+ const importRegex = /(?:export|import)\s.*?from\s+['"](\.[^'"]*)['"]/g;
1219
+ let match;
1220
+ while ((match = importRegex.exec(content)) !== null) {
1221
+ const target = match[1];
1222
+ if (!target || target.includes(".")) {
1223
+ continue;
1224
+ }
1225
+ const targetPath = resolve(dirname(filePath), target);
1226
+ if (existsSync(targetPath) && statSync(targetPath).isDirectory()) {
1227
+ issues.push(`Directory import '${target}' in ${filePath.split("/").pop()}`);
1228
+ }
1229
+ }
1230
+ }
1231
+ function findFiles(dir, predicate) {
1232
+ if (!existsSync(dir)) {
1233
+ return [];
1234
+ }
1235
+ const results = [];
1236
+ function walk(d) {
1237
+ try {
1238
+ for (const entry of readdirSync(d, { withFileTypes: true })) {
1239
+ const full = join(d, entry.name);
1240
+ if (entry.isDirectory()) {
1241
+ walk(full);
1242
+ } else if (predicate(full)) {
1243
+ results.push(entry.name);
1244
+ }
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ walk(dir);
1250
+ return results;
1251
+ }
1252
+
1253
+ // src/pipeline.ts
1254
+ async function runReleasePipeline(options) {
1255
+ const {
1256
+ cwd,
1257
+ repoRoot,
1258
+ scopeCwd,
1259
+ scope,
1260
+ config,
1261
+ dryRun = false,
1262
+ skipChecks = false,
1263
+ skipBuild = false,
1264
+ skipVerify = false,
1265
+ checks: checkConfigs,
1266
+ publisher,
1267
+ changelog: changelogGen,
1268
+ logger,
1269
+ onProgress
1270
+ } = options;
1271
+ const startTime = Date.now();
1272
+ const progress = (stage, msg) => {
1273
+ logger?.info?.(msg);
1274
+ onProgress?.(stage, msg);
1275
+ };
1276
+ progress("planning", "Discovering packages and planning release...");
1277
+ const plan = await planRelease({
1278
+ cwd: repoRoot,
1279
+ config,
1280
+ scope,
1281
+ bumpOverride: config.bump
1282
+ });
1283
+ if (plan.packages.length === 0) {
1284
+ return {
1285
+ success: false,
1286
+ plan,
1287
+ report: buildReport("planning", plan, repoRoot, dryRun, startTime, {
1288
+ ok: false,
1289
+ errors: [`No packages found for scope: ${scope || "all"}`],
1290
+ timingMs: 0
1291
+ })
1292
+ };
1293
+ }
1294
+ progress("planning", `Found ${plan.packages.length} package(s) to release`);
1295
+ await saveSnapshot({ cwd: repoRoot, plan });
1296
+ if (!skipChecks && checkConfigs && checkConfigs.length > 0) {
1297
+ progress("checking", `Running ${checkConfigs.length} pre-release check(s)...`);
1298
+ const packagePaths = plan.packages.map((p) => p.path);
1299
+ const checkResults = await runReleaseChecks(checkConfigs, {
1300
+ repoRoot,
1301
+ packagePaths,
1302
+ scopePath: scopeCwd,
1303
+ logger
1304
+ });
1305
+ const failed = checkResults.filter((r) => !r.ok && r.hint !== "optional");
1306
+ if (failed.length > 0) {
1307
+ await restoreSnapshot(repoRoot);
1308
+ return {
1309
+ success: false,
1310
+ plan,
1311
+ report: buildReport("checking", plan, repoRoot, dryRun, startTime, {
1312
+ ok: false,
1313
+ checks: Object.fromEntries(checkResults.map((r) => [r.id, r])),
1314
+ errors: [`Pre-release checks failed: ${failed.map((f) => f.id).join(", ")}`],
1315
+ timingMs: Date.now() - startTime
1316
+ })
1317
+ };
1318
+ }
1319
+ progress("checking", "Pre-release checks passed");
1320
+ }
1321
+ if (!skipBuild && !dryRun) {
1322
+ progress("versioning", `Building ${plan.packages.length} package(s)...`);
1323
+ const buildResults = await buildPackages(plan.packages, { logger });
1324
+ const buildFailed = buildResults.filter((r) => !r.success);
1325
+ if (buildFailed.length > 0) {
1326
+ await restoreSnapshot(repoRoot);
1327
+ return {
1328
+ success: false,
1329
+ plan,
1330
+ report: buildReport("versioning", plan, repoRoot, dryRun, startTime, {
1331
+ ok: false,
1332
+ errors: buildFailed.map((f) => `Build failed: ${f.name} \u2014 ${f.error}`),
1333
+ timingMs: Date.now() - startTime
1334
+ })
1335
+ };
1336
+ }
1337
+ }
1338
+ if (!skipVerify && !dryRun) {
1339
+ progress("verifying", "Verifying package artifacts...");
1340
+ const verifyResults = await verifyPackages(plan.packages, { logger });
1341
+ const verifyFailed = verifyResults.filter((r) => !r.success);
1342
+ if (verifyFailed.length > 0) {
1343
+ await restoreSnapshot(repoRoot);
1344
+ const allIssues = verifyFailed.flatMap((r) => r.issues.map((i) => `${r.name}: ${i}`));
1345
+ return {
1346
+ success: false,
1347
+ plan,
1348
+ report: buildReport("verifying", plan, repoRoot, dryRun, startTime, {
1349
+ ok: false,
1350
+ errors: [`Package verification failed:
1351
+ ${allIssues.join("\n ")}`],
1352
+ timingMs: Date.now() - startTime
1353
+ })
1354
+ };
1355
+ }
1356
+ progress("verifying", "Package artifacts verified");
1357
+ }
1358
+ progress("versioning", "Updating package versions...");
1359
+ if (!dryRun) {
1360
+ const versionUpdates = await updatePackageVersions(plan);
1361
+ const failedUpdates = versionUpdates.filter((u) => !u.updated);
1362
+ if (failedUpdates.length > 0) {
1363
+ await restoreSnapshot(repoRoot);
1364
+ return {
1365
+ success: false,
1366
+ plan,
1367
+ report: buildReport("versioning", plan, repoRoot, dryRun, startTime, {
1368
+ ok: false,
1369
+ errors: failedUpdates.map((u) => `Version update failed: ${u.package}`),
1370
+ versionUpdates,
1371
+ timingMs: Date.now() - startTime
1372
+ })
1373
+ };
1374
+ }
1375
+ }
1376
+ let changelogMd = "";
1377
+ if (changelogGen) {
1378
+ progress("versioning", "Generating changelog...");
1379
+ try {
1380
+ changelogMd = await changelogGen.generate(plan, { repoRoot, gitCwd: scopeCwd, config });
1381
+ } catch (err) {
1382
+ logger?.warn?.(`Changelog generation failed: ${err instanceof Error ? err.message : String(err)}`);
1383
+ }
1384
+ }
1385
+ if (changelogMd && !dryRun) {
1386
+ await copyChangelogToPackages({ plan, changelog: changelogMd });
1387
+ const changelogPath = join(repoRoot, ".kb", "release", "CHANGELOG.md");
1388
+ await mkdir(join(repoRoot, ".kb", "release"), { recursive: true });
1389
+ await writeFile(changelogPath, changelogMd, "utf-8");
1390
+ }
1391
+ progress("publishing", dryRun ? "Simulating publish (dry-run)..." : "Publishing packages...");
1392
+ const packagesToPublish = plan.packages.map((pkg) => ({
1393
+ name: pkg.name,
1394
+ version: pkg.nextVersion,
1395
+ path: pkg.path
1396
+ }));
1397
+ const publishResult = await publisher.publish(packagesToPublish, {
1398
+ dryRun,
1399
+ access: "public"
1400
+ });
1401
+ let gitResult;
1402
+ if (!dryRun && publishResult.errors.length === 0) {
1403
+ progress("verifying", "Committing and tagging release...");
1404
+ gitResult = await commitAndTagRelease({ cwd: scopeCwd, plan, dryRun });
1405
+ }
1406
+ const report = buildReport("verifying", plan, repoRoot, dryRun, startTime, {
1407
+ ok: publishResult.errors.length === 0,
1408
+ published: publishResult.published,
1409
+ skipped: publishResult.skipped,
1410
+ changelog: changelogMd || void 0,
1411
+ git: gitResult ?? void 0,
1412
+ errors: publishResult.errors.length > 0 ? publishResult.errors : void 0,
1413
+ timingMs: Date.now() - startTime
1414
+ });
1415
+ const scopeDir = scope ? scope.replace(/[@/]/g, "-").replace(/^-/, "") : "root";
1416
+ const historyDir = join(repoRoot, ".kb", "release", "history", scopeDir, (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
1417
+ await mkdir(historyDir, { recursive: true });
1418
+ await writeFile(join(historyDir, "report.json"), JSON.stringify(report, null, 2), "utf-8");
1419
+ return { success: report.result.ok, plan, report };
1420
+ }
1421
+ function buildReport(stage, plan, repoRoot, dryRun, startTime, result) {
1422
+ return {
1423
+ schemaVersion: "1.0",
1424
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1425
+ context: { repo: repoRoot, cwd: repoRoot, branch: "unknown", dryRun },
1426
+ stage,
1427
+ plan,
1428
+ result: { ...result, timingMs: result.timingMs ?? Date.now() - startTime }
1429
+ };
1430
+ }
1431
+ async function resolveScopePath(repoRoot, scope) {
1432
+ if (!scope || scope === "root") {
1433
+ return repoRoot;
1434
+ }
1435
+ if (scope.startsWith("@")) {
1436
+ const packageJsonPaths = await globby("**/package.json", {
1437
+ cwd: repoRoot,
1438
+ absolute: true,
1439
+ onlyFiles: true,
1440
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/.*/**"]
1441
+ });
1442
+ for (const pkgJsonPath of packageJsonPaths) {
1443
+ try {
1444
+ const pkg = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
1445
+ if (pkg.name === scope) {
1446
+ return join(pkgJsonPath, "..");
1447
+ }
1448
+ } catch {
1449
+ }
1450
+ }
1451
+ }
1452
+ return join(repoRoot, scope);
1453
+ }
1454
+
1455
+ export { applyVersionStrategy, buildPackages, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, generateChangelog, generateEnhancedChangelog, isBuildCommand, matchesPackagePattern, planRelease, publishPackages, renderJson, renderMarkdown, renderText, resolveScopePath, restoreSnapshot, runRelease, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyPackage, verifyPackages };
1456
+ //# sourceMappingURL=index.js.map
1457
+ //# sourceMappingURL=index.js.map