@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RepoConfig } from "../config/types.js";
|
|
2
2
|
import { RepoInfo } from "../shared/repo-detector.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { DebugInfoLog } from "../shared/logger.js";
|
|
4
4
|
import type { ISyncWorkflow, IWorkStrategy, IAuthOptionsBuilder, IRepositorySession, IBranchManager, ICommitPushManager, IPRMergeHandler, ProcessorOptions, ProcessorResult } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* Orchestrates the common sync workflow steps.
|
|
@@ -13,6 +13,6 @@ export declare class SyncWorkflow implements ISyncWorkflow {
|
|
|
13
13
|
private readonly commitPushManager;
|
|
14
14
|
private readonly prMergeHandler;
|
|
15
15
|
private readonly log;
|
|
16
|
-
constructor(authOptionsBuilder: IAuthOptionsBuilder, repositorySession: IRepositorySession, branchManager: IBranchManager, commitPushManager: ICommitPushManager, prMergeHandler: IPRMergeHandler, log:
|
|
16
|
+
constructor(authOptionsBuilder: IAuthOptionsBuilder, repositorySession: IRepositorySession, branchManager: IBranchManager, commitPushManager: ICommitPushManager, prMergeHandler: IPRMergeHandler, log: DebugInfoLog);
|
|
17
17
|
execute(repoConfig: RepoConfig, repoInfo: RepoInfo, options: ProcessorOptions, workStrategy: IWorkStrategy): Promise<ProcessorResult>;
|
|
18
18
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getRepoDisplayName } from "../shared/repo-detector.js";
|
|
2
2
|
import { safeCleanup } from "../shared/type-guards.js";
|
|
3
|
-
import { defaultExecutor } from "../shared/command-executor.js";
|
|
4
3
|
/**
|
|
5
4
|
* Orchestrates the common sync workflow steps.
|
|
6
5
|
* Used by RepositoryProcessor with different strategies for file sync vs manifest.
|
|
@@ -22,35 +21,36 @@ export class SyncWorkflow {
|
|
|
22
21
|
}
|
|
23
22
|
async execute(repoConfig, repoInfo, options, workStrategy) {
|
|
24
23
|
const repoName = getRepoDisplayName(repoInfo);
|
|
25
|
-
const { branchName
|
|
26
|
-
const dryRun = options.dryRun ?? false;
|
|
27
|
-
const retries = options.retries ?? 3;
|
|
28
|
-
const executor = options.executor ?? defaultExecutor;
|
|
24
|
+
const { branchName } = options;
|
|
29
25
|
const authResult = await this.authOptionsBuilder.resolve(repoInfo, repoName, options.token);
|
|
30
26
|
if (!authResult.ok) {
|
|
31
27
|
return authResult.skipResult;
|
|
32
28
|
}
|
|
33
29
|
const mergeMode = repoConfig.prOptions?.merge ?? "auto";
|
|
34
30
|
const isDirectMode = mergeMode === "direct";
|
|
31
|
+
const runCtx = {
|
|
32
|
+
workDir: options.workDir,
|
|
33
|
+
dryRun: options.dryRun ?? false,
|
|
34
|
+
retries: options.retries ?? 3,
|
|
35
|
+
token: authResult.token,
|
|
36
|
+
executor: options.executor,
|
|
37
|
+
};
|
|
35
38
|
let session = null;
|
|
36
39
|
try {
|
|
37
40
|
session = await this.repositorySession.setup(repoInfo, {
|
|
38
|
-
workDir,
|
|
39
|
-
dryRun,
|
|
40
|
-
retries,
|
|
41
|
+
workDir: runCtx.workDir,
|
|
42
|
+
dryRun: runCtx.dryRun,
|
|
43
|
+
retries: runCtx.retries,
|
|
44
|
+
executor: runCtx.executor,
|
|
41
45
|
authOptions: authResult.authOptions,
|
|
42
46
|
});
|
|
43
47
|
await this.branchManager.setupBranch({
|
|
48
|
+
...runCtx,
|
|
44
49
|
repoInfo,
|
|
45
50
|
branchName,
|
|
46
51
|
baseBranch: session.baseBranch,
|
|
47
|
-
workDir,
|
|
48
52
|
isDirectMode,
|
|
49
|
-
dryRun,
|
|
50
|
-
retries,
|
|
51
|
-
token: authResult.token,
|
|
52
53
|
gitOps: session.gitOps,
|
|
53
|
-
executor,
|
|
54
54
|
});
|
|
55
55
|
const workResult = await workStrategy.execute(repoConfig, repoInfo, session, options);
|
|
56
56
|
if (!workResult) {
|
|
@@ -63,17 +63,14 @@ export class SyncWorkflow {
|
|
|
63
63
|
}
|
|
64
64
|
const pushBranch = isDirectMode ? session.baseBranch : branchName;
|
|
65
65
|
const commitResult = await this.commitPushManager.commitAndPush({
|
|
66
|
+
...runCtx,
|
|
66
67
|
repoInfo,
|
|
67
68
|
gitOps: session.gitOps,
|
|
68
|
-
workDir,
|
|
69
69
|
fileChanges: workResult.fileChanges,
|
|
70
70
|
commitMessage: workResult.commitMessage,
|
|
71
71
|
pushBranch,
|
|
72
72
|
isDirectMode,
|
|
73
|
-
|
|
74
|
-
retries,
|
|
75
|
-
token: authResult.token,
|
|
76
|
-
executor,
|
|
73
|
+
hasAppCredentials: options.hasAppCredentials,
|
|
77
74
|
});
|
|
78
75
|
if (!commitResult.success) {
|
|
79
76
|
return commitResult.errorResult;
|
|
@@ -102,14 +99,10 @@ export class SyncWorkflow {
|
|
|
102
99
|
repoInfo,
|
|
103
100
|
repoConfig,
|
|
104
101
|
options: {
|
|
102
|
+
...runCtx,
|
|
105
103
|
branchName,
|
|
106
104
|
baseBranch: session.baseBranch,
|
|
107
|
-
workDir,
|
|
108
|
-
dryRun,
|
|
109
|
-
retries,
|
|
110
105
|
prTemplate: options.prTemplate,
|
|
111
|
-
token: authResult.token,
|
|
112
|
-
executor,
|
|
113
106
|
},
|
|
114
107
|
changedFiles: workResult.changedFiles,
|
|
115
108
|
repoName,
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { FileContent, RepoConfig } from "../config/types.js";
|
|
2
2
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
3
|
-
import type { ILocalGitOps, IGitOps, GitAuthOptions } from "../vcs/
|
|
3
|
+
import type { ILocalGitOps, IGitOps, GitAuthOptions } from "../vcs/types.js";
|
|
4
4
|
import type { GitOpsOptions } from "../vcs/git-ops.js";
|
|
5
5
|
import type { DiffStats } from "./diff-utils.js";
|
|
6
6
|
import type { ILogger } from "../shared/logger.js";
|
|
7
7
|
import type { XfgManifest } from "./manifest.js";
|
|
8
8
|
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
9
|
-
import type { FileAction } from "../vcs/
|
|
9
|
+
import type { FileAction } from "../vcs/types.js";
|
|
10
10
|
export type GitOpsFactory = (options: GitOpsOptions, auth?: GitAuthOptions, retries?: number) => IGitOps;
|
|
11
11
|
export interface FileWriteResult {
|
|
12
12
|
fileName: string;
|
|
@@ -21,7 +21,7 @@ export interface FileWriteContext {
|
|
|
21
21
|
noDelete: boolean;
|
|
22
22
|
configId: string;
|
|
23
23
|
/** True when using GraphQL commit strategy (GitHub App) which cannot set file modes */
|
|
24
|
-
|
|
24
|
+
hasAppCredentials?: boolean;
|
|
25
25
|
}
|
|
26
26
|
export interface FileWriterDeps {
|
|
27
27
|
gitOps: ILocalGitOps;
|
|
@@ -52,18 +52,21 @@ export interface IManifestManager {
|
|
|
52
52
|
deleteOrphans(filesToDelete: string[], options: OrphanDeleteOptions, deps: OrphanDeleteDeps): Promise<void>;
|
|
53
53
|
saveUpdatedManifest(workDir: string, manifest: XfgManifest, existingManifest: XfgManifest | null, dryRun: boolean, fileChanges: Map<string, FileWriteResult>): void;
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
branchName: string;
|
|
58
|
-
baseBranch: string;
|
|
55
|
+
/** Common runtime context shared across workflow step options bags. */
|
|
56
|
+
export interface RunContext {
|
|
59
57
|
workDir: string;
|
|
60
|
-
isDirectMode: boolean;
|
|
61
58
|
dryRun: boolean;
|
|
62
59
|
retries: number;
|
|
63
60
|
token?: string;
|
|
64
|
-
gitOps: IGitOps;
|
|
65
61
|
executor: ICommandExecutor;
|
|
66
62
|
}
|
|
63
|
+
export interface BranchSetupOptions extends RunContext {
|
|
64
|
+
repoInfo: RepoInfo;
|
|
65
|
+
branchName: string;
|
|
66
|
+
baseBranch: string;
|
|
67
|
+
isDirectMode: boolean;
|
|
68
|
+
gitOps: IGitOps;
|
|
69
|
+
}
|
|
67
70
|
export interface IBranchManager {
|
|
68
71
|
setupBranch(options: BranchSetupOptions): Promise<void>;
|
|
69
72
|
}
|
|
@@ -76,12 +79,13 @@ export type AuthResult = {
|
|
|
76
79
|
skipResult: ProcessorResult;
|
|
77
80
|
};
|
|
78
81
|
export interface IAuthOptionsBuilder {
|
|
79
|
-
resolve(repoInfo: RepoInfo, repoName: string,
|
|
82
|
+
resolve(repoInfo: RepoInfo, repoName: string, token?: string): Promise<AuthResult>;
|
|
80
83
|
}
|
|
81
84
|
export interface SessionOptions {
|
|
82
85
|
workDir: string;
|
|
83
86
|
dryRun: boolean;
|
|
84
87
|
retries: number;
|
|
88
|
+
executor: ICommandExecutor;
|
|
85
89
|
authOptions?: GitAuthOptions;
|
|
86
90
|
}
|
|
87
91
|
export interface SessionContext {
|
|
@@ -92,18 +96,14 @@ export interface SessionContext {
|
|
|
92
96
|
export interface IRepositorySession {
|
|
93
97
|
setup(repoInfo: RepoInfo, options: SessionOptions): Promise<SessionContext>;
|
|
94
98
|
}
|
|
95
|
-
export interface CommitPushOptions {
|
|
99
|
+
export interface CommitPushOptions extends RunContext {
|
|
96
100
|
repoInfo: RepoInfo;
|
|
97
101
|
gitOps: IGitOps;
|
|
98
|
-
workDir: string;
|
|
99
102
|
fileChanges: Map<string, FileWriteResult>;
|
|
100
103
|
commitMessage: string;
|
|
101
104
|
pushBranch: string;
|
|
102
105
|
isDirectMode: boolean;
|
|
103
|
-
|
|
104
|
-
retries: number;
|
|
105
|
-
token?: string;
|
|
106
|
-
executor: ICommandExecutor;
|
|
106
|
+
hasAppCredentials?: boolean;
|
|
107
107
|
}
|
|
108
108
|
export type CommitPushResult = {
|
|
109
109
|
success: true;
|
|
@@ -124,13 +124,13 @@ export interface ProcessorOptions {
|
|
|
124
124
|
configId: string;
|
|
125
125
|
dryRun?: boolean;
|
|
126
126
|
retries?: number;
|
|
127
|
-
executor
|
|
127
|
+
executor: ICommandExecutor;
|
|
128
128
|
prTemplate?: string;
|
|
129
129
|
noDelete?: boolean;
|
|
130
|
-
/**
|
|
130
|
+
/** GitHub token for authentication (resolved by caller) */
|
|
131
131
|
token?: string;
|
|
132
132
|
/** True when using GraphQL commit strategy (GitHub App) which cannot set file modes */
|
|
133
|
-
|
|
133
|
+
hasAppCredentials?: boolean;
|
|
134
134
|
}
|
|
135
135
|
export interface FileChangeDetail {
|
|
136
136
|
path: string;
|
|
@@ -162,15 +162,10 @@ export interface FileSyncResult {
|
|
|
162
162
|
export interface IFileSyncOrchestrator {
|
|
163
163
|
sync(repoConfig: RepoConfig, repoInfo: RepoInfo, session: SessionContext, options: ProcessorOptions): Promise<FileSyncResult>;
|
|
164
164
|
}
|
|
165
|
-
export interface PRHandlerOptions {
|
|
165
|
+
export interface PRHandlerOptions extends RunContext {
|
|
166
166
|
branchName: string;
|
|
167
167
|
baseBranch: string;
|
|
168
|
-
workDir: string;
|
|
169
|
-
dryRun: boolean;
|
|
170
|
-
retries: number;
|
|
171
168
|
prTemplate?: string;
|
|
172
|
-
token?: string;
|
|
173
|
-
executor: ICommandExecutor;
|
|
174
169
|
}
|
|
175
170
|
export interface CreateAndMergeInput {
|
|
176
171
|
repoInfo: RepoInfo;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
2
|
+
import type { DebugLog } from "../shared/logger.js";
|
|
2
3
|
import type { GitAuthOptions, ILocalGitOps, IGitOps } from "./types.js";
|
|
3
|
-
export type { GitAuthOptions, ILocalGitOps, INetworkGitOps, IGitOps, } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Adds authentication to network git operations and delegates local ops.
|
|
6
6
|
*
|
|
@@ -15,9 +15,7 @@ export declare class AuthenticatedGitOps implements IGitOps {
|
|
|
15
15
|
private readonly retries;
|
|
16
16
|
private readonly auth?;
|
|
17
17
|
private readonly log?;
|
|
18
|
-
constructor(localOps: ILocalGitOps, executor: ICommandExecutor, workDir: string, retries: number, auth?: GitAuthOptions | undefined, log?:
|
|
19
|
-
debug(msg: string): void;
|
|
20
|
-
} | undefined);
|
|
18
|
+
constructor(localOps: ILocalGitOps, executor: ICommandExecutor, workDir: string, retries: number, auth?: GitAuthOptions | undefined, log?: DebugLog | undefined);
|
|
21
19
|
private execWithRetry;
|
|
22
20
|
/**
|
|
23
21
|
* Build the authenticated remote URL.
|
|
@@ -31,6 +29,7 @@ export declare class AuthenticatedGitOps implements IGitOps {
|
|
|
31
29
|
wouldChange(fileName: string, content: string): boolean;
|
|
32
30
|
hasChanges(): Promise<boolean>;
|
|
33
31
|
getChangedFiles(): Promise<string[]>;
|
|
32
|
+
stageAll(): Promise<void>;
|
|
34
33
|
hasStagedChanges(): Promise<boolean>;
|
|
35
34
|
fileExistsOnBranch(fileName: string, branch: string): Promise<boolean>;
|
|
36
35
|
fileExists(fileName: string): boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
2
2
|
import { withRetry } from "../shared/retry-utils.js";
|
|
3
3
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
4
|
+
import { SyncError } from "../shared/errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* Adds authentication to network git operations and delegates local ops.
|
|
6
7
|
*
|
|
@@ -33,7 +34,7 @@ export class AuthenticatedGitOps {
|
|
|
33
34
|
*/
|
|
34
35
|
getAuthenticatedUrl() {
|
|
35
36
|
if (!this.auth) {
|
|
36
|
-
throw new
|
|
37
|
+
throw new SyncError("getAuthenticatedUrl() called without auth options");
|
|
37
38
|
}
|
|
38
39
|
const { token, host, owner, repo } = this.auth;
|
|
39
40
|
return `https://x-access-token:${token}@${host}/${owner}/${repo}`;
|
|
@@ -63,6 +64,9 @@ export class AuthenticatedGitOps {
|
|
|
63
64
|
getChangedFiles() {
|
|
64
65
|
return this.localOps.getChangedFiles();
|
|
65
66
|
}
|
|
67
|
+
stageAll() {
|
|
68
|
+
return this.localOps.stageAll();
|
|
69
|
+
}
|
|
66
70
|
hasStagedChanges() {
|
|
67
71
|
return this.localOps.hasStagedChanges();
|
|
68
72
|
}
|
|
@@ -4,9 +4,14 @@ 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 AzurePRStrategy extends BasePRStrategy {
|
|
7
|
-
constructor(executor
|
|
7
|
+
constructor(executor: ICommandExecutor, log?: IPRStrategyLogger);
|
|
8
8
|
private getOrgUrl;
|
|
9
9
|
private buildPRUrl;
|
|
10
|
+
/**
|
|
11
|
+
* Query Azure DevOps for an existing PR ID matching source/target branches.
|
|
12
|
+
* Returns the raw PR ID string, or null if none found.
|
|
13
|
+
*/
|
|
14
|
+
private findExistingPRId;
|
|
10
15
|
checkExistingPR(options: CloseExistingPROptions): Promise<string | null>;
|
|
11
16
|
closeExistingPR(options: CloseExistingPROptions): Promise<boolean>;
|
|
12
17
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
@@ -2,9 +2,11 @@ import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
4
|
import { assertAzureDevOpsRepo, } from "../shared/repo-detector.js";
|
|
5
|
+
import { SyncError } from "../shared/errors.js";
|
|
5
6
|
import { BasePRStrategy } from "./pr-strategy.js";
|
|
6
|
-
import { withRetry } from "../shared/retry-utils.js";
|
|
7
|
+
import { withRetry, isPermanentError } from "../shared/retry-utils.js";
|
|
7
8
|
import { toErrorMessage, safeCleanup } from "../shared/type-guards.js";
|
|
9
|
+
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
8
10
|
import { sanitizeCredentials } from "../shared/sanitize-utils.js";
|
|
9
11
|
import { getStderr } from "../shared/command-executor.js";
|
|
10
12
|
export class AzurePRStrategy extends BasePRStrategy {
|
|
@@ -18,17 +20,21 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
18
20
|
buildPRUrl(repoInfo, prId) {
|
|
19
21
|
return `https://dev.azure.com/${encodeURIComponent(repoInfo.organization)}/${encodeURIComponent(repoInfo.project)}/_git/${encodeURIComponent(repoInfo.repo)}/pullrequest/${prId.trim()}`;
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Query Azure DevOps for an existing PR ID matching source/target branches.
|
|
25
|
+
* Returns the raw PR ID string, or null if none found.
|
|
26
|
+
*/
|
|
27
|
+
async findExistingPRId(azureRepoInfo, branchName, baseBranch, workDir, retries) {
|
|
25
28
|
const orgUrl = this.getOrgUrl(azureRepoInfo);
|
|
26
29
|
const command = `az repos pr list --repository ${escapeShellArg(azureRepoInfo.repo)} --source-branch ${escapeShellArg(branchName)} --target-branch ${escapeShellArg(baseBranch)} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(azureRepoInfo.project)} --query "[0].pullRequestId" -o tsv`;
|
|
27
30
|
try {
|
|
28
|
-
const existingPRId = await withRetry(() => this.executor.exec(command, workDir), { retries });
|
|
29
|
-
return existingPRId ?
|
|
31
|
+
const existingPRId = await withRetry(() => this.executor.exec(command, workDir), { retries, log: this.log });
|
|
32
|
+
return existingPRId ? existingPRId.trim() : null;
|
|
30
33
|
}
|
|
31
34
|
catch (error) {
|
|
35
|
+
if (isPermanentError(error)) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
32
38
|
const stderr = getStderr(error);
|
|
33
39
|
if (stderr && !stderr.includes("does not exist")) {
|
|
34
40
|
this.log?.debug(`Azure PR check failed - ${sanitizeCredentials(stderr).trim()}`);
|
|
@@ -36,46 +42,41 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
36
42
|
return null;
|
|
37
43
|
}
|
|
38
44
|
}
|
|
45
|
+
async checkExistingPR(options) {
|
|
46
|
+
const { repoInfo, branchName, baseBranch, workDir, retries = 3 } = options;
|
|
47
|
+
assertAzureDevOpsRepo(repoInfo, "Azure PR strategy");
|
|
48
|
+
const azureRepoInfo = repoInfo;
|
|
49
|
+
const prId = await this.findExistingPRId(azureRepoInfo, branchName, baseBranch, workDir, retries);
|
|
50
|
+
return prId ? this.buildPRUrl(azureRepoInfo, prId) : null;
|
|
51
|
+
}
|
|
39
52
|
async closeExistingPR(options) {
|
|
40
53
|
const { repoInfo, branchName, baseBranch, workDir, retries = 3 } = options;
|
|
41
54
|
assertAzureDevOpsRepo(repoInfo, "Azure PR strategy");
|
|
42
55
|
const azureRepoInfo = repoInfo;
|
|
43
56
|
const orgUrl = this.getOrgUrl(azureRepoInfo);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
repoInfo,
|
|
47
|
-
branchName,
|
|
48
|
-
baseBranch,
|
|
49
|
-
workDir,
|
|
50
|
-
retries,
|
|
51
|
-
});
|
|
52
|
-
if (!existingUrl) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
// Extract PR ID from URL
|
|
56
|
-
const prInfo = this.parsePRUrl(existingUrl);
|
|
57
|
-
if (!prInfo) {
|
|
58
|
-
this.log?.warn(`Could not parse PR URL: ${existingUrl}`);
|
|
57
|
+
const prId = await this.findExistingPRId(azureRepoInfo, branchName, baseBranch, workDir, retries);
|
|
58
|
+
if (!prId) {
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
61
61
|
// Abandon the PR (Azure DevOps equivalent of closing)
|
|
62
|
-
const abandonCommand = `az repos pr update --id ${escapeShellArg(
|
|
62
|
+
const abandonCommand = `az repos pr update --id ${escapeShellArg(prId)} --status abandoned --org ${escapeShellArg(orgUrl)}`;
|
|
63
63
|
try {
|
|
64
64
|
await withRetry(() => this.executor.exec(abandonCommand, workDir), {
|
|
65
65
|
retries,
|
|
66
|
+
log: this.log,
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
catch (error) {
|
|
69
70
|
const message = toErrorMessage(error);
|
|
70
|
-
this.log?.warn(`Failed to abandon PR #${
|
|
71
|
+
this.log?.warn(`Failed to abandon PR #${prId}: ${message}`);
|
|
71
72
|
return false;
|
|
72
73
|
}
|
|
73
74
|
try {
|
|
74
75
|
const getRefCommand = `az repos ref list --repository ${escapeShellArg(azureRepoInfo.repo)} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(azureRepoInfo.project)} --filter heads/${escapeShellArg(branchName)} --query "[0].objectId" -o tsv`;
|
|
75
|
-
const objectId = await withRetry(() => this.executor.exec(getRefCommand, workDir), { retries });
|
|
76
|
+
const objectId = await withRetry(() => this.executor.exec(getRefCommand, workDir), { retries, log: this.log });
|
|
76
77
|
if (objectId) {
|
|
77
78
|
const deleteBranchCommand = `az repos ref delete --name refs/heads/${escapeShellArg(branchName)} --repository ${escapeShellArg(azureRepoInfo.repo)} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(azureRepoInfo.project)} --object-id ${escapeShellArg(objectId)}`;
|
|
78
|
-
await withRetry(() => this.executor.exec(deleteBranchCommand, workDir), { retries });
|
|
79
|
+
await withRetry(() => this.executor.exec(deleteBranchCommand, workDir), { retries, log: this.log });
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
catch (error) {
|
|
@@ -91,12 +92,18 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
91
92
|
const azureRepoInfo = repoInfo;
|
|
92
93
|
const orgUrl = this.getOrgUrl(azureRepoInfo);
|
|
93
94
|
const descFile = join(workDir, this.bodyFilePath);
|
|
94
|
-
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(descFile, body, "utf-8");
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new SyncError(`Failed to write PR description to ${descFile}: ${toErrorMessage(err)}`);
|
|
100
|
+
}
|
|
95
101
|
// Azure CLI @file syntax: escape the full @path to handle special chars in workDir
|
|
96
102
|
const command = `az repos pr create --repository ${escapeShellArg(azureRepoInfo.repo)} --source-branch ${escapeShellArg(branchName)} --target-branch ${escapeShellArg(baseBranch)} --title ${escapeShellArg(title)} --description ${escapeShellArg("@" + descFile)} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(azureRepoInfo.project)} --query "pullRequestId" -o tsv`;
|
|
97
103
|
try {
|
|
98
104
|
const prId = await withRetry(() => this.executor.exec(command, workDir), {
|
|
99
105
|
retries,
|
|
106
|
+
log: this.log,
|
|
100
107
|
});
|
|
101
108
|
return {
|
|
102
109
|
url: this.buildPRUrl(azureRepoInfo, prId),
|
|
@@ -108,7 +115,7 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
108
115
|
safeCleanup(() => {
|
|
109
116
|
if (existsSync(descFile))
|
|
110
117
|
unlinkSync(descFile);
|
|
111
|
-
}, `failed to remove ${descFile}`, this.log ??
|
|
118
|
+
}, `failed to remove ${descFile}`, this.log ?? NO_OP_DEBUG_LOG);
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
@@ -128,7 +135,6 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
128
135
|
}
|
|
129
136
|
async merge(options) {
|
|
130
137
|
const { prUrl, config, workDir, retries = 3 } = options;
|
|
131
|
-
// Manual mode: do nothing
|
|
132
138
|
if (config.mode === "manual") {
|
|
133
139
|
return {
|
|
134
140
|
success: true,
|
|
@@ -136,7 +142,6 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
136
142
|
merged: false,
|
|
137
143
|
};
|
|
138
144
|
}
|
|
139
|
-
// Parse PR URL to extract details
|
|
140
145
|
const prInfo = this.parsePRUrl(prUrl);
|
|
141
146
|
if (!prInfo) {
|
|
142
147
|
return {
|
|
@@ -161,6 +166,7 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
161
166
|
}
|
|
162
167
|
if (config.mode === "force") {
|
|
163
168
|
const bypassReason = config.bypassReason ?? "Automated config sync via xfg";
|
|
169
|
+
this.log?.warn(`Bypassing policies for PR ${prInfo.prId} (reason: ${bypassReason})`);
|
|
164
170
|
const forceCommand = `az repos pr update --id ${escapeShellArg(prInfo.prId)} --bypass-policy true --bypass-policy-reason ${escapeShellArg(bypassReason)} --status completed ${squashFlag} ${deleteBranchFlag} --org ${escapeShellArg(orgUrl)}`.trim();
|
|
165
171
|
return this.executeMergeCommand(() => this.executor.exec(forceCommand, workDir), retries, {
|
|
166
172
|
success: true,
|
|
@@ -168,9 +174,10 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
168
174
|
merged: true,
|
|
169
175
|
}, "Failed to bypass policies and complete PR");
|
|
170
176
|
}
|
|
177
|
+
const _exhaustive = config.mode;
|
|
171
178
|
return {
|
|
172
179
|
success: false,
|
|
173
|
-
message: `
|
|
180
|
+
message: `Merge not applicable for mode: ${_exhaustive}`,
|
|
174
181
|
merged: false,
|
|
175
182
|
};
|
|
176
183
|
}
|
|
@@ -2,26 +2,17 @@ import { RepoInfo } from "../shared/repo-detector.js";
|
|
|
2
2
|
import type { ICommitStrategy } from "./types.js";
|
|
3
3
|
import { GitHubAppTokenManager } from "./github-app-token-manager.js";
|
|
4
4
|
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
5
|
+
interface GitHubAppCredentials {
|
|
6
|
+
appId: string;
|
|
7
|
+
privateKey: string;
|
|
8
|
+
}
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Both XFG_GITHUB_APP_ID and XFG_GITHUB_APP_PRIVATE_KEY must be set.
|
|
10
|
+
* Creates a GitHubAppTokenManager from credentials, or null if not provided.
|
|
8
11
|
*/
|
|
9
|
-
export declare function
|
|
12
|
+
export declare function createTokenManager(credentials?: GitHubAppCredentials): GitHubAppTokenManager | null;
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* Returns GraphQLCommitStrategy for GitHub repos with App credentials (verified commits),
|
|
15
|
+
* or GitCommitStrategy for all other cases.
|
|
12
16
|
*/
|
|
13
|
-
export declare function
|
|
14
|
-
|
|
15
|
-
* Factory function to get the appropriate commit strategy for a repository.
|
|
16
|
-
*
|
|
17
|
-
* For GitHub repositories with GitHub App credentials (XFG_GITHUB_APP_ID and
|
|
18
|
-
* XFG_GITHUB_APP_PRIVATE_KEY), returns GraphQLCommitStrategy which creates
|
|
19
|
-
* verified commits via the GitHub GraphQL API.
|
|
20
|
-
*
|
|
21
|
-
* For all other cases (GitHub with PAT, Azure DevOps, GitLab), returns GitCommitStrategy
|
|
22
|
-
* which uses standard git commands.
|
|
23
|
-
*
|
|
24
|
-
* @param repoInfo - Repository information
|
|
25
|
-
* @param executor - Optional command executor for shell commands
|
|
26
|
-
*/
|
|
27
|
-
export declare function getCommitStrategy(repoInfo: RepoInfo, executor?: ICommandExecutor): ICommitStrategy;
|
|
17
|
+
export declare function getCommitStrategy(repoInfo: RepoInfo, executor: ICommandExecutor, hasAppCredentials?: boolean): ICommitStrategy;
|
|
18
|
+
export {};
|
|
@@ -3,36 +3,20 @@ import { GitCommitStrategy } from "./git-commit-strategy.js";
|
|
|
3
3
|
import { GraphQLCommitStrategy } from "./graphql-commit-strategy.js";
|
|
4
4
|
import { GitHubAppTokenManager } from "./github-app-token-manager.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Both XFG_GITHUB_APP_ID and XFG_GITHUB_APP_PRIVATE_KEY must be set.
|
|
6
|
+
* Creates a GitHubAppTokenManager from credentials, or null if not provided.
|
|
8
7
|
*/
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Creates a GitHubAppTokenManager if credentials are configured, otherwise null.
|
|
14
|
-
*/
|
|
15
|
-
export function createTokenManager() {
|
|
16
|
-
if (!hasGitHubAppCredentials()) {
|
|
8
|
+
export function createTokenManager(credentials) {
|
|
9
|
+
if (!credentials) {
|
|
17
10
|
return null;
|
|
18
11
|
}
|
|
19
|
-
return new GitHubAppTokenManager(
|
|
12
|
+
return new GitHubAppTokenManager(credentials.appId, credentials.privateKey);
|
|
20
13
|
}
|
|
21
14
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* For GitHub repositories with GitHub App credentials (XFG_GITHUB_APP_ID and
|
|
25
|
-
* XFG_GITHUB_APP_PRIVATE_KEY), returns GraphQLCommitStrategy which creates
|
|
26
|
-
* verified commits via the GitHub GraphQL API.
|
|
27
|
-
*
|
|
28
|
-
* For all other cases (GitHub with PAT, Azure DevOps, GitLab), returns GitCommitStrategy
|
|
29
|
-
* which uses standard git commands.
|
|
30
|
-
*
|
|
31
|
-
* @param repoInfo - Repository information
|
|
32
|
-
* @param executor - Optional command executor for shell commands
|
|
15
|
+
* Returns GraphQLCommitStrategy for GitHub repos with App credentials (verified commits),
|
|
16
|
+
* or GitCommitStrategy for all other cases.
|
|
33
17
|
*/
|
|
34
|
-
export function getCommitStrategy(repoInfo, executor) {
|
|
35
|
-
if (isGitHubRepo(repoInfo) &&
|
|
18
|
+
export function getCommitStrategy(repoInfo, executor, hasAppCredentials) {
|
|
19
|
+
if (isGitHubRepo(repoInfo) && hasAppCredentials) {
|
|
36
20
|
return new GraphQLCommitStrategy(executor);
|
|
37
21
|
}
|
|
38
22
|
return new GitCommitStrategy(executor);
|
|
@@ -7,7 +7,7 @@ import { ICommandExecutor } from "../shared/command-executor.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class GitCommitStrategy implements ICommitStrategy {
|
|
9
9
|
private executor;
|
|
10
|
-
constructor(executor
|
|
10
|
+
constructor(executor: ICommandExecutor);
|
|
11
11
|
/**
|
|
12
12
|
* Create a commit with the given file changes and push to remote.
|
|
13
13
|
* Runs: git add -A, git commit, git push (with optional --force-with-lease)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { defaultExecutor, } from "../shared/command-executor.js";
|
|
2
1
|
import { withRetry } from "../shared/retry-utils.js";
|
|
3
2
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
4
3
|
/**
|
|
@@ -9,7 +8,7 @@ import { escapeShellArg } from "../shared/shell-utils.js";
|
|
|
9
8
|
export class GitCommitStrategy {
|
|
10
9
|
executor;
|
|
11
10
|
constructor(executor) {
|
|
12
|
-
this.executor = executor
|
|
11
|
+
this.executor = executor;
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
15
14
|
* Create a commit with the given file changes and push to remote.
|
|
@@ -34,7 +33,6 @@ export class GitCommitStrategy {
|
|
|
34
33
|
retries,
|
|
35
34
|
});
|
|
36
35
|
}
|
|
37
|
-
// Get the commit SHA
|
|
38
36
|
const sha = await this.executor.exec("git rev-parse HEAD", workDir);
|
|
39
37
|
return {
|
|
40
38
|
sha: sha.trim(),
|
package/dist/vcs/git-ops.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
2
|
+
import type { DebugLog } from "../shared/logger.js";
|
|
2
3
|
import type { ILocalGitOps } from "./types.js";
|
|
3
4
|
export interface GitOpsOptions {
|
|
4
5
|
workDir: string;
|
|
5
6
|
dryRun?: boolean;
|
|
6
|
-
executor
|
|
7
|
+
executor: ICommandExecutor;
|
|
7
8
|
/** Optional logger for debug messages */
|
|
8
|
-
log?:
|
|
9
|
-
debug(msg: string): void;
|
|
10
|
-
};
|
|
9
|
+
log?: DebugLog;
|
|
11
10
|
}
|
|
12
11
|
export declare class GitOps implements ILocalGitOps {
|
|
13
12
|
private readonly _workDir;
|
|
@@ -54,10 +53,7 @@ export declare class GitOps implements ILocalGitOps {
|
|
|
54
53
|
* Uses the same this.exec() pattern as other methods in this class.
|
|
55
54
|
*/
|
|
56
55
|
getChangedFiles(): Promise<string[]>;
|
|
57
|
-
|
|
58
|
-
* Check if there are staged changes ready to commit.
|
|
59
|
-
* Uses `git diff --cached --quiet` which exits with 1 if there are staged changes.
|
|
60
|
-
*/
|
|
56
|
+
stageAll(): Promise<void>;
|
|
61
57
|
hasStagedChanges(): Promise<boolean>;
|
|
62
58
|
/**
|
|
63
59
|
* Check if a file exists on a specific branch.
|