@aspruyt/xfg 6.0.0 → 6.0.1
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/sync-command.js +33 -35
- package/dist/cli/types.d.ts +12 -11
- package/dist/{output → cli}/unified-summary.d.ts +3 -3
- package/dist/{output → cli}/unified-summary.js +4 -4
- package/dist/config/file-reference-resolver.js +24 -56
- package/dist/config/normalizer.js +29 -40
- package/dist/config/validator.js +94 -102
- package/dist/lifecycle/ado-migration-source.d.ts +1 -1
- package/dist/lifecycle/ado-migration-source.js +1 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +5 -6
- package/dist/lifecycle/github-lifecycle-provider.js +50 -20
- package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
- package/dist/lifecycle/lifecycle-formatter.js +1 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +16 -6
- package/dist/lifecycle/types.d.ts +30 -8
- package/dist/output/lifecycle-report.d.ts +4 -2
- package/dist/output/settings-report.d.ts +4 -4
- package/dist/repo/detector.d.ts +8 -0
- package/dist/{shared/repo-detector.js → repo/detector.js} +1 -4
- package/dist/repo/index.d.ts +4 -0
- package/dist/repo/index.js +3 -0
- package/dist/{shared/repo-metadata-provider.d.ts → repo/metadata-provider.d.ts} +3 -3
- package/dist/{shared/repo-metadata-provider.js → repo/metadata-provider.js} +3 -3
- package/dist/{shared/repo-detector.d.ts → repo/types.d.ts} +1 -7
- package/dist/repo/types.js +1 -0
- package/dist/{shared/repo-info-utils.d.ts → repo/utils.d.ts} +1 -1
- package/dist/{shared/repo-info-utils.js → repo/utils.js} +1 -1
- package/dist/settings/base-processor.d.ts +1 -1
- package/dist/settings/base-processor.js +1 -1
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +1 -1
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +1 -1
- package/dist/settings/code-scanning/processor.d.ts +2 -2
- package/dist/settings/code-scanning/types.d.ts +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/labels/formatter.js +16 -11
- package/dist/settings/labels/github-labels-strategy.d.ts +1 -1
- package/dist/settings/labels/github-labels-strategy.js +1 -1
- package/dist/settings/labels/processor.d.ts +1 -1
- package/dist/settings/labels/types.d.ts +1 -1
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +1 -1
- package/dist/settings/repo-settings/formatter.js +2 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +4 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +4 -4
- package/dist/settings/repo-settings/processor.d.ts +2 -2
- package/dist/settings/repo-settings/processor.js +5 -5
- package/dist/settings/repo-settings/types.d.ts +4 -4
- package/dist/settings/rulesets/diff-algorithm.js +1 -1
- package/dist/settings/rulesets/formatter.js +0 -3
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +1 -1
- package/dist/settings/rulesets/github-ruleset-strategy.js +1 -1
- package/dist/settings/rulesets/processor.d.ts +1 -1
- package/dist/settings/rulesets/types.d.ts +1 -1
- package/dist/shared/command-executor.js +3 -3
- package/dist/shared/gh-api-utils.d.ts +7 -4
- package/dist/shared/gh-api-utils.js +2 -2
- package/dist/shared/retry-utils.js +1 -1
- package/dist/shared/xfg-template.d.ts +22 -2
- package/dist/sync/auth-options-builder.d.ts +1 -1
- package/dist/sync/auth-options-builder.js +1 -1
- package/dist/sync/branch-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.d.ts +2 -2
- package/dist/sync/commit-push-manager.js +5 -3
- package/dist/sync/file-sync-orchestrator.d.ts +1 -1
- package/dist/sync/file-sync-strategy.d.ts +1 -1
- package/dist/sync/file-writer.js +44 -10
- package/dist/sync/repository-processor.d.ts +1 -1
- package/dist/sync/repository-session.d.ts +1 -1
- package/dist/sync/sync-workflow.d.ts +1 -1
- package/dist/sync/sync-workflow.js +2 -1
- package/dist/sync/types.d.ts +7 -4
- package/dist/vcs/{azure-pr-strategy.d.ts → ado-pr-strategy.d.ts} +2 -2
- package/dist/vcs/{azure-pr-strategy.js → ado-pr-strategy.js} +4 -4
- package/dist/vcs/authenticated-git-ops.d.ts +2 -0
- package/dist/vcs/authenticated-git-ops.js +6 -0
- package/dist/vcs/commit-strategy-selector.d.ts +1 -1
- package/dist/vcs/commit-strategy-selector.js +1 -1
- package/dist/vcs/file-mode-fixup-commit-strategy.d.ts +8 -6
- package/dist/vcs/file-mode-fixup-commit-strategy.js +79 -30
- package/dist/vcs/git-ops.d.ts +15 -3
- package/dist/vcs/git-ops.js +57 -24
- package/dist/vcs/github-app-token-manager.d.ts +1 -1
- package/dist/vcs/github-pr-strategy.d.ts +1 -1
- package/dist/vcs/github-pr-strategy.js +4 -4
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +4 -4
- package/dist/vcs/graphql-commit-strategy.js +8 -3
- package/dist/vcs/index.d.ts +1 -1
- package/dist/vcs/pr-creator.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +3 -3
- package/dist/vcs/pr-strategy.d.ts +1 -1
- package/dist/vcs/pr-strategy.js +1 -1
- package/dist/vcs/types.d.ts +10 -3
- package/package.json +2 -2
- /package/dist/{shared → vcs}/sanitize-utils.d.ts +0 -0
- /package/dist/{shared → vcs}/sanitize-utils.js +0 -0
package/dist/vcs/git-ops.js
CHANGED
|
@@ -4,18 +4,18 @@ import { escapeShellArg } from "../shared/shell-utils.js";
|
|
|
4
4
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
5
5
|
import { ValidationError, SyncError } from "../shared/errors.js";
|
|
6
6
|
export class GitOps {
|
|
7
|
-
|
|
7
|
+
workDir;
|
|
8
8
|
dryRun;
|
|
9
|
-
|
|
9
|
+
executor;
|
|
10
10
|
log;
|
|
11
11
|
constructor(options) {
|
|
12
|
-
this.
|
|
12
|
+
this.workDir = options.workDir;
|
|
13
13
|
this.dryRun = options.dryRun ?? false;
|
|
14
|
-
this.
|
|
14
|
+
this.executor = options.executor;
|
|
15
15
|
this.log = options.log;
|
|
16
16
|
}
|
|
17
17
|
exec(command, cwd) {
|
|
18
|
-
return this.
|
|
18
|
+
return this.executor.exec(command, cwd ?? this.workDir);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Validates that a file path doesn't escape the workspace directory.
|
|
@@ -23,9 +23,9 @@ export class GitOps {
|
|
|
23
23
|
* @throws ValidationError if path traversal is detected
|
|
24
24
|
*/
|
|
25
25
|
validatePath(fileName) {
|
|
26
|
-
const filePath = join(this.
|
|
26
|
+
const filePath = join(this.workDir, fileName);
|
|
27
27
|
const resolvedPath = resolve(filePath);
|
|
28
|
-
const resolvedWorkDir = resolve(this.
|
|
28
|
+
const resolvedWorkDir = resolve(this.workDir);
|
|
29
29
|
const relativePath = relative(resolvedWorkDir, resolvedPath);
|
|
30
30
|
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
31
31
|
throw new ValidationError(`Path traversal detected: ${fileName}`);
|
|
@@ -34,13 +34,13 @@ export class GitOps {
|
|
|
34
34
|
}
|
|
35
35
|
cleanWorkspace() {
|
|
36
36
|
try {
|
|
37
|
-
if (existsSync(this.
|
|
38
|
-
rmSync(this.
|
|
37
|
+
if (existsSync(this.workDir)) {
|
|
38
|
+
rmSync(this.workDir, { recursive: true, force: true });
|
|
39
39
|
}
|
|
40
|
-
mkdirSync(this.
|
|
40
|
+
mkdirSync(this.workDir, { recursive: true });
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
throw new SyncError(`Failed to clean workspace '${this.
|
|
43
|
+
throw new SyncError(`Failed to clean workspace '${this.workDir}': ${toErrorMessage(error)}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
@@ -50,7 +50,7 @@ export class GitOps {
|
|
|
50
50
|
*/
|
|
51
51
|
async createBranch(branchName) {
|
|
52
52
|
try {
|
|
53
|
-
await this.exec(`git checkout -b ${escapeShellArg(branchName)}`, this.
|
|
53
|
+
await this.exec(`git checkout -b ${escapeShellArg(branchName)}`, this.workDir);
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
56
|
const message = toErrorMessage(error);
|
|
@@ -89,8 +89,41 @@ export class GitOps {
|
|
|
89
89
|
throw new SyncError(`Failed to set executable permissions on '${fileName}': ${toErrorMessage(error)}`);
|
|
90
90
|
}
|
|
91
91
|
// Also update git's index so the executable bit is committed
|
|
92
|
-
const relativePath = relative(this.
|
|
93
|
-
await this.exec(`git update-index --add --chmod=+x ${escapeShellArg(relativePath)}`, this.
|
|
92
|
+
const relativePath = relative(this.workDir, filePath);
|
|
93
|
+
await this.exec(`git update-index --add --chmod=+x ${escapeShellArg(relativePath)}`, this.workDir);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Clears the executable bit on a file both on the filesystem and in git's index.
|
|
97
|
+
* Symmetric inverse of setExecutable.
|
|
98
|
+
* @param fileName - The file path relative to the work directory
|
|
99
|
+
*/
|
|
100
|
+
async clearExecutable(fileName) {
|
|
101
|
+
if (this.dryRun)
|
|
102
|
+
return;
|
|
103
|
+
const filePath = this.validatePath(fileName);
|
|
104
|
+
try {
|
|
105
|
+
chmodSync(filePath, 0o644);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new SyncError(`Failed to clear executable permissions on '${fileName}': ${toErrorMessage(error)}`);
|
|
109
|
+
}
|
|
110
|
+
await this.exec(`git update-index --chmod=-x -- ${escapeShellArg(fileName)}`, this.workDir);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Returns the git index mode for a tracked file ("100755" or "100644"),
|
|
114
|
+
* or null if the file is not tracked.
|
|
115
|
+
* @param fileName - The file path relative to the work directory
|
|
116
|
+
*/
|
|
117
|
+
async getFileMode(fileName) {
|
|
118
|
+
this.validatePath(fileName);
|
|
119
|
+
const output = await this.exec(`git ls-files -s -- ${escapeShellArg(fileName)}`, this.workDir);
|
|
120
|
+
const line = output.trim();
|
|
121
|
+
if (!line)
|
|
122
|
+
return null;
|
|
123
|
+
const mode = line.split(/\s+/, 1)[0];
|
|
124
|
+
if (mode === "100755" || mode === "100644")
|
|
125
|
+
return mode;
|
|
126
|
+
return null;
|
|
94
127
|
}
|
|
95
128
|
/**
|
|
96
129
|
* Get the content of a file in the workspace.
|
|
@@ -138,7 +171,7 @@ export class GitOps {
|
|
|
138
171
|
}
|
|
139
172
|
}
|
|
140
173
|
async hasChanges() {
|
|
141
|
-
const status = await this.exec("git status --porcelain", this.
|
|
174
|
+
const status = await this.exec("git status --porcelain", this.workDir);
|
|
142
175
|
return status.length > 0;
|
|
143
176
|
}
|
|
144
177
|
/**
|
|
@@ -146,7 +179,7 @@ export class GitOps {
|
|
|
146
179
|
* Returns relative file paths for files that are modified, added, or untracked.
|
|
147
180
|
*/
|
|
148
181
|
async getChangedFiles() {
|
|
149
|
-
const status = await this.exec("git status --porcelain", this.
|
|
182
|
+
const status = await this.exec("git status --porcelain", this.workDir);
|
|
150
183
|
if (!status)
|
|
151
184
|
return [];
|
|
152
185
|
return status
|
|
@@ -155,10 +188,10 @@ export class GitOps {
|
|
|
155
188
|
.map((line) => line.slice(3)); // Remove status prefix (e.g., " M ", "?? ", "A ")
|
|
156
189
|
}
|
|
157
190
|
async stageAll() {
|
|
158
|
-
await this.exec("git add -A", this.
|
|
191
|
+
await this.exec("git add -A", this.workDir);
|
|
159
192
|
}
|
|
160
193
|
async hasStagedChanges() {
|
|
161
|
-
const diff = await this.exec("git diff --cached --name-only", this.
|
|
194
|
+
const diff = await this.exec("git diff --cached --name-only", this.workDir);
|
|
162
195
|
return diff.length > 0;
|
|
163
196
|
}
|
|
164
197
|
/**
|
|
@@ -167,7 +200,7 @@ export class GitOps {
|
|
|
167
200
|
*/
|
|
168
201
|
async fileExistsOnBranch(fileName, branch) {
|
|
169
202
|
try {
|
|
170
|
-
await this.exec(`git show ${escapeShellArg(branch)}:${escapeShellArg(fileName)}`, this.
|
|
203
|
+
await this.exec(`git show ${escapeShellArg(branch)}:${escapeShellArg(fileName)}`, this.workDir);
|
|
171
204
|
return true;
|
|
172
205
|
}
|
|
173
206
|
catch (error) {
|
|
@@ -209,19 +242,19 @@ export class GitOps {
|
|
|
209
242
|
/**
|
|
210
243
|
* Stage all changes and commit with the given message.
|
|
211
244
|
* Uses --no-verify to skip pre-commit hooks (config sync should always succeed).
|
|
212
|
-
* @returns true if a commit was made
|
|
245
|
+
* @returns true if a commit was made, or false if there were no staged changes. In dry-run mode, always returns true without inspecting the working tree.
|
|
213
246
|
*/
|
|
214
247
|
async commit(message) {
|
|
215
248
|
if (this.dryRun) {
|
|
216
249
|
return true;
|
|
217
250
|
}
|
|
218
|
-
await this.exec("git add -A", this.
|
|
251
|
+
await this.exec("git add -A", this.workDir);
|
|
219
252
|
// Check if there are actually staged changes after git add
|
|
220
253
|
if (!(await this.hasStagedChanges())) {
|
|
221
254
|
return false; // No changes to commit
|
|
222
255
|
}
|
|
223
256
|
// Use --no-verify to skip pre-commit hooks
|
|
224
|
-
await this.exec(`git commit --no-verify -m ${escapeShellArg(message)}`, this.
|
|
257
|
+
await this.exec(`git commit --no-verify -m ${escapeShellArg(message)}`, this.workDir);
|
|
225
258
|
return true;
|
|
226
259
|
}
|
|
227
260
|
/**
|
|
@@ -230,7 +263,7 @@ export class GitOps {
|
|
|
230
263
|
*/
|
|
231
264
|
async getDefaultBranchLocal() {
|
|
232
265
|
try {
|
|
233
|
-
await this.exec("git rev-parse --verify origin/main", this.
|
|
266
|
+
await this.exec("git rev-parse --verify origin/main", this.workDir);
|
|
234
267
|
return { branch: "main", method: "origin/main exists" };
|
|
235
268
|
}
|
|
236
269
|
catch (error) {
|
|
@@ -238,7 +271,7 @@ export class GitOps {
|
|
|
238
271
|
this.log?.debug(`origin/main check failed - ${msg}`);
|
|
239
272
|
}
|
|
240
273
|
try {
|
|
241
|
-
await this.exec("git rev-parse --verify origin/master", this.
|
|
274
|
+
await this.exec("git rev-parse --verify origin/master", this.workDir);
|
|
242
275
|
return { branch: "master", method: "origin/master exists" };
|
|
243
276
|
}
|
|
244
277
|
catch (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GitHubRepoInfo } from "../
|
|
1
|
+
import type { GitHubRepoInfo } from "../repo/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Manages GitHub App authentication tokens for multiple organizations.
|
|
4
4
|
* Handles JWT generation, installation discovery, and token caching.
|
|
@@ -2,7 +2,7 @@ import type { PRResult } from "./types.js";
|
|
|
2
2
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
3
3
|
import type { PRStrategyOptions, CloseExistingPROptions, MergeOptions, MergeResult } from "./types.js";
|
|
4
4
|
export declare class GitHubPRStrategy extends BasePRStrategy {
|
|
5
|
-
|
|
5
|
+
findExistingPRUrl(options: CloseExistingPROptions): Promise<string | null>;
|
|
6
6
|
closeExistingPR(options: CloseExistingPROptions): Promise<boolean>;
|
|
7
7
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
8
8
|
/**
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { escapeShellArg, escapeRegExp } from "../shared/shell-utils.js";
|
|
4
|
-
import { assertGitHubRepo
|
|
4
|
+
import { assertGitHubRepo } from "../repo/index.js";
|
|
5
5
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
6
6
|
import { withRetry, isPermanentError } from "../shared/retry-utils.js";
|
|
7
|
-
import { sanitizeCredentials } from "
|
|
7
|
+
import { sanitizeCredentials } from "./sanitize-utils.js";
|
|
8
8
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
9
9
|
import { safeCleanup } from "../shared/cleanup-utils.js";
|
|
10
10
|
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
@@ -26,7 +26,7 @@ function buildPRUrlRegex(host) {
|
|
|
26
26
|
return new RegExp(`https://${escapedHost}/[\\w-]+/[\\w.-]+/pull/\\d+`);
|
|
27
27
|
}
|
|
28
28
|
export class GitHubPRStrategy extends BasePRStrategy {
|
|
29
|
-
async
|
|
29
|
+
async findExistingPRUrl(options) {
|
|
30
30
|
const { repoInfo, branchName, workDir, retries = 3, token } = options;
|
|
31
31
|
assertGitHubRepo(repoInfo, "GitHub PR strategy");
|
|
32
32
|
const repoFlag = getRepoFlag(repoInfo);
|
|
@@ -51,7 +51,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
51
51
|
const { repoInfo, branchName, baseBranch, workDir, retries = 3, token, } = options;
|
|
52
52
|
assertGitHubRepo(repoInfo, "GitHub PR strategy");
|
|
53
53
|
// First check if there's an existing PR (pass token through)
|
|
54
|
-
const existingUrl = await this.
|
|
54
|
+
const existingUrl = await this.findExistingPRUrl({
|
|
55
55
|
repoInfo,
|
|
56
56
|
branchName,
|
|
57
57
|
baseBranch,
|
|
@@ -22,7 +22,7 @@ export declare class GitLabPRStrategy extends BasePRStrategy {
|
|
|
22
22
|
* Build merge strategy flags for glab mr merge command.
|
|
23
23
|
*/
|
|
24
24
|
private getMergeStrategyFlag;
|
|
25
|
-
|
|
25
|
+
findExistingPRUrl(options: CloseExistingPROptions): Promise<string | null>;
|
|
26
26
|
closeExistingPR(options: CloseExistingPROptions): Promise<boolean>;
|
|
27
27
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
28
28
|
merge(options: MergeOptions): Promise<MergeResult>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
|
-
import { assertGitLabRepo
|
|
4
|
+
import { assertGitLabRepo } from "../repo/index.js";
|
|
5
5
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
6
6
|
import { withRetry, isPermanentError } from "../shared/retry-utils.js";
|
|
7
7
|
import { getStderr, } from "../shared/command-executor.js";
|
|
8
8
|
import { parseApiJson } from "../shared/json-utils.js";
|
|
9
|
-
import { sanitizeCredentials } from "
|
|
9
|
+
import { sanitizeCredentials } from "./sanitize-utils.js";
|
|
10
10
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
11
11
|
import { safeCleanup } from "../shared/cleanup-utils.js";
|
|
12
12
|
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
@@ -65,7 +65,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
65
65
|
return "";
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
async
|
|
68
|
+
async findExistingPRUrl(options) {
|
|
69
69
|
const { repoInfo, branchName, workDir, retries = 3 } = options;
|
|
70
70
|
assertGitLabRepo(repoInfo, "GitLab PR strategy");
|
|
71
71
|
const repoFlag = this.getRepoFlag(repoInfo);
|
|
@@ -98,7 +98,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
|
|
|
98
98
|
const { repoInfo, branchName, baseBranch, workDir, retries = 3 } = options;
|
|
99
99
|
assertGitLabRepo(repoInfo, "GitLab PR strategy");
|
|
100
100
|
// First check if there's an existing MR
|
|
101
|
-
const existingUrl = await this.
|
|
101
|
+
const existingUrl = await this.findExistingPRUrl({
|
|
102
102
|
repoInfo,
|
|
103
103
|
branchName,
|
|
104
104
|
baseBranch,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isGitHubRepo } from "../
|
|
1
|
+
import { isGitHubRepo } from "../repo/index.js";
|
|
2
2
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
3
3
|
import { withRetry, CORE_PERMANENT_ERROR_PATTERNS, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "../shared/retry-utils.js";
|
|
4
4
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
@@ -79,8 +79,13 @@ export class GraphQLCommitStrategy {
|
|
|
79
79
|
throw new ValidationError(`GraphQL commit strategy requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
80
80
|
}
|
|
81
81
|
validateSafeBranchName(branchName);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
82
|
+
const contentFileChanges = fileChanges.filter((fc) => !fc.modeOnly);
|
|
83
|
+
const additions = contentFileChanges.filter((fc) => fc.content !== null);
|
|
84
|
+
const deletions = contentFileChanges.filter((fc) => fc.content === null);
|
|
85
|
+
if (additions.length === 0 && deletions.length === 0) {
|
|
86
|
+
throw new GraphQLApiError("GraphQLCommitStrategy: no content changes to commit. " +
|
|
87
|
+
"This strategy should not be invoked when all file changes are modeOnly.");
|
|
88
|
+
}
|
|
84
89
|
// Base64 encoding adds ~33% overhead to raw content size
|
|
85
90
|
const totalSize = additions.reduce((sum, fc) => {
|
|
86
91
|
const base64Size = Math.ceil((fc.content.length * 4) / 3);
|
package/dist/vcs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { PRMergeConfig, FileChange, FileAction, IGitOps, ILocalGitOps, IPRStrategy, GitAuthOptions, PRResult, ICommitStrategy, } from "./types.js";
|
|
1
|
+
export type { PRMergeConfig, FileChange, FileAction, FileActionKind, IGitOps, ILocalGitOps, IPRStrategy, GitAuthOptions, PRResult, ICommitStrategy, } from "./types.js";
|
|
2
2
|
export type { GitOpsOptions } from "./git-ops.js";
|
|
3
3
|
export { createCommitStrategy, createTokenManager, } from "./commit-strategy-selector.js";
|
|
4
4
|
export { FileModeFixupCommitStrategy } from "./file-mode-fixup-commit-strategy.js";
|
package/dist/vcs/pr-creator.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RepoInfo } from "../
|
|
1
|
+
import type { RepoInfo } from "../repo/index.js";
|
|
2
2
|
import type { IPRStrategyLogger } from "./pr-strategy.js";
|
|
3
3
|
import type { FileAction, IPRStrategy, MergeResult, PRMergeConfig, PRResult } from "./types.js";
|
|
4
4
|
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RepoInfo } from "../
|
|
1
|
+
import { type RepoInfo } from "../repo/index.js";
|
|
2
2
|
import type { IPRStrategy } from "./types.js";
|
|
3
3
|
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
4
4
|
import type { IPRStrategyLogger } from "./pr-strategy.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, } from "../
|
|
1
|
+
import { isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, } from "../repo/index.js";
|
|
2
2
|
import { SyncError } from "../shared/errors.js";
|
|
3
3
|
import { GitHubPRStrategy } from "./github-pr-strategy.js";
|
|
4
|
-
import {
|
|
4
|
+
import { AdoPRStrategy } from "./ado-pr-strategy.js";
|
|
5
5
|
import { GitLabPRStrategy } from "./gitlab-pr-strategy.js";
|
|
6
6
|
export function createPRStrategy(repoInfo, executor, log) {
|
|
7
7
|
if (isGitHubRepo(repoInfo)) {
|
|
8
8
|
return new GitHubPRStrategy(executor, log);
|
|
9
9
|
}
|
|
10
10
|
if (isAzureDevOpsRepo(repoInfo)) {
|
|
11
|
-
return new
|
|
11
|
+
return new AdoPRStrategy(executor, log);
|
|
12
12
|
}
|
|
13
13
|
if (isGitLabRepo(repoInfo)) {
|
|
14
14
|
return new GitLabPRStrategy(executor, log);
|
|
@@ -12,7 +12,7 @@ export declare abstract class BasePRStrategy implements IPRStrategy {
|
|
|
12
12
|
protected executor: ICommandExecutor;
|
|
13
13
|
protected log?: IPRStrategyLogger;
|
|
14
14
|
constructor(executor: ICommandExecutor, log?: IPRStrategyLogger);
|
|
15
|
-
abstract
|
|
15
|
+
abstract findExistingPRUrl(options: CloseExistingPROptions): Promise<string | null>;
|
|
16
16
|
abstract closeExistingPR(options: CloseExistingPROptions): Promise<boolean>;
|
|
17
17
|
abstract create(options: PRStrategyOptions): Promise<PRResult>;
|
|
18
18
|
abstract merge(options: MergeOptions): Promise<MergeResult>;
|
package/dist/vcs/pr-strategy.js
CHANGED
|
@@ -30,7 +30,7 @@ export class PRWorkflowExecutor {
|
|
|
30
30
|
}
|
|
31
31
|
async execute(options) {
|
|
32
32
|
try {
|
|
33
|
-
const existingUrl = await this.strategy.
|
|
33
|
+
const existingUrl = await this.strategy.findExistingPRUrl(options);
|
|
34
34
|
if (existingUrl) {
|
|
35
35
|
return {
|
|
36
36
|
url: existingUrl,
|
package/dist/vcs/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RepoInfo } from "../
|
|
1
|
+
import type { RepoInfo } from "../repo/index.js";
|
|
2
2
|
import type { MergeMode, MergeStrategy } from "../config/index.js";
|
|
3
3
|
export interface GitAuthOptions {
|
|
4
4
|
token: string;
|
|
@@ -16,6 +16,8 @@ export interface ILocalGitOps {
|
|
|
16
16
|
createBranch(branchName: string): Promise<void>;
|
|
17
17
|
writeFile(fileName: string, content: string): void;
|
|
18
18
|
setExecutable(fileName: string): Promise<void>;
|
|
19
|
+
clearExecutable(fileName: string): Promise<void>;
|
|
20
|
+
getFileMode(fileName: string): Promise<"100755" | "100644" | null>;
|
|
19
21
|
getFileContent(fileName: string): string | null;
|
|
20
22
|
wouldChange(fileName: string, content: string): boolean;
|
|
21
23
|
hasChanges(): Promise<boolean>;
|
|
@@ -109,7 +111,7 @@ export interface CloseExistingPROptions {
|
|
|
109
111
|
}
|
|
110
112
|
/**
|
|
111
113
|
* Interface for PR creation strategies (platform-specific implementations).
|
|
112
|
-
* Strategies focus on platform-specific logic (
|
|
114
|
+
* Strategies focus on platform-specific logic (findExistingPRUrl, create, merge).
|
|
113
115
|
* Use PRWorkflowExecutor for full workflow orchestration with error handling.
|
|
114
116
|
*
|
|
115
117
|
* Error contract: create() and merge() may throw on infrastructure failures
|
|
@@ -122,7 +124,7 @@ export interface IPRStrategy {
|
|
|
122
124
|
* Check if a PR already exists for the given branch.
|
|
123
125
|
* @returns PR URL if exists, null if not found or on error
|
|
124
126
|
*/
|
|
125
|
-
|
|
127
|
+
findExistingPRUrl(options: CloseExistingPROptions): Promise<string | null>;
|
|
126
128
|
/**
|
|
127
129
|
* Close an existing PR and delete its branch.
|
|
128
130
|
* @returns true if PR was closed, false if no PR existed
|
|
@@ -143,16 +145,21 @@ export interface FileAction {
|
|
|
143
145
|
fileName: string;
|
|
144
146
|
action: "create" | "update" | "skip" | "delete";
|
|
145
147
|
}
|
|
148
|
+
export type FileActionKind = FileAction["action"];
|
|
146
149
|
export interface FileChange {
|
|
147
150
|
path: string;
|
|
148
151
|
content: string | null;
|
|
149
152
|
/** Git file mode. Only set for executable files ("100755"). "100644" is included
|
|
150
153
|
* in the union for type completeness — non-executable files omit this field. */
|
|
151
154
|
mode?: "100755" | "100644";
|
|
155
|
+
/** True when this entry represents a mode change only (no content diff).
|
|
156
|
+
* GraphQL strategies should skip these; FileModeFixupCommitStrategy handles them. */
|
|
157
|
+
modeOnly?: true;
|
|
152
158
|
}
|
|
153
159
|
export interface CommitOptions {
|
|
154
160
|
repoInfo: RepoInfo;
|
|
155
161
|
branchName: string;
|
|
162
|
+
baseBranch?: string;
|
|
156
163
|
message: string;
|
|
157
164
|
fileChanges: FileChange[];
|
|
158
165
|
workDir: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspruyt/xfg",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab — declaratively, from a single YAML config",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"url": "https://github.com/anthony-spruyt/xfg/issues"
|
|
30
30
|
},
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
32
|
+
"node": ">=20"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc",
|
|
File without changes
|
|
File without changes
|