merge-steward 0.8.12 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +4 -0
- package/dist/config.js +13 -0
- package/dist/github/shell-git.d.ts +9 -4
- package/dist/github/shell-git.js +37 -9
- package/dist/server.js +2 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export declare const stewardConfigSchema: z.ZodObject<{
|
|
|
31
31
|
admissionLabel: z.ZodDefault<z.ZodString>;
|
|
32
32
|
mergeQueueCheckName: z.ZodDefault<z.ZodString>;
|
|
33
33
|
excludeBranches: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
|
+
autoResolvePatterns: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
35
|
+
glob: z.ZodString;
|
|
36
|
+
command: z.ZodArray<z.ZodString>;
|
|
37
|
+
}, z.core.$strip>>>;
|
|
34
38
|
webhookSecret: z.ZodOptional<z.ZodString>;
|
|
35
39
|
}, z.core.$strip>;
|
|
36
40
|
export type StewardConfig = z.infer<typeof stewardConfigSchema>;
|
package/dist/config.js
CHANGED
|
@@ -33,6 +33,19 @@ export const stewardConfigSchema = z.object({
|
|
|
33
33
|
mergeQueueCheckName: z.string().default(DEFAULT_MERGE_QUEUE_CHECK_NAME),
|
|
34
34
|
/** Branch name patterns to exclude from admission (glob-style). */
|
|
35
35
|
excludeBranches: z.array(z.string()).default(["release-please--*"]),
|
|
36
|
+
/**
|
|
37
|
+
* File patterns with regen commands for auto-resolving merge conflicts.
|
|
38
|
+
* When all conflicting files match a pattern, the command is run to regenerate them.
|
|
39
|
+
* Defaults to lockfile resolution for npm/pnpm/yarn.
|
|
40
|
+
*/
|
|
41
|
+
autoResolvePatterns: z.array(z.object({
|
|
42
|
+
glob: z.string().min(1),
|
|
43
|
+
command: z.array(z.string()).min(1),
|
|
44
|
+
})).default([
|
|
45
|
+
{ glob: "**/package-lock.json", command: ["npm", "install", "--package-lock-only"] },
|
|
46
|
+
{ glob: "**/pnpm-lock.yaml", command: ["pnpm", "install", "--lockfile-only"] },
|
|
47
|
+
{ glob: "**/yarn.lock", command: ["yarn", "install", "--mode", "update-lockfile"] },
|
|
48
|
+
]),
|
|
36
49
|
webhookSecret: z.string().optional(),
|
|
37
50
|
});
|
|
38
51
|
function readEnvFile(filePath) {
|
|
@@ -4,14 +4,20 @@ export interface BotIdentity {
|
|
|
4
4
|
name: string;
|
|
5
5
|
email: string;
|
|
6
6
|
}
|
|
7
|
+
export interface AutoResolvePattern {
|
|
8
|
+
glob: string;
|
|
9
|
+
command: string[];
|
|
10
|
+
}
|
|
7
11
|
export declare class ShellGitOperations implements GitOperations, SpeculativeBranchBuilder {
|
|
8
12
|
private readonly clonePath;
|
|
9
13
|
private readonly repoFullName;
|
|
10
14
|
private readonly gitBin;
|
|
11
15
|
private readonly worktreeBase;
|
|
12
16
|
private botIdentity;
|
|
17
|
+
private autoResolvePatterns;
|
|
13
18
|
constructor(clonePath: string, repoFullName: string, gitBin?: string);
|
|
14
19
|
setBotIdentity(identity: BotIdentity): void;
|
|
20
|
+
setAutoResolvePatterns(patterns: AutoResolvePattern[]): void;
|
|
15
21
|
private git;
|
|
16
22
|
/** Run a git command in a specific directory (worktree). */
|
|
17
23
|
private gitIn;
|
|
@@ -27,10 +33,9 @@ export declare class ShellGitOperations implements GitOperations, SpeculativeBra
|
|
|
27
33
|
*/
|
|
28
34
|
buildSpeculative(prBranch: string, baseBranch: string, specName: string, mergeMessage?: string): Promise<MergeResult>;
|
|
29
35
|
/**
|
|
30
|
-
* During a merge conflict, check if
|
|
31
|
-
*
|
|
32
|
-
* package.json via `npm install --package-lock-only`.
|
|
36
|
+
* During a merge conflict, check if all unmerged files match a configured
|
|
37
|
+
* auto-resolve pattern. If so, regenerate them via the pattern's command.
|
|
33
38
|
*/
|
|
34
|
-
private
|
|
39
|
+
private tryAutoResolveConflict;
|
|
35
40
|
deleteSpeculative(specName: string): Promise<void>;
|
|
36
41
|
}
|
package/dist/github/shell-git.js
CHANGED
|
@@ -12,12 +12,34 @@ function parseConflicts(stderr) {
|
|
|
12
12
|
});
|
|
13
13
|
return files.length > 0 ? files : undefined;
|
|
14
14
|
}
|
|
15
|
+
/** Check if a file path matches a glob pattern (simple suffix/wildcard match). */
|
|
16
|
+
function matchesGlob(filePath, pattern) {
|
|
17
|
+
if (pattern.startsWith("**/")) {
|
|
18
|
+
return filePath.endsWith(pattern.slice(2));
|
|
19
|
+
}
|
|
20
|
+
if (pattern.startsWith("*/")) {
|
|
21
|
+
const name = filePath.split("/").pop() ?? filePath;
|
|
22
|
+
return name === pattern.slice(2);
|
|
23
|
+
}
|
|
24
|
+
return filePath === pattern || filePath.endsWith(`/${pattern}`);
|
|
25
|
+
}
|
|
26
|
+
/** If all conflicting files match a single auto-resolve pattern, return its command. */
|
|
27
|
+
function detectAutoResolveCommand(conflictFiles, patterns) {
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
if (conflictFiles.every((f) => matchesGlob(f, pattern.glob))) {
|
|
30
|
+
const [bin, ...args] = pattern.command;
|
|
31
|
+
return bin ? { bin, args } : undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
15
36
|
export class ShellGitOperations {
|
|
16
37
|
clonePath;
|
|
17
38
|
repoFullName;
|
|
18
39
|
gitBin;
|
|
19
40
|
worktreeBase;
|
|
20
41
|
botIdentity;
|
|
42
|
+
autoResolvePatterns = [];
|
|
21
43
|
constructor(clonePath, repoFullName, gitBin = "git") {
|
|
22
44
|
this.clonePath = clonePath;
|
|
23
45
|
this.repoFullName = repoFullName;
|
|
@@ -27,6 +49,9 @@ export class ShellGitOperations {
|
|
|
27
49
|
setBotIdentity(identity) {
|
|
28
50
|
this.botIdentity = identity;
|
|
29
51
|
}
|
|
52
|
+
setAutoResolvePatterns(patterns) {
|
|
53
|
+
this.autoResolvePatterns = patterns;
|
|
54
|
+
}
|
|
30
55
|
git(args, opts) {
|
|
31
56
|
return exec(this.gitBin, ["-C", this.clonePath, ...args], {
|
|
32
57
|
timeoutMs: opts?.timeoutMs ?? 120_000,
|
|
@@ -98,7 +123,7 @@ export class ShellGitOperations {
|
|
|
98
123
|
if (result.exitCode !== 0) {
|
|
99
124
|
const conflictFiles = parseConflicts(result.stderr);
|
|
100
125
|
// Lockfile-only conflicts can be auto-resolved by regenerating.
|
|
101
|
-
if (await this.
|
|
126
|
+
if (await this.tryAutoResolveConflict(wtPath)) {
|
|
102
127
|
const sha = (await this.gitIn(wtPath, ["rev-parse", "HEAD"])).stdout.trim();
|
|
103
128
|
await this.git(["worktree", "remove", "--force", wtPath], { allowNonZero: true });
|
|
104
129
|
return { success: true, sha };
|
|
@@ -114,25 +139,28 @@ export class ShellGitOperations {
|
|
|
114
139
|
return { success: true, sha };
|
|
115
140
|
}
|
|
116
141
|
/**
|
|
117
|
-
* During a merge conflict, check if
|
|
118
|
-
*
|
|
119
|
-
* package.json via `npm install --package-lock-only`.
|
|
142
|
+
* During a merge conflict, check if all unmerged files match a configured
|
|
143
|
+
* auto-resolve pattern. If so, regenerate them via the pattern's command.
|
|
120
144
|
*/
|
|
121
|
-
async
|
|
145
|
+
async tryAutoResolveConflict(wtPath) {
|
|
146
|
+
if (this.autoResolvePatterns.length === 0)
|
|
147
|
+
return false;
|
|
122
148
|
try {
|
|
123
149
|
const unmerged = await this.gitIn(wtPath, ["diff", "--name-only", "--diff-filter=U"]);
|
|
124
150
|
const files = unmerged.stdout.trim().split("\n").filter(Boolean);
|
|
125
|
-
if (files.length === 0
|
|
151
|
+
if (files.length === 0)
|
|
152
|
+
return false;
|
|
153
|
+
const regenerate = detectAutoResolveCommand(files, this.autoResolvePatterns);
|
|
154
|
+
if (!regenerate)
|
|
126
155
|
return false;
|
|
127
|
-
}
|
|
128
156
|
for (const file of files) {
|
|
129
157
|
await this.gitIn(wtPath, ["checkout", "--ours", "--", file]);
|
|
130
158
|
}
|
|
131
|
-
const
|
|
159
|
+
const result = await exec(regenerate.bin, regenerate.args, {
|
|
132
160
|
cwd: wtPath,
|
|
133
161
|
timeoutMs: 60_000,
|
|
134
162
|
});
|
|
135
|
-
if (
|
|
163
|
+
if (result.exitCode !== 0)
|
|
136
164
|
return false;
|
|
137
165
|
for (const file of files) {
|
|
138
166
|
await this.gitIn(wtPath, ["add", file]);
|
package/dist/server.js
CHANGED
|
@@ -24,6 +24,8 @@ async function createRepoInstance(config, logger, botIdentity) {
|
|
|
24
24
|
const git = new ShellGitOperations(clone.path, config.repoFullName, config.gitBin);
|
|
25
25
|
if (botIdentity)
|
|
26
26
|
git.setBotIdentity(botIdentity);
|
|
27
|
+
if (config.autoResolvePatterns.length > 0)
|
|
28
|
+
git.setAutoResolvePatterns(config.autoResolvePatterns);
|
|
27
29
|
const ci = new GitHubActionsRunner(config.repoFullName, config.requiredChecks);
|
|
28
30
|
const github = new GitHubPRClient(config.repoFullName);
|
|
29
31
|
const eviction = new GitHubCheckRunReporter(config.repoFullName, config.server.bind, config.server.port, config.server.publicBaseUrl, config.admissionLabel, config.mergeQueueCheckName);
|