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.
- package/README.md +126 -108
- package/dist/cli/index.d.ts +69 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +94 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/init.tpl.d.ts +70 -0
- package/dist/generators/init.tpl.d.ts.map +1 -1
- package/dist/generators/init.tpl.js +100 -8
- package/dist/generators/init.tpl.js.map +1 -1
- package/dist/generators/workflows.tpl.d.ts +86 -0
- package/dist/generators/workflows.tpl.d.ts.map +1 -1
- package/dist/generators/workflows.tpl.js +101 -9
- package/dist/generators/workflows.tpl.js.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts +47 -0
- package/dist/templates/actions/calculate-version.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/calculate-version.yml.tpl.js +72 -3
- package/dist/templates/actions/calculate-version.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-pr.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-pr.yml.tpl.js +22 -2
- package/dist/templates/actions/create-pr.yml.tpl.js.map +1 -1
- package/dist/templates/actions/create-release.yml.tpl.d.ts +18 -0
- package/dist/templates/actions/create-release.yml.tpl.d.ts.map +1 -0
- package/dist/templates/actions/create-release.yml.tpl.js +141 -0
- package/dist/templates/actions/create-release.yml.tpl.js.map +1 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/create-tag.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/create-tag.yml.tpl.js +22 -2
- package/dist/templates/actions/create-tag.yml.tpl.js.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts +70 -0
- package/dist/templates/actions/detect-changes.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/detect-changes.yml.tpl.js +98 -2
- package/dist/templates/actions/detect-changes.yml.tpl.js.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/manage-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/manage-branch.yml.tpl.js +22 -2
- package/dist/templates/actions/manage-branch.yml.tpl.js.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts +14 -0
- package/dist/templates/actions/promote-branch.yml.tpl.d.ts.map +1 -1
- package/dist/templates/actions/promote-branch.yml.tpl.js +50 -7
- package/dist/templates/actions/promote-branch.yml.tpl.js.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts +114 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.d.ts.map +1 -1
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js +479 -74
- package/dist/templates/workflows/pipeline-path-based.yml.tpl.js.map +1 -1
- package/dist/types/index.d.ts +231 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/ast-path-operations.d.ts.map +1 -1
- package/dist/utils/ast-path-operations.js +24 -11
- package/dist/utils/ast-path-operations.js.map +1 -1
- package/dist/utils/config.d.ts +63 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +71 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/github-setup.d.ts +323 -2
- package/dist/utils/github-setup.d.ts.map +1 -1
- package/dist/utils/github-setup.js +551 -23
- package/dist/utils/github-setup.js.map +1 -1
- package/dist/utils/idempotency.d.ts +147 -7
- package/dist/utils/idempotency.d.ts.map +1 -1
- package/dist/utils/idempotency.js +143 -15
- package/dist/utils/idempotency.js.map +1 -1
- package/dist/utils/logger.d.ts +134 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +153 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/preflight.d.ts +223 -12
- package/dist/utils/preflight.d.ts.map +1 -1
- package/dist/utils/preflight.js +216 -16
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/versioning.d.ts +160 -8
- package/dist/utils/versioning.d.ts.map +1 -1
- package/dist/utils/versioning.js +179 -15
- package/dist/utils/versioning.js.map +1 -1
- package/package.json +26 -5
- package/.claude/settings.local.json +0 -35
- package/.github/actions/calculate-version/action.yml +0 -106
- package/.github/actions/create-pr/action.yml +0 -122
- package/.github/actions/create-release/action.yml +0 -74
- package/.github/actions/create-tag/action.yml +0 -94
- package/.github/actions/detect-changes/action.yml +0 -62
- package/.github/actions/manage-branch/action.yml +0 -113
- package/.github/actions/promote-branch/action.yml +0 -352
- package/.github/example/workflows/job.analyze.code.yml +0 -26
- package/.github/example/workflows/job.analyze.docker.yml +0 -32
- package/.github/example/workflows/job.app.api.deploy.yml +0 -127
- package/.github/example/workflows/job.app.api.test.yml +0 -102
- package/.github/example/workflows/job.app.docs.deploy.yml +0 -40
- package/.github/example/workflows/job.app.docs.test.yml +0 -50
- package/.github/example/workflows/job.app.web.deploy.yml +0 -96
- package/.github/example/workflows/job.app.web.test.yml +0 -49
- package/.github/example/workflows/job.changes.yml +0 -82
- package/.github/example/workflows/job.create-pr.yml +0 -96
- package/.github/example/workflows/job.env-check.yml +0 -43
- package/.github/example/workflows/job.fast-forward.yml +0 -103
- package/.github/example/workflows/job.lint.yml +0 -64
- package/.github/example/workflows/job.pr-name.yml +0 -70
- package/.github/example/workflows/job.pr-squash-name.yml +0 -27
- package/.github/example/workflows/job.tag.yml +0 -38
- package/.github/example/workflows/job.version.yml +0 -99
- package/.github/example/workflows/lib.plugin-base.publish.yml +0 -59
- package/.github/example/workflows/pipe.yml +0 -178
- package/.github/example/workflows/scripts/fastforward.sh +0 -51
- package/.github/example/workflows/scripts/pipe-cleaner.sh +0 -70
- package/.github/scripts/fastforward.sh +0 -51
- package/.github/scripts/pipe-cleaner.sh +0 -70
- package/.github/workflows/pipeline.yml +0 -220
- package/.github/workflows/pr-title-check.yml +0 -70
- package/.github/workflows/publish.yml +0 -39
- package/.pipecraftrc.json +0 -78
- package/.release-it.cjs +0 -71
- package/PIPELINE_TESTING_PLAN.md +0 -499
- package/TRUNK_FLOW_PLAN.md +0 -401
- package/assets/logo_banner.png +0 -0
- package/assets/logo_banner.webp +0 -0
- package/docs/USER_JOURNEY_ERRORS.md +0 -352
- package/eslint.config.js +0 -77
- package/examples/basic-config.json +0 -41
- package/examples/monorepo-config.json +0 -49
- package/examples/usage.md +0 -289
- package/scripts/debug-workflows.sh +0 -339
- package/scripts/parse-pipeline.js +0 -29
- package/scripts/test-job-order.cjs +0 -140
- package/scripts/validate-pipeline.cjs +0 -379
- package/scripts/verify-job-order.sh +0 -30
- package/src/cli/index.ts +0 -414
- package/src/generators/init.tpl.ts +0 -126
- package/src/generators/workflows.tpl.ts +0 -80
- package/src/templates/actions/calculate-version.yml.tpl.ts +0 -124
- package/src/templates/actions/create-pr.yml.tpl.ts +0 -147
- package/src/templates/actions/create-tag.yml.tpl.ts +0 -112
- package/src/templates/actions/detect-changes.yml.tpl.ts +0 -87
- package/src/templates/actions/manage-branch.yml.tpl.ts +0 -132
- package/src/templates/actions/promote-branch.yml.tpl.ts +0 -364
- package/src/templates/workflows/pipeline-path-based.yml.tpl.ts +0 -706
- package/src/types/index.ts +0 -64
- package/src/utils/README-ast-path-operations.md +0 -390
- package/src/utils/ast-path-operations.ts +0 -581
- package/src/utils/config.ts +0 -64
- package/src/utils/github-setup.ts +0 -558
- package/src/utils/idempotency.ts +0 -215
- package/src/utils/preflight.ts +0 -306
- package/src/utils/versioning.ts +0 -244
- package/tests/README.md +0 -229
- package/tests/TEST_STRUCTURE.md +0 -256
- package/tests/act/run-act-tests.sh +0 -345
- package/tests/debugging/debug-utils.ts +0 -538
- package/tests/debugging/debug-workflow.test.ts +0 -339
- package/tests/debugging/debug-workflows.sh +0 -339
- package/tests/debugging/iterative-debug.ts +0 -652
- package/tests/debugging/run-debug-tests.sh +0 -431
- package/tests/fixtures/basic-config.json +0 -51
- package/tests/fixtures/invalid-config.json +0 -9
- package/tests/fixtures/pipeline-generated.yml +0 -235
- package/tests/fixtures/pipeline-preserve-comments.yml +0 -245
- package/tests/fixtures/pipeline-user-modified.yml +0 -245
- package/tests/fixtures/test-config.json +0 -58
- package/tests/github-live/README.md +0 -250
- package/tests/github-local/JOB_WORKFLOW_TESTS.md +0 -296
- package/tests/github-local/README.md +0 -240
- package/tests/github-local/run-all-tests.sh +0 -422
- package/tests/github-local/test-job-workflows.sh +0 -631
- package/tests/github-local/test-pipeline-workflow.sh +0 -440
- package/tests/integration/generators.test.ts +0 -578
- package/tests/integration/path-based-template.test.ts +0 -510
- package/tests/integration/simple-path-based.test.ts +0 -415
- package/tests/setup.ts +0 -56
- package/tests/unit/ast-path-operations-extended.test.ts +0 -302
- package/tests/unit/cli.test.ts +0 -541
- package/tests/unit/config-extended.test.ts +0 -412
- package/tests/unit/config.test.ts +0 -152
- package/tests/unit/github-setup.test.ts +0 -189
- package/tests/unit/idempotency-isolated.test.ts +0 -297
- package/tests/unit/job-order.test.ts +0 -157
- package/tests/unit/pipeline-path-based.test.ts +0 -511
- package/tests/unit/validate-pipeline.test.ts +0 -384
- package/tests/unit/versioning-extended.test.ts +0 -279
- package/tests/unit/versioning.test.ts +0 -241
- package/tsconfig.debug.json +0 -17
- package/tsconfig.json +0 -28
- 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
|
-
*
|
|
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', {
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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!');
|