@grunnverk/commands-publish 1.5.7-dev.20260131223731.750cd33 → 1.5.7-dev.20260131224204.ba5749e
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
CHANGED
|
@@ -4,13 +4,13 @@ import { createStorage, calculateTargetVersion, incrementPatchVersion as increme
|
|
|
4
4
|
import { scanForPackageJsonFiles } from '@grunnverk/tree-core';
|
|
5
5
|
import * as GitHub from '@grunnverk/github-tools';
|
|
6
6
|
import { getOctokit } from '@grunnverk/github-tools';
|
|
7
|
-
import fs, { readFile } from 'fs/promises';
|
|
7
|
+
import fs, { readFile, access } from 'fs/promises';
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import path__default from 'path';
|
|
10
10
|
import * as Commit from '@grunnverk/commands-git';
|
|
11
11
|
import { Formatter } from '@kjerneverk/riotprompt';
|
|
12
12
|
import 'dotenv/config';
|
|
13
|
-
import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt
|
|
13
|
+
import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt } from '@grunnverk/ai-service';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Create retroactive working branch tags for past releases
|
|
@@ -537,6 +537,80 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
537
537
|
'examples/',
|
|
538
538
|
'test-*/'
|
|
539
539
|
];
|
|
540
|
+
/**
|
|
541
|
+
* Checks if .gitignore contains required patterns to prevent publishing
|
|
542
|
+
* development artifacts and sensitive files.
|
|
543
|
+
*/ async function checkGitignorePatterns$1(directory, checks) {
|
|
544
|
+
const logger = getLogger();
|
|
545
|
+
const gitignorePath = path.join(directory, '.gitignore');
|
|
546
|
+
// Required patterns that must be present in .gitignore
|
|
547
|
+
const requiredPatterns = [
|
|
548
|
+
'node_modules',
|
|
549
|
+
'dist',
|
|
550
|
+
'package-lock.json',
|
|
551
|
+
'.env',
|
|
552
|
+
'output/',
|
|
553
|
+
'coverage',
|
|
554
|
+
'.kodrdriv*'
|
|
555
|
+
];
|
|
556
|
+
// Check if .gitignore exists
|
|
557
|
+
try {
|
|
558
|
+
await access(gitignorePath);
|
|
559
|
+
} catch {
|
|
560
|
+
checks.gitignore.passed = false;
|
|
561
|
+
checks.gitignore.issues.push('.gitignore file not found');
|
|
562
|
+
checks.gitignore.issues.push(`Create .gitignore with patterns: ${requiredPatterns.join(', ')}`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Read .gitignore content
|
|
566
|
+
let gitignoreContent;
|
|
567
|
+
try {
|
|
568
|
+
gitignoreContent = await readFile(gitignorePath, 'utf-8');
|
|
569
|
+
} catch (error) {
|
|
570
|
+
checks.gitignore.passed = false;
|
|
571
|
+
checks.gitignore.issues.push(`Failed to read .gitignore: ${error.message}`);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
// Parse .gitignore into lines, ignoring comments and empty lines
|
|
575
|
+
const gitignoreLines = gitignoreContent.split('\n').map((line)=>line.trim()).filter((line)=>line && !line.startsWith('#'));
|
|
576
|
+
// Check for missing patterns
|
|
577
|
+
const missingPatterns = [];
|
|
578
|
+
for (const pattern of requiredPatterns){
|
|
579
|
+
// Check if the pattern exists in any of the gitignore lines
|
|
580
|
+
const found = gitignoreLines.some((line)=>{
|
|
581
|
+
// Exact match
|
|
582
|
+
if (line === pattern) return true;
|
|
583
|
+
// Pattern with wildcard - check if the pattern or any matching line exists
|
|
584
|
+
if (pattern.includes('*')) {
|
|
585
|
+
const basePattern = pattern.replace('*', '');
|
|
586
|
+
// Accept exact wildcard pattern, base pattern, or any line starting with base
|
|
587
|
+
return line === pattern || line === basePattern || line.startsWith(basePattern);
|
|
588
|
+
}
|
|
589
|
+
// Pattern with trailing slash - check both with and without slash
|
|
590
|
+
if (pattern.endsWith('/')) {
|
|
591
|
+
const basePattern = pattern.slice(0, -1);
|
|
592
|
+
return line === basePattern || line === pattern || line.startsWith(pattern);
|
|
593
|
+
}
|
|
594
|
+
// Line with trailing slash - check if it matches the pattern (e.g., "node_modules/" matches "node_modules")
|
|
595
|
+
if (line.endsWith('/')) {
|
|
596
|
+
const lineBase = line.slice(0, -1);
|
|
597
|
+
return lineBase === pattern || line.startsWith(pattern + '/');
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
});
|
|
601
|
+
if (!found) {
|
|
602
|
+
missingPatterns.push(pattern);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// Report missing patterns (relaxed check - allow variations)
|
|
606
|
+
const criticalMissing = missingPatterns.filter((p)=>!p.includes('coverage') && p !== 'package-lock.json');
|
|
607
|
+
if (criticalMissing.length > 0) {
|
|
608
|
+
checks.gitignore.passed = false;
|
|
609
|
+
checks.gitignore.issues.push(`Missing required patterns: ${criticalMissing.join(', ')}`);
|
|
610
|
+
checks.gitignore.issues.push('These patterns prevent committing build artifacts and sensitive files');
|
|
611
|
+
}
|
|
612
|
+
logger.debug(`Gitignore check: ${checks.gitignore.passed ? 'passed' : 'failed'}`);
|
|
613
|
+
}
|
|
540
614
|
/**
|
|
541
615
|
* Execute check-development command
|
|
542
616
|
*/ async function execute$2(config) {
|
|
@@ -564,6 +638,10 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
564
638
|
const isTree = packageJsonFiles.length > 1;
|
|
565
639
|
logger.info(`Detected ${isTree ? 'tree' : 'single package'} with ${packageJsonFiles.length} package(s)`);
|
|
566
640
|
const checks = {
|
|
641
|
+
gitignore: {
|
|
642
|
+
passed: true,
|
|
643
|
+
issues: []
|
|
644
|
+
},
|
|
567
645
|
branch: {
|
|
568
646
|
passed: true,
|
|
569
647
|
issues: []
|
|
@@ -600,6 +678,8 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
600
678
|
const packagesToCheck = isTree ? packageJsonFiles : [
|
|
601
679
|
path.join(directory, 'package.json')
|
|
602
680
|
];
|
|
681
|
+
// Check .gitignore patterns (required for publish to succeed)
|
|
682
|
+
await checkGitignorePatterns$1(directory, checks);
|
|
603
683
|
// Build a set of all local package names for link status checking
|
|
604
684
|
const localPackageNames = new Set();
|
|
605
685
|
for (const pkgJsonPath of packagesToCheck){
|
|
@@ -683,7 +763,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
683
763
|
// Ignore - there might not be a merge to abort if it was a fast-forward
|
|
684
764
|
}
|
|
685
765
|
} catch (mergeError) {
|
|
686
|
-
var _mergeError_message, _mergeError_stderr;
|
|
766
|
+
var _mergeError_message, _mergeError_stderr, _mergeError_stdout;
|
|
687
767
|
// Abort any partial merge
|
|
688
768
|
try {
|
|
689
769
|
await run('git merge --abort', {
|
|
@@ -694,7 +774,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
694
774
|
// Ignore abort errors
|
|
695
775
|
}
|
|
696
776
|
// If merge failed, there are likely conflicts
|
|
697
|
-
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'))) {
|
|
777
|
+
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')) || ((_mergeError_stdout = mergeError.stdout) === null || _mergeError_stdout === void 0 ? void 0 : _mergeError_stdout.includes('CONFLICT'))) {
|
|
698
778
|
checks.mergeConflicts.passed = false;
|
|
699
779
|
checks.mergeConflicts.issues.push(`${pkgName}: Merge conflicts detected with ${targetBranch} branch`);
|
|
700
780
|
} else {
|
|
@@ -841,7 +921,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
841
921
|
// Build summary - linkStatus and releaseWorkflow are not included in allPassed (recommendations)
|
|
842
922
|
// mergeConflicts is ALWAYS checked (critical for preventing post-merge failures)
|
|
843
923
|
// openPRs is only checked when validateRelease is true
|
|
844
|
-
const allPassed = checks.branch.passed && checks.remoteSync.passed && checks.mergeConflicts.passed && checks.devVersion.passed && (validateRelease ? checks.openPRs.passed : true);
|
|
924
|
+
const allPassed = checks.gitignore.passed && checks.branch.passed && checks.remoteSync.passed && checks.mergeConflicts.passed && checks.devVersion.passed && (validateRelease ? checks.openPRs.passed : true);
|
|
845
925
|
const hasWarnings = checks.linkStatus.warnings.length > 0 || checks.mergeConflicts.warnings.length > 0 || checks.openPRs.warnings.length > 0 || checks.releaseWorkflow.warnings.length > 0;
|
|
846
926
|
// Log results
|
|
847
927
|
let summary = `\n${'='.repeat(60)}\n`;
|
|
@@ -852,6 +932,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
852
932
|
if (allPassed) {
|
|
853
933
|
summary += `✅ Status: READY FOR DEVELOPMENT\n\n`;
|
|
854
934
|
summary += `All required checks passed:\n`;
|
|
935
|
+
summary += ` ✓ Gitignore patterns\n`;
|
|
855
936
|
summary += ` ✓ Branch status\n`;
|
|
856
937
|
summary += ` ✓ Remote sync\n`;
|
|
857
938
|
summary += ` ✓ No merge conflicts with main\n`;
|
|
@@ -864,6 +945,11 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
864
945
|
}
|
|
865
946
|
} else {
|
|
866
947
|
summary += `⚠️ Status: NOT READY\n\n`;
|
|
948
|
+
if (!checks.gitignore.passed) {
|
|
949
|
+
summary += `❌ Gitignore Issues:\n`;
|
|
950
|
+
checks.gitignore.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
951
|
+
summary += `\n`;
|
|
952
|
+
}
|
|
867
953
|
if (!checks.branch.passed) {
|
|
868
954
|
summary += `❌ Branch Issues:\n`;
|
|
869
955
|
checks.branch.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
@@ -878,6 +964,13 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
|
|
|
878
964
|
summary += `❌ Merge Conflict Issues:\n`;
|
|
879
965
|
checks.mergeConflicts.issues.forEach((issue)=>summary += ` - ${issue}\n`);
|
|
880
966
|
summary += `\n`;
|
|
967
|
+
summary += ` To resolve merge conflicts:\n`;
|
|
968
|
+
summary += ` 1. Fetch latest: git fetch origin main\n`;
|
|
969
|
+
summary += ` 2. Merge main into your branch: git merge origin/main\n`;
|
|
970
|
+
summary += ` 3. Resolve conflicts in your editor\n`;
|
|
971
|
+
summary += ` 4. Commit the merge: git add . && git commit\n`;
|
|
972
|
+
summary += ` 5. Run check-development again to verify\n`;
|
|
973
|
+
summary += `\n`;
|
|
881
974
|
}
|
|
882
975
|
if (!checks.devVersion.passed) {
|
|
883
976
|
summary += `❌ Dev Version Issues:\n`;
|
|
@@ -1536,7 +1629,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
|
|
|
1536
1629
|
if (targetBranchExists) {
|
|
1537
1630
|
const syncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
|
|
1538
1631
|
if (!syncStatus.inSync) {
|
|
1539
|
-
var _runConfig_publish2;
|
|
1540
1632
|
logger.error(`BRANCH_SYNC_FAILED: Target branch not synchronized with remote | Branch: ${effectiveTargetBranch} | Status: out-of-sync | Impact: Cannot proceed with publish`);
|
|
1541
1633
|
logger.error('');
|
|
1542
1634
|
if (syncStatus.error) {
|
|
@@ -1544,48 +1636,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
|
|
|
1544
1636
|
} else if (syncStatus.localSha && syncStatus.remoteSha) {
|
|
1545
1637
|
logger.error(`BRANCH_SYNC_DIVERGENCE: Local and remote commits differ | Local SHA: ${syncStatus.localSha.substring(0, 8)} | Remote SHA: ${syncStatus.remoteSha.substring(0, 8)}`);
|
|
1546
1638
|
}
|
|
1547
|
-
// Check if agentic publish is enabled
|
|
1548
|
-
if ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.agenticPublish) {
|
|
1549
|
-
logger.info('');
|
|
1550
|
-
logger.info('AGENTIC_PUBLISH_STARTING: Attempting automatic diagnosis and fix | Mode: agentic | Feature: AI-powered recovery');
|
|
1551
|
-
try {
|
|
1552
|
-
var _syncStatus_localSha, _syncStatus_remoteSha, _runConfig_publish3;
|
|
1553
|
-
const currentBranch = await GitHub.getCurrentBranchName();
|
|
1554
|
-
const agenticResult = await runAgenticPublish({
|
|
1555
|
-
targetBranch: effectiveTargetBranch,
|
|
1556
|
-
sourceBranch: currentBranch,
|
|
1557
|
-
issue: 'branch_sync',
|
|
1558
|
-
issueDetails: syncStatus.error || `Local SHA: ${(_syncStatus_localSha = syncStatus.localSha) === null || _syncStatus_localSha === void 0 ? void 0 : _syncStatus_localSha.substring(0, 8)}, Remote SHA: ${(_syncStatus_remoteSha = syncStatus.remoteSha) === null || _syncStatus_remoteSha === void 0 ? void 0 : _syncStatus_remoteSha.substring(0, 8)}`,
|
|
1559
|
-
workingDirectory: process.cwd(),
|
|
1560
|
-
maxIterations: ((_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.agenticPublishMaxIterations) || 10,
|
|
1561
|
-
storage,
|
|
1562
|
-
logger,
|
|
1563
|
-
dryRun: runConfig.dryRun
|
|
1564
|
-
});
|
|
1565
|
-
// Display the formatted result
|
|
1566
|
-
const formattedResult = formatAgenticPublishResult(agenticResult);
|
|
1567
|
-
logger.info(formattedResult);
|
|
1568
|
-
if (agenticResult.success) {
|
|
1569
|
-
logger.info('AGENTIC_PUBLISH_SUCCESS: Issue resolved automatically | Status: ready-to-retry | Action: Re-running prechecks');
|
|
1570
|
-
// Re-run the sync check to verify it was fixed
|
|
1571
|
-
const reSyncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
|
|
1572
|
-
if (reSyncStatus.inSync) {
|
|
1573
|
-
logger.info(`BRANCH_SYNC_VERIFIED: Target branch is now synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
|
|
1574
|
-
return; // Continue with publish
|
|
1575
|
-
} else {
|
|
1576
|
-
logger.warn('AGENTIC_PUBLISH_VERIFICATION_FAILED: Branch still not in sync after agentic fix | Status: needs-attention');
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
if (agenticResult.requiresManualIntervention) {
|
|
1580
|
-
throw new Error(`Target branch '${effectiveTargetBranch}' requires manual intervention. Please see the steps above.`);
|
|
1581
|
-
} else {
|
|
1582
|
-
throw new Error(`Agentic publish could not resolve the issue automatically. Please see the analysis above.`);
|
|
1583
|
-
}
|
|
1584
|
-
} catch (agenticError) {
|
|
1585
|
-
logger.warn(`AGENTIC_PUBLISH_FAILED: Agentic recovery failed | Error: ${agenticError.message} | Fallback: Manual steps`);
|
|
1586
|
-
// Fall through to manual steps
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
1639
|
logger.error('');
|
|
1590
1640
|
logger.error('RESOLUTION_STEPS: Manual intervention required to sync branches:');
|
|
1591
1641
|
logger.error(` Step 1: Switch to target branch | Command: git checkout ${effectiveTargetBranch}`);
|
|
@@ -1594,7 +1644,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
|
|
|
1594
1644
|
logger.error(' Step 4: Return to feature branch and retry publish');
|
|
1595
1645
|
logger.error('');
|
|
1596
1646
|
logger.error(`ALTERNATIVE_OPTION: Automatic sync available | Command: kodrdriv publish --sync-target | Branch: ${effectiveTargetBranch}`);
|
|
1597
|
-
logger.error(`ALTERNATIVE_OPTION_AI: AI-powered recovery available | Command: kodrdriv publish --agentic-publish | Branch: ${effectiveTargetBranch}`);
|
|
1598
1647
|
throw new Error(`Target branch '${effectiveTargetBranch}' is not in sync with remote. Please sync the branch before running publish.`);
|
|
1599
1648
|
} else {
|
|
1600
1649
|
logger.info(`BRANCH_SYNC_VERIFIED: Target branch is synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
|
|
@@ -2753,8 +2802,12 @@ const execute = async (runConfig)=>{
|
|
|
2753
2802
|
try {
|
|
2754
2803
|
// Verify that remote working branch is ancestor of main (safety check)
|
|
2755
2804
|
try {
|
|
2756
|
-
await run(`git fetch origin ${currentBranch}
|
|
2757
|
-
|
|
2805
|
+
await run(`git fetch origin ${currentBranch}`, {
|
|
2806
|
+
suppressErrorLogging: true
|
|
2807
|
+
});
|
|
2808
|
+
await run(`git merge-base --is-ancestor origin/${currentBranch} ${targetBranch}`, {
|
|
2809
|
+
suppressErrorLogging: true
|
|
2810
|
+
});
|
|
2758
2811
|
logger.verbose(`✓ Safety check passed: origin/${currentBranch} is ancestor of ${targetBranch}`);
|
|
2759
2812
|
} catch {
|
|
2760
2813
|
// Remote branch might not exist yet, or already in sync - both OK
|