pipecraft 0.0.0-releaseit → 0.26.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.
Files changed (183) hide show
  1. package/README.md +126 -108
  2. package/dist/cli/index.d.ts +69 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +94 -22
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/generators/init.tpl.d.ts +70 -0
  7. package/dist/generators/init.tpl.d.ts.map +1 -1
  8. package/dist/generators/init.tpl.js +100 -8
  9. package/dist/generators/init.tpl.js.map +1 -1
  10. package/dist/generators/workflows.tpl.d.ts +86 -0
  11. package/dist/generators/workflows.tpl.d.ts.map +1 -1
  12. package/dist/generators/workflows.tpl.js +101 -9
  13. package/dist/generators/workflows.tpl.js.map +1 -1
  14. package/dist/templates/actions/calculate-version.yml.tpl.d.ts +47 -0
  15. package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
  16. package/dist/templates/actions/calculate-version.yml.tpl.js +72 -3
  17. package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
  18. package/dist/templates/actions/create-pr.yml.tpl.d.ts +14 -0
  19. package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
  20. package/dist/templates/actions/create-pr.yml.tpl.js +22 -2
  21. package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
  22. package/dist/templates/actions/create-release.yml.tpl.d.ts +18 -0
  23. package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -0
  24. package/dist/templates/actions/create-release.yml.tpl.js +141 -0
  25. package/dist/templates/actions/create-release.yml.tpl.js.map +1 -0
  26. package/dist/templates/actions/create-tag.yml.tpl.d.ts +14 -0
  27. package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
  28. package/dist/templates/actions/create-tag.yml.tpl.js +22 -2
  29. package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
  30. package/dist/templates/actions/detect-changes.yml.tpl.d.ts +70 -0
  31. package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
  32. package/dist/templates/actions/detect-changes.yml.tpl.js +98 -2
  33. package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
  34. package/dist/templates/actions/manage-branch.yml.tpl.d.ts +14 -0
  35. package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
  36. package/dist/templates/actions/manage-branch.yml.tpl.js +22 -2
  37. package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
  38. package/dist/templates/actions/promote-branch.yml.tpl.d.ts +14 -0
  39. package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
  40. package/dist/templates/actions/promote-branch.yml.tpl.js +50 -7
  41. package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
  42. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +114 -1
  43. package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -1
  44. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +479 -74
  45. package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -1
  46. package/dist/types/index.d.ts +231 -8
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/index.js +9 -0
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/ast-path-operations.d.ts.map +1 -1
  51. package/dist/utils/ast-path-operations.js +24 -11
  52. package/dist/utils/ast-path-operations.js.map +1 -1
  53. package/dist/utils/config.d.ts +63 -0
  54. package/dist/utils/config.d.ts.map +1 -1
  55. package/dist/utils/config.js +71 -0
  56. package/dist/utils/config.js.map +1 -1
  57. package/dist/utils/github-setup.d.ts +323 -2
  58. package/dist/utils/github-setup.d.ts.map +1 -1
  59. package/dist/utils/github-setup.js +551 -23
  60. package/dist/utils/github-setup.js.map +1 -1
  61. package/dist/utils/idempotency.d.ts +147 -7
  62. package/dist/utils/idempotency.d.ts.map +1 -1
  63. package/dist/utils/idempotency.js +143 -15
  64. package/dist/utils/idempotency.js.map +1 -1
  65. package/dist/utils/logger.d.ts +134 -0
  66. package/dist/utils/logger.d.ts.map +1 -0
  67. package/dist/utils/logger.js +153 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/dist/utils/preflight.d.ts +223 -12
  70. package/dist/utils/preflight.d.ts.map +1 -1
  71. package/dist/utils/preflight.js +216 -16
  72. package/dist/utils/preflight.js.map +1 -1
  73. package/dist/utils/versioning.d.ts +160 -8
  74. package/dist/utils/versioning.d.ts.map +1 -1
  75. package/dist/utils/versioning.js +179 -15
  76. package/dist/utils/versioning.js.map +1 -1
  77. package/package.json +26 -5
  78. package/.claude/settings.local.json +0 -35
  79. package/.github/actions/calculate-version/action.yml +0 -106
  80. package/.github/actions/create-pr/action.yml +0 -122
  81. package/.github/actions/create-release/action.yml +0 -74
  82. package/.github/actions/create-tag/action.yml +0 -94
  83. package/.github/actions/detect-changes/action.yml +0 -62
  84. package/.github/actions/manage-branch/action.yml +0 -113
  85. package/.github/actions/promote-branch/action.yml +0 -352
  86. package/.github/example/workflows/job.analyze.code.yml +0 -26
  87. package/.github/example/workflows/job.analyze.docker.yml +0 -32
  88. package/.github/example/workflows/job.app.api.deploy.yml +0 -127
  89. package/.github/example/workflows/job.app.api.test.yml +0 -102
  90. package/.github/example/workflows/job.app.docs.deploy.yml +0 -40
  91. package/.github/example/workflows/job.app.docs.test.yml +0 -50
  92. package/.github/example/workflows/job.app.web.deploy.yml +0 -96
  93. package/.github/example/workflows/job.app.web.test.yml +0 -49
  94. package/.github/example/workflows/job.changes.yml +0 -82
  95. package/.github/example/workflows/job.create-pr.yml +0 -96
  96. package/.github/example/workflows/job.env-check.yml +0 -43
  97. package/.github/example/workflows/job.fast-forward.yml +0 -103
  98. package/.github/example/workflows/job.lint.yml +0 -64
  99. package/.github/example/workflows/job.pr-name.yml +0 -70
  100. package/.github/example/workflows/job.pr-squash-name.yml +0 -27
  101. package/.github/example/workflows/job.tag.yml +0 -38
  102. package/.github/example/workflows/job.version.yml +0 -99
  103. package/.github/example/workflows/lib.plugin-base.publish.yml +0 -59
  104. package/.github/example/workflows/pipe.yml +0 -178
  105. package/.github/example/workflows/scripts/fastforward.sh +0 -51
  106. package/.github/example/workflows/scripts/pipe-cleaner.sh +0 -70
  107. package/.github/scripts/fastforward.sh +0 -51
  108. package/.github/scripts/pipe-cleaner.sh +0 -70
  109. package/.github/workflows/pipeline.yml +0 -220
  110. package/.github/workflows/pr-title-check.yml +0 -70
  111. package/.github/workflows/publish.yml +0 -39
  112. package/.pipecraftrc.json +0 -78
  113. package/.release-it.cjs +0 -71
  114. package/PIPELINE_TESTING_PLAN.md +0 -499
  115. package/TRUNK_FLOW_PLAN.md +0 -401
  116. package/assets/logo_banner.png +0 -0
  117. package/assets/logo_banner.webp +0 -0
  118. package/docs/USER_JOURNEY_ERRORS.md +0 -352
  119. package/eslint.config.js +0 -77
  120. package/examples/basic-config.json +0 -41
  121. package/examples/monorepo-config.json +0 -49
  122. package/examples/usage.md +0 -289
  123. package/scripts/debug-workflows.sh +0 -339
  124. package/scripts/parse-pipeline.js +0 -29
  125. package/scripts/test-job-order.cjs +0 -140
  126. package/scripts/validate-pipeline.cjs +0 -379
  127. package/scripts/verify-job-order.sh +0 -30
  128. package/src/cli/index.ts +0 -414
  129. package/src/generators/init.tpl.ts +0 -126
  130. package/src/generators/workflows.tpl.ts +0 -80
  131. package/src/templates/actions/calculate-version.yml.tpl.ts +0 -124
  132. package/src/templates/actions/create-pr.yml.tpl.ts +0 -147
  133. package/src/templates/actions/create-tag.yml.tpl.ts +0 -112
  134. package/src/templates/actions/detect-changes.yml.tpl.ts +0 -87
  135. package/src/templates/actions/manage-branch.yml.tpl.ts +0 -132
  136. package/src/templates/actions/promote-branch.yml.tpl.ts +0 -364
  137. package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +0 -706
  138. package/src/types/index.ts +0 -64
  139. package/src/utils/README-ast-path-operations.md +0 -390
  140. package/src/utils/ast-path-operations.ts +0 -581
  141. package/src/utils/config.ts +0 -64
  142. package/src/utils/github-setup.ts +0 -558
  143. package/src/utils/idempotency.ts +0 -215
  144. package/src/utils/preflight.ts +0 -306
  145. package/src/utils/versioning.ts +0 -244
  146. package/tests/README.md +0 -229
  147. package/tests/TEST_STRUCTURE.md +0 -256
  148. package/tests/act/run-act-tests.sh +0 -345
  149. package/tests/debugging/debug-utils.ts +0 -538
  150. package/tests/debugging/debug-workflow.test.ts +0 -339
  151. package/tests/debugging/debug-workflows.sh +0 -339
  152. package/tests/debugging/iterative-debug.ts +0 -652
  153. package/tests/debugging/run-debug-tests.sh +0 -431
  154. package/tests/fixtures/basic-config.json +0 -51
  155. package/tests/fixtures/invalid-config.json +0 -9
  156. package/tests/fixtures/pipeline-generated.yml +0 -235
  157. package/tests/fixtures/pipeline-preserve-comments.yml +0 -245
  158. package/tests/fixtures/pipeline-user-modified.yml +0 -245
  159. package/tests/fixtures/test-config.json +0 -58
  160. package/tests/github-live/README.md +0 -250
  161. package/tests/github-local/JOB_WORKFLOW_TESTS.md +0 -296
  162. package/tests/github-local/README.md +0 -240
  163. package/tests/github-local/run-all-tests.sh +0 -422
  164. package/tests/github-local/test-job-workflows.sh +0 -631
  165. package/tests/github-local/test-pipeline-workflow.sh +0 -440
  166. package/tests/integration/generators.test.ts +0 -578
  167. package/tests/integration/path-based-template.test.ts +0 -510
  168. package/tests/integration/simple-path-based.test.ts +0 -415
  169. package/tests/setup.ts +0 -56
  170. package/tests/unit/ast-path-operations-extended.test.ts +0 -302
  171. package/tests/unit/cli.test.ts +0 -541
  172. package/tests/unit/config-extended.test.ts +0 -412
  173. package/tests/unit/config.test.ts +0 -152
  174. package/tests/unit/github-setup.test.ts +0 -189
  175. package/tests/unit/idempotency-isolated.test.ts +0 -297
  176. package/tests/unit/job-order.test.ts +0 -157
  177. package/tests/unit/pipeline-path-based.test.ts +0 -511
  178. package/tests/unit/validate-pipeline.test.ts +0 -384
  179. package/tests/unit/versioning-extended.test.ts +0 -279
  180. package/tests/unit/versioning.test.ts +0 -241
  181. package/tsconfig.debug.json +0 -17
  182. package/tsconfig.json +0 -28
  183. package/vitest.config.ts +0 -51
