@aspruyt/json-config-sync 3.7.0 → 3.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/README.md +89 -20
- package/dist/config-formatter.d.ts +1 -1
- package/dist/config-formatter.js +8 -0
- package/dist/config-normalizer.js +29 -0
- package/dist/config-validator.js +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/file-reference-resolver.js +10 -0
- package/dist/index.js +34 -1
- package/dist/pr-creator.d.ts +10 -0
- package/dist/pr-creator.js +24 -1
- package/dist/repository-processor.d.ts +5 -0
- package/dist/repository-processor.js +34 -2
- package/dist/strategies/azure-pr-strategy.d.ts +6 -1
- package/dist/strategies/azure-pr-strategy.js +92 -1
- package/dist/strategies/github-pr-strategy.d.ts +11 -1
- package/dist/strategies/github-pr-strategy.js +115 -1
- package/dist/strategies/index.d.ts +1 -1
- package/dist/strategies/pr-strategy.d.ts +26 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@aspruyt/json-config-sync)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
A CLI tool that syncs JSON, YAML, or text configuration files across multiple GitHub and Azure DevOps repositories by creating pull requests. Output format is automatically detected from the target filename extension (`.json` → JSON, `.yaml`/`.yml` → YAML, other → text).
|
|
8
|
+
A CLI tool that syncs JSON, JSON5, YAML, or text configuration files across multiple GitHub and Azure DevOps repositories by creating pull requests. Output format is automatically detected from the target filename extension (`.json` → JSON, `.json5` → JSON5, `.yaml`/`.yml` → YAML, other → text).
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
@@ -73,6 +73,7 @@ json-config-sync --config ./config.yaml
|
|
|
73
73
|
- **Empty Files** - Create files with no content (e.g., `.prettierignore`)
|
|
74
74
|
- **YAML Comments** - Add header comments and schema directives to YAML files
|
|
75
75
|
- **GitHub & Azure DevOps** - Works with both platforms
|
|
76
|
+
- **Auto-Merge PRs** - Automatically merge PRs when checks pass, or force merge with admin privileges
|
|
76
77
|
- **Dry-Run Mode** - Preview changes without creating PRs
|
|
77
78
|
- **Error Resilience** - Continues processing if individual repos fail
|
|
78
79
|
- **Automatic Retries** - Retries transient network errors with exponential backoff
|
|
@@ -135,13 +136,16 @@ json-config-sync --config ./config.yaml --branch feature/update-eslint
|
|
|
135
136
|
|
|
136
137
|
### Options
|
|
137
138
|
|
|
138
|
-
| Option
|
|
139
|
-
|
|
|
140
|
-
| `--config`
|
|
141
|
-
| `--dry-run`
|
|
142
|
-
| `--work-dir`
|
|
143
|
-
| `--retries`
|
|
144
|
-
| `--branch`
|
|
139
|
+
| Option | Alias | Description | Required |
|
|
140
|
+
| ------------------ | ----- | ------------------------------------------------------------------------------------ | -------- |
|
|
141
|
+
| `--config` | `-c` | Path to YAML config file | Yes |
|
|
142
|
+
| `--dry-run` | `-d` | Show what would be done without making changes | No |
|
|
143
|
+
| `--work-dir` | `-w` | Temporary directory for cloning (default: `./tmp`) | No |
|
|
144
|
+
| `--retries` | `-r` | Number of retries for network operations (default: 3) | No |
|
|
145
|
+
| `--branch` | `-b` | Override branch name (default: `chore/sync-config`) | No |
|
|
146
|
+
| `--merge` | `-m` | PR merge mode: `manual` (default), `auto` (merge when checks pass), `force` (bypass) | No |
|
|
147
|
+
| `--merge-strategy` | | Merge strategy: `merge` (default), `squash`, `rebase` | No |
|
|
148
|
+
| `--delete-branch` | | Delete source branch after merge | No |
|
|
145
149
|
|
|
146
150
|
## Configuration Format
|
|
147
151
|
|
|
@@ -164,10 +168,11 @@ repos: # List of repositories
|
|
|
164
168
|
|
|
165
169
|
### Root-Level Fields
|
|
166
170
|
|
|
167
|
-
| Field
|
|
168
|
-
|
|
|
169
|
-
| `files`
|
|
170
|
-
| `repos`
|
|
171
|
+
| Field | Description | Required |
|
|
172
|
+
| ----------- | ---------------------------------------------------- | -------- |
|
|
173
|
+
| `files` | Map of target filenames to configs | Yes |
|
|
174
|
+
| `repos` | Array of repository configurations | Yes |
|
|
175
|
+
| `prOptions` | Global PR merge options (can be overridden per-repo) | No |
|
|
171
176
|
|
|
172
177
|
### Per-File Fields
|
|
173
178
|
|
|
@@ -182,10 +187,20 @@ repos: # List of repositories
|
|
|
182
187
|
|
|
183
188
|
### Per-Repo Fields
|
|
184
189
|
|
|
185
|
-
| Field
|
|
186
|
-
|
|
|
187
|
-
| `git`
|
|
188
|
-
| `files`
|
|
190
|
+
| Field | Description | Required |
|
|
191
|
+
| ----------- | -------------------------------------------- | -------- |
|
|
192
|
+
| `git` | Git URL (string) or array of URLs | Yes |
|
|
193
|
+
| `files` | Per-repo file overrides (optional) | No |
|
|
194
|
+
| `prOptions` | Per-repo PR merge options (overrides global) | No |
|
|
195
|
+
|
|
196
|
+
### PR Options Fields
|
|
197
|
+
|
|
198
|
+
| Field | Description | Default |
|
|
199
|
+
| --------------- | --------------------------------------------------------------------------- | -------- |
|
|
200
|
+
| `merge` | Merge mode: `manual` (leave open), `auto` (merge when checks pass), `force` | `manual` |
|
|
201
|
+
| `mergeStrategy` | How to merge: `merge`, `squash`, `rebase` | `merge` |
|
|
202
|
+
| `deleteBranch` | Delete source branch after merge | `false` |
|
|
203
|
+
| `bypassReason` | Reason for bypassing policies (Azure DevOps only, required for `force`) | - |
|
|
189
204
|
|
|
190
205
|
### Per-Repo File Override Fields
|
|
191
206
|
|
|
@@ -521,7 +536,7 @@ repos:
|
|
|
521
536
|
- **String content** (`content: |-`) - Direct text output with environment variable interpolation. Merging always replaces the base.
|
|
522
537
|
- **Lines array** (`content: ['line1', 'line2']`) - Each line joined with newlines. Supports merge strategies (`append`, `prepend`, `replace`).
|
|
523
538
|
|
|
524
|
-
**Validation:** JSON/YAML file extensions (`.json`, `.yaml`, `.yml`) require object content. Other extensions require string or string[] content.
|
|
539
|
+
**Validation:** JSON/JSON5/YAML file extensions (`.json`, `.json5`, `.yaml`, `.yml`) require object content. Other extensions require string or string[] content.
|
|
525
540
|
|
|
526
541
|
### Executable Files
|
|
527
542
|
|
|
@@ -615,7 +630,7 @@ repos:
|
|
|
615
630
|
|
|
616
631
|
- File references start with `@` followed by a relative path
|
|
617
632
|
- Paths are resolved relative to the config file's directory
|
|
618
|
-
- JSON/YAML files are parsed as objects, other files as strings
|
|
633
|
+
- JSON/JSON5/YAML files are parsed as objects, other files as strings
|
|
619
634
|
- Metadata fields (`header`, `schemaUrl`, `createOnly`, `mergeStrategy`) remain in the config
|
|
620
635
|
- Per-repo overlays still work - they merge onto the resolved file content
|
|
621
636
|
|
|
@@ -632,6 +647,60 @@ config/
|
|
|
632
647
|
|
|
633
648
|
**Security:** File references are restricted to the config file's directory tree. Paths like `@../../../etc/passwd` or `@/etc/passwd` are blocked.
|
|
634
649
|
|
|
650
|
+
### Auto-Merge PRs
|
|
651
|
+
|
|
652
|
+
Configure PRs to merge automatically when checks pass, or force merge using admin privileges:
|
|
653
|
+
|
|
654
|
+
```yaml
|
|
655
|
+
files:
|
|
656
|
+
.prettierrc.json:
|
|
657
|
+
content:
|
|
658
|
+
semi: false
|
|
659
|
+
singleQuote: true
|
|
660
|
+
|
|
661
|
+
# Global PR options - apply to all repos
|
|
662
|
+
prOptions:
|
|
663
|
+
merge: auto # auto-merge when checks pass
|
|
664
|
+
mergeStrategy: squash # squash commits
|
|
665
|
+
deleteBranch: true # cleanup after merge
|
|
666
|
+
|
|
667
|
+
repos:
|
|
668
|
+
# These repos use global prOptions (auto-merge)
|
|
669
|
+
- git:
|
|
670
|
+
- git@github.com:org/frontend.git
|
|
671
|
+
- git@github.com:org/backend.git
|
|
672
|
+
|
|
673
|
+
# This repo overrides to force merge (bypass required reviews)
|
|
674
|
+
- git: git@github.com:org/internal-tool.git
|
|
675
|
+
prOptions:
|
|
676
|
+
merge: force
|
|
677
|
+
bypassReason: "Automated config sync" # Azure DevOps only
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Merge Modes:**
|
|
681
|
+
|
|
682
|
+
| Mode | GitHub Behavior | Azure DevOps Behavior |
|
|
683
|
+
| -------- | --------------------------------------- | -------------------------------------- |
|
|
684
|
+
| `manual` | Leave PR open for review (default) | Leave PR open for review |
|
|
685
|
+
| `auto` | Enable auto-merge (requires repo setup) | Enable auto-complete |
|
|
686
|
+
| `force` | Merge with `--admin` (bypass checks) | Bypass policies with `--bypass-policy` |
|
|
687
|
+
|
|
688
|
+
**GitHub Auto-Merge Note:** The `auto` mode requires auto-merge to be enabled in the repository settings. If not enabled, the tool will warn and leave the PR open for manual review. Enable it with:
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
gh repo edit org/repo --enable-auto-merge
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**CLI Override:** You can override config file settings with CLI flags:
|
|
695
|
+
|
|
696
|
+
```bash
|
|
697
|
+
# Force merge all PRs (useful for urgent updates)
|
|
698
|
+
json-config-sync --config ./config.yaml --merge force
|
|
699
|
+
|
|
700
|
+
# Enable auto-merge with squash
|
|
701
|
+
json-config-sync --config ./config.yaml --merge auto --merge-strategy squash --delete-branch
|
|
702
|
+
```
|
|
703
|
+
|
|
635
704
|
## Supported Git URL Formats
|
|
636
705
|
|
|
637
706
|
### GitHub
|
|
@@ -659,7 +728,7 @@ flowchart TB
|
|
|
659
728
|
|
|
660
729
|
subgraph Processing["For Each Repository"]
|
|
661
730
|
CLONE[Clone Repo] --> BRANCH[Create/Checkout Branch<br/>--branch or chore/sync-config]
|
|
662
|
-
BRANCH --> WRITE[Write All Config Files<br/>JSON or YAML]
|
|
731
|
+
BRANCH --> WRITE[Write All Config Files<br/>JSON, JSON5, or YAML]
|
|
663
732
|
WRITE --> CHECK{Changes?}
|
|
664
733
|
CHECK -->|No| SKIP[Skip - No Changes]
|
|
665
734
|
CHECK -->|Yes| COMMIT[Commit Changes]
|
|
@@ -689,7 +758,7 @@ For each repository in the config, the tool:
|
|
|
689
758
|
4. Cleans the temporary workspace
|
|
690
759
|
5. Clones the repository
|
|
691
760
|
6. Creates/checks out branch (custom `--branch` or default `chore/sync-config`)
|
|
692
|
-
7. Writes all config files (JSON or YAML based on filename extension)
|
|
761
|
+
7. Writes all config files (JSON, JSON5, or YAML based on filename extension)
|
|
693
762
|
8. Checks for changes (skips if no changes)
|
|
694
763
|
9. Commits and pushes changes
|
|
695
764
|
10. Creates a pull request
|
package/dist/config-formatter.js
CHANGED
|
@@ -7,6 +7,9 @@ export function detectOutputFormat(fileName) {
|
|
|
7
7
|
if (ext === "yaml" || ext === "yml") {
|
|
8
8
|
return "yaml";
|
|
9
9
|
}
|
|
10
|
+
if (ext === "json5") {
|
|
11
|
+
return "json5";
|
|
12
|
+
}
|
|
10
13
|
return "json";
|
|
11
14
|
}
|
|
12
15
|
/**
|
|
@@ -87,6 +90,11 @@ export function convertContentToString(content, fileName, options) {
|
|
|
87
90
|
}
|
|
88
91
|
return stringify(doc, { indent: 2 });
|
|
89
92
|
}
|
|
93
|
+
if (format === "json5") {
|
|
94
|
+
// JSON5 format - output standard JSON (which is valid JSON5)
|
|
95
|
+
// Using JSON.stringify for standard JSON output that's compatible everywhere
|
|
96
|
+
return JSON.stringify(content, null, 2) + "\n";
|
|
97
|
+
}
|
|
90
98
|
// JSON format - comments not supported, ignore header/schemaUrl
|
|
91
99
|
return JSON.stringify(content, null, 2) + "\n";
|
|
92
100
|
}
|
|
@@ -10,6 +10,32 @@ function normalizeHeader(header) {
|
|
|
10
10
|
return [header];
|
|
11
11
|
return header;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Merges PR options: per-repo overrides global defaults.
|
|
15
|
+
* Returns undefined if no options are set.
|
|
16
|
+
*/
|
|
17
|
+
function mergePROptions(global, perRepo) {
|
|
18
|
+
if (!global && !perRepo)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (!global)
|
|
21
|
+
return perRepo;
|
|
22
|
+
if (!perRepo)
|
|
23
|
+
return global;
|
|
24
|
+
const result = {};
|
|
25
|
+
const merge = perRepo.merge ?? global.merge;
|
|
26
|
+
const mergeStrategy = perRepo.mergeStrategy ?? global.mergeStrategy;
|
|
27
|
+
const deleteBranch = perRepo.deleteBranch ?? global.deleteBranch;
|
|
28
|
+
const bypassReason = perRepo.bypassReason ?? global.bypassReason;
|
|
29
|
+
if (merge !== undefined)
|
|
30
|
+
result.merge = merge;
|
|
31
|
+
if (mergeStrategy !== undefined)
|
|
32
|
+
result.mergeStrategy = mergeStrategy;
|
|
33
|
+
if (deleteBranch !== undefined)
|
|
34
|
+
result.deleteBranch = deleteBranch;
|
|
35
|
+
if (bypassReason !== undefined)
|
|
36
|
+
result.bypassReason = bypassReason;
|
|
37
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
38
|
+
}
|
|
13
39
|
/**
|
|
14
40
|
* Normalizes raw config into expanded, merged config.
|
|
15
41
|
* Pipeline: expand git arrays -> merge content -> interpolate env vars
|
|
@@ -95,9 +121,12 @@ export function normalizeConfig(raw) {
|
|
|
95
121
|
schemaUrl,
|
|
96
122
|
});
|
|
97
123
|
}
|
|
124
|
+
// Merge PR options: per-repo overrides global
|
|
125
|
+
const prOptions = mergePROptions(raw.prOptions, rawRepo.prOptions);
|
|
98
126
|
expandedRepos.push({
|
|
99
127
|
git: gitUrl,
|
|
100
128
|
files,
|
|
129
|
+
prOptions,
|
|
101
130
|
});
|
|
102
131
|
}
|
|
103
132
|
}
|
package/dist/config-validator.js
CHANGED
|
@@ -19,7 +19,7 @@ function isObjectContent(content) {
|
|
|
19
19
|
*/
|
|
20
20
|
function isStructuredFileExtension(fileName) {
|
|
21
21
|
const ext = fileName.toLowerCase().split(".").pop();
|
|
22
|
-
return ext === "json" || ext === "yaml" || ext === "yml";
|
|
22
|
+
return ext === "json" || ext === "json5" || ext === "yaml" || ext === "yml";
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Validates raw config structure before normalization.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ArrayMergeStrategy } from "./merge.js";
|
|
2
2
|
export { convertContentToString } from "./config-formatter.js";
|
|
3
|
+
export type MergeMode = "manual" | "auto" | "force";
|
|
4
|
+
export type MergeStrategy = "merge" | "squash" | "rebase";
|
|
5
|
+
export interface PRMergeOptions {
|
|
6
|
+
merge?: MergeMode;
|
|
7
|
+
mergeStrategy?: MergeStrategy;
|
|
8
|
+
deleteBranch?: boolean;
|
|
9
|
+
bypassReason?: string;
|
|
10
|
+
}
|
|
3
11
|
export type ContentValue = Record<string, unknown> | string | string[];
|
|
4
12
|
export interface RawFileConfig {
|
|
5
13
|
content?: ContentValue;
|
|
@@ -20,10 +28,12 @@ export interface RawRepoFileOverride {
|
|
|
20
28
|
export interface RawRepoConfig {
|
|
21
29
|
git: string | string[];
|
|
22
30
|
files?: Record<string, RawRepoFileOverride | false>;
|
|
31
|
+
prOptions?: PRMergeOptions;
|
|
23
32
|
}
|
|
24
33
|
export interface RawConfig {
|
|
25
34
|
files: Record<string, RawFileConfig>;
|
|
26
35
|
repos: RawRepoConfig[];
|
|
36
|
+
prOptions?: PRMergeOptions;
|
|
27
37
|
}
|
|
28
38
|
export interface FileContent {
|
|
29
39
|
fileName: string;
|
|
@@ -36,6 +46,7 @@ export interface FileContent {
|
|
|
36
46
|
export interface RepoConfig {
|
|
37
47
|
git: string;
|
|
38
48
|
files: FileContent[];
|
|
49
|
+
prOptions?: PRMergeOptions;
|
|
39
50
|
}
|
|
40
51
|
export interface Config {
|
|
41
52
|
repos: RepoConfig[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve, isAbsolute, normalize, extname } from "node:path";
|
|
3
|
+
import JSON5 from "json5";
|
|
3
4
|
import { parse as parseYaml } from "yaml";
|
|
4
5
|
/**
|
|
5
6
|
* Check if a value is a file reference (string starting with @)
|
|
@@ -52,6 +53,15 @@ export function resolveFileReference(reference, configDir) {
|
|
|
52
53
|
throw new Error(`Invalid JSON in "${reference}": ${msg}`);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
if (ext === ".json5") {
|
|
57
|
+
try {
|
|
58
|
+
return JSON5.parse(content);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
62
|
+
throw new Error(`Invalid JSON5 in "${reference}": ${msg}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
55
65
|
if (ext === ".yaml" || ext === ".yml") {
|
|
56
66
|
try {
|
|
57
67
|
return parseYaml(content);
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,21 @@ program
|
|
|
26
26
|
.option("-w, --work-dir <path>", "Temporary directory for cloning", "./tmp")
|
|
27
27
|
.option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (v) => parseInt(v, 10), 3)
|
|
28
28
|
.option("-b, --branch <name>", "Override the branch name (default: chore/sync-{filename} or chore/sync-config)")
|
|
29
|
+
.option("-m, --merge <mode>", "PR merge mode: manual (default), auto (merge when checks pass), force (bypass requirements)", (value) => {
|
|
30
|
+
const valid = ["manual", "auto", "force"];
|
|
31
|
+
if (!valid.includes(value)) {
|
|
32
|
+
throw new Error(`Invalid merge mode: ${value}. Valid: ${valid.join(", ")}`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
})
|
|
36
|
+
.option("--merge-strategy <strategy>", "Merge strategy: merge (default), squash, rebase", (value) => {
|
|
37
|
+
const valid = ["merge", "squash", "rebase"];
|
|
38
|
+
if (!valid.includes(value)) {
|
|
39
|
+
throw new Error(`Invalid merge strategy: ${value}. Valid: ${valid.join(", ")}`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
})
|
|
43
|
+
.option("--delete-branch", "Delete source branch after merge")
|
|
29
44
|
.parse();
|
|
30
45
|
const options = program.opts();
|
|
31
46
|
/**
|
|
@@ -88,6 +103,15 @@ async function main() {
|
|
|
88
103
|
const processor = defaultProcessorFactory();
|
|
89
104
|
for (let i = 0; i < config.repos.length; i++) {
|
|
90
105
|
const repoConfig = config.repos[i];
|
|
106
|
+
// Apply CLI merge overrides to repo config
|
|
107
|
+
if (options.merge || options.mergeStrategy || options.deleteBranch) {
|
|
108
|
+
repoConfig.prOptions = {
|
|
109
|
+
...repoConfig.prOptions,
|
|
110
|
+
merge: options.merge ?? repoConfig.prOptions?.merge,
|
|
111
|
+
mergeStrategy: options.mergeStrategy ?? repoConfig.prOptions?.mergeStrategy,
|
|
112
|
+
deleteBranch: options.deleteBranch ?? repoConfig.prOptions?.deleteBranch,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
91
115
|
const current = i + 1;
|
|
92
116
|
let repoInfo;
|
|
93
117
|
try {
|
|
@@ -112,7 +136,16 @@ async function main() {
|
|
|
112
136
|
logger.skip(current, repoName, result.message);
|
|
113
137
|
}
|
|
114
138
|
else if (result.success) {
|
|
115
|
-
|
|
139
|
+
let message = result.prUrl ? `PR: ${result.prUrl}` : result.message;
|
|
140
|
+
if (result.mergeResult) {
|
|
141
|
+
if (result.mergeResult.merged) {
|
|
142
|
+
message += " (merged)";
|
|
143
|
+
}
|
|
144
|
+
else if (result.mergeResult.autoMergeEnabled) {
|
|
145
|
+
message += " (auto-merge enabled)";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
logger.success(current, repoName, message);
|
|
116
149
|
}
|
|
117
150
|
else {
|
|
118
151
|
logger.error(current, repoName, result.message);
|
package/dist/pr-creator.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RepoInfo } from "./repo-detector.js";
|
|
2
|
+
import { MergeResult, PRMergeConfig } from "./strategies/index.js";
|
|
2
3
|
export { escapeShellArg } from "./shell-utils.js";
|
|
3
4
|
export interface FileAction {
|
|
4
5
|
fileName: string;
|
|
@@ -28,3 +29,12 @@ export declare function formatPRBody(files: FileAction[]): string;
|
|
|
28
29
|
*/
|
|
29
30
|
export declare function formatPRTitle(files: FileAction[]): string;
|
|
30
31
|
export declare function createPR(options: PROptions): Promise<PRResult>;
|
|
32
|
+
export interface MergePROptions {
|
|
33
|
+
repoInfo: RepoInfo;
|
|
34
|
+
prUrl: string;
|
|
35
|
+
mergeConfig: PRMergeConfig;
|
|
36
|
+
workDir: string;
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
retries?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function mergePR(options: MergePROptions): Promise<MergeResult>;
|
package/dist/pr-creator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { getPRStrategy } from "./strategies/index.js";
|
|
4
|
+
import { getPRStrategy, } from "./strategies/index.js";
|
|
5
5
|
// Re-export for backwards compatibility and testing
|
|
6
6
|
export { escapeShellArg } from "./shell-utils.js";
|
|
7
7
|
function loadPRTemplate() {
|
|
@@ -104,3 +104,26 @@ export async function createPR(options) {
|
|
|
104
104
|
retries,
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
+
export async function mergePR(options) {
|
|
108
|
+
const { repoInfo, prUrl, mergeConfig, workDir, dryRun, retries } = options;
|
|
109
|
+
if (dryRun) {
|
|
110
|
+
const modeText = mergeConfig.mode === "force"
|
|
111
|
+
? "force merge"
|
|
112
|
+
: mergeConfig.mode === "auto"
|
|
113
|
+
? "enable auto-merge"
|
|
114
|
+
: "leave open for manual review";
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
message: `[DRY RUN] Would ${modeText}`,
|
|
118
|
+
merged: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Get the appropriate strategy and execute merge
|
|
122
|
+
const strategy = getPRStrategy(repoInfo);
|
|
123
|
+
return strategy.merge({
|
|
124
|
+
prUrl,
|
|
125
|
+
config: mergeConfig,
|
|
126
|
+
workDir,
|
|
127
|
+
retries,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -20,6 +20,11 @@ export interface ProcessorResult {
|
|
|
20
20
|
message: string;
|
|
21
21
|
prUrl?: string;
|
|
22
22
|
skipped?: boolean;
|
|
23
|
+
mergeResult?: {
|
|
24
|
+
merged: boolean;
|
|
25
|
+
autoMergeEnabled?: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
23
28
|
}
|
|
24
29
|
export declare class RepositoryProcessor {
|
|
25
30
|
private gitOps;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { convertContentToString } from "./config.js";
|
|
3
|
+
import { convertContentToString, } from "./config.js";
|
|
4
4
|
import { getRepoDisplayName } from "./repo-detector.js";
|
|
5
5
|
import { GitOps } from "./git-ops.js";
|
|
6
|
-
import { createPR } from "./pr-creator.js";
|
|
6
|
+
import { createPR, mergePR } from "./pr-creator.js";
|
|
7
7
|
import { logger } from "./logger.js";
|
|
8
8
|
/**
|
|
9
9
|
* Determines if a file should be marked as executable.
|
|
@@ -142,11 +142,43 @@ export class RepositoryProcessor {
|
|
|
142
142
|
dryRun,
|
|
143
143
|
retries,
|
|
144
144
|
});
|
|
145
|
+
// Step 10: Handle merge options if configured
|
|
146
|
+
const mergeMode = repoConfig.prOptions?.merge ?? "manual";
|
|
147
|
+
let mergeResult;
|
|
148
|
+
if (prResult.success && prResult.url && mergeMode !== "manual") {
|
|
149
|
+
this.log.info(`Handling merge (mode: ${mergeMode})...`);
|
|
150
|
+
const mergeConfig = {
|
|
151
|
+
mode: mergeMode,
|
|
152
|
+
strategy: repoConfig.prOptions?.mergeStrategy,
|
|
153
|
+
deleteBranch: repoConfig.prOptions?.deleteBranch,
|
|
154
|
+
bypassReason: repoConfig.prOptions?.bypassReason,
|
|
155
|
+
};
|
|
156
|
+
const result = await mergePR({
|
|
157
|
+
repoInfo,
|
|
158
|
+
prUrl: prResult.url,
|
|
159
|
+
mergeConfig,
|
|
160
|
+
workDir,
|
|
161
|
+
dryRun,
|
|
162
|
+
retries,
|
|
163
|
+
});
|
|
164
|
+
mergeResult = {
|
|
165
|
+
merged: result.merged ?? false,
|
|
166
|
+
autoMergeEnabled: result.autoMergeEnabled,
|
|
167
|
+
message: result.message,
|
|
168
|
+
};
|
|
169
|
+
if (!result.success) {
|
|
170
|
+
this.log.info(`Warning: Merge operation failed - ${result.message}`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.log.info(result.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
145
176
|
return {
|
|
146
177
|
success: prResult.success,
|
|
147
178
|
repoName,
|
|
148
179
|
message: prResult.message,
|
|
149
180
|
prUrl: prResult.url,
|
|
181
|
+
mergeResult,
|
|
150
182
|
};
|
|
151
183
|
}
|
|
152
184
|
finally {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PRResult } from "../pr-creator.js";
|
|
2
|
-
import { BasePRStrategy, PRStrategyOptions } from "./pr-strategy.js";
|
|
2
|
+
import { BasePRStrategy, PRStrategyOptions, MergeOptions, MergeResult } from "./pr-strategy.js";
|
|
3
3
|
import { CommandExecutor } from "../command-executor.js";
|
|
4
4
|
export declare class AzurePRStrategy extends BasePRStrategy {
|
|
5
5
|
constructor(executor?: CommandExecutor);
|
|
@@ -7,4 +7,9 @@ export declare class AzurePRStrategy extends BasePRStrategy {
|
|
|
7
7
|
private buildPRUrl;
|
|
8
8
|
checkExistingPR(options: PRStrategyOptions): Promise<string | null>;
|
|
9
9
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Extract PR ID and repo info from Azure DevOps PR URL.
|
|
12
|
+
*/
|
|
13
|
+
private parsePRUrl;
|
|
14
|
+
merge(options: MergeOptions): Promise<MergeResult>;
|
|
10
15
|
}
|
|
@@ -2,7 +2,7 @@ import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { escapeShellArg } from "../shell-utils.js";
|
|
4
4
|
import { isAzureDevOpsRepo } from "../repo-detector.js";
|
|
5
|
-
import { BasePRStrategy } from "./pr-strategy.js";
|
|
5
|
+
import { BasePRStrategy, } from "./pr-strategy.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
7
|
import { withRetry, isPermanentError } from "../retry-utils.js";
|
|
8
8
|
export class AzurePRStrategy extends BasePRStrategy {
|
|
@@ -75,4 +75,95 @@ export class AzurePRStrategy extends BasePRStrategy {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Extract PR ID and repo info from Azure DevOps PR URL.
|
|
80
|
+
*/
|
|
81
|
+
parsePRUrl(prUrl) {
|
|
82
|
+
// URL format: https://dev.azure.com/{org}/{project}/_git/{repo}/pullrequest/{prId}
|
|
83
|
+
const match = prUrl.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/(\d+)/);
|
|
84
|
+
if (!match)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
organization: decodeURIComponent(match[1]),
|
|
88
|
+
project: decodeURIComponent(match[2]),
|
|
89
|
+
repo: decodeURIComponent(match[3]),
|
|
90
|
+
prId: match[4],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async merge(options) {
|
|
94
|
+
const { prUrl, config, workDir, retries = 3 } = options;
|
|
95
|
+
// Manual mode: do nothing
|
|
96
|
+
if (config.mode === "manual") {
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
message: "PR left open for manual review",
|
|
100
|
+
merged: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Parse PR URL to extract details
|
|
104
|
+
const prInfo = this.parsePRUrl(prUrl);
|
|
105
|
+
if (!prInfo) {
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
message: `Invalid Azure DevOps PR URL: ${prUrl}`,
|
|
109
|
+
merged: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const orgUrl = `https://dev.azure.com/${encodeURIComponent(prInfo.organization)}`;
|
|
113
|
+
const squashFlag = config.strategy === "squash" ? "--squash true" : "";
|
|
114
|
+
const deleteBranchFlag = config.deleteBranch
|
|
115
|
+
? "--delete-source-branch true"
|
|
116
|
+
: "";
|
|
117
|
+
if (config.mode === "auto") {
|
|
118
|
+
// Enable auto-complete (no pre-check needed - always available in Azure DevOps)
|
|
119
|
+
const command = `az repos pr update --id ${escapeShellArg(prInfo.prId)} --auto-complete true ${squashFlag} ${deleteBranchFlag} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(prInfo.project)}`.trim();
|
|
120
|
+
try {
|
|
121
|
+
await withRetry(() => this.executor.exec(command, workDir), {
|
|
122
|
+
retries,
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
message: "Auto-complete enabled. PR will merge when all policies pass.",
|
|
127
|
+
merged: false,
|
|
128
|
+
autoMergeEnabled: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
message: `Failed to enable auto-complete: ${message}`,
|
|
136
|
+
merged: false,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (config.mode === "force") {
|
|
141
|
+
// Bypass policies and complete the PR
|
|
142
|
+
const bypassReason = config.bypassReason ?? "Automated config sync via json-config-sync";
|
|
143
|
+
const command = `az repos pr update --id ${escapeShellArg(prInfo.prId)} --bypass-policy true --bypass-policy-reason ${escapeShellArg(bypassReason)} --status completed ${squashFlag} ${deleteBranchFlag} --org ${escapeShellArg(orgUrl)} --project ${escapeShellArg(prInfo.project)}`.trim();
|
|
144
|
+
try {
|
|
145
|
+
await withRetry(() => this.executor.exec(command, workDir), {
|
|
146
|
+
retries,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
message: "PR completed by bypassing policies.",
|
|
151
|
+
merged: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
message: `Failed to bypass policies and complete PR: ${message}`,
|
|
159
|
+
merged: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
message: `Unknown merge mode: ${config.mode}`,
|
|
166
|
+
merged: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
78
169
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { GitHubRepoInfo } from "../repo-detector.js";
|
|
1
2
|
import { PRResult } from "../pr-creator.js";
|
|
2
|
-
import { BasePRStrategy, PRStrategyOptions } from "./pr-strategy.js";
|
|
3
|
+
import { BasePRStrategy, PRStrategyOptions, MergeOptions, MergeResult } from "./pr-strategy.js";
|
|
3
4
|
export declare class GitHubPRStrategy extends BasePRStrategy {
|
|
4
5
|
checkExistingPR(options: PRStrategyOptions): Promise<string | null>;
|
|
5
6
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if auto-merge is enabled on the repository.
|
|
9
|
+
*/
|
|
10
|
+
checkAutoMergeEnabled(repoInfo: GitHubRepoInfo, workDir: string, retries?: number): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Build merge strategy flag for gh pr merge command.
|
|
13
|
+
*/
|
|
14
|
+
private getMergeStrategyFlag;
|
|
15
|
+
merge(options: MergeOptions): Promise<MergeResult>;
|
|
6
16
|
}
|
|
@@ -2,7 +2,7 @@ import { existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { escapeShellArg } from "../shell-utils.js";
|
|
4
4
|
import { isGitHubRepo } from "../repo-detector.js";
|
|
5
|
-
import { BasePRStrategy } from "./pr-strategy.js";
|
|
5
|
+
import { BasePRStrategy, } from "./pr-strategy.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
7
|
import { withRetry, isPermanentError } from "../retry-utils.js";
|
|
8
8
|
export class GitHubPRStrategy extends BasePRStrategy {
|
|
@@ -62,4 +62,118 @@ export class GitHubPRStrategy extends BasePRStrategy {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if auto-merge is enabled on the repository.
|
|
67
|
+
*/
|
|
68
|
+
async checkAutoMergeEnabled(repoInfo, workDir, retries = 3) {
|
|
69
|
+
const command = `gh api repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)} --jq '.allow_auto_merge // false'`;
|
|
70
|
+
try {
|
|
71
|
+
const result = await withRetry(() => this.executor.exec(command, workDir), { retries });
|
|
72
|
+
return result.trim() === "true";
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// If we can't check, assume auto-merge is not enabled
|
|
76
|
+
logger.info(`Warning: Could not check auto-merge status: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build merge strategy flag for gh pr merge command.
|
|
82
|
+
*/
|
|
83
|
+
getMergeStrategyFlag(strategy) {
|
|
84
|
+
switch (strategy) {
|
|
85
|
+
case "squash":
|
|
86
|
+
return "--squash";
|
|
87
|
+
case "rebase":
|
|
88
|
+
return "--rebase";
|
|
89
|
+
case "merge":
|
|
90
|
+
default:
|
|
91
|
+
return "--merge";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async merge(options) {
|
|
95
|
+
const { prUrl, config, workDir, retries = 3 } = options;
|
|
96
|
+
// Manual mode: do nothing
|
|
97
|
+
if (config.mode === "manual") {
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
message: "PR left open for manual review",
|
|
101
|
+
merged: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const strategyFlag = this.getMergeStrategyFlag(config.strategy);
|
|
105
|
+
const deleteBranchFlag = config.deleteBranch ? "--delete-branch" : "";
|
|
106
|
+
if (config.mode === "auto") {
|
|
107
|
+
// Check if auto-merge is enabled on the repo
|
|
108
|
+
// Extract owner/repo from PR URL
|
|
109
|
+
const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
110
|
+
if (match) {
|
|
111
|
+
const repoInfo = {
|
|
112
|
+
type: "github",
|
|
113
|
+
gitUrl: prUrl,
|
|
114
|
+
owner: match[1],
|
|
115
|
+
repo: match[2],
|
|
116
|
+
};
|
|
117
|
+
const autoMergeEnabled = await this.checkAutoMergeEnabled(repoInfo, workDir, retries);
|
|
118
|
+
if (!autoMergeEnabled) {
|
|
119
|
+
logger.info(`Warning: Auto-merge not enabled for '${repoInfo.owner}/${repoInfo.repo}'. PR left open for manual review.`);
|
|
120
|
+
logger.info(`To enable: gh repo edit ${repoInfo.owner}/${repoInfo.repo} --enable-auto-merge (requires admin)`);
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
message: `Auto-merge not enabled for repository. PR left open for manual review.`,
|
|
124
|
+
merged: false,
|
|
125
|
+
autoMergeEnabled: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Enable auto-merge
|
|
130
|
+
const command = `gh pr merge ${escapeShellArg(prUrl)} --auto ${strategyFlag} ${deleteBranchFlag}`.trim();
|
|
131
|
+
try {
|
|
132
|
+
await withRetry(() => this.executor.exec(command, workDir), {
|
|
133
|
+
retries,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
message: "Auto-merge enabled. PR will merge when checks pass.",
|
|
138
|
+
merged: false,
|
|
139
|
+
autoMergeEnabled: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
message: `Failed to enable auto-merge: ${message}`,
|
|
147
|
+
merged: false,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (config.mode === "force") {
|
|
152
|
+
// Force merge using admin privileges
|
|
153
|
+
const command = `gh pr merge ${escapeShellArg(prUrl)} --admin ${strategyFlag} ${deleteBranchFlag}`.trim();
|
|
154
|
+
try {
|
|
155
|
+
await withRetry(() => this.executor.exec(command, workDir), {
|
|
156
|
+
retries,
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
message: "PR merged successfully using admin privileges.",
|
|
161
|
+
merged: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
message: `Failed to force merge: ${message}`,
|
|
169
|
+
merged: false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
message: `Unknown merge mode: ${config.mode}`,
|
|
176
|
+
merged: false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
65
179
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RepoInfo } from "../repo-detector.js";
|
|
2
2
|
import type { PRStrategy } from "./pr-strategy.js";
|
|
3
|
-
export type { PRStrategy, PRStrategyOptions } from "./pr-strategy.js";
|
|
3
|
+
export type { PRStrategy, PRStrategyOptions, PRMergeConfig, MergeOptions, MergeResult, } from "./pr-strategy.js";
|
|
4
4
|
export { BasePRStrategy, PRWorkflowExecutor } from "./pr-strategy.js";
|
|
5
5
|
export { GitHubPRStrategy } from "./github-pr-strategy.js";
|
|
6
6
|
export { AzurePRStrategy } from "./azure-pr-strategy.js";
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { PRResult } from "../pr-creator.js";
|
|
2
2
|
import { RepoInfo } from "../repo-detector.js";
|
|
3
3
|
import { CommandExecutor } from "../command-executor.js";
|
|
4
|
+
import type { MergeMode, MergeStrategy } from "../config.js";
|
|
5
|
+
export interface PRMergeConfig {
|
|
6
|
+
mode: MergeMode;
|
|
7
|
+
strategy?: MergeStrategy;
|
|
8
|
+
deleteBranch?: boolean;
|
|
9
|
+
bypassReason?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MergeResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
merged?: boolean;
|
|
15
|
+
autoMergeEnabled?: boolean;
|
|
16
|
+
}
|
|
4
17
|
export interface PRStrategyOptions {
|
|
5
18
|
repoInfo: RepoInfo;
|
|
6
19
|
title: string;
|
|
@@ -11,9 +24,15 @@ export interface PRStrategyOptions {
|
|
|
11
24
|
/** Number of retries for API operations (default: 3) */
|
|
12
25
|
retries?: number;
|
|
13
26
|
}
|
|
27
|
+
export interface MergeOptions {
|
|
28
|
+
prUrl: string;
|
|
29
|
+
config: PRMergeConfig;
|
|
30
|
+
workDir: string;
|
|
31
|
+
retries?: number;
|
|
32
|
+
}
|
|
14
33
|
/**
|
|
15
34
|
* Interface for PR creation strategies (platform-specific implementations).
|
|
16
|
-
* Strategies focus on platform-specific logic (checkExistingPR, create).
|
|
35
|
+
* Strategies focus on platform-specific logic (checkExistingPR, create, merge).
|
|
17
36
|
* Use PRWorkflowExecutor for full workflow orchestration with error handling.
|
|
18
37
|
*/
|
|
19
38
|
export interface PRStrategy {
|
|
@@ -27,6 +46,11 @@ export interface PRStrategy {
|
|
|
27
46
|
* @returns Result with URL and status
|
|
28
47
|
*/
|
|
29
48
|
create(options: PRStrategyOptions): Promise<PRResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Merge or enable auto-merge for a PR
|
|
51
|
+
* @returns Result with merge status
|
|
52
|
+
*/
|
|
53
|
+
merge(options: MergeOptions): Promise<MergeResult>;
|
|
30
54
|
/**
|
|
31
55
|
* Execute the full PR creation workflow
|
|
32
56
|
* @deprecated Use PRWorkflowExecutor.execute() for better SRP
|
|
@@ -39,6 +63,7 @@ export declare abstract class BasePRStrategy implements PRStrategy {
|
|
|
39
63
|
constructor(executor?: CommandExecutor);
|
|
40
64
|
abstract checkExistingPR(options: PRStrategyOptions): Promise<string | null>;
|
|
41
65
|
abstract create(options: PRStrategyOptions): Promise<PRResult>;
|
|
66
|
+
abstract merge(options: MergeOptions): Promise<MergeResult>;
|
|
42
67
|
/**
|
|
43
68
|
* Execute the full PR creation workflow:
|
|
44
69
|
* 1. Check for existing PR
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspruyt/json-config-sync",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "CLI tool to sync JSON or YAML configuration files across multiple GitHub and Azure DevOps repositories",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"commander": "^14.0.2",
|
|
49
|
+
"json5": "^2.2.3",
|
|
49
50
|
"p-retry": "^7.1.1",
|
|
50
51
|
"yaml": "^2.4.5"
|
|
51
52
|
},
|