@aspruyt/xfg 4.0.2 → 4.0.4
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/cli/index.d.ts +1 -1
- package/dist/cli/index.js +0 -6
- package/dist/cli/program.js +3 -2
- package/dist/cli/settings-report-builder.js +4 -4
- package/dist/cli/sync-command.js +72 -36
- package/dist/cli/sync-report-builder.d.ts +2 -6
- package/dist/cli/types.d.ts +2 -14
- package/dist/cli/types.js +1 -9
- package/dist/config/file-reference-resolver.js +13 -23
- package/dist/config/formatter.d.ts +0 -6
- package/dist/config/formatter.js +0 -9
- package/dist/config/index.d.ts +1 -2
- package/dist/config/index.js +0 -2
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +3 -3
- package/dist/config/normalizer.d.ts +1 -1
- package/dist/config/normalizer.js +44 -57
- package/dist/config/validator.d.ts +1 -1
- package/dist/config/validator.js +120 -121
- package/dist/config/validators/file-validator.d.ts +2 -4
- package/dist/config/validators/file-validator.js +3 -7
- package/dist/config/validators/repo-settings-validator.js +1 -1
- package/dist/config/validators/ruleset-validator.js +28 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +0 -1
- package/dist/lifecycle/ado-migration-source.d.ts +2 -1
- package/dist/lifecycle/ado-migration-source.js +7 -5
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -3
- package/dist/lifecycle/github-lifecycle-provider.js +29 -19
- package/dist/lifecycle/lifecycle-formatter.js +2 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +5 -1
- package/dist/lifecycle/lifecycle-helpers.js +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.js +12 -9
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +4 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +11 -7
- package/dist/lifecycle/types.d.ts +0 -15
- package/dist/output/github-summary.d.ts +6 -5
- package/dist/output/github-summary.js +36 -52
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.d.ts +2 -12
- package/dist/output/lifecycle-report.js +18 -35
- package/dist/output/settings-report.d.ts +4 -4
- package/dist/output/settings-report.js +6 -6
- package/dist/output/sync-report.d.ts +4 -6
- package/dist/output/sync-report.js +2 -2
- package/dist/output/unified-summary.d.ts +1 -0
- package/dist/output/unified-summary.js +8 -8
- package/dist/settings/base-processor.d.ts +1 -1
- package/dist/settings/base-processor.js +1 -1
- package/dist/settings/index.d.ts +3 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/diff.js +3 -2
- package/dist/settings/labels/formatter.js +3 -3
- package/dist/settings/labels/github-labels-strategy.d.ts +2 -23
- package/dist/settings/labels/github-labels-strategy.js +8 -28
- package/dist/settings/labels/index.d.ts +1 -0
- package/dist/settings/labels/index.js +2 -0
- package/dist/settings/labels/processor.d.ts +2 -2
- package/dist/settings/labels/processor.js +3 -4
- package/dist/settings/labels/types.d.ts +0 -3
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +2 -2
- package/dist/settings/repo-settings/formatter.d.ts +1 -1
- package/dist/settings/repo-settings/formatter.js +4 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -7
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +9 -17
- package/dist/settings/repo-settings/index.d.ts +1 -0
- package/dist/settings/repo-settings/index.js +2 -0
- package/dist/settings/repo-settings/processor.d.ts +2 -2
- package/dist/settings/repo-settings/processor.js +5 -6
- package/dist/settings/repo-settings/types.d.ts +9 -13
- package/dist/settings/repo-settings/types.js +1 -14
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -1
- package/dist/settings/rulesets/diff-algorithm.js +6 -8
- package/dist/settings/rulesets/formatter.js +15 -51
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -20
- package/dist/settings/rulesets/github-ruleset-strategy.js +6 -30
- package/dist/settings/rulesets/index.d.ts +2 -1
- package/dist/settings/rulesets/index.js +3 -1
- package/dist/settings/rulesets/processor.d.ts +2 -2
- package/dist/settings/rulesets/processor.js +3 -4
- package/dist/{vcs → shared}/branch-utils.js +5 -4
- package/dist/shared/command-executor.d.ts +2 -1
- package/dist/shared/command-executor.js +9 -5
- package/dist/shared/env.d.ts +6 -6
- package/dist/shared/env.js +10 -17
- package/dist/shared/errors.d.ts +26 -0
- package/dist/shared/errors.js +34 -0
- package/dist/shared/gh-api-utils.d.ts +21 -14
- package/dist/shared/gh-api-utils.js +33 -22
- package/dist/shared/index.d.ts +9 -2
- package/dist/shared/index.js +16 -2
- package/dist/shared/logger.d.ts +24 -1
- package/dist/shared/logger.js +8 -3
- package/dist/shared/repo-detector.js +9 -11
- package/dist/shared/retry-utils.d.ts +5 -7
- package/dist/shared/retry-utils.js +3 -10
- package/dist/shared/shell-utils.d.ts +0 -3
- package/dist/shared/shell-utils.js +2 -4
- package/dist/shared/type-guards.d.ts +2 -9
- package/dist/shared/type-guards.js +0 -6
- package/dist/shared/xfg-template.d.ts +2 -2
- package/dist/shared/xfg-template.js +2 -1
- package/dist/sync/auth-options-builder.d.ts +3 -2
- package/dist/sync/auth-options-builder.js +14 -10
- package/dist/sync/branch-manager.d.ts +12 -7
- package/dist/sync/branch-manager.js +4 -7
- package/dist/sync/commit-message.d.ts +1 -1
- package/dist/sync/commit-push-manager.d.ts +8 -2
- package/dist/sync/commit-push-manager.js +6 -5
- package/dist/sync/file-sync-orchestrator.js +17 -21
- package/dist/sync/file-writer.js +3 -5
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/manifest-manager.d.ts +1 -0
- package/dist/sync/manifest.d.ts +4 -7
- package/dist/sync/manifest.js +42 -45
- package/dist/sync/repository-processor.d.ts +5 -2
- package/dist/sync/repository-processor.js +11 -17
- package/dist/sync/repository-session.js +2 -1
- package/dist/sync/sync-workflow.d.ts +2 -2
- package/dist/sync/sync-workflow.js +16 -23
- package/dist/sync/types.d.ts +20 -25
- package/dist/vcs/authenticated-git-ops.d.ts +3 -4
- package/dist/vcs/authenticated-git-ops.js +5 -1
- package/dist/vcs/azure-pr-strategy.d.ts +6 -1
- package/dist/vcs/azure-pr-strategy.js +38 -31
- package/dist/vcs/commit-strategy-selector.d.ts +10 -19
- package/dist/vcs/commit-strategy-selector.js +8 -24
- package/dist/vcs/git-commit-strategy.d.ts +1 -1
- package/dist/vcs/git-commit-strategy.js +1 -3
- package/dist/vcs/git-ops.d.ts +4 -8
- package/dist/vcs/git-ops.js +18 -22
- package/dist/vcs/github-app-token-manager.js +9 -8
- package/dist/vcs/github-pr-strategy.js +18 -11
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +14 -7
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -7
- package/dist/vcs/graphql-commit-strategy.js +24 -32
- package/dist/vcs/index.d.ts +2 -1
- package/dist/vcs/pr-creator.d.ts +6 -9
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +2 -1
- package/dist/vcs/pr-strategy.d.ts +1 -1
- package/dist/vcs/pr-strategy.js +2 -3
- package/dist/vcs/types.d.ts +6 -10
- package/package.json +2 -2
- package/dist/config/errors.d.ts +0 -9
- package/dist/config/errors.js +0 -11
- /package/dist/{vcs → shared}/branch-utils.d.ts +0 -0
package/dist/vcs/git-ops.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { rmSync, existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, } from "node:fs";
|
|
1
|
+
import { rmSync, existsSync, statSync, mkdirSync, writeFileSync, readFileSync, chmodSync, } from "node:fs";
|
|
2
2
|
import { join, resolve, relative, isAbsolute, dirname } from "node:path";
|
|
3
3
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
|
-
import { defaultExecutor, } from "../shared/command-executor.js";
|
|
5
4
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
5
|
+
import { ValidationError, SyncError } from "../shared/errors.js";
|
|
6
6
|
export class GitOps {
|
|
7
7
|
_workDir;
|
|
8
8
|
dryRun;
|
|
@@ -11,7 +11,7 @@ export class GitOps {
|
|
|
11
11
|
constructor(options) {
|
|
12
12
|
this._workDir = options.workDir;
|
|
13
13
|
this.dryRun = options.dryRun ?? false;
|
|
14
|
-
this._executor = options.executor
|
|
14
|
+
this._executor = options.executor;
|
|
15
15
|
this.log = options.log;
|
|
16
16
|
}
|
|
17
17
|
async exec(command, cwd) {
|
|
@@ -28,7 +28,7 @@ export class GitOps {
|
|
|
28
28
|
const resolvedWorkDir = resolve(this._workDir);
|
|
29
29
|
const relativePath = relative(resolvedWorkDir, resolvedPath);
|
|
30
30
|
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
31
|
-
throw new
|
|
31
|
+
throw new ValidationError(`Path traversal detected: ${fileName}`);
|
|
32
32
|
}
|
|
33
33
|
return filePath;
|
|
34
34
|
}
|
|
@@ -49,7 +49,7 @@ export class GitOps {
|
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
51
51
|
const message = toErrorMessage(error);
|
|
52
|
-
throw new
|
|
52
|
+
throw new SyncError(`Failed to create branch '${branchName}': ${message}`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
writeFile(fileName, content) {
|
|
@@ -86,14 +86,18 @@ export class GitOps {
|
|
|
86
86
|
*/
|
|
87
87
|
getFileContent(fileName) {
|
|
88
88
|
const filePath = this.validatePath(fileName);
|
|
89
|
-
if (!existsSync(filePath)) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
89
|
try {
|
|
90
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
93
|
return readFileSync(filePath, "utf-8");
|
|
94
94
|
}
|
|
95
95
|
catch (error) {
|
|
96
|
-
|
|
96
|
+
const code = error.code;
|
|
97
|
+
if (code === "ENOENT" || code === "EACCES") {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
this.log?.debug(`Unexpected error reading ${fileName}: ${error.message}`);
|
|
97
101
|
return null;
|
|
98
102
|
}
|
|
99
103
|
}
|
|
@@ -136,20 +140,12 @@ export class GitOps {
|
|
|
136
140
|
.filter((line) => line.length > 0)
|
|
137
141
|
.map((line) => line.slice(3)); // Remove status prefix (e.g., " M ", "?? ", "A ")
|
|
138
142
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*/
|
|
143
|
+
async stageAll() {
|
|
144
|
+
await this.exec("git add -A", this._workDir);
|
|
145
|
+
}
|
|
143
146
|
async hasStagedChanges() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return false; // Exit code 0 = no staged changes
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
// Exit code 1 is expected when staged changes exist
|
|
150
|
-
this.log?.debug(`hasStagedChanges: ${toErrorMessage(error)}`);
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
147
|
+
const diff = await this.exec("git diff --cached --name-only", this._workDir);
|
|
148
|
+
return diff.length > 0;
|
|
153
149
|
}
|
|
154
150
|
/**
|
|
155
151
|
* Check if a file exists on a specific branch.
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { createSign } from "node:crypto";
|
|
2
2
|
import { withRetry } from "../shared/retry-utils.js";
|
|
3
|
+
import { SyncError } from "../shared/errors.js";
|
|
3
4
|
/** Duration to cache tokens (45 minutes in milliseconds) */
|
|
4
5
|
const TOKEN_CACHE_DURATION_MS = 45 * 60 * 1000;
|
|
6
|
+
async function assertOkResponse(res, context) {
|
|
7
|
+
if (!res.ok) {
|
|
8
|
+
const body = await res.text().catch(() => "");
|
|
9
|
+
throw new SyncError(`${context}: ${res.status}${body ? ` - ${body}` : ""}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
/**
|
|
6
13
|
* Manages GitHub App authentication tokens for multiple organizations.
|
|
7
14
|
* Handles JWT generation, installation discovery, and token caching.
|
|
@@ -59,10 +66,7 @@ export class GitHubAppTokenManager {
|
|
|
59
66
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
60
67
|
},
|
|
61
68
|
});
|
|
62
|
-
|
|
63
|
-
const body = await res.text().catch(() => "");
|
|
64
|
-
throw new Error(`GitHub API error: ${res.status}${body ? ` - ${body}` : ""}`);
|
|
65
|
-
}
|
|
69
|
+
await assertOkResponse(res, "GitHub App installations");
|
|
66
70
|
return res;
|
|
67
71
|
});
|
|
68
72
|
const installations = (await response.json());
|
|
@@ -104,10 +108,7 @@ export class GitHubAppTokenManager {
|
|
|
104
108
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
105
109
|
},
|
|
106
110
|
});
|
|
107
|
-
|
|
108
|
-
const body = await res.text().catch(() => "");
|
|
109
|
-
throw new Error(`GitHub API error: ${res.status}${body ? ` - ${body}` : ""}`);
|
|
110
|
-
}
|
|
111
|
+
await assertOkResponse(res, "GitHub App access token");
|
|
111
112
|
return res;
|
|
112
113
|
});
|
|
113
114
|
const tokenResponse = (await response.json());
|
|
@@ -3,17 +3,19 @@ import { join } from "node:path";
|
|
|
3
3
|
import { escapeShellArg, escapeRegExp } from "../shared/shell-utils.js";
|
|
4
4
|
import { assertGitHubRepo } from "../shared/repo-detector.js";
|
|
5
5
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
6
|
-
import { withRetry } from "../shared/retry-utils.js";
|
|
6
|
+
import { withRetry, isPermanentError } from "../shared/retry-utils.js";
|
|
7
7
|
import { sanitizeCredentials } from "../shared/sanitize-utils.js";
|
|
8
8
|
import { toErrorMessage, safeCleanup } from "../shared/type-guards.js";
|
|
9
|
+
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
9
10
|
import { getStderr } from "../shared/command-executor.js";
|
|
10
11
|
import { buildTokenEnv, getHostnameFlag } from "../shared/gh-api-utils.js";
|
|
12
|
+
import { SyncError } from "../shared/errors.js";
|
|
11
13
|
/**
|
|
12
14
|
* Get the repo flag value for gh CLI commands.
|
|
13
15
|
* Returns HOST/OWNER/REPO for GHE, OWNER/REPO for github.com.
|
|
14
16
|
*/
|
|
15
17
|
function getRepoFlag(repoInfo) {
|
|
16
|
-
if (repoInfo.host
|
|
18
|
+
if (repoInfo.host !== "github.com") {
|
|
17
19
|
return `${repoInfo.host}/${repoInfo.owner}/${repoInfo.repo}`;
|
|
18
20
|
}
|
|
19
21
|
return `${repoInfo.owner}/${repoInfo.repo}`;
|
|
@@ -33,10 +35,13 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
33
35
|
const tokenEnv = buildTokenEnv(token);
|
|
34
36
|
const command = `gh pr list --repo ${escapeShellArg(repoFlag)} --head ${escapeShellArg(branchName)} --json url --jq '.[0].url'`;
|
|
35
37
|
try {
|
|
36
|
-
const existingPR = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries });
|
|
38
|
+
const existingPR = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries, log: this.log });
|
|
37
39
|
return existingPR || null;
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
42
|
+
if (isPermanentError(error)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
40
45
|
const stderr = getStderr(error);
|
|
41
46
|
if (stderr && !stderr.includes("no pull requests match")) {
|
|
42
47
|
this.log?.debug(`GitHub PR check failed - ${sanitizeCredentials(stderr).trim()}`);
|
|
@@ -69,7 +74,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
69
74
|
const tokenEnv = buildTokenEnv(token);
|
|
70
75
|
const command = `gh pr close ${escapeShellArg(prNumber)} --repo ${escapeShellArg(repoFlag)} --delete-branch`;
|
|
71
76
|
try {
|
|
72
|
-
await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries });
|
|
77
|
+
await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries, log: this.log });
|
|
73
78
|
return true;
|
|
74
79
|
}
|
|
75
80
|
catch (error) {
|
|
@@ -92,13 +97,13 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
try {
|
|
95
|
-
const result = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries });
|
|
100
|
+
const result = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries, log: this.log });
|
|
96
101
|
// Extract URL from output - use strict regex for valid PR URLs only
|
|
97
102
|
const host = repoInfo.host;
|
|
98
103
|
const urlRegex = buildPRUrlRegex(host);
|
|
99
104
|
const urlMatch = result.match(urlRegex);
|
|
100
105
|
if (!urlMatch) {
|
|
101
|
-
throw new
|
|
106
|
+
throw new SyncError(`Could not parse PR URL from output: ${result}`);
|
|
102
107
|
}
|
|
103
108
|
return {
|
|
104
109
|
url: urlMatch[0],
|
|
@@ -110,7 +115,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
110
115
|
safeCleanup(() => {
|
|
111
116
|
if (existsSync(bodyFile))
|
|
112
117
|
unlinkSync(bodyFile);
|
|
113
|
-
}, `failed to remove ${bodyFile}`, this.log ??
|
|
118
|
+
}, `failed to remove ${bodyFile}`, this.log ?? NO_OP_DEBUG_LOG);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
/**
|
|
@@ -122,7 +127,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
122
127
|
const tokenEnv = buildTokenEnv(token);
|
|
123
128
|
const command = `gh api ${hostnamePart}repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)} --jq '.allow_auto_merge // false'`;
|
|
124
129
|
try {
|
|
125
|
-
const result = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries });
|
|
130
|
+
const result = await withRetry(() => this.executor.exec(command, workDir, { env: tokenEnv }), { retries, log: this.log });
|
|
126
131
|
return result.trim() === "true";
|
|
127
132
|
}
|
|
128
133
|
catch (error) {
|
|
@@ -147,7 +152,6 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
147
152
|
}
|
|
148
153
|
async merge(options) {
|
|
149
154
|
const { prUrl, repoInfo, config, workDir, retries = 3, token } = options;
|
|
150
|
-
// Manual mode: do nothing
|
|
151
155
|
if (config.mode === "manual") {
|
|
152
156
|
return {
|
|
153
157
|
success: true,
|
|
@@ -182,7 +186,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
182
186
|
}, "Failed to enable auto-merge");
|
|
183
187
|
}
|
|
184
188
|
if (config.mode === "force") {
|
|
185
|
-
|
|
189
|
+
this.log?.warn(`Force-merging PR ${prUrl} using admin privileges (bypasses branch protection)`);
|
|
186
190
|
const forceCommand = `gh pr merge ${escapeShellArg(prUrl)} --admin ${strategyFlag} ${deleteBranchFlag}`.trim();
|
|
187
191
|
return this.executeMergeCommand(() => this.executor.exec(forceCommand, workDir, { env: tokenEnv }), retries, {
|
|
188
192
|
success: true,
|
|
@@ -190,9 +194,12 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
190
194
|
merged: true,
|
|
191
195
|
}, "Failed to force merge");
|
|
192
196
|
}
|
|
197
|
+
// "direct" mode doesn't create PRs, so merge() should not be called for it.
|
|
198
|
+
// This is a defensive fallback for type safety.
|
|
199
|
+
const _exhaustive = config.mode;
|
|
193
200
|
return {
|
|
194
201
|
success: false,
|
|
195
|
-
message: `
|
|
202
|
+
message: `Merge not applicable for mode: ${_exhaustive}`,
|
|
196
203
|
merged: false,
|
|
197
204
|
};
|
|
198
205
|
}
|
|
@@ -4,7 +4,7 @@ import type { IPRStrategyLogger } from "./pr-strategy.js";
|
|
|
4
4
|
import type { PRStrategyOptions, CloseExistingPROptions, MergeOptions, MergeResult } from "./types.js";
|
|
5
5
|
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
6
6
|
export declare class GitLabPRStrategy extends BasePRStrategy {
|
|
7
|
-
constructor(executor
|
|
7
|
+
constructor(executor: ICommandExecutor, log?: IPRStrategyLogger);
|
|
8
8
|
/**
|
|
9
9
|
* Build the repo flag for glab commands.
|
|
10
10
|
* Format: namespace/repo (supports nested groups)
|
|
@@ -3,11 +3,13 @@ import { join } from "node:path";
|
|
|
3
3
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
4
|
import { assertGitLabRepo } from "../shared/repo-detector.js";
|
|
5
5
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
6
|
-
import { withRetry } from "../shared/retry-utils.js";
|
|
6
|
+
import { withRetry, isPermanentError } from "../shared/retry-utils.js";
|
|
7
7
|
import { getStderr } from "../shared/command-executor.js";
|
|
8
8
|
import { parseApiJson } from "../shared/gh-api-utils.js";
|
|
9
9
|
import { sanitizeCredentials } from "../shared/sanitize-utils.js";
|
|
10
10
|
import { toErrorMessage, safeCleanup } from "../shared/type-guards.js";
|
|
11
|
+
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
12
|
+
import { SyncError } from "../shared/errors.js";
|
|
11
13
|
export class GitLabPRStrategy extends BasePRStrategy {
|
|
12
14
|
constructor(executor, log) {
|
|
13
15
|
super(executor, log);
|
|
@@ -70,7 +72,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
70
72
|
// Note: glab mr list returns open MRs by default (use -c for closed, -M for merged)
|
|
71
73
|
const command = `glab mr list --source-branch ${escapeShellArg(branchName)} -R ${escapeShellArg(repoFlag)} -F json`;
|
|
72
74
|
try {
|
|
73
|
-
const result = await withRetry(() => this.executor.exec(command, workDir), { retries });
|
|
75
|
+
const result = await withRetry(() => this.executor.exec(command, workDir), { retries, log: this.log });
|
|
74
76
|
if (!result || result.trim() === "" || result.trim() === "[]") {
|
|
75
77
|
return null;
|
|
76
78
|
}
|
|
@@ -81,6 +83,9 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
catch (error) {
|
|
86
|
+
if (isPermanentError(error)) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
84
89
|
const stderr = getStderr(error);
|
|
85
90
|
if (stderr && !stderr.includes("no merge requests")) {
|
|
86
91
|
this.log?.debug(`GitLab MR check failed - ${sanitizeCredentials(stderr).trim()}`);
|
|
@@ -114,6 +119,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
114
119
|
try {
|
|
115
120
|
await withRetry(() => this.executor.exec(closeCommand, workDir), {
|
|
116
121
|
retries,
|
|
122
|
+
log: this.log,
|
|
117
123
|
});
|
|
118
124
|
}
|
|
119
125
|
catch (error) {
|
|
@@ -125,6 +131,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
125
131
|
try {
|
|
126
132
|
await withRetry(() => this.executor.exec(deleteBranchCommand, workDir), {
|
|
127
133
|
retries,
|
|
134
|
+
log: this.log,
|
|
128
135
|
});
|
|
129
136
|
}
|
|
130
137
|
catch (error) {
|
|
@@ -143,7 +150,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
143
150
|
// glab mr create with description from file
|
|
144
151
|
const command = `glab mr create --source-branch ${escapeShellArg(branchName)} --target-branch ${escapeShellArg(baseBranch)} --title ${escapeShellArg(title)} --description "$(cat ${escapeShellArg(descFile)})" --yes -R ${escapeShellArg(repoFlag)}`;
|
|
145
152
|
try {
|
|
146
|
-
const result = await withRetry(() => this.executor.exec(command, workDir), { retries });
|
|
153
|
+
const result = await withRetry(() => this.executor.exec(command, workDir), { retries, log: this.log });
|
|
147
154
|
// Extract MR URL from output
|
|
148
155
|
// glab typically outputs the URL directly
|
|
149
156
|
const urlMatch = result.match(/https:\/\/[^\s]+\/-\/merge_requests\/\d+/);
|
|
@@ -163,18 +170,17 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
163
170
|
message: "MR created successfully",
|
|
164
171
|
};
|
|
165
172
|
}
|
|
166
|
-
throw new
|
|
173
|
+
throw new SyncError(`Could not parse MR URL from output: ${result}`);
|
|
167
174
|
}
|
|
168
175
|
finally {
|
|
169
176
|
safeCleanup(() => {
|
|
170
177
|
if (existsSync(descFile))
|
|
171
178
|
unlinkSync(descFile);
|
|
172
|
-
}, `failed to remove ${descFile}`, this.log ??
|
|
179
|
+
}, `failed to remove ${descFile}`, this.log ?? NO_OP_DEBUG_LOG);
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
async merge(options) {
|
|
176
183
|
const { prUrl, config, workDir, retries = 3 } = options;
|
|
177
|
-
// Manual mode: do nothing
|
|
178
184
|
if (config.mode === "manual") {
|
|
179
185
|
return {
|
|
180
186
|
success: true,
|
|
@@ -221,9 +227,10 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
221
227
|
merged: true,
|
|
222
228
|
}, "Failed to force merge");
|
|
223
229
|
}
|
|
230
|
+
const _exhaustive = config.mode;
|
|
224
231
|
return {
|
|
225
232
|
success: false,
|
|
226
|
-
message: `
|
|
233
|
+
message: `Merge not applicable for mode: ${_exhaustive}`,
|
|
227
234
|
merged: false,
|
|
228
235
|
};
|
|
229
236
|
}
|
|
@@ -37,7 +37,7 @@ export declare class GraphQLCommitStrategy implements ICommitStrategy {
|
|
|
37
37
|
*/
|
|
38
38
|
private static readonly GRAPHQL_PERMANENT_ERROR_PATTERNS;
|
|
39
39
|
private executor;
|
|
40
|
-
constructor(executor
|
|
40
|
+
constructor(executor: ICommandExecutor);
|
|
41
41
|
/**
|
|
42
42
|
* Create a commit with the given file changes using GitHub's GraphQL API.
|
|
43
43
|
* Uses the createCommitOnBranch mutation for verified commits.
|
|
@@ -86,12 +86,6 @@ export declare class GraphQLCommitStrategy implements ICommitStrategy {
|
|
|
86
86
|
* Returns repositoryId (always) and refId (null if branch doesn't exist).
|
|
87
87
|
*/
|
|
88
88
|
private queryRemoteRef;
|
|
89
|
-
/**
|
|
90
|
-
* Create a branch ref on the remote via GraphQL createRef mutation.
|
|
91
|
-
*/
|
|
92
89
|
private createRemoteRef;
|
|
93
|
-
/**
|
|
94
|
-
* Delete a branch ref on the remote via GraphQL deleteRef mutation.
|
|
95
|
-
*/
|
|
96
90
|
private deleteRemoteRef;
|
|
97
91
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { defaultExecutor, } from "../shared/command-executor.js";
|
|
2
1
|
import { isGitHubRepo } from "../shared/repo-detector.js";
|
|
3
2
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
3
|
import { withRetry, CORE_PERMANENT_ERROR_PATTERNS, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "../shared/retry-utils.js";
|
|
5
4
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
6
|
-
import { parseApiJson } from "../shared/gh-api-utils.js";
|
|
5
|
+
import { parseApiJson, buildTokenEnv } from "../shared/gh-api-utils.js";
|
|
6
|
+
import { ValidationError, GraphQLApiError } from "../shared/errors.js";
|
|
7
7
|
/**
|
|
8
8
|
* Maximum payload size for GitHub GraphQL API (50MB).
|
|
9
9
|
* Base64 encoding adds ~33% overhead, so raw content should be checked.
|
|
@@ -27,7 +27,7 @@ export const SAFE_BRANCH_NAME_PATTERN = /^[a-zA-Z0-9][-a-zA-Z0-9_./]*$/;
|
|
|
27
27
|
*/
|
|
28
28
|
export function validateSafeBranchName(branchName) {
|
|
29
29
|
if (!SAFE_BRANCH_NAME_PATTERN.test(branchName)) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new ValidationError(`Invalid branch name for GraphQL commit strategy: "${branchName}". ` +
|
|
31
31
|
`Branch names must start with alphanumeric and contain only ` +
|
|
32
32
|
`alphanumeric characters, hyphens, underscores, dots, and forward slashes.`);
|
|
33
33
|
}
|
|
@@ -62,7 +62,7 @@ export class GraphQLCommitStrategy {
|
|
|
62
62
|
];
|
|
63
63
|
executor;
|
|
64
64
|
constructor(executor) {
|
|
65
|
-
this.executor = executor
|
|
65
|
+
this.executor = executor;
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* Create a commit with the given file changes using GitHub's GraphQL API.
|
|
@@ -74,10 +74,9 @@ export class GraphQLCommitStrategy {
|
|
|
74
74
|
async commit(options) {
|
|
75
75
|
const { repoInfo, branchName, message, fileChanges, workDir, retries = 3, token, } = options;
|
|
76
76
|
if (!isGitHubRepo(repoInfo)) {
|
|
77
|
-
throw new
|
|
77
|
+
throw new ValidationError(`GraphQL commit strategy requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
78
78
|
}
|
|
79
79
|
validateSafeBranchName(branchName);
|
|
80
|
-
const githubInfo = repoInfo;
|
|
81
80
|
const additions = fileChanges.filter((fc) => fc.content !== null);
|
|
82
81
|
const deletions = fileChanges.filter((fc) => fc.content === null);
|
|
83
82
|
// Base64 encoding adds ~33% overhead to raw content size
|
|
@@ -86,13 +85,13 @@ export class GraphQLCommitStrategy {
|
|
|
86
85
|
return sum + base64Size;
|
|
87
86
|
}, 0);
|
|
88
87
|
if (totalSize > MAX_PAYLOAD_SIZE) {
|
|
89
|
-
throw new
|
|
88
|
+
throw new ValidationError(`GraphQL payload exceeds 50 MB limit (${Math.round(totalSize / (1024 * 1024))} MB). ` +
|
|
90
89
|
`Consider using smaller files or the git commit strategy.`);
|
|
91
90
|
}
|
|
92
91
|
const gitOps = options.gitOps;
|
|
93
92
|
// createCommitOnBranch requires the branch to already exist on remote.
|
|
94
93
|
// For PR branches (force=true), force-update ensures a fresh start from main.
|
|
95
|
-
await this.ensureBranchExistsOnRemote(branchName, workDir, options.force,
|
|
94
|
+
await this.ensureBranchExistsOnRemote(branchName, workDir, options.force, repoInfo, token);
|
|
96
95
|
// Outer retry loop for expectedHeadOid mismatch — each iteration re-fetches
|
|
97
96
|
// the remote HEAD so the next mutation uses a fresh OID.
|
|
98
97
|
let lastError = null;
|
|
@@ -107,7 +106,7 @@ export class GraphQLCommitStrategy {
|
|
|
107
106
|
}
|
|
108
107
|
// Get the remote HEAD SHA for this branch (not local HEAD)
|
|
109
108
|
const headSha = await this.executor.exec(`git rev-parse origin/${safeBranch}`, workDir);
|
|
110
|
-
const result = await this.executeGraphQLMutation(
|
|
109
|
+
const result = await this.executeGraphQLMutation(repoInfo, branchName, message, headSha.trim(), additions, deletions, workDir, token);
|
|
111
110
|
return result;
|
|
112
111
|
}
|
|
113
112
|
catch (error) {
|
|
@@ -119,7 +118,7 @@ export class GraphQLCommitStrategy {
|
|
|
119
118
|
throw lastError;
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
|
-
throw lastError ?? new
|
|
121
|
+
throw (lastError ?? new GraphQLApiError("Unexpected error in GraphQL commit"));
|
|
123
122
|
}
|
|
124
123
|
/**
|
|
125
124
|
* Execute the createCommitOnBranch GraphQL mutation.
|
|
@@ -161,7 +160,7 @@ export class GraphQLCommitStrategy {
|
|
|
161
160
|
const hostnameArg = repoInfo.host !== "github.com"
|
|
162
161
|
? `--hostname ${escapeShellArg(repoInfo.host)}`
|
|
163
162
|
: "";
|
|
164
|
-
const tokenEnv = token
|
|
163
|
+
const tokenEnv = buildTokenEnv(token);
|
|
165
164
|
const command = `echo ${escapeShellArg(requestBody)} | gh api graphql ${hostnameArg} --input -`;
|
|
166
165
|
let response;
|
|
167
166
|
try {
|
|
@@ -177,14 +176,11 @@ export class GraphQLCommitStrategy {
|
|
|
177
176
|
}
|
|
178
177
|
const parsed = parseApiJson(response, "GraphQL createCommitOnBranch response");
|
|
179
178
|
if (parsed.errors) {
|
|
180
|
-
|
|
181
|
-
throw new Error(`GraphQL error: ${errors.map((e) => e.message).join(", ")}`);
|
|
179
|
+
throw new GraphQLApiError(parsed.errors.map((e) => e.message).join(", "));
|
|
182
180
|
}
|
|
183
|
-
const
|
|
184
|
-
const commit = data?.createCommitOnBranch?.commit;
|
|
185
|
-
const oid = commit?.oid;
|
|
181
|
+
const oid = parsed.data?.createCommitOnBranch?.commit?.oid;
|
|
186
182
|
if (!oid) {
|
|
187
|
-
throw new
|
|
183
|
+
throw new GraphQLApiError("Response missing commit OID");
|
|
188
184
|
}
|
|
189
185
|
return {
|
|
190
186
|
sha: oid,
|
|
@@ -206,7 +202,7 @@ export class GraphQLCommitStrategy {
|
|
|
206
202
|
*/
|
|
207
203
|
async ensureBranchExistsOnRemote(branchName, workDir, force, repoInfo, token) {
|
|
208
204
|
if (!repoInfo) {
|
|
209
|
-
throw new
|
|
205
|
+
throw new GraphQLApiError("repoInfo is required for ref operations");
|
|
210
206
|
}
|
|
211
207
|
const { repositoryId, refId } = await this.queryRemoteRef(repoInfo, branchName, workDir, token);
|
|
212
208
|
if (refId && force) {
|
|
@@ -258,7 +254,7 @@ export class GraphQLCommitStrategy {
|
|
|
258
254
|
if (cleanMessage.length > 2000) {
|
|
259
255
|
cleanMessage = cleanMessage.substring(0, 2000) + "... (truncated)";
|
|
260
256
|
}
|
|
261
|
-
return new
|
|
257
|
+
return new GraphQLApiError(`Commit failed for ${repo}: ${cleanMessage}`);
|
|
262
258
|
}
|
|
263
259
|
/**
|
|
264
260
|
* Check if an error is due to expectedHeadOid mismatch (optimistic locking failure).
|
|
@@ -278,7 +274,7 @@ export class GraphQLCommitStrategy {
|
|
|
278
274
|
const hostnameArg = repoInfo.host !== "github.com"
|
|
279
275
|
? `--hostname ${escapeShellArg(repoInfo.host)}`
|
|
280
276
|
: "";
|
|
281
|
-
const tokenEnv = token
|
|
277
|
+
const tokenEnv = buildTokenEnv(token);
|
|
282
278
|
const command = `echo ${escapeShellArg(requestBody)} | gh api graphql ${hostnameArg} --input -`;
|
|
283
279
|
let response;
|
|
284
280
|
try {
|
|
@@ -291,7 +287,7 @@ export class GraphQLCommitStrategy {
|
|
|
291
287
|
}
|
|
292
288
|
const parsed = parseApiJson(response, "GraphQL API response");
|
|
293
289
|
if (parsed.errors) {
|
|
294
|
-
throw new
|
|
290
|
+
throw new GraphQLApiError(parsed.errors.map((e) => e.message).join(", "));
|
|
295
291
|
}
|
|
296
292
|
return parsed;
|
|
297
293
|
}
|
|
@@ -301,24 +297,20 @@ export class GraphQLCommitStrategy {
|
|
|
301
297
|
*/
|
|
302
298
|
async queryRemoteRef(repoInfo, branchName, workDir, token) {
|
|
303
299
|
const query = `{ repository(owner: ${JSON.stringify(repoInfo.owner)}, name: ${JSON.stringify(repoInfo.repo)}) { id ref(qualifiedName: ${JSON.stringify(`refs/heads/${branchName}`)}) { id } } }`;
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const repositoryId = repo?.id;
|
|
300
|
+
const repoResponse = await this.executeGraphQLRefOp(query, repoInfo, workDir, token);
|
|
301
|
+
const repositoryId = repoResponse.data?.repository?.id;
|
|
307
302
|
if (!repositoryId) {
|
|
308
|
-
throw new
|
|
303
|
+
throw new GraphQLApiError(`Response missing repository ID for ${repoInfo.owner}/${repoInfo.repo}`);
|
|
309
304
|
}
|
|
310
|
-
return {
|
|
305
|
+
return {
|
|
306
|
+
repositoryId,
|
|
307
|
+
refId: repoResponse.data?.repository?.ref?.id ?? null,
|
|
308
|
+
};
|
|
311
309
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Create a branch ref on the remote via GraphQL createRef mutation.
|
|
314
|
-
*/
|
|
315
310
|
async createRemoteRef(repositoryId, branchName, oid, workDir, repoInfo, token) {
|
|
316
311
|
const mutation = `mutation { createRef(input: { repositoryId: ${JSON.stringify(repositoryId)}, name: ${JSON.stringify(`refs/heads/${branchName}`)}, oid: ${JSON.stringify(oid)} }) { clientMutationId } }`;
|
|
317
312
|
await this.executeGraphQLRefOp(mutation, repoInfo, workDir, token);
|
|
318
313
|
}
|
|
319
|
-
/**
|
|
320
|
-
* Delete a branch ref on the remote via GraphQL deleteRef mutation.
|
|
321
|
-
*/
|
|
322
314
|
async deleteRemoteRef(refId, workDir, repoInfo, token) {
|
|
323
315
|
const mutation = `mutation { deleteRef(input: { refId: ${JSON.stringify(refId)} }) { clientMutationId } }`;
|
|
324
316
|
await this.executeGraphQLRefOp(mutation, repoInfo, workDir, token);
|
package/dist/vcs/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type { PRMergeConfig, FileChange } from "./types.js";
|
|
1
|
+
export type { PRMergeConfig, FileChange, FileAction, IGitOps, ILocalGitOps, INetworkGitOps, IPRStrategy, GitAuthOptions, PRResult, PRStrategyOptions, MergeOptions, MergeResult, CloseExistingPROptions, CommitOptions, CommitResult, ICommitStrategy, } from "./types.js";
|
|
2
|
+
export type { GitOpsOptions } from "./git-ops.js";
|
|
2
3
|
export { getCommitStrategy, createTokenManager, } from "./commit-strategy-selector.js";
|
|
3
4
|
export { getPRStrategy } from "./pr-strategy-factory.js";
|
package/dist/vcs/pr-creator.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { RepoInfo } from "../shared/repo-detector.js";
|
|
2
2
|
import type { IPRStrategyLogger } from "./pr-strategy.js";
|
|
3
|
-
import type { MergeResult, PRMergeConfig, PRResult } from "./types.js";
|
|
3
|
+
import type { FileAction, MergeResult, PRMergeConfig, PRResult } from "./types.js";
|
|
4
4
|
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
5
|
-
export
|
|
6
|
-
fileName: string;
|
|
7
|
-
action: "create" | "update" | "skip" | "delete";
|
|
8
|
-
}
|
|
5
|
+
export type { FileAction };
|
|
9
6
|
interface PROptions {
|
|
10
7
|
repoInfo: RepoInfo;
|
|
11
8
|
branchName: string;
|
|
@@ -17,8 +14,8 @@ interface PROptions {
|
|
|
17
14
|
retries?: number;
|
|
18
15
|
/** Custom PR body template */
|
|
19
16
|
prTemplate?: string;
|
|
20
|
-
/**
|
|
21
|
-
executor
|
|
17
|
+
/** Command executor for shell commands */
|
|
18
|
+
executor: ICommandExecutor;
|
|
22
19
|
/** GitHub App installation token for authentication */
|
|
23
20
|
token?: string;
|
|
24
21
|
/** Labels to apply to the created PR */
|
|
@@ -50,8 +47,8 @@ interface MergePROptions {
|
|
|
50
47
|
workDir: string;
|
|
51
48
|
dryRun?: boolean;
|
|
52
49
|
retries?: number;
|
|
53
|
-
/**
|
|
54
|
-
executor
|
|
50
|
+
/** Command executor for shell commands */
|
|
51
|
+
executor: ICommandExecutor;
|
|
55
52
|
/** GitHub App installation token for authentication */
|
|
56
53
|
token?: string;
|
|
57
54
|
/** Optional logger for PR strategy debug/warn/info messages */
|
|
@@ -2,4 +2,4 @@ import { RepoInfo } from "../shared/repo-detector.js";
|
|
|
2
2
|
import type { IPRStrategy } from "./types.js";
|
|
3
3
|
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
4
4
|
import type { IPRStrategyLogger } from "./pr-strategy.js";
|
|
5
|
-
export declare function getPRStrategy(repoInfo: RepoInfo, executor
|
|
5
|
+
export declare function getPRStrategy(repoInfo: RepoInfo, executor: ICommandExecutor, log?: IPRStrategyLogger): IPRStrategy;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, } from "../shared/repo-detector.js";
|
|
2
|
+
import { SyncError } from "../shared/errors.js";
|
|
2
3
|
import { GitHubPRStrategy } from "./github-pr-strategy.js";
|
|
3
4
|
import { AzurePRStrategy } from "./azure-pr-strategy.js";
|
|
4
5
|
import { GitLabPRStrategy } from "./gitlab-pr-strategy.js";
|
|
@@ -13,5 +14,5 @@ export function getPRStrategy(repoInfo, executor, log) {
|
|
|
13
14
|
return new GitLabPRStrategy(executor, log);
|
|
14
15
|
}
|
|
15
16
|
const _exhaustive = repoInfo;
|
|
16
|
-
throw new
|
|
17
|
+
throw new SyncError(`Unknown repository type: ${JSON.stringify(_exhaustive)}`);
|
|
17
18
|
}
|
|
@@ -10,7 +10,7 @@ export declare abstract class BasePRStrategy implements IPRStrategy {
|
|
|
10
10
|
protected bodyFilePath: string;
|
|
11
11
|
protected executor: ICommandExecutor;
|
|
12
12
|
protected log?: IPRStrategyLogger;
|
|
13
|
-
constructor(executor
|
|
13
|
+
constructor(executor: ICommandExecutor, log?: IPRStrategyLogger);
|
|
14
14
|
abstract checkExistingPR(options: CloseExistingPROptions): Promise<string | null>;
|
|
15
15
|
abstract closeExistingPR(options: CloseExistingPROptions): Promise<boolean>;
|
|
16
16
|
abstract create(options: PRStrategyOptions): Promise<PRResult>;
|
package/dist/vcs/pr-strategy.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
2
2
|
import { withRetry } from "../shared/retry-utils.js";
|
|
3
|
-
import { defaultExecutor, } from "../shared/command-executor.js";
|
|
4
3
|
export class BasePRStrategy {
|
|
5
4
|
bodyFilePath = ".pr-body.md";
|
|
6
5
|
executor;
|
|
7
6
|
log;
|
|
8
7
|
constructor(executor, log) {
|
|
9
|
-
this.executor = executor
|
|
8
|
+
this.executor = executor;
|
|
10
9
|
this.log = log;
|
|
11
10
|
}
|
|
12
11
|
/**
|
|
@@ -15,7 +14,7 @@ export class BasePRStrategy {
|
|
|
15
14
|
*/
|
|
16
15
|
async executeMergeCommand(execFn, retries, successResult, errorPrefix) {
|
|
17
16
|
try {
|
|
18
|
-
await withRetry(execFn, { retries });
|
|
17
|
+
await withRetry(execFn, { retries, log: this.log });
|
|
19
18
|
return successResult;
|
|
20
19
|
}
|
|
21
20
|
catch (error) {
|