@@ -1,15 +1,87 @@
1
+ /**
2
+ * GitHub Repository Setup and Configuration
3
+ *
4
+ * This module provides utilities for setting up and configuring GitHub repositories
5
+ * for use with PipeCraft workflows. It handles:
6
+ * - Repository information extraction from git remotes
7
+ * - GitHub authentication token management
8
+ * - Workflow permissions configuration
9
+ * - Repository merge settings (strategies, auto-merge, PR updates)
10
+ * - Merge commit message format settings
11
+ * - Branch protection rules setup
12
+ * - Auto-merge enablement (two-layer system)
13
+ *
14
+ * These setup utilities ensure that GitHub repositories have the correct permissions
15
+ * and settings for PipeCraft workflows to function properly, including:
16
+ * - Workflows can create pull requests
17
+ * - Merge commits always use PR titles (not individual commit messages)
18
+ * - Auto-merge is enabled based on .pipecraftrc.json config
19
+ * - Branch protection is configured appropriately
20
+ * - Required status checks are enforced
21
+ *
22
+ * ## Auto-Merge Two-Layer System
23
+ *
24
+ * Auto-merge in GitHub works with two layers:
25
+ *
26
+ * 1. **Repository-Level Setting** (`allow_auto_merge`):
27
+ * - Must be ON for auto-merge to be available at all
28
+ * - Controls whether the "Enable auto-merge" button appears on PRs
29
+ * - Configured by this module based on .pipecraftrc.json
30
+ *
31
+ * 2. **Per-PR Activation**:
32
+ * - Must be explicitly enabled on each PR (via button, CLI, or API)
33
+ * - Pipecraft workflows automatically enable it for configured branches
34
+ * - Configured per-branch in .pipecraftrc.json autoMerge setting
35
+ *
36
+ * Example config:
37
+ * ```json
38
+ * {
39
+ * "branchFlow": ["develop", "staging", "main"],
40
+ * "autoMerge": {
41
+ * "staging": true, // Auto-merge PRs to staging
42
+ * "main": true // Auto-merge PRs to main
43
+ * }
44
+ * }
45
+ * ```
46
+ * Result: develop requires manual review, staging and main auto-merge when checks pass
47
+ *
48
+ * @module utils/github-setup
49
+ */
1
50
  import { execSync } from 'child_process';
