@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, runAgenticPublish, formatAgenticPublishResult } from '@grunnverk/ai-service';
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
- await run(`git merge-base --is-ancestor origin/${currentBranch} ${targetBranch}`);
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