@grunnverk/commands-publish 1.5.1 → 1.5.3
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 +693 -64
- package/dist/index.js.map +1 -1
- package/dist/src/commands/check-development.d.ts +6 -0
- package/dist/src/commands/check-development.d.ts.map +1 -0
- package/dist/src/commands/development.d.ts.map +1 -1
- package/dist/src/commands/publish.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/utils/checkpoints.d.ts +92 -0
- package/dist/src/utils/checkpoints.d.ts.map +1 -0
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { getDryRunLogger, findDevelopmentBranch, KODRDRIV_DEFAULTS, incrementPatchVersion, incrementMajorVersion, incrementMinorVersion, DEFAULT_TO_COMMIT_ALIAS, Log, DEFAULT_MAX_DIFF_BYTES, Diff, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_OUTPUT_DIRECTORY, toAIConfig, createStorageAdapter, createLoggerAdapter, getOutputPath, getTimestampedResponseFilename, getTimestampedRequestFilename, filterContent, getTimestampedReleaseNotesFilename, improveContentWithLLM, validateReleaseSummary, runGitWithLock, calculateBranchDependentVersion, checkIfTagExists, confirmVersionInteractively,
|
|
2
|
-
import { getCurrentBranch, run, localBranchExists, safeJsonParse, validatePackageJson, getDefaultFromRef, remoteBranchExists, runSecure, runWithDryRunSupport, validateGitRef, safeSyncBranchWithRemote, isBranchInSyncWithRemote } from '@grunnverk/git-tools';
|
|
1
|
+
import { getDryRunLogger, findDevelopmentBranch, KODRDRIV_DEFAULTS, incrementPatchVersion, incrementMajorVersion, incrementMinorVersion, getLogger, isDevelopmentVersion, DEFAULT_TO_COMMIT_ALIAS, Log, DEFAULT_MAX_DIFF_BYTES, Diff, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_OUTPUT_DIRECTORY, toAIConfig, createStorageAdapter, createLoggerAdapter, getOutputPath, getTimestampedResponseFilename, getTimestampedRequestFilename, filterContent, getTimestampedReleaseNotesFilename, improveContentWithLLM, validateReleaseSummary, runGitWithLock, calculateBranchDependentVersion, checkIfTagExists, confirmVersionInteractively, ValidationError } from '@grunnverk/core';
|
|
2
|
+
import { getCurrentBranch, run, localBranchExists, safeJsonParse, validatePackageJson, getGitStatusSummary, getLinkedDependencies, getDefaultFromRef, remoteBranchExists, runSecure, runWithDryRunSupport, validateGitRef, safeSyncBranchWithRemote, isBranchInSyncWithRemote } from '@grunnverk/git-tools';
|
|
3
3
|
import { createStorage, calculateTargetVersion, incrementPatchVersion as incrementPatchVersion$1 } from '@grunnverk/shared';
|
|
4
|
-
import
|
|
4
|
+
import { scanForPackageJsonFiles } from '@grunnverk/tree-core';
|
|
5
|
+
import * as GitHub from '@grunnverk/github-tools';
|
|
6
|
+
import { getOctokit } from '@grunnverk/github-tools';
|
|
7
|
+
import fs, { readFile } from 'fs/promises';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import path__default from 'path';
|
|
5
10
|
import * as Commit from '@grunnverk/commands-git';
|
|
6
11
|
import { Formatter } from '@riotprompt/riotprompt';
|
|
7
12
|
import 'dotenv/config';
|
|
8
13
|
import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt, runAgenticPublish, formatAgenticPublishResult } from '@grunnverk/ai-service';
|
|
9
|
-
import * as GitHub from '@grunnverk/github-tools';
|
|
10
|
-
import fs from 'fs/promises';
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Create retroactive working branch tags for past releases
|
|
@@ -84,7 +87,7 @@ import fs from 'fs/promises';
|
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
89
|
* Execute the development command
|
|
87
|
-
*/ const execute$
|
|
90
|
+
*/ const execute$3 = async (runConfig)=>{
|
|
88
91
|
const isDryRun = runConfig.dryRun || false;
|
|
89
92
|
const logger = getDryRunLogger(isDryRun);
|
|
90
93
|
logger.info('DEV_BRANCH_NAVIGATION: Navigating to working branch for development | Purpose: Start development cycle | Next: Version bump and sync');
|
|
@@ -340,10 +343,18 @@ import fs from 'fs/promises';
|
|
|
340
343
|
try {
|
|
341
344
|
const packageJson = JSON.parse(await fs.readFile('package.json', 'utf-8'));
|
|
342
345
|
const currentVersion = packageJson.version;
|
|
343
|
-
// If current version already has the dev tag,
|
|
346
|
+
// If current version already has the dev tag, check if base version is published
|
|
344
347
|
if (currentVersion.includes(`-${prereleaseTag}.`)) {
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
// Check if base version is already published on npm
|
|
349
|
+
const baseVersion = currentVersion.split('-')[0];
|
|
350
|
+
const { getNpmPublishedVersion } = await import('@grunnverk/core');
|
|
351
|
+
const publishedVersion = await getNpmPublishedVersion(packageJson.name);
|
|
352
|
+
if (publishedVersion !== baseVersion) {
|
|
353
|
+
logger.info(`DEV_ALREADY_DEV_VERSION: Already on working branch with development version | Branch: ${workingBranch} | Version: ${currentVersion} | Status: no-bump-needed`);
|
|
354
|
+
return 'Already on working branch with development version';
|
|
355
|
+
}
|
|
356
|
+
logger.warn(`DEV_VERSION_CONFLICT: Base version already published | Current: ${currentVersion} | Published: ${publishedVersion} | Action: Will bump to next dev version`);
|
|
357
|
+
// Continue with version bump logic
|
|
347
358
|
}
|
|
348
359
|
} catch {
|
|
349
360
|
logger.debug('Could not check current version, proceeding with version bump');
|
|
@@ -518,6 +529,384 @@ import fs from 'fs/promises';
|
|
|
518
529
|
}
|
|
519
530
|
};
|
|
520
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Default patterns for subprojects to exclude from scanning
|
|
534
|
+
*/ const DEFAULT_EXCLUDE_SUBPROJECTS = [
|
|
535
|
+
'doc/',
|
|
536
|
+
'docs/',
|
|
537
|
+
'examples/',
|
|
538
|
+
'test-*/'
|
|
539
|
+
];
|
|
540
|
+
/**
|
|
541
|
+
* Execute check-development command
|
|
542
|
+
*/ async function execute$2(config) {
|
|
543
|
+
var _config_validateReleaseWorkflow;
|
|
544
|
+
var _config_tree_directories, _config_tree;
|
|
545
|
+
const logger = getLogger();
|
|
546
|
+
// Get directory from config - check multiple possible locations
|
|
547
|
+
const directory = config.directory || ((_config_tree = config.tree) === null || _config_tree === void 0 ? void 0 : (_config_tree_directories = _config_tree.directories) === null || _config_tree_directories === void 0 ? void 0 : _config_tree_directories[0]) || process.cwd();
|
|
548
|
+
// Get validateRelease flag - controls merge conflicts and open PRs checks
|
|
549
|
+
const validateRelease = (_config_validateReleaseWorkflow = config.validateReleaseWorkflow) !== null && _config_validateReleaseWorkflow !== void 0 ? _config_validateReleaseWorkflow : false;
|
|
550
|
+
logger.info(`Checking development readiness in ${directory}${validateRelease ? ' (full release validation)' : ' (quick check)'}`);
|
|
551
|
+
// Build exclusion patterns
|
|
552
|
+
const excludedPatterns = [
|
|
553
|
+
'**/node_modules/**',
|
|
554
|
+
'**/dist/**',
|
|
555
|
+
'**/build/**',
|
|
556
|
+
'**/.git/**',
|
|
557
|
+
...DEFAULT_EXCLUDE_SUBPROJECTS.map((pattern)=>{
|
|
558
|
+
const normalized = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern;
|
|
559
|
+
return `**/${normalized}/**`;
|
|
560
|
+
})
|
|
561
|
+
];
|
|
562
|
+
// Determine if this is a tree or single package
|
|
563
|
+
const packageJsonFiles = await scanForPackageJsonFiles(directory, excludedPatterns);
|
|
564
|
+
const isTree = packageJsonFiles.length > 1;
|
|
565
|
+
logger.info(`Detected ${isTree ? 'tree' : 'single package'} with ${packageJsonFiles.length} package(s)`);
|
|
566
|
+
const checks = {
|
|
567
|
+
branch: {
|
|
568
|
+
passed: true,
|
|
569
|
+
issues: []
|
|
570
|
+
},
|
|
571
|
+
remoteSync: {
|
|
572
|
+
passed: true,
|
|
573
|
+
issues: []
|
|
574
|
+
},
|
|
575
|
+
mergeConflicts: {
|
|
576
|
+
passed: true,
|
|
577
|
+
issues: [],
|
|
578
|
+
warnings: []
|
|
579
|
+
},
|
|
580
|
+
devVersion: {
|
|
581
|
+
passed: true,
|
|
582
|
+
issues: []
|
|
583
|
+
},
|
|
584
|
+
linkStatus: {
|
|
585
|
+
passed: true,
|
|
586
|
+
issues: [],
|
|
587
|
+
warnings: []
|
|
588
|
+
},
|
|
589
|
+
openPRs: {
|
|
590
|
+
passed: true,
|
|
591
|
+
issues: [],
|
|
592
|
+
warnings: []
|
|
593
|
+
},
|
|
594
|
+
releaseWorkflow: {
|
|
595
|
+
passed: true,
|
|
596
|
+
issues: [],
|
|
597
|
+
warnings: []
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
const packagesToCheck = isTree ? packageJsonFiles : [
|
|
601
|
+
path.join(directory, 'package.json')
|
|
602
|
+
];
|
|
603
|
+
// Build a set of all local package names for link status checking
|
|
604
|
+
const localPackageNames = new Set();
|
|
605
|
+
for (const pkgJsonPath of packagesToCheck){
|
|
606
|
+
try {
|
|
607
|
+
const pkgJsonContent = await readFile(pkgJsonPath, 'utf-8');
|
|
608
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
609
|
+
if (pkgJson.name) {
|
|
610
|
+
localPackageNames.add(pkgJson.name);
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
613
|
+
// Skip packages we can't read
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (const pkgJsonPath of packagesToCheck){
|
|
617
|
+
var _pkgJson_repository;
|
|
618
|
+
const pkgDir = path.dirname(pkgJsonPath);
|
|
619
|
+
const pkgJsonContent = await readFile(pkgJsonPath, 'utf-8');
|
|
620
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
621
|
+
const pkgName = pkgJson.name || path.basename(pkgDir);
|
|
622
|
+
// 1. Check branch status
|
|
623
|
+
try {
|
|
624
|
+
const gitStatus = await getGitStatusSummary(pkgDir);
|
|
625
|
+
if (gitStatus.branch === 'main' || gitStatus.branch === 'master') {
|
|
626
|
+
checks.branch.passed = false;
|
|
627
|
+
checks.branch.issues.push(`${pkgName} is on ${gitStatus.branch} branch`);
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
checks.branch.issues.push(`${pkgName}: Could not check branch - ${error.message || error}`);
|
|
631
|
+
}
|
|
632
|
+
// 2. Check remote sync status
|
|
633
|
+
try {
|
|
634
|
+
await run('git fetch', {
|
|
635
|
+
cwd: pkgDir
|
|
636
|
+
});
|
|
637
|
+
const { stdout: statusOutput } = await run('git status -sb', {
|
|
638
|
+
cwd: pkgDir
|
|
639
|
+
});
|
|
640
|
+
if (statusOutput.includes('behind')) {
|
|
641
|
+
checks.remoteSync.passed = false;
|
|
642
|
+
const match = statusOutput.match(/behind (\d+)/);
|
|
643
|
+
const count = match ? match[1] : 'some';
|
|
644
|
+
checks.remoteSync.issues.push(`${pkgName} is ${count} commits behind remote`);
|
|
645
|
+
}
|
|
646
|
+
} catch (error) {
|
|
647
|
+
checks.remoteSync.issues.push(`${pkgName}: Could not check remote sync - ${error.message || error}`);
|
|
648
|
+
}
|
|
649
|
+
// 3. Check for merge conflicts with target branch (main) - ALWAYS CHECK THIS
|
|
650
|
+
try {
|
|
651
|
+
const gitStatus = await getGitStatusSummary(pkgDir);
|
|
652
|
+
const currentBranch = gitStatus.branch;
|
|
653
|
+
const targetBranch = 'main'; // The branch we'll merge into during publish
|
|
654
|
+
// Skip if we're already on main
|
|
655
|
+
if (currentBranch !== 'main' && currentBranch !== 'master') {
|
|
656
|
+
// Fetch latest to ensure we have up-to-date refs
|
|
657
|
+
await run('git fetch origin', {
|
|
658
|
+
cwd: pkgDir
|
|
659
|
+
});
|
|
660
|
+
// Try a test merge to detect conflicts
|
|
661
|
+
// Use --no-commit --no-ff to simulate the merge without actually doing it
|
|
662
|
+
try {
|
|
663
|
+
// Check if there would be conflicts using git merge --no-commit --no-ff
|
|
664
|
+
// This is safer as it doesn't modify the working tree
|
|
665
|
+
await run(`git merge --no-commit --no-ff origin/${targetBranch}`, {
|
|
666
|
+
cwd: pkgDir
|
|
667
|
+
});
|
|
668
|
+
// If we get here, check if there are conflicts
|
|
669
|
+
const { stdout: statusAfterMerge } = await run('git status --porcelain', {
|
|
670
|
+
cwd: pkgDir
|
|
671
|
+
});
|
|
672
|
+
if (statusAfterMerge.includes('UU ') || statusAfterMerge.includes('AA ') || statusAfterMerge.includes('DD ') || statusAfterMerge.includes('AU ') || statusAfterMerge.includes('UA ') || statusAfterMerge.includes('DU ') || statusAfterMerge.includes('UD ')) {
|
|
673
|
+
checks.mergeConflicts.passed = false;
|
|
674
|
+
checks.mergeConflicts.issues.push(`${pkgName}: Merge conflicts detected with ${targetBranch} branch`);
|
|
675
|
+
}
|
|
676
|
+
// Abort the test merge (only if there's actually a merge in progress)
|
|
677
|
+
try {
|
|
678
|
+
await run('git merge --abort', {
|
|
679
|
+
cwd: pkgDir
|
|
680
|
+
});
|
|
681
|
+
} catch {
|
|
682
|
+
// Ignore - there might not be a merge to abort if it was a fast-forward
|
|
683
|
+
}
|
|
684
|
+
} catch (mergeError) {
|
|
685
|
+
var _mergeError_message, _mergeError_stderr;
|
|
686
|
+
// Abort any partial merge
|
|
687
|
+
try {
|
|
688
|
+
await run('git merge --abort', {
|
|
689
|
+
cwd: pkgDir
|
|
690
|
+
});
|
|
691
|
+
} catch {
|
|
692
|
+
// Ignore abort errors
|
|
693
|
+
}
|
|
694
|
+
// If merge failed, there are likely conflicts
|
|
695
|
+
if (((_mergeError_message = mergeError.message) === null || _mergeError_message === void 0 ? void 0 : _mergeError_message.includes('CONFLICT')) || ((_mergeError_stderr = mergeError.stderr) === null || _mergeError_stderr === void 0 ? void 0 : _mergeError_stderr.includes('CONFLICT'))) {
|
|
696
|
+
checks.mergeConflicts.passed = false;
|
|
697
|
+
checks.mergeConflicts.issues.push(`${pkgName}: Merge conflicts detected with ${targetBranch} branch`);
|
|
698
|
+
} else {
|
|
699
|
+
// Some other error - log as warning
|
|
700
|
+
checks.mergeConflicts.warnings.push(`${pkgName}: Could not check for merge conflicts - ${mergeError.message || mergeError}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch (error) {
|
|
705
|
+
checks.mergeConflicts.warnings.push(`${pkgName}: Could not check for merge conflicts - ${error.message || error}`);
|
|
706
|
+
}
|
|
707
|
+
// 4. Check dev version status
|
|
708
|
+
const version = pkgJson.version;
|
|
709
|
+
if (!version) {
|
|
710
|
+
checks.devVersion.issues.push(`${pkgName}: No version field in package.json`);
|
|
711
|
+
} else if (!isDevelopmentVersion(version)) {
|
|
712
|
+
checks.devVersion.passed = false;
|
|
713
|
+
checks.devVersion.issues.push(`${pkgName} has non-dev version: ${version}`);
|
|
714
|
+
} else {
|
|
715
|
+
// Check if base version exists on npm
|
|
716
|
+
const baseVersion = version.split('-')[0];
|
|
717
|
+
try {
|
|
718
|
+
const { stdout } = await run(`npm view ${pkgName}@${baseVersion} version`, {
|
|
719
|
+
cwd: pkgDir
|
|
720
|
+
});
|
|
721
|
+
if (stdout.trim() === baseVersion) {
|
|
722
|
+
checks.devVersion.passed = false;
|
|
723
|
+
checks.devVersion.issues.push(`${pkgName}: Base version ${baseVersion} already published (current: ${version})`);
|
|
724
|
+
}
|
|
725
|
+
} catch {
|
|
726
|
+
// Version doesn't exist on npm, which is good
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// 5. Check link status (warning only - links are recommended but not required)
|
|
730
|
+
if (pkgJson.dependencies || pkgJson.devDependencies) {
|
|
731
|
+
try {
|
|
732
|
+
const linkedDeps = await getLinkedDependencies(pkgDir);
|
|
733
|
+
const allDeps = {
|
|
734
|
+
...pkgJson.dependencies,
|
|
735
|
+
...pkgJson.devDependencies
|
|
736
|
+
};
|
|
737
|
+
const localDeps = Object.keys(allDeps).filter((dep)=>localPackageNames.has(dep));
|
|
738
|
+
const unlinkedLocal = localDeps.filter((dep)=>!linkedDeps.has(dep));
|
|
739
|
+
if (unlinkedLocal.length > 0) {
|
|
740
|
+
// Don't fail the check, just warn - links are recommended but not required
|
|
741
|
+
checks.linkStatus.warnings.push(`${pkgName}: Local dependencies not linked (recommended): ${unlinkedLocal.join(', ')}`);
|
|
742
|
+
}
|
|
743
|
+
} catch (error) {
|
|
744
|
+
checks.linkStatus.warnings.push(`${pkgName}: Could not check link status - ${error.message || error}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// 6. Check for open PRs from working branch - only if validateRelease is true
|
|
748
|
+
if (validateRelease && ((_pkgJson_repository = pkgJson.repository) === null || _pkgJson_repository === void 0 ? void 0 : _pkgJson_repository.url)) {
|
|
749
|
+
try {
|
|
750
|
+
const gitStatus = await getGitStatusSummary(pkgDir);
|
|
751
|
+
const currentBranch = gitStatus.branch;
|
|
752
|
+
// Extract owner/repo from repository URL
|
|
753
|
+
const repoUrl = pkgJson.repository.url;
|
|
754
|
+
const match = repoUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
755
|
+
if (match) {
|
|
756
|
+
const [, owner, repo] = match;
|
|
757
|
+
try {
|
|
758
|
+
const octokit = getOctokit();
|
|
759
|
+
const { data: openPRs } = await octokit.pulls.list({
|
|
760
|
+
owner,
|
|
761
|
+
repo,
|
|
762
|
+
state: 'open',
|
|
763
|
+
head: `${owner}:${currentBranch}`
|
|
764
|
+
});
|
|
765
|
+
if (openPRs.length > 0) {
|
|
766
|
+
checks.openPRs.passed = false;
|
|
767
|
+
for (const pr of openPRs){
|
|
768
|
+
const prInfo = `PR #${pr.number}: ${pr.title} (${pr.html_url})`;
|
|
769
|
+
checks.openPRs.issues.push(`${pkgName}: ${prInfo}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} catch (prError) {
|
|
773
|
+
var _prError_message;
|
|
774
|
+
// Only log if it's not a 404 (repo might not exist on GitHub)
|
|
775
|
+
if (!((_prError_message = prError.message) === null || _prError_message === void 0 ? void 0 : _prError_message.includes('404')) && (!prError.status || prError.status !== 404)) {
|
|
776
|
+
checks.openPRs.warnings.push(`${pkgName}: Could not check PRs - ${prError.message || prError}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
} catch (error) {
|
|
781
|
+
// Don't fail the check if we can't check PRs
|
|
782
|
+
checks.openPRs.warnings.push(`${pkgName}: Could not check for open PRs - ${error.message || error}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// 7. Check release workflow readiness (validate build, test, publish dry-run)
|
|
786
|
+
// This check validates that the package can be successfully published
|
|
787
|
+
// Only run if explicitly requested via config flag
|
|
788
|
+
if (config.validateReleaseWorkflow) {
|
|
789
|
+
try {
|
|
790
|
+
logger.info(`${pkgName}: Validating release workflow readiness...`);
|
|
791
|
+
// Check 1: Build succeeds
|
|
792
|
+
try {
|
|
793
|
+
await run('npm run build', {
|
|
794
|
+
cwd: pkgDir
|
|
795
|
+
});
|
|
796
|
+
logger.debug(`${pkgName}: Build check passed`);
|
|
797
|
+
} catch (buildError) {
|
|
798
|
+
checks.releaseWorkflow.passed = false;
|
|
799
|
+
checks.releaseWorkflow.issues.push(`${pkgName}: Build fails - ${buildError.message || buildError}`);
|
|
800
|
+
}
|
|
801
|
+
// Check 2: Tests pass
|
|
802
|
+
try {
|
|
803
|
+
await run('npm test', {
|
|
804
|
+
cwd: pkgDir
|
|
805
|
+
});
|
|
806
|
+
logger.debug(`${pkgName}: Test check passed`);
|
|
807
|
+
} catch (testError) {
|
|
808
|
+
checks.releaseWorkflow.passed = false;
|
|
809
|
+
checks.releaseWorkflow.issues.push(`${pkgName}: Tests fail - ${testError.message || testError}`);
|
|
810
|
+
}
|
|
811
|
+
// Check 3: Publish dry-run succeeds
|
|
812
|
+
try {
|
|
813
|
+
await run('npm publish --dry-run', {
|
|
814
|
+
cwd: pkgDir
|
|
815
|
+
});
|
|
816
|
+
logger.debug(`${pkgName}: Publish dry-run check passed`);
|
|
817
|
+
} catch (publishError) {
|
|
818
|
+
checks.releaseWorkflow.passed = false;
|
|
819
|
+
checks.releaseWorkflow.issues.push(`${pkgName}: Publish dry-run fails - ${publishError.message || publishError}`);
|
|
820
|
+
}
|
|
821
|
+
// Check 4: NPM_TOKEN environment variable
|
|
822
|
+
if (!process.env.NPM_TOKEN) {
|
|
823
|
+
checks.releaseWorkflow.warnings.push(`${pkgName}: NPM_TOKEN environment variable not set (required for publishing)`);
|
|
824
|
+
}
|
|
825
|
+
// Check 5: GitHub workflow file exists
|
|
826
|
+
const workflowPath = path.join(pkgDir, '.github', 'workflows', 'npm-publish.yml');
|
|
827
|
+
try {
|
|
828
|
+
await readFile(workflowPath, 'utf-8');
|
|
829
|
+
logger.debug(`${pkgName}: GitHub workflow file exists`);
|
|
830
|
+
} catch {
|
|
831
|
+
checks.releaseWorkflow.warnings.push(`${pkgName}: GitHub workflow file not found at .github/workflows/npm-publish.yml`);
|
|
832
|
+
}
|
|
833
|
+
} catch (error) {
|
|
834
|
+
checks.releaseWorkflow.warnings.push(`${pkgName}: Could not validate release workflow - ${error.message || error}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Build summary - linkStatus and releaseWorkflow are not included in allPassed (recommendations)
|
|
839
|
+
// mergeConflicts is ALWAYS checked (critical for preventing post-merge failures)
|
|
840
|
+
// openPRs is only checked when validateRelease is true
|
|
841
|
+
const allPassed = checks.branch.passed && checks.remoteSync.passed && checks.mergeConflicts.passed && checks.devVersion.passed && (validateRelease ? checks.openPRs.passed : true);
|
|
842
|
+
const hasWarnings = checks.linkStatus.warnings.length > 0 || checks.mergeConflicts.warnings.length > 0 || checks.openPRs.warnings.length > 0 || checks.releaseWorkflow.warnings.length > 0;
|
|
843
|
+
// Log results
|
|
844
|
+
let summary = `\n${'='.repeat(60)}\n`;
|
|
845
|
+
summary += `Development Readiness Check${validateRelease ? ' (Full Release Validation)' : ' (Quick Check)'}\n`;
|
|
846
|
+
summary += `${'='.repeat(60)}\n\n`;
|
|
847
|
+
summary += `Type: ${isTree ? 'Tree (monorepo)' : 'Single package'}\n`;
|
|
848
|
+
summary += `Packages checked: ${packagesToCheck.length}\n\n`;
|
|
849
|
+
if (allPassed) {
|
|
850
|
+
summary += `✅ Status: READY FOR DEVELOPMENT\n\n`;
|
|
851
|
+
summary += `All required checks passed:\n`;
|
|
852
|
+
summary += ` ✓ Branch status\n`;
|
|
853
|
+
summary += ` ✓ Remote sync\n`;
|
|
854
|
+
summary += ` ✓ No merge conflicts with main\n`;
|
|
855
|
+
summary += ` ✓ Dev versions\n`;
|
|
856
|
+
if (validateRelease) {
|
|
857
|
+
summary += ` ✓ No open PRs\n`;
|
|
858
|
+
}
|
|
859
|
+
if (!hasWarnings) {
|
|
860
|
+
summary += ` ✓ All local dependencies linked\n`;
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
summary += `⚠️ Status: NOT READY\n\n`;
|
|
864
|
+
if (!checks.branch.passed) {
|
|
865
|
+
summary += `❌ Branch Issues:\n`;
|
|
866
|
+
checks.branch.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
867
|
+
summary += `\n`;
|
|
868
|
+
}
|
|
869
|
+
if (!checks.remoteSync.passed) {
|
|
870
|
+
summary += `❌ Remote Sync Issues:\n`;
|
|
871
|
+
checks.remoteSync.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
872
|
+
summary += `\n`;
|
|
873
|
+
}
|
|
874
|
+
if (!checks.mergeConflicts.passed) {
|
|
875
|
+
summary += `❌ Merge Conflict Issues:\n`;
|
|
876
|
+
checks.mergeConflicts.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
877
|
+
summary += `\n`;
|
|
878
|
+
}
|
|
879
|
+
if (!checks.devVersion.passed) {
|
|
880
|
+
summary += `❌ Dev Version Issues:\n`;
|
|
881
|
+
checks.devVersion.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
882
|
+
summary += `\n`;
|
|
883
|
+
}
|
|
884
|
+
if (validateRelease && !checks.openPRs.passed) {
|
|
885
|
+
summary += `❌ Open PR Issues:\n`;
|
|
886
|
+
checks.openPRs.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
887
|
+
summary += `\n`;
|
|
888
|
+
}
|
|
889
|
+
if (!checks.releaseWorkflow.passed) {
|
|
890
|
+
summary += `❌ Release Workflow Issues:\n`;
|
|
891
|
+
checks.releaseWorkflow.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
892
|
+
summary += `\n`;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
// Log warnings separately (non-blocking)
|
|
896
|
+
if (hasWarnings) {
|
|
897
|
+
summary += `⚠️ Recommendations:\n`;
|
|
898
|
+
checks.linkStatus.warnings.forEach((warning)=>summary += ` - ${warning}\n`);
|
|
899
|
+
checks.mergeConflicts.warnings.forEach((warning)=>summary += ` - ${warning}\n`);
|
|
900
|
+
if (validateRelease) {
|
|
901
|
+
checks.openPRs.warnings.forEach((warning)=>summary += ` - ${warning}\n`);
|
|
902
|
+
}
|
|
903
|
+
checks.releaseWorkflow.warnings.forEach((warning)=>summary += ` - ${warning}\n`);
|
|
904
|
+
summary += `\n`;
|
|
905
|
+
}
|
|
906
|
+
summary += `${'='.repeat(60)}\n`;
|
|
907
|
+
return summary;
|
|
908
|
+
}
|
|
909
|
+
|
|
521
910
|
// Helper function to read context files
|
|
522
911
|
async function readContextFiles(contextFiles, logger) {
|
|
523
912
|
if (!contextFiles || contextFiles.length === 0) {
|
|
@@ -873,7 +1262,7 @@ const execute$1 = async (runConfig)=>{
|
|
|
873
1262
|
|
|
874
1263
|
const scanNpmrcForEnvVars = async (storage)=>{
|
|
875
1264
|
const logger = getLogger();
|
|
876
|
-
const npmrcPath =
|
|
1265
|
+
const npmrcPath = path__default.join(process.cwd(), '.npmrc');
|
|
877
1266
|
const envVars = [];
|
|
878
1267
|
if (await storage.exists(npmrcPath)) {
|
|
879
1268
|
try {
|
|
@@ -903,7 +1292,7 @@ const scanNpmrcForEnvVars = async (storage)=>{
|
|
|
903
1292
|
* development artifacts and sensitive files.
|
|
904
1293
|
*/ const checkGitignorePatterns = async (storage, isDryRun)=>{
|
|
905
1294
|
const logger = getDryRunLogger(isDryRun);
|
|
906
|
-
const gitignorePath =
|
|
1295
|
+
const gitignorePath = path__default.join(process.cwd(), '.gitignore');
|
|
907
1296
|
// Required patterns that must be present in .gitignore
|
|
908
1297
|
const requiredPatterns = [
|
|
909
1298
|
'node_modules',
|
|
@@ -995,7 +1384,7 @@ const scanNpmrcForEnvVars = async (storage)=>{
|
|
|
995
1384
|
* and cleans them up if found by removing package-lock.json and regenerating it.
|
|
996
1385
|
*/ const cleanupNpmLinkReferences = async (isDryRun)=>{
|
|
997
1386
|
const logger = getDryRunLogger(isDryRun);
|
|
998
|
-
const packageLockPath =
|
|
1387
|
+
const packageLockPath = path__default.join(process.cwd(), 'package-lock.json');
|
|
999
1388
|
try {
|
|
1000
1389
|
// Check if package-lock.json exists
|
|
1001
1390
|
try {
|
|
@@ -1239,7 +1628,7 @@ const runPrechecks = async (runConfig, targetBranch)=>{
|
|
|
1239
1628
|
}
|
|
1240
1629
|
// Check if prepublishOnly script exists in package.json
|
|
1241
1630
|
logger.info('PRECHECK_PREPUBLISH: Checking for prepublishOnly script in package.json | Requirement: Must exist to run pre-flight checks | Expected: clean, lint, build, test');
|
|
1242
|
-
const packageJsonPath =
|
|
1631
|
+
const packageJsonPath = path__default.join(process.cwd(), 'package.json');
|
|
1243
1632
|
if (!await storage.exists(packageJsonPath)) {
|
|
1244
1633
|
if (!isDryRun) {
|
|
1245
1634
|
throw new Error('package.json not found in current directory.');
|
|
@@ -1421,6 +1810,63 @@ const execute = async (runConfig)=>{
|
|
|
1421
1810
|
currentBranch = 'mock-branch';
|
|
1422
1811
|
} else {
|
|
1423
1812
|
currentBranch = await GitHub.getCurrentBranchName();
|
|
1813
|
+
}
|
|
1814
|
+
// Determine target branch EARLY (before expensive operations) for early necessity check
|
|
1815
|
+
let targetBranch = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
|
|
1816
|
+
let branchDependentVersioning = false;
|
|
1817
|
+
// Check for branches configuration
|
|
1818
|
+
if (runConfig.branches && runConfig.branches[currentBranch]) {
|
|
1819
|
+
branchDependentVersioning = true;
|
|
1820
|
+
const branchConfig = runConfig.branches[currentBranch];
|
|
1821
|
+
if (branchConfig.targetBranch) {
|
|
1822
|
+
targetBranch = branchConfig.targetBranch;
|
|
1823
|
+
}
|
|
1824
|
+
logger.info(`BRANCH_DEPENDENT_TARGETING: Branch-specific configuration active | Source: ${currentBranch} | Target: ${targetBranch} | Feature: Branch-dependent versioning and targeting`);
|
|
1825
|
+
logger.info(`BRANCH_CONFIGURATION_SOURCE: Current branch | Branch: ${currentBranch} | Type: source`);
|
|
1826
|
+
logger.info(`BRANCH_CONFIGURATION_TARGET: Target branch for publish | Branch: ${targetBranch} | Type: destination`);
|
|
1827
|
+
// Look at target branch config to show version strategy
|
|
1828
|
+
const targetBranchConfig = runConfig.branches[targetBranch];
|
|
1829
|
+
if (targetBranchConfig === null || targetBranchConfig === void 0 ? void 0 : targetBranchConfig.version) {
|
|
1830
|
+
const versionType = targetBranchConfig.version.type;
|
|
1831
|
+
const versionTag = targetBranchConfig.version.tag;
|
|
1832
|
+
const versionIncrement = targetBranchConfig.version.increment;
|
|
1833
|
+
logger.info(`VERSION_STRATEGY: Target branch version configuration | Branch: ${targetBranch} | Type: ${versionType} | Tag: ${versionTag || 'none'} | Increment: ${versionIncrement ? 'enabled' : 'disabled'}`);
|
|
1834
|
+
}
|
|
1835
|
+
} else {
|
|
1836
|
+
logger.debug(`BRANCH_TARGETING_DEFAULT: No branch-specific configuration found | Branch: ${currentBranch} | Action: Using default target | Target: ${targetBranch}`);
|
|
1837
|
+
}
|
|
1838
|
+
// Handle --sync-target flag
|
|
1839
|
+
if ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.syncTarget) {
|
|
1840
|
+
await handleTargetBranchSyncRecovery(runConfig, targetBranch);
|
|
1841
|
+
return; // Exit after sync operation
|
|
1842
|
+
}
|
|
1843
|
+
// OPTIMIZATION: Early check if release is necessary BEFORE expensive operations
|
|
1844
|
+
// This can save 2-4 minutes for packages with no changes by skipping:
|
|
1845
|
+
// - git fetch (~30-60s)
|
|
1846
|
+
// - current branch sync (~30-60s)
|
|
1847
|
+
// - target branch setup (~30-60s)
|
|
1848
|
+
// - prechecks (~30-60s)
|
|
1849
|
+
if (!isDryRun) {
|
|
1850
|
+
logger.info('RELEASE_NECESSITY_CHECK_EARLY: Quick check if release is required | Comparison: current branch vs target | Target: ' + targetBranch + ' | Purpose: Skip expensive operations for unchanged packages');
|
|
1851
|
+
try {
|
|
1852
|
+
const necessity = await isReleaseNecessaryComparedToTarget(targetBranch, isDryRun);
|
|
1853
|
+
if (!necessity.necessary) {
|
|
1854
|
+
logger.info(`\nRELEASE_SKIPPED_EARLY: No meaningful changes detected, skipping publish | Reason: ${necessity.reason} | Target: ${targetBranch} | Time saved: ~2-4 minutes`);
|
|
1855
|
+
// Emit a machine-readable marker so tree mode can detect skip and avoid propagating versions
|
|
1856
|
+
// CRITICAL: Use console.log to write to stdout (logger.info goes to stderr via winston)
|
|
1857
|
+
// eslint-disable-next-line no-console
|
|
1858
|
+
console.log('KODRDRIV_PUBLISH_SKIPPED');
|
|
1859
|
+
return;
|
|
1860
|
+
} else {
|
|
1861
|
+
logger.verbose(`RELEASE_PROCEEDING_EARLY: Meaningful changes detected, continuing with full publish workflow | Reason: ${necessity.reason} | Target: ${targetBranch}`);
|
|
1862
|
+
}
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
// If early check fails (e.g., target branch doesn't exist yet), continue with normal flow
|
|
1865
|
+
logger.verbose(`RELEASE_NECESSITY_CHECK_EARLY_SKIPPED: Unable to perform early check | Error: ${error.message} | Action: Continuing with full workflow | Reason: Target branch may not exist yet or other setup needed`);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
// Now perform expensive operations only if we're proceeding with publish
|
|
1869
|
+
if (!isDryRun) {
|
|
1424
1870
|
// Fetch latest remote information to avoid conflicts
|
|
1425
1871
|
logger.info('GIT_FETCH_STARTING: Fetching latest remote information | Remote: origin | Purpose: Avoid conflicts during publish | Command: git fetch origin');
|
|
1426
1872
|
try {
|
|
@@ -1457,35 +1903,6 @@ const execute = async (runConfig)=>{
|
|
|
1457
1903
|
}
|
|
1458
1904
|
}
|
|
1459
1905
|
}
|
|
1460
|
-
// Determine target branch and version strategy based on branch configuration
|
|
1461
|
-
let targetBranch = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
|
|
1462
|
-
let branchDependentVersioning = false;
|
|
1463
|
-
// Check for branches configuration
|
|
1464
|
-
if (runConfig.branches && runConfig.branches[currentBranch]) {
|
|
1465
|
-
branchDependentVersioning = true;
|
|
1466
|
-
const branchConfig = runConfig.branches[currentBranch];
|
|
1467
|
-
if (branchConfig.targetBranch) {
|
|
1468
|
-
targetBranch = branchConfig.targetBranch;
|
|
1469
|
-
}
|
|
1470
|
-
logger.info(`BRANCH_DEPENDENT_TARGETING: Branch-specific configuration active | Source: ${currentBranch} | Target: ${targetBranch} | Feature: Branch-dependent versioning and targeting`);
|
|
1471
|
-
logger.info(`BRANCH_CONFIGURATION_SOURCE: Current branch | Branch: ${currentBranch} | Type: source`);
|
|
1472
|
-
logger.info(`BRANCH_CONFIGURATION_TARGET: Target branch for publish | Branch: ${targetBranch} | Type: destination`);
|
|
1473
|
-
// Look at target branch config to show version strategy
|
|
1474
|
-
const targetBranchConfig = runConfig.branches[targetBranch];
|
|
1475
|
-
if (targetBranchConfig === null || targetBranchConfig === void 0 ? void 0 : targetBranchConfig.version) {
|
|
1476
|
-
const versionType = targetBranchConfig.version.type;
|
|
1477
|
-
const versionTag = targetBranchConfig.version.tag;
|
|
1478
|
-
const versionIncrement = targetBranchConfig.version.increment;
|
|
1479
|
-
logger.info(`VERSION_STRATEGY: Target branch version configuration | Branch: ${targetBranch} | Type: ${versionType} | Tag: ${versionTag || 'none'} | Increment: ${versionIncrement ? 'enabled' : 'disabled'}`);
|
|
1480
|
-
}
|
|
1481
|
-
} else {
|
|
1482
|
-
logger.debug(`BRANCH_TARGETING_DEFAULT: No branch-specific configuration found | Branch: ${currentBranch} | Action: Using default target | Target: ${targetBranch}`);
|
|
1483
|
-
}
|
|
1484
|
-
// Handle --sync-target flag
|
|
1485
|
-
if ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.syncTarget) {
|
|
1486
|
-
await handleTargetBranchSyncRecovery(runConfig, targetBranch);
|
|
1487
|
-
return; // Exit after sync operation
|
|
1488
|
-
}
|
|
1489
1906
|
// Check if target branch exists and create it if needed
|
|
1490
1907
|
logger.info(`TARGET_BRANCH_CHECK: Verifying target branch existence | Branch: ${targetBranch} | Action: Create if missing | Source: Current HEAD`);
|
|
1491
1908
|
if (isDryRun) {
|
|
@@ -1540,24 +1957,6 @@ const execute = async (runConfig)=>{
|
|
|
1540
1957
|
}
|
|
1541
1958
|
// Run prechecks before starting any work
|
|
1542
1959
|
await runPrechecks(runConfig, targetBranch);
|
|
1543
|
-
// Early check: determine if a release is necessary compared to target branch
|
|
1544
|
-
logger.info('RELEASE_NECESSITY_CHECK: Evaluating if release is required | Comparison: current branch vs target | Target: ' + targetBranch + ' | Purpose: Avoid unnecessary publishes');
|
|
1545
|
-
try {
|
|
1546
|
-
const necessity = await isReleaseNecessaryComparedToTarget(targetBranch, isDryRun);
|
|
1547
|
-
if (!necessity.necessary) {
|
|
1548
|
-
logger.info(`\nRELEASE_SKIPPED: No meaningful changes detected, skipping publish | Reason: ${necessity.reason} | Target: ${targetBranch}`);
|
|
1549
|
-
// Emit a machine-readable marker so tree mode can detect skip and avoid propagating versions
|
|
1550
|
-
// CRITICAL: Use console.log to write to stdout (logger.info goes to stderr via winston)
|
|
1551
|
-
// eslint-disable-next-line no-console
|
|
1552
|
-
console.log('KODRDRIV_PUBLISH_SKIPPED');
|
|
1553
|
-
return;
|
|
1554
|
-
} else {
|
|
1555
|
-
logger.verbose(`RELEASE_PROCEEDING: Meaningful changes detected, continuing with publish | Reason: ${necessity.reason} | Target: ${targetBranch}`);
|
|
1556
|
-
}
|
|
1557
|
-
} catch (error) {
|
|
1558
|
-
// On unexpected errors, proceed with publish to avoid false negatives blocking releases
|
|
1559
|
-
logger.verbose(`RELEASE_NECESSITY_CHECK_ERROR: Unable to determine release necessity | Error: ${error.message} | Action: Proceeding conservatively with publish | Rationale: Avoid blocking valid releases`);
|
|
1560
|
-
}
|
|
1561
1960
|
logger.info('RELEASE_PROCESS_STARTING: Initiating release workflow | Target: ' + targetBranch + ' | Phase: dependency updates and version management');
|
|
1562
1961
|
let pr = null;
|
|
1563
1962
|
let newVersion = ''; // Will be set during version determination phase
|
|
@@ -1788,9 +2187,11 @@ const execute = async (runConfig)=>{
|
|
|
1788
2187
|
logger.info('');
|
|
1789
2188
|
if ((_runConfig_publish16 = runConfig.publish) === null || _runConfig_publish16 === void 0 ? void 0 : _runConfig_publish16.skipAlreadyPublished) {
|
|
1790
2189
|
logger.info('PUBLISH_SKIPPED_FLAG: Skipping package due to flag | Flag: --skip-already-published | Version: ' + proposedVersion + ' | Status: skipped');
|
|
1791
|
-
// Emit skip marker for tree mode detection
|
|
2190
|
+
// Emit skip marker for tree mode detection with reason
|
|
1792
2191
|
// eslint-disable-next-line no-console
|
|
1793
2192
|
console.log('KODRDRIV_PUBLISH_SKIPPED');
|
|
2193
|
+
// eslint-disable-next-line no-console
|
|
2194
|
+
console.log('KODRDRIV_PUBLISH_SKIP_REASON:already-published');
|
|
1794
2195
|
return; // Exit without error
|
|
1795
2196
|
} else {
|
|
1796
2197
|
throw new Error(`Version ${proposedVersion} already published. Use --skip-already-published to continue.`);
|
|
@@ -2568,7 +2969,7 @@ const execute = async (runConfig)=>{
|
|
|
2568
2969
|
* Check for required scripts in package.json
|
|
2569
2970
|
*/ async function checkScripts(result, _isDryRun) {
|
|
2570
2971
|
const storage = createStorage();
|
|
2571
|
-
const packageJsonPath =
|
|
2972
|
+
const packageJsonPath = path__default.join(process.cwd(), 'package.json');
|
|
2572
2973
|
try {
|
|
2573
2974
|
var _packageJson_scripts;
|
|
2574
2975
|
if (!await storage.exists(packageJsonPath)) {
|
|
@@ -2610,7 +3011,7 @@ const execute = async (runConfig)=>{
|
|
|
2610
3011
|
if (runConfig.tree) {
|
|
2611
3012
|
const storage = createStorage();
|
|
2612
3013
|
const rootPath = process.cwd();
|
|
2613
|
-
const packageJsonPath =
|
|
3014
|
+
const packageJsonPath = path__default.join(rootPath, 'package.json');
|
|
2614
3015
|
try {
|
|
2615
3016
|
if (!await storage.exists(packageJsonPath)) {
|
|
2616
3017
|
result.errors.push({
|
|
@@ -2848,5 +3249,233 @@ const execute = async (runConfig)=>{
|
|
|
2848
3249
|
};
|
|
2849
3250
|
}
|
|
2850
3251
|
|
|
2851
|
-
|
|
3252
|
+
const logger = getLogger();
|
|
3253
|
+
/**
|
|
3254
|
+
* Get the checkpoint file path for a directory
|
|
3255
|
+
*/ function getCheckpointPath(workingDirectory) {
|
|
3256
|
+
return path__default.join(workingDirectory, '.kodrdriv-publish-checkpoint.json');
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Save a checkpoint
|
|
3260
|
+
*/ async function saveCheckpoint(checkpoint) {
|
|
3261
|
+
const storage = createStorage();
|
|
3262
|
+
const checkpointPath = getCheckpointPath(checkpoint.workingDirectory);
|
|
3263
|
+
try {
|
|
3264
|
+
await storage.writeFile(checkpointPath, JSON.stringify(checkpoint, null, 2), 'utf-8');
|
|
3265
|
+
logger.debug(`CHECKPOINT_SAVED: Saved publish checkpoint | Phase: ${checkpoint.phase} | Package: ${checkpoint.packageName} | Version: ${checkpoint.version}`);
|
|
3266
|
+
} catch (error) {
|
|
3267
|
+
logger.warn(`CHECKPOINT_SAVE_FAILED: Failed to save checkpoint | Error: ${error.message}`);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Load the most recent checkpoint
|
|
3272
|
+
*/ async function loadCheckpoint(workingDirectory) {
|
|
3273
|
+
const storage = createStorage();
|
|
3274
|
+
const checkpointPath = getCheckpointPath(workingDirectory);
|
|
3275
|
+
try {
|
|
3276
|
+
if (!await storage.exists(checkpointPath)) {
|
|
3277
|
+
return null;
|
|
3278
|
+
}
|
|
3279
|
+
const content = await storage.readFile(checkpointPath, 'utf-8');
|
|
3280
|
+
const checkpoint = JSON.parse(content);
|
|
3281
|
+
logger.debug(`CHECKPOINT_LOADED: Loaded publish checkpoint | Phase: ${checkpoint.phase} | Package: ${checkpoint.packageName} | Version: ${checkpoint.version}`);
|
|
3282
|
+
return checkpoint;
|
|
3283
|
+
} catch (error) {
|
|
3284
|
+
logger.warn(`CHECKPOINT_LOAD_FAILED: Failed to load checkpoint | Error: ${error.message}`);
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Delete checkpoint (cleanup after successful publish)
|
|
3290
|
+
*/ async function deleteCheckpoint(workingDirectory) {
|
|
3291
|
+
const storage = createStorage();
|
|
3292
|
+
const checkpointPath = getCheckpointPath(workingDirectory);
|
|
3293
|
+
try {
|
|
3294
|
+
if (await storage.exists(checkpointPath)) {
|
|
3295
|
+
await storage.deleteFile(checkpointPath);
|
|
3296
|
+
logger.debug('CHECKPOINT_DELETED: Removed publish checkpoint after successful completion');
|
|
3297
|
+
}
|
|
3298
|
+
} catch (error) {
|
|
3299
|
+
logger.warn(`CHECKPOINT_DELETE_FAILED: Failed to delete checkpoint | Error: ${error.message}`);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Update an existing checkpoint with new data
|
|
3304
|
+
*/ async function updateCheckpoint(workingDirectory, updates) {
|
|
3305
|
+
const existing = await loadCheckpoint(workingDirectory);
|
|
3306
|
+
if (!existing) {
|
|
3307
|
+
logger.warn('CHECKPOINT_UPDATE_FAILED: No existing checkpoint to update');
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const updated = {
|
|
3311
|
+
...existing,
|
|
3312
|
+
...updates,
|
|
3313
|
+
timestamp: new Date().toISOString()
|
|
3314
|
+
};
|
|
3315
|
+
await saveCheckpoint(updated);
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Determine the appropriate recovery strategy based on checkpoint
|
|
3319
|
+
*/ function analyzeRecoveryStrategy(checkpoint) {
|
|
3320
|
+
const phase = checkpoint.phase;
|
|
3321
|
+
// Phase: initialized, validated, pr-created
|
|
3322
|
+
// These are safe - nothing permanent has happened yet
|
|
3323
|
+
if (phase === 'initialized' || phase === 'validated' || phase === 'pr-created') {
|
|
3324
|
+
return {
|
|
3325
|
+
recoverable: true,
|
|
3326
|
+
action: 'continue',
|
|
3327
|
+
explanation: 'Publish can be continued or restarted safely',
|
|
3328
|
+
steps: [
|
|
3329
|
+
'Fix any issues that caused the failure',
|
|
3330
|
+
'Run: kodrdriv publish --continue',
|
|
3331
|
+
'Or restart: kodrdriv publish'
|
|
3332
|
+
],
|
|
3333
|
+
risks: [
|
|
3334
|
+
'None - no permanent changes have been made'
|
|
3335
|
+
]
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
// Phase: pr-merged (but not published)
|
|
3339
|
+
// This is the danger zone - merge is permanent but npm publish hasn't happened
|
|
3340
|
+
if (phase === 'pr-merged' && !checkpoint.npmPublished) {
|
|
3341
|
+
return {
|
|
3342
|
+
recoverable: true,
|
|
3343
|
+
action: 'rollback',
|
|
3344
|
+
explanation: 'PR merged but npm publish failed - can rollback the merge',
|
|
3345
|
+
steps: [
|
|
3346
|
+
'Revert the merge commit on main branch',
|
|
3347
|
+
'Delete any git tags that were created',
|
|
3348
|
+
'Reset working branch to clean state',
|
|
3349
|
+
'Increment version for next attempt',
|
|
3350
|
+
'Run: kodrdriv publish --rollback'
|
|
3351
|
+
],
|
|
3352
|
+
risks: [
|
|
3353
|
+
'Creates a revert commit in main branch history',
|
|
3354
|
+
'Any work based on the merge will need to be rebased',
|
|
3355
|
+
'Tags will be deleted and recreated on next publish'
|
|
3356
|
+
]
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
// Phase: tagged, npm-publishing (but not completed)
|
|
3360
|
+
// Tags exist but npm publish incomplete
|
|
3361
|
+
if ((phase === 'tagged' || phase === 'npm-publishing') && !checkpoint.npmPublished) {
|
|
3362
|
+
return {
|
|
3363
|
+
recoverable: true,
|
|
3364
|
+
action: 'rollback',
|
|
3365
|
+
explanation: 'Tags created but npm publish incomplete - can rollback',
|
|
3366
|
+
steps: [
|
|
3367
|
+
'Delete git tags',
|
|
3368
|
+
'Revert merge commit if it exists',
|
|
3369
|
+
'Reset working branch',
|
|
3370
|
+
'Increment version',
|
|
3371
|
+
'Run: kodrdriv publish --rollback'
|
|
3372
|
+
],
|
|
3373
|
+
risks: [
|
|
3374
|
+
'Tags will be deleted and recreated',
|
|
3375
|
+
'Merge commit will be reverted if it exists'
|
|
3376
|
+
]
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
// Phase: npm-published or completed
|
|
3380
|
+
// Package is published - can't rollback npm
|
|
3381
|
+
if (checkpoint.npmPublished || phase === 'npm-published' || phase === 'completed') {
|
|
3382
|
+
return {
|
|
3383
|
+
recoverable: false,
|
|
3384
|
+
action: 'fix-forward',
|
|
3385
|
+
explanation: 'Package already published to npm - must fix forward',
|
|
3386
|
+
steps: [
|
|
3387
|
+
'Package is live on npm - cannot unpublish',
|
|
3388
|
+
'If there are issues, publish a patch version',
|
|
3389
|
+
'Run: kodrdriv publish --target-version patch',
|
|
3390
|
+
'Or manually fix and increment version'
|
|
3391
|
+
],
|
|
3392
|
+
risks: [
|
|
3393
|
+
'Cannot undo npm publish',
|
|
3394
|
+
'Must publish new version to fix issues'
|
|
3395
|
+
]
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
// Phase: failed (generic failure)
|
|
3399
|
+
if (phase === 'failed') {
|
|
3400
|
+
// Determine based on what was completed
|
|
3401
|
+
if (checkpoint.npmPublished) {
|
|
3402
|
+
return analyzeRecoveryStrategy({
|
|
3403
|
+
...checkpoint,
|
|
3404
|
+
phase: 'npm-published'
|
|
3405
|
+
});
|
|
3406
|
+
} else if (checkpoint.prNumber) {
|
|
3407
|
+
return analyzeRecoveryStrategy({
|
|
3408
|
+
...checkpoint,
|
|
3409
|
+
phase: 'pr-merged'
|
|
3410
|
+
});
|
|
3411
|
+
} else {
|
|
3412
|
+
return analyzeRecoveryStrategy({
|
|
3413
|
+
...checkpoint,
|
|
3414
|
+
phase: 'initialized'
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
// Unknown phase or state
|
|
3419
|
+
return {
|
|
3420
|
+
recoverable: false,
|
|
3421
|
+
action: 'none',
|
|
3422
|
+
explanation: 'Unable to determine recovery strategy from checkpoint',
|
|
3423
|
+
steps: [
|
|
3424
|
+
'Manually inspect the repository state',
|
|
3425
|
+
'Check: git status, git log, npm view <package>',
|
|
3426
|
+
'Determine what was completed',
|
|
3427
|
+
'Contact support if needed'
|
|
3428
|
+
],
|
|
3429
|
+
risks: [
|
|
3430
|
+
'Manual intervention required'
|
|
3431
|
+
]
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Get a human-readable summary of the checkpoint
|
|
3436
|
+
*/ function getCheckpointSummary(checkpoint) {
|
|
3437
|
+
const lines = [];
|
|
3438
|
+
lines.push('='.repeat(60));
|
|
3439
|
+
lines.push('Publish Checkpoint Summary');
|
|
3440
|
+
lines.push('='.repeat(60));
|
|
3441
|
+
lines.push('');
|
|
3442
|
+
lines.push(`Package: ${checkpoint.packageName}`);
|
|
3443
|
+
lines.push(`Version: ${checkpoint.version}`);
|
|
3444
|
+
lines.push(`Phase: ${checkpoint.phase}`);
|
|
3445
|
+
lines.push(`Branch: ${checkpoint.branch}`);
|
|
3446
|
+
lines.push(`Timestamp: ${checkpoint.timestamp}`);
|
|
3447
|
+
lines.push('');
|
|
3448
|
+
if (checkpoint.prNumber) {
|
|
3449
|
+
lines.push(`Pull Request: #${checkpoint.prNumber}`);
|
|
3450
|
+
if (checkpoint.prUrl) {
|
|
3451
|
+
lines.push(`PR URL: ${checkpoint.prUrl}`);
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
if (checkpoint.tags.length > 0) {
|
|
3455
|
+
lines.push(`Tags Created: ${checkpoint.tags.join(', ')}`);
|
|
3456
|
+
}
|
|
3457
|
+
if (checkpoint.npmPublished) {
|
|
3458
|
+
lines.push('✅ npm Published: Yes');
|
|
3459
|
+
} else {
|
|
3460
|
+
lines.push('❌ npm Published: No');
|
|
3461
|
+
}
|
|
3462
|
+
if (checkpoint.workflowRunUrl) {
|
|
3463
|
+
lines.push(`Workflow: ${checkpoint.workflowRunUrl}`);
|
|
3464
|
+
}
|
|
3465
|
+
if (checkpoint.error) {
|
|
3466
|
+
lines.push('');
|
|
3467
|
+
lines.push('Error:');
|
|
3468
|
+
lines.push(` ${checkpoint.error}`);
|
|
3469
|
+
}
|
|
3470
|
+
lines.push('');
|
|
3471
|
+
lines.push('='.repeat(60));
|
|
3472
|
+
return lines.join('\n');
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Check if a checkpoint indicates a failed publish
|
|
3476
|
+
*/ function isFailedPublish(checkpoint) {
|
|
3477
|
+
return checkpoint.phase === 'failed' || checkpoint.phase === 'pr-merged' && !checkpoint.npmPublished || checkpoint.phase === 'tagged' && !checkpoint.npmPublished || checkpoint.phase === 'npm-publishing' && !checkpoint.npmPublished;
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
export { analyzeRecoveryStrategy, execute$2 as checkDevelopment, createDryRunReport, deleteCheckpoint, execute$3 as development, getCheckpointSummary, isFailedPublish, loadCheckpoint, logDryRunReport, logTreeDryRunReport, logValidationResults, execute as publish, execute$1 as release, runPreflightValidation, saveCheckpoint, throwIfValidationFailed, updateCheckpoint };
|
|
2852
3481
|
//# sourceMappingURL=index.js.map
|