2
51
  import { prompt } from '@featherscloud/pinion';
3
52
  import { loadConfig } from './config.js';
4
53
  /**
5
- * Get repository information from git remote
54
+ * Extract GitHub repository information from git remote configuration.
55
+ *
56
+ * Parses the git remote URL for the 'origin' remote to extract owner and
57
+ * repository name. Supports both HTTPS and SSH GitHub URLs:
58
+ * - HTTPS: https://github.com/owner/repo.git
59
+ * - SSH: git@github.com:owner/repo.git
60
+ *
61
+ * This information is required for GitHub API calls to configure repository
62
+ * settings and permissions.
63
+ *
64
+ * @returns Repository information object
65
+ * @throws {Error} If origin remote is not configured
66
+ * @throws {Error} If remote URL is not a valid GitHub URL
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const info = getRepositoryInfo()
71
+ * console.log(`Owner: ${info.owner}, Repo: ${info.repo}`)
72
+ * // Owner: jamesvillarrubia, Repo: pipecraft
73
+ * ```
6
74
  */
7
75
  export function getRepositoryInfo() {
8
76
  try {
9
- // Get the remote URL
10
- const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
11
- // Parse GitHub URL
12
- // Supports: https://github.com/owner/repo.git, git@github.com:owner/repo.git
77
+ // Get the remote URL from git configuration
78
+ const remoteUrl = execSync('git remote get-url origin', {
79
+ encoding: 'utf8',
80
+ stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
81
+ }).trim();
82
+ // Parse GitHub URL - supports both HTTPS and SSH formats
83
+ // HTTPS: https://github.com/owner/repo.git
84
+ // SSH: git@github.com:owner/repo.git
13
85
  const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)(\.git)?$/);
14
86
  if (!match) {
15
87
  throw new Error('Could not parse GitHub repository URL from git remote');
@@ -25,7 +97,30 @@ export function getRepositoryInfo() {
25
97
  }
26
98
  }
27
99
  /**
28
- * Get GitHub token from environment or gh CLI
100
+ * Get GitHub authentication token from environment or GitHub CLI.
101
+ *
102
+ * Attempts to retrieve a GitHub personal access token from multiple sources
103
+ * in this order:
104
+ * 1. GITHUB_TOKEN environment variable
105
+ * 2. GH_TOKEN environment variable
106
+ * 3. GitHub CLI (`gh auth token`) if authenticated
107
+ *
108
+ * The token is required for GitHub API calls to configure repository settings.
109
+ * Token must have 'repo' and 'workflow' scopes.
110
+ *
111
+ * @returns GitHub personal access token
112
+ * @throws {Error} If no token is found in any source
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // Set token via environment
117
+ * process.env.GITHUB_TOKEN = 'ghp_xxxxxxxxxxxx'
118
+ * const token = getGitHubToken()
119
+ *
120
+ * // Or authenticate with GitHub CLI first
121
+ * // $ gh auth login
122
+ * const token = getGitHubToken() // Uses gh CLI token
123
+ * ```
29
124
  */
30
125
  export function getGitHubToken() {
31
126
  // Check environment variables
@@ -159,6 +254,411 @@ export async function promptPermissionChanges(currentPermissions) {
159
254
  }
160
255
  return Object.keys(changes).length > 0 ? changes : null;
161
256
  }
257
+ /**
258
+ * Get current merge commit message settings for the repository.
259
+ *
260
+ * Retrieves the default formats for merge and squash commit messages.
261
+ * These settings control how GitHub formats commit messages when PRs are merged.
262
+ *
263
+ * @param owner - Repository owner (organization or user)
264
+ * @param repo - Repository name
265
+ * @param token - GitHub authentication token
266
+ * @returns Current merge commit settings
267
+ * @throws {Error} If the API call fails
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const settings = await getMergeCommitSettings('owner', 'repo', token)
272
+ * console.log(settings.squash_merge_commit_title) // 'PR_TITLE' or 'COMMIT_OR_PR_TITLE'
273
+ * ```
274
+ */
275
+ export async function getMergeCommitSettings(owner, repo, token) {
276
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
277
+ headers: {
278
+ 'Authorization': `Bearer ${token}`,
279
+ 'Accept': 'application/vnd.github+json',
280
+ 'X-GitHub-Api-Version': '2022-11-28'
281
+ }
282
+ });
283
+ if (!response.ok) {
284
+ const error = await response.text();
285
+ throw new Error(`Failed to get merge commit settings: ${response.status} ${error}`);
286
+ }
287
+ const data = await response.json();
288
+ return {
289
+ squash_merge_commit_title: data.squash_merge_commit_title,
290
+ squash_merge_commit_message: data.squash_merge_commit_message,
291
+ merge_commit_title: data.merge_commit_title,
292
+ merge_commit_message: data.merge_commit_message,
293
+ allow_squash_merge: data.allow_squash_merge,
294
+ allow_merge_commit: data.allow_merge_commit,
295
+ allow_rebase_merge: data.allow_rebase_merge
296
+ };
297
+ }
298
+ /**
299
+ * Update merge commit message settings for the repository.
300
+ *
301
+ * Configures how GitHub formats commit messages when PRs are merged.
302
+ * PipeCraft recommends using PR_TITLE to ensure consistent commit messages
303
+ * regardless of whether a PR has one or multiple commits.
304
+ *
305
+ * @param owner - Repository owner (organization or user)
306
+ * @param repo - Repository name
307
+ * @param token - GitHub authentication token
308
+ * @param settings - Merge commit settings to apply
309
+ * @throws {Error} If the API call fails
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * await updateMergeCommitSettings('owner', 'repo', token, {
314
+ * squash_merge_commit_title: 'PR_TITLE',
315
+ * merge_commit_title: 'PR_TITLE'
316
+ * })
317
+ * ```
318
+ */
319
+ export async function updateMergeCommitSettings(owner, repo, token, settings) {
320
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
321
+ method: 'PATCH',
322
+ headers: {
323
+ 'Authorization': `Bearer ${token}`,
324
+ 'Accept': 'application/vnd.github+json',
325
+ 'X-GitHub-Api-Version': '2022-11-28',
326
+ 'Content-Type': 'application/json'
327
+ },
328
+ body: JSON.stringify(settings)
329
+ });
330
+ if (!response.ok) {
331
+ const error = await response.text();
332
+ throw new Error(`Failed to update merge commit settings: ${response.status} ${error}`);
333
+ }
334
+ }
335
+ /**
336
+ * Determine required merge commit setting changes without prompting.
337
+ *
338
+ * Checks if merge commit settings match PipeCraft's recommended configuration:
339
+ * - Always use PR title for squash and merge commits
340
+ * - This ensures consistent commit messages regardless of commit count
341
+ * - Only configures settings for enabled merge strategies
342
+ *
343
+ * @param currentSettings - Current merge commit settings
344
+ * @returns Changes object if changes needed, null if already correct
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * const settings = await getMergeCommitSettings(owner, repo, token)
349
+ * const changes = getRequiredMergeCommitChanges(settings)
350
+ * if (changes) {
351
+ * await updateMergeCommitSettings(owner, repo, token, changes)
352
+ * }
353
+ * ```
354
+ */
355
+ export function getRequiredMergeCommitChanges(currentSettings) {
356
+ const changes = {};
357
+ // For squash merges, always use PR title (not commit message)
358
+ // Only configure if squash merge is enabled
359
+ if (currentSettings.allow_squash_merge && currentSettings.squash_merge_commit_title !== 'PR_TITLE') {
360
+ changes.squash_merge_commit_title = 'PR_TITLE';
361
+ }
362
+ // For merge commits, always use PR title
363
+ // Only configure if merge commit is enabled
364
+ if (currentSettings.allow_merge_commit && currentSettings.merge_commit_title !== 'PR_TITLE') {
365
+ changes.merge_commit_title = 'PR_TITLE';
366
+ }
367
+ return Object.keys(changes).length > 0 ? changes : null;
368
+ }
369
+ /**
370
+ * Display current merge commit settings and prompt for changes.
371
+ *
372
+ * Shows the user their current merge commit message format settings and
373
+ * asks if they want to change them to PipeCraft's recommended configuration.
374
+ * Only prompts for settings that are relevant to enabled merge strategies.
375
+ *
376
+ * @param currentSettings - Current merge commit settings
377
+ * @returns Changes object if user accepted, 'declined' if declined, null if already correct
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const settings = await getMergeCommitSettings(owner, repo, token)
382
+ * const changes = await promptMergeCommitChanges(settings)
383
+ * if (changes && changes !== 'declined') {
384
+ * await updateMergeCommitSettings(owner, repo, token, changes)
385
+ * }
386
+ * ```
387
+ */
388
+ export async function promptMergeCommitChanges(currentSettings) {
389
+ console.log('\n📋 Current Merge Commit Message Settings:');
390
+ // Only show settings for enabled merge strategies
391
+ if (currentSettings.allow_squash_merge) {
392
+ console.log(` Squash merge title: ${currentSettings.squash_merge_commit_title || 'not set'}`);
393
+ }
394
+ if (currentSettings.allow_merge_commit) {
395
+ console.log(` Merge commit title: ${currentSettings.merge_commit_title || 'not set'}`);
396
+ }
397
+ if (!currentSettings.allow_squash_merge && !currentSettings.allow_merge_commit) {
398
+ console.log(' No merge strategies enabled - skipping configuration');
399
+ return null;
400
+ }
401
+ // Check if settings are already correct for enabled strategies
402
+ const squashCorrect = !currentSettings.allow_squash_merge || currentSettings.squash_merge_commit_title === 'PR_TITLE';
403
+ const mergeCorrect = !currentSettings.allow_merge_commit || currentSettings.merge_commit_title === 'PR_TITLE';
404
+ if (squashCorrect && mergeCorrect) {
405
+ console.log('\n✅ Merge commit settings are already configured correctly!');
406
+ return null;
407
+ }
408
+ console.log('\n⚠️ PipeCraft recommends the following settings:');
409
+ if (currentSettings.allow_squash_merge && !squashCorrect) {
410
+ console.log(' • Squash merge title: PR_TITLE (always use PR title, not commit message)');
411
+ }
412
+ if (currentSettings.allow_merge_commit && !mergeCorrect) {
413
+ console.log(' • Merge commit title: PR_TITLE (always use PR title)');
414
+ }
415
+ console.log('\n This ensures consistent commit messages when PRs have single or multiple commits.');
416
+ const response = await prompt({
417
+ type: 'confirm',
418
+ name: 'updateSettings',
419
+ message: 'Update merge commit message settings to use PR titles?',
420
+ default: true
421
+ });
422
+ if (!response.updateSettings) {
423
+ return 'declined';
424
+ }
425
+ const changes = {};
426
+ // Only include changes for enabled strategies
427
+ if (currentSettings.allow_squash_merge && currentSettings.squash_merge_commit_title !== 'PR_TITLE') {
428
+ changes.squash_merge_commit_title = 'PR_TITLE';
429
+ }
430
+ if (currentSettings.allow_merge_commit && currentSettings.merge_commit_title !== 'PR_TITLE') {
431
+ changes.merge_commit_title = 'PR_TITLE';
432
+ }
433
+ return Object.keys(changes).length > 0 ? changes : null;
434
+ }
435
+ /**
436
+ * Check if auto-merge should be enabled based on .pipecraftrc.json config.
437
+ *
438
+ * Auto-merge should be enabled at the repository level if ANY branch
439
+ * has autoMerge configured in the config file.
440
+ */
441
+ export function shouldEnableAutoMerge() {
442
+ try {
443
+ const config = loadConfig('.pipecraftrc.json');
444
+ if (!config.autoMerge) {
445
+ return false;
446
+ }
447
+ if (typeof config.autoMerge === 'boolean') {
448
+ return config.autoMerge;
449
+ }
450
+ if (typeof config.autoMerge === 'object') {
451
+ // Check if any branch has auto-merge enabled
452
+ return Object.values(config.autoMerge).some(enabled => enabled === true);
453
+ }
454
+ return false;
455
+ }
456
+ catch (error) {
457
+ // No config file or autoMerge not configured
458
+ return false;
459
+ }
460
+ }
461
+ /**
462
+ * Get Pipecraft's recommended repository settings.
463
+ *
464
+ * These are the settings that work best with Pipecraft workflows:
465
+ * - Allow auto-merge: ON if any branch has autoMerge in config, OFF otherwise
466
+ * - Always suggest updating PR branches: ON
467
+ * - Allow merge commits: OFF
468
+ * - Allow rebase merging: OFF
469
+ * - Allow squash merging: ON
470
+ * - Squash merge commit title: PR_TITLE
471
+ * - Squash merge commit message: COMMIT_MESSAGES (PR title + commit details)
472
+ */
473
+ export function getRecommendedRepositorySettings() {
474
+ return {
475
+ allow_auto_merge: shouldEnableAutoMerge(),
476
+ allow_update_branch: true,
477
+ allow_merge_commit: false,
478
+ allow_rebase_merge: false,
479
+ allow_squash_merge: true,
480
+ squash_merge_commit_title: 'PR_TITLE',
481
+ squash_merge_commit_message: 'COMMIT_MESSAGES'
482
+ };
483
+ }
484
+ /**
485
+ * Get current repository settings from GitHub API.
486
+ */
487
+ export async function getRepositorySettings(owner, repo, token) {
488
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
489
+ headers: {
490
+ 'Authorization': `Bearer ${token}`,
491
+ 'Accept': 'application/vnd.github+json',
492
+ 'X-GitHub-Api-Version': '2022-11-28'
493
+ }
494
+ });
495
+ if (!response.ok) {
496
+ const error = await response.text();
497
+ throw new Error(`Failed to get repository settings: ${response.status} ${error}`);
498
+ }
499
+ const data = await response.json();
500
+ return {
501
+ allow_auto_merge: data.allow_auto_merge,
502
+ allow_update_branch: data.allow_update_branch,
503
+ allow_merge_commit: data.allow_merge_commit,
504
+ allow_rebase_merge: data.allow_rebase_merge,
505
+ allow_squash_merge: data.allow_squash_merge,
506
+ squash_merge_commit_title: data.squash_merge_commit_title,
507
+ squash_merge_commit_message: data.squash_merge_commit_message,
508
+ merge_commit_title: data.merge_commit_title,
509
+ merge_commit_message: data.merge_commit_message
510
+ };
511
+ }
512
+ /**
513
+ * Update repository settings via GitHub API.
514
+ */
515
+ export async function updateRepositorySettings(owner, repo, token, settings) {
516
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
517
+ method: 'PATCH',
518
+ headers: {
519
+ 'Authorization': `Bearer ${token}`,
520
+ 'Accept': 'application/vnd.github+json',
521
+ 'X-GitHub-Api-Version': '2022-11-28',
522
+ 'Content-Type': 'application/json'
523
+ },
524
+ body: JSON.stringify(settings)
525
+ });
526
+ if (!response.ok) {
527
+ const error = await response.text();
528
+ throw new Error(`Failed to update repository settings: ${response.status} ${error}`);
529
+ }
530
+ }
531
+ /**
532
+ * Compare current settings with recommended settings and return differences.
533
+ */
534
+ export function getSettingsGaps(current, recommended) {
535
+ const gaps = {};
536
+ // Check each setting
537
+ if (current.allow_auto_merge !== recommended.allow_auto_merge) {
538
+ gaps.allow_auto_merge = recommended.allow_auto_merge;
539
+ }
540
+ if (current.allow_update_branch !== recommended.allow_update_branch) {
541
+ gaps.allow_update_branch = recommended.allow_update_branch;
542
+ }
543
+ if (current.allow_merge_commit !== recommended.allow_merge_commit) {
544
+ gaps.allow_merge_commit = recommended.allow_merge_commit;
545
+ }
546
+ if (current.allow_rebase_merge !== recommended.allow_rebase_merge) {
547
+ gaps.allow_rebase_merge = recommended.allow_rebase_merge;
548
+ }
549
+ if (current.allow_squash_merge !== recommended.allow_squash_merge) {
550
+ gaps.allow_squash_merge = recommended.allow_squash_merge;
551
+ }
552
+ // Only check squash merge settings if squash merge will be enabled
553
+ const squashWillBeEnabled = gaps.allow_squash_merge ?? current.allow_squash_merge;
554
+ if (squashWillBeEnabled) {
555
+ if (current.squash_merge_commit_title !== recommended.squash_merge_commit_title) {
556
+ gaps.squash_merge_commit_title = recommended.squash_merge_commit_title;
557
+ }
558
+ if (current.squash_merge_commit_message !== recommended.squash_merge_commit_message) {
559
+ gaps.squash_merge_commit_message = recommended.squash_merge_commit_message;
560
+ }
561
+ }
562
+ return gaps;
563
+ }
564
+ /**
565
+ * Display a comparison table of current vs recommended settings.
566
+ */
567
+ export function displaySettingsComparison(current, recommended, gaps) {
568
+ console.log('\n📊 Repository Settings Comparison:\n');
569
+ const formatValue = (value) => {
570
+ if (typeof value === 'boolean')
571
+ return value ? 'ON' : 'OFF';
572
+ if (value === undefined || value === null)
573
+ return 'not set';
574
+ return String(value);
575
+ };
576
+ const hasGap = (key) => key in gaps;
577
+ const settings = [
578
+ { key: 'allow_auto_merge', label: 'Allow auto-merge' },
579
+ { key: 'allow_update_branch', label: 'Always suggest updating PR branches' },
580
+ { key: 'allow_merge_commit', label: 'Allow merge commits' },
581
+ { key: 'allow_rebase_merge', label: 'Allow rebase merging' },
582
+ { key: 'allow_squash_merge', label: 'Allow squash merging' },
583
+ { key: 'squash_merge_commit_title', label: 'Squash merge commit title' },
584
+ { key: 'squash_merge_commit_message', label: 'Squash merge commit message' }
585
+ ];
586
+ settings.forEach(({ key, label }) => {
587
+ const currentVal = formatValue(current[key]);
588
+ const recommendedVal = formatValue(recommended[key]);
589
+ const gap = hasGap(key);
590
+ if (gap) {
591
+ console.log(` ${label}:`);
592
+ console.log(` Current: ${currentVal}`);
593
+ console.log(` Recommended: ${recommendedVal} ⚠️`);
594
+ }
595
+ else {
596
+ console.log(` ${label}: ${currentVal} ✅`);
597
+ }
598
+ });
599
+ const gapCount = Object.keys(gaps).length;
600
+ if (gapCount > 0) {
601
+ console.log(`\n⚠️ Found ${gapCount} setting${gapCount > 1 ? 's' : ''} that differ from recommendations`);
602
+ }
603
+ else {
604
+ console.log('\n✅ All settings match Pipecraft recommendations!');
605
+ }
606
+ // Show auto-merge branch configuration if auto-merge is enabled
607
+ if (current.allow_auto_merge || recommended.allow_auto_merge) {
608
+ try {
609
+ const config = loadConfig('.pipecraftrc.json');
610
+ if (config.autoMerge) {
611
+ console.log('\n📋 Auto-Merge Branch Configuration:');
612
+ console.log(' (Repository-level allow_auto_merge must be ON for these to work)\n');
613
+ if (typeof config.autoMerge === 'boolean') {
614
+ if (config.autoMerge) {
615
+ console.log(' All branches: Auto-merge ENABLED');
616
+ }
617
+ else {
618
+ console.log(' All branches: Auto-merge DISABLED');
619
+ }
620
+ }
621
+ else if (typeof config.autoMerge === 'object') {
622
+ const branches = config.branchFlow || [];
623
+ const autoMergeConfig = config.autoMerge;
624
+ branches.forEach(branch => {
625
+ const enabled = autoMergeConfig[branch];
626
+ if (enabled === true) {
627
+ console.log(` ${branch}: Auto-merge ENABLED ✅`);
628
+ }
629
+ else if (enabled === false) {
630
+ console.log(` ${branch}: Auto-merge DISABLED (manual review required)`);
631
+ }
632
+ else {
633
+ console.log(` ${branch}: Auto-merge not configured (manual review required)`);
634
+ }
635
+ });
636
+ }
637
+ console.log('\n ℹ️ Auto-merge means PRs will automatically merge when all checks pass.');
638
+ console.log(' ℹ️ Branches without auto-merge require manual approval and merge.');
639
+ }
640
+ }
641
+ catch (error) {
642
+ // Config file not found or invalid - skip branch config display
643
+ }
644
+ }
645
+ }
646
+ /**
647
+ * Prompt user whether to apply recommended settings.
648
+ */
649
+ export async function promptApplySettings(gaps) {
650
+ const gapCount = Object.keys(gaps).length;
651
+ if (gapCount === 0) {
652
+ return 'declined'; // Nothing to apply
653
+ }
654
+ const response = await prompt({
655
+ type: 'confirm',
656
+ name: 'applySettings',
657
+ message: `Apply ${gapCount} recommended setting${gapCount > 1 ? 's' : ''}?`,
658
+ default: true
659
+ });
660
+ return response.applySettings ? 'apply' : 'declined';
661
+ }
162
662
  /**
163
663
  * Get branch protection rules
164
664
  */
@@ -266,25 +766,11 @@ export async function configureBranchProtection(repoInfo, token, autoApply) {
266
766
  return;
267
767
  }
268
768
  if (!config.autoMerge || !config.branchFlow) {
269
- console.log('ℹ️ No autoMerge configuration found - skipping auto-merge setup');
270
- return;
271
- }
272
- // Enable repository-level auto-merge feature
273
- try {
274
- const wasEnabled = await enableAutoMerge(repoInfo.owner, repoInfo.repo, token);
275
- if (wasEnabled) {
276
- console.log('✅ Enabled auto-merge for repository');
277
- }
278
- else {
279
- console.log('✅ Auto-merge already enabled for repository');
280
- }
281
- }
282
- catch (error) {
283
- console.error(`⚠️ Could not enable auto-merge: ${error.message}`);
284
- console.log(' You can enable it manually at:');
285
- console.log(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings`);
769
+ console.log('ℹ️ No autoMerge configuration found - skipping branch protection setup');
286
770
  return;
287
771
  }
772
+ // Note: Repository-level auto-merge is now handled by repository settings configuration
773
+ // This function only configures branch protection for the branches that need it
288
774
  // Determine which branches need auto-merge
289
775
  const autoMergeConfig = config.autoMerge;
290
776
  const branchesNeedingProtection = [];
@@ -409,6 +895,48 @@ export async function setupGitHubPermissions(autoApply = false) {
409
895
  await updateWorkflowPermissions(repoInfo.owner, repoInfo.repo, token, changes);
410
896
  console.log('✅ GitHub Actions permissions updated successfully!');
411
897
  }
898
+ // Check and configure repository settings (merge strategies, auto-merge, etc.)
899
+ console.log('\n🔍 Checking repository settings...');
900
+ try {
901
+ const currentSettings = await getRepositorySettings(repoInfo.owner, repoInfo.repo, token);
902
+ const recommendedSettings = getRecommendedRepositorySettings();
903
+ const gaps = getSettingsGaps(currentSettings, recommendedSettings);
904
+ // Display current vs recommended comparison
905
+ displaySettingsComparison(currentSettings, recommendedSettings, gaps);
906
+ if (Object.keys(gaps).length === 0) {
907
+ // No changes needed
908
+ console.log('');
909
+ }
910
+ else {
911
+ // Changes needed
912
+ let shouldApply = false;
913
+ if (autoApply) {
914
+ // Auto-apply mode
915
+ shouldApply = true;
916
+ console.log('\n🔧 Auto-applying recommended settings...');
917
+ }
918
+ else {
919
+ // Interactive mode: prompt user
920
+ const decision = await promptApplySettings(gaps);
921
+ shouldApply = decision === 'apply';
922
+ }
923
+ if (shouldApply) {
924
+ console.log('\n🔄 Updating repository settings...');
925
+ await updateRepositorySettings(repoInfo.owner, repoInfo.repo, token, gaps);
926
+ console.log('✅ Repository settings updated successfully!');
927
+ }
928
+ else {
929
+ console.log('\n⚠️ Repository settings were not updated');
930
+ console.log('💡 You can update them manually at:');
931
+ console.log(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings`);
932
+ }
933
+ }
934
+ }
935
+ catch (error) {
936
+ console.error(`⚠️ Could not configure repository settings: ${error.message}`);
937
+ console.log('💡 You can update them manually at:');
938
+ console.log(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings`);
939
+ }
412
940
  // Configure branch protection for auto-merge
413
941
  await configureBranchProtection(repoInfo, token, autoApply);
414
942
  console.log('\n✨ Setup complete!');