@aspruyt/xfg 5.1.2 → 5.1.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/README.md +1 -0
- package/dist/cli/lifecycle-report-builder.d.ts +2 -0
- package/dist/cli/lifecycle-report-builder.js +15 -0
- package/dist/cli/program.js +16 -11
- package/dist/cli/settings-report-builder.d.ts +1 -1
- package/dist/cli/settings-report-builder.js +20 -32
- package/dist/cli/sync-command.js +59 -64
- package/dist/cli/types.d.ts +3 -6
- package/dist/config/formatter.js +1 -6
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.js +2 -0
- package/dist/config/merge.d.ts +0 -6
- package/dist/config/merge.js +0 -13
- package/dist/config/types.d.ts +0 -9
- package/dist/config/validator.d.ts +1 -1
- package/dist/config/validator.js +1 -1
- package/dist/lifecycle/github-lifecycle-provider.js +28 -45
- package/dist/lifecycle/lifecycle-helpers.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +1 -1
- package/dist/lifecycle/types.d.ts +4 -4
- package/dist/output/github-summary.d.ts +0 -50
- package/dist/output/github-summary.js +0 -201
- package/dist/output/lifecycle-report.d.ts +0 -1
- package/dist/output/lifecycle-report.js +0 -15
- package/dist/output/settings-report.d.ts +1 -1
- package/dist/output/settings-report.js +15 -77
- package/dist/output/sync-report.d.ts +1 -0
- package/dist/output/sync-report.js +19 -1
- package/dist/output/unified-summary.d.ts +1 -2
- package/dist/output/unified-summary.js +1 -19
- package/dist/settings/base-processor.d.ts +8 -0
- package/dist/settings/base-processor.js +7 -0
- package/dist/settings/index.d.ts +2 -2
- package/dist/settings/index.js +3 -1
- package/dist/settings/labels/converter.d.ts +1 -1
- package/dist/settings/labels/diff.d.ts +1 -1
- package/dist/settings/labels/formatter.d.ts +1 -1
- package/dist/settings/repo-settings/processor.js +9 -5
- package/dist/settings/rulesets/formatter.js +97 -85
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -2
- package/dist/settings/rulesets/github-ruleset-strategy.js +7 -4
- package/dist/settings/rulesets/index.d.ts +1 -1
- package/dist/settings/rulesets/index.js +0 -2
- package/dist/settings/rulesets/processor.js +5 -1
- package/dist/settings/rulesets/types.d.ts +6 -1
- package/dist/shared/gh-api-utils.d.ts +1 -1
- package/dist/shared/logger.d.ts +8 -8
- package/dist/shared/logger.js +9 -8
- package/dist/shared/repo-detector.js +0 -1
- package/dist/sync/branch-manager.js +2 -2
- package/dist/sync/commit-push-manager.js +2 -2
- package/dist/sync/diff-utils.d.ts +0 -9
- package/dist/sync/diff-utils.js +0 -9
- package/dist/sync/file-sync-orchestrator.js +1 -1
- package/dist/sync/file-sync-strategy.d.ts +1 -1
- package/dist/sync/file-writer.d.ts +1 -1
- package/dist/sync/file-writer.js +3 -1
- package/dist/sync/index.d.ts +1 -2
- package/dist/sync/index.js +1 -1
- package/dist/sync/manifest-manager.d.ts +1 -1
- package/dist/sync/manifest-manager.js +1 -1
- package/dist/sync/manifest.js +9 -1
- package/dist/sync/pr-merge-handler.js +2 -2
- package/dist/sync/sync-workflow.d.ts +1 -1
- package/dist/sync/types.d.ts +2 -2
- package/dist/vcs/authenticated-git-ops.d.ts +2 -1
- package/dist/vcs/commit-strategy-selector.d.ts +1 -1
- package/dist/vcs/commit-strategy-selector.js +1 -1
- package/dist/vcs/git-ops.d.ts +0 -1
- package/dist/vcs/git-ops.js +15 -8
- package/dist/vcs/github-pr-strategy.d.ts +0 -3
- package/dist/vcs/github-pr-strategy.js +4 -7
- package/dist/vcs/index.d.ts +3 -3
- package/dist/vcs/index.js +2 -2
- package/dist/vcs/pr-creator.js +9 -9
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +1 -1
- package/dist/vcs/pr-strategy.js +6 -11
- package/package.json +1 -1
- package/dist/output/summary-utils.d.ts +0 -20
- package/dist/output/summary-utils.js +0 -79
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/anthony-spruyt/xfg/actions/workflows/ci.yaml)
|
|
4
4
|
[](https://codecov.io/gh/anthony-spruyt/xfg)
|
|
5
|
+
[](https://socket.dev/npm/package/@aspruyt/xfg)
|
|
5
6
|
[](https://www.npmjs.com/package/@aspruyt/xfg)
|
|
6
7
|
[](https://www.npmjs.com/package/@aspruyt/xfg)
|
|
7
8
|
[](https://github.com/marketplace/actions/xfg-repo-as-code)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function buildLifecycleReport(results) {
|
|
2
|
+
const actions = [];
|
|
3
|
+
const totals = { created: 0, forked: 0, migrated: 0, existed: 0 };
|
|
4
|
+
for (const result of results) {
|
|
5
|
+
actions.push({
|
|
6
|
+
repoName: result.repoName,
|
|
7
|
+
action: result.action,
|
|
8
|
+
upstream: result.upstream,
|
|
9
|
+
source: result.source,
|
|
10
|
+
settings: result.settings,
|
|
11
|
+
});
|
|
12
|
+
totals[result.action]++;
|
|
13
|
+
}
|
|
14
|
+
return { actions, totals };
|
|
15
|
+
}
|
package/dist/cli/program.js
CHANGED
|
@@ -6,12 +6,14 @@ import { ValidationError } from "../shared/errors.js";
|
|
|
6
6
|
import { runSync } from "./sync-command.js";
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
function getVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../..", "package.json"), "utf-8"));
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
// =============================================================================
|
|
17
19
|
// Shared CLI Options
|
|
@@ -50,7 +52,7 @@ export function parseMergeStrategy(value) {
|
|
|
50
52
|
program
|
|
51
53
|
.name("xfg")
|
|
52
54
|
.description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab")
|
|
53
|
-
.version(
|
|
55
|
+
.version(getVersion());
|
|
54
56
|
// Sync command (file synchronization)
|
|
55
57
|
const syncCommand = new Command("sync")
|
|
56
58
|
.description("Sync configuration files across repositories")
|
|
@@ -58,11 +60,14 @@ const syncCommand = new Command("sync")
|
|
|
58
60
|
.option("-m, --merge <mode>", "PR merge mode: manual, auto (default, merge when checks pass), force (bypass requirements), direct (push to default branch, no PR)", parseMergeMode)
|
|
59
61
|
.option("--merge-strategy <strategy>", "Merge strategy: merge, squash (default), rebase", parseMergeStrategy)
|
|
60
62
|
.option("--delete-branch", "Delete source branch after merge")
|
|
61
|
-
.action((opts) => {
|
|
62
|
-
|
|
63
|
+
.action(async (opts) => {
|
|
64
|
+
try {
|
|
65
|
+
await runSync(opts);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
63
68
|
console.error("Fatal error:", error);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
69
|
+
return process.exit(1);
|
|
70
|
+
}
|
|
66
71
|
});
|
|
67
72
|
addSharedOptions(syncCommand);
|
|
68
73
|
program.addCommand(syncCommand);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SettingsReport } from "../output/settings-report.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type RepoSettingsPlanEntry, type RulesetPlanEntry, type LabelsPlanEntry } from "../settings/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Result from processing a repository's settings and rulesets.
|
|
5
5
|
* Used to collect results during settings command execution.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { countActions, isActiveAction, } from "../settings/index.js";
|
|
1
2
|
export function buildSettingsReport(results) {
|
|
2
3
|
const repos = [];
|
|
3
4
|
const totals = {
|
|
@@ -19,64 +20,51 @@ export function buildSettingsReport(results) {
|
|
|
19
20
|
if (entry.oldValue === undefined && entry.newValue === undefined) {
|
|
20
21
|
continue;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
+
repoChanges.settings.push({
|
|
23
24
|
name: entry.property,
|
|
24
25
|
action: entry.action,
|
|
25
26
|
oldValue: entry.oldValue,
|
|
26
27
|
newValue: entry.newValue,
|
|
27
|
-
};
|
|
28
|
-
repoChanges.settings.push(settingChange);
|
|
29
|
-
if (entry.action === "create") {
|
|
30
|
-
totals.settings.create++;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
totals.settings.update++;
|
|
34
|
-
}
|
|
28
|
+
});
|
|
35
29
|
}
|
|
30
|
+
const counts = countActions(repoChanges.settings);
|
|
31
|
+
totals.settings.create += counts.create;
|
|
32
|
+
totals.settings.update += counts.update;
|
|
36
33
|
}
|
|
37
34
|
// Convert ruleset processor output
|
|
38
35
|
if (result.rulesetResult?.planOutput?.entries) {
|
|
39
36
|
for (const entry of result.rulesetResult.planOutput.entries) {
|
|
40
|
-
if (entry
|
|
37
|
+
if (!isActiveAction(entry))
|
|
41
38
|
continue;
|
|
42
|
-
|
|
39
|
+
repoChanges.rulesets.push({
|
|
43
40
|
name: entry.name,
|
|
44
41
|
action: entry.action,
|
|
45
42
|
propertyDiffs: entry.propertyDiffs,
|
|
46
43
|
config: entry.config,
|
|
47
|
-
};
|
|
48
|
-
repoChanges.rulesets.push(rulesetChange);
|
|
49
|
-
if (entry.action === "create") {
|
|
50
|
-
totals.rulesets.create++;
|
|
51
|
-
}
|
|
52
|
-
else if (entry.action === "update") {
|
|
53
|
-
totals.rulesets.update++;
|
|
54
|
-
}
|
|
55
|
-
else if (entry.action === "delete") {
|
|
56
|
-
totals.rulesets.delete++;
|
|
57
|
-
}
|
|
44
|
+
});
|
|
58
45
|
}
|
|
46
|
+
const counts = countActions(repoChanges.rulesets);
|
|
47
|
+
totals.rulesets.create += counts.create;
|
|
48
|
+
totals.rulesets.update += counts.update;
|
|
49
|
+
totals.rulesets.delete += counts.delete;
|
|
59
50
|
}
|
|
60
51
|
// Convert labels processor output
|
|
61
52
|
if (result.labelsResult?.planOutput?.entries) {
|
|
62
53
|
for (const entry of result.labelsResult.planOutput.entries) {
|
|
63
|
-
if (entry
|
|
54
|
+
if (!isActiveAction(entry))
|
|
64
55
|
continue;
|
|
65
|
-
|
|
56
|
+
repoChanges.labels.push({
|
|
66
57
|
name: entry.name,
|
|
67
58
|
action: entry.action,
|
|
68
59
|
newName: entry.newName,
|
|
69
60
|
propertyChanges: entry.propertyChanges,
|
|
70
61
|
config: entry.config,
|
|
71
|
-
};
|
|
72
|
-
repoChanges.labels.push(labelChange);
|
|
73
|
-
if (entry.action === "create")
|
|
74
|
-
totals.labels.create++;
|
|
75
|
-
else if (entry.action === "update")
|
|
76
|
-
totals.labels.update++;
|
|
77
|
-
else if (entry.action === "delete")
|
|
78
|
-
totals.labels.delete++;
|
|
62
|
+
});
|
|
79
63
|
}
|
|
64
|
+
const counts = countActions(repoChanges.labels);
|
|
65
|
+
totals.labels.create += counts.create;
|
|
66
|
+
totals.labels.update += counts.update;
|
|
67
|
+
totals.labels.delete += counts.delete;
|
|
80
68
|
}
|
|
81
69
|
if (result.error) {
|
|
82
70
|
repoChanges.error = result.error;
|
package/dist/cli/sync-command.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { resolve, join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { loadRawConfig, normalizeConfig } from "../config/index.js";
|
|
3
|
+
import { loadRawConfig, normalizeConfig, validateForSync, } from "../config/index.js";
|
|
4
4
|
import { ValidationError, SyncError } from "../shared/errors.js";
|
|
5
|
-
import { validateForSync } from "../config/validator.js";
|
|
6
5
|
import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../shared/repo-detector.js";
|
|
7
6
|
import { sanitizeBranchName, validateBranchName, } from "../shared/branch-utils.js";
|
|
8
7
|
import { createTokenManager } from "../vcs/index.js";
|
|
@@ -32,18 +31,16 @@ function createDefaultLabelsProcessorFactory() {
|
|
|
32
31
|
return () => new LabelsProcessor(new GitHubLabelsStrategy(getDefaultExecutor(), { cwd }));
|
|
33
32
|
}
|
|
34
33
|
import { ResultsCollector } from "./results-collector.js";
|
|
35
|
-
import { buildSettingsReport } from "./settings-report-builder.js";
|
|
34
|
+
import { buildSettingsReport, } from "./settings-report-builder.js";
|
|
36
35
|
import { formatSettingsReportCLI } from "../output/settings-report.js";
|
|
37
36
|
import { buildSyncReport } from "./sync-report-builder.js";
|
|
38
37
|
import { formatSyncReportCLI } from "../output/sync-report.js";
|
|
39
|
-
import { buildLifecycleReport
|
|
38
|
+
import { buildLifecycleReport } from "./lifecycle-report-builder.js";
|
|
39
|
+
import { formatLifecycleReportCLI, hasLifecycleChanges, } from "../output/lifecycle-report.js";
|
|
40
40
|
import { writeUnifiedSummary } from "../output/unified-summary.js";
|
|
41
41
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
42
42
|
import { resolveGitHubToken } from "../shared/gh-api-utils.js";
|
|
43
43
|
import { RepoLifecycleManager, runLifecycleCheck, toCreateRepoSettings, } from "../lifecycle/index.js";
|
|
44
|
-
/**
|
|
45
|
-
* Get unique file names from all repos in the config
|
|
46
|
-
*/
|
|
47
44
|
function getUniqueFileNames(config) {
|
|
48
45
|
const fileNames = new Set();
|
|
49
46
|
for (const repo of config.repos) {
|
|
@@ -53,18 +50,12 @@ function getUniqueFileNames(config) {
|
|
|
53
50
|
}
|
|
54
51
|
return Array.from(fileNames);
|
|
55
52
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Generate default branch name based on files being synced
|
|
58
|
-
*/
|
|
59
53
|
function generateBranchName(fileNames) {
|
|
60
54
|
if (fileNames.length === 1) {
|
|
61
55
|
return `chore/sync-${sanitizeBranchName(fileNames[0])}`;
|
|
62
56
|
}
|
|
63
57
|
return "chore/sync-config";
|
|
64
58
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Format file names for display
|
|
67
|
-
*/
|
|
68
59
|
function formatFileNames(fileNames) {
|
|
69
60
|
if (fileNames.length === 1) {
|
|
70
61
|
return fileNames[0];
|
|
@@ -85,7 +76,7 @@ function determineMergeOutcome(result) {
|
|
|
85
76
|
return "auto";
|
|
86
77
|
return "manual";
|
|
87
78
|
}
|
|
88
|
-
function logSettingsResult(result, label,
|
|
79
|
+
function logSettingsResult(result, label, repoNumber, repoName, settingsCollector) {
|
|
89
80
|
if (result.planOutput?.lines?.length) {
|
|
90
81
|
getLogger().info("");
|
|
91
82
|
getLogger().info(`${repoName} - ${label}:`);
|
|
@@ -99,70 +90,74 @@ function logSettingsResult(result, label, current, repoName, settingsCollector)
|
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
else if (!result.skipped && result.success) {
|
|
102
|
-
getLogger().success(
|
|
93
|
+
getLogger().success(repoNumber, repoName, `${label}: ${result.message}`);
|
|
103
94
|
}
|
|
104
95
|
if (!result.success && !result.skipped) {
|
|
105
|
-
getLogger().error(
|
|
96
|
+
getLogger().error(repoNumber, repoName, `${label}: ${result.message}`);
|
|
106
97
|
settingsCollector.appendError(repoName, result.message);
|
|
107
98
|
}
|
|
108
99
|
}
|
|
109
|
-
|
|
110
|
-
const { repoConfig, repoInfo,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
function buildSettingsDescriptors(ctx) {
|
|
101
|
+
const { repoConfig, repoInfo, options, token, repoName, settingsCollector, rulesetProcessorFactory, repoSettingsProcessorFactory, labelsProcessorFactory, } = ctx;
|
|
102
|
+
const sharedOpts = {
|
|
103
|
+
dryRun: options.dryRun,
|
|
104
|
+
noDelete: options.noDelete,
|
|
105
|
+
token,
|
|
106
|
+
};
|
|
107
|
+
// Each processor returns a subtype of BaseProcessorResult whose planOutput
|
|
108
|
+
// contains both `lines` (for CLI display) and `entries` (for report building).
|
|
109
|
+
// ProcessorResults fields capture only the `entries` slice; the runtime object
|
|
110
|
+
// satisfies both views, so we assign with an explicit per-field cast.
|
|
111
|
+
const runAndStore = async (factory, opts, assign) => {
|
|
112
|
+
const result = await runSettingsProcessor(factory, repoConfig, repoInfo, opts);
|
|
113
|
+
if (!result.skipped) {
|
|
114
|
+
assign(settingsCollector.getOrCreate(repoName), result);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
return [
|
|
114
119
|
{
|
|
115
120
|
key: "rulesets",
|
|
116
121
|
label: "Rulesets",
|
|
117
|
-
run:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
noDelete: options.noDelete,
|
|
121
|
-
token,
|
|
122
|
-
});
|
|
123
|
-
if (!result.skipped) {
|
|
124
|
-
settingsCollector.getOrCreate(repoName).rulesetResult = result;
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
},
|
|
122
|
+
run: () => runAndStore(rulesetProcessorFactory, sharedOpts, (e, r) => {
|
|
123
|
+
e.rulesetResult = r;
|
|
124
|
+
}),
|
|
128
125
|
},
|
|
129
126
|
{
|
|
130
127
|
key: "labels",
|
|
131
128
|
label: "Labels",
|
|
132
|
-
run:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
noDelete: options.noDelete,
|
|
136
|
-
token,
|
|
137
|
-
});
|
|
138
|
-
if (!result.skipped) {
|
|
139
|
-
settingsCollector.getOrCreate(repoName).labelsResult = result;
|
|
140
|
-
}
|
|
141
|
-
return result;
|
|
142
|
-
},
|
|
129
|
+
run: () => runAndStore(labelsProcessorFactory, sharedOpts, (e, r) => {
|
|
130
|
+
e.labelsResult = r;
|
|
131
|
+
}),
|
|
143
132
|
},
|
|
144
133
|
{
|
|
145
134
|
key: "repo",
|
|
146
135
|
label: "Repo Settings",
|
|
147
|
-
run:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
settingsCollector.getOrCreate(repoName).settingsResult = result;
|
|
151
|
-
}
|
|
152
|
-
return result;
|
|
153
|
-
},
|
|
136
|
+
run: () => runAndStore(repoSettingsProcessorFactory, { dryRun: options.dryRun, token }, (e, r) => {
|
|
137
|
+
e.settingsResult = r;
|
|
138
|
+
}),
|
|
154
139
|
},
|
|
155
140
|
];
|
|
156
|
-
|
|
141
|
+
}
|
|
142
|
+
function runSettingsProcessor(factory, repoConfig, repoInfo, processOptions) {
|
|
143
|
+
return factory()
|
|
144
|
+
.process(repoConfig, repoInfo, processOptions)
|
|
145
|
+
.then((result) => result);
|
|
146
|
+
}
|
|
147
|
+
async function applyRepoSettings(ctx) {
|
|
148
|
+
const { repoConfig, repoInfo, repoName, repoNumber, settingsCollector } = ctx;
|
|
149
|
+
if (!repoConfig.settings || !isGitHubRepo(repoInfo))
|
|
150
|
+
return;
|
|
151
|
+
for (const desc of buildSettingsDescriptors(ctx)) {
|
|
157
152
|
const settingsValue = repoConfig.settings[desc.key];
|
|
158
153
|
if (!settingsValue || Object.keys(settingsValue).length === 0)
|
|
159
154
|
continue;
|
|
160
155
|
try {
|
|
161
156
|
const result = await desc.run();
|
|
162
|
-
logSettingsResult(result, desc.label,
|
|
157
|
+
logSettingsResult(result, desc.label, repoNumber, repoName, settingsCollector);
|
|
163
158
|
}
|
|
164
159
|
catch (error) {
|
|
165
|
-
getLogger().error(
|
|
160
|
+
getLogger().error(repoNumber, repoName, `${desc.label}: ${toErrorMessage(error)}`);
|
|
166
161
|
settingsCollector.appendError(repoName, error);
|
|
167
162
|
}
|
|
168
163
|
}
|
|
@@ -208,7 +203,7 @@ function displayReports(reportResults, lifecycleReportInputs, settingsCollector,
|
|
|
208
203
|
*/
|
|
209
204
|
async function processSingleRepo(repoConfig, index, ctx) {
|
|
210
205
|
const { config, options } = ctx;
|
|
211
|
-
const
|
|
206
|
+
const repoNumber = index + 1;
|
|
212
207
|
// Apply CLI-level PR option overrides
|
|
213
208
|
if (options.merge || options.mergeStrategy || options.deleteBranch) {
|
|
214
209
|
repoConfig.prOptions = {
|
|
@@ -229,7 +224,7 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
229
224
|
});
|
|
230
225
|
}
|
|
231
226
|
catch (error) {
|
|
232
|
-
getLogger().error(
|
|
227
|
+
getLogger().error(repoNumber, repoConfig.git, toErrorMessage(error));
|
|
233
228
|
ctx.reportResults.push({
|
|
234
229
|
repoName: repoConfig.git,
|
|
235
230
|
success: false,
|
|
@@ -267,7 +262,7 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
267
262
|
repoConfig,
|
|
268
263
|
repoInfo,
|
|
269
264
|
repoName,
|
|
270
|
-
|
|
265
|
+
repoNumber,
|
|
271
266
|
options,
|
|
272
267
|
token: repoToken,
|
|
273
268
|
settingsCollector: ctx.settingsCollector,
|
|
@@ -281,7 +276,7 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
281
276
|
* Returns true if the main loop should skip file sync for this repo.
|
|
282
277
|
*/
|
|
283
278
|
async function runLifecyclePhase(repo, ctx) {
|
|
284
|
-
const
|
|
279
|
+
const repoNumber = repo.index + 1;
|
|
285
280
|
try {
|
|
286
281
|
const { outputLines, lifecycleResult } = await runLifecycleCheck(repo.repoConfig, repo.repoInfo, {
|
|
287
282
|
dryRun: ctx.options.dryRun ?? false,
|
|
@@ -320,7 +315,7 @@ async function runLifecyclePhase(repo, ctx) {
|
|
|
320
315
|
return false;
|
|
321
316
|
}
|
|
322
317
|
catch (error) {
|
|
323
|
-
getLogger().error(
|
|
318
|
+
getLogger().error(repoNumber, repo.repoName, `Lifecycle error: ${toErrorMessage(error)}`);
|
|
324
319
|
ctx.reportResults.push({
|
|
325
320
|
repoName: repo.repoName,
|
|
326
321
|
success: false,
|
|
@@ -334,9 +329,9 @@ async function runLifecyclePhase(repo, ctx) {
|
|
|
334
329
|
* Run the file sync processor for a single repo and collect results.
|
|
335
330
|
*/
|
|
336
331
|
async function runFileSyncPhase(repo, ctx) {
|
|
337
|
-
const
|
|
332
|
+
const repoNumber = repo.index + 1;
|
|
338
333
|
try {
|
|
339
|
-
getLogger().progress(
|
|
334
|
+
getLogger().progress(repoNumber, repo.repoName, "Processing...");
|
|
340
335
|
const result = await ctx.processor.process(repo.repoConfig, repo.repoInfo, {
|
|
341
336
|
branchName: ctx.branchName,
|
|
342
337
|
workDir: repo.workDir,
|
|
@@ -363,17 +358,17 @@ async function runFileSyncPhase(repo, ctx) {
|
|
|
363
358
|
error: result.success ? undefined : result.message,
|
|
364
359
|
});
|
|
365
360
|
if (result.skipped) {
|
|
366
|
-
getLogger().skip(
|
|
361
|
+
getLogger().skip(repoNumber, repo.repoName, result.message);
|
|
367
362
|
}
|
|
368
363
|
else if (result.success) {
|
|
369
|
-
getLogger().success(
|
|
364
|
+
getLogger().success(repoNumber, repo.repoName, result.message);
|
|
370
365
|
}
|
|
371
366
|
else {
|
|
372
|
-
getLogger().error(
|
|
367
|
+
getLogger().error(repoNumber, repo.repoName, result.message);
|
|
373
368
|
}
|
|
374
369
|
}
|
|
375
370
|
catch (error) {
|
|
376
|
-
getLogger().error(
|
|
371
|
+
getLogger().error(repoNumber, repo.repoName, toErrorMessage(error));
|
|
377
372
|
ctx.reportResults.push({
|
|
378
373
|
repoName: repo.repoName,
|
|
379
374
|
success: false,
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MergeMode, MergeStrategy, RepoConfig } from "../config/index.js";
|
|
2
2
|
import type { IRepoLifecycleManager } from "../lifecycle/index.js";
|
|
3
3
|
import type { IRepositoryProcessor } from "../sync/index.js";
|
|
4
|
-
import type { ISettingsProcessor, IRulesetProcessor, IRepoSettingsProcessor, ILabelsProcessor } from "../settings/index.js";
|
|
4
|
+
import type { ISettingsProcessor, IRulesetProcessor, IRepoSettingsProcessor, ILabelsProcessor, BaseProcessorResult } from "../settings/index.js";
|
|
5
5
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
6
6
|
import type { ResultsCollector } from "./results-collector.js";
|
|
7
7
|
export type ProcessorFactory = () => IRepositoryProcessor;
|
|
@@ -44,10 +44,7 @@ export interface SyncResultEntry {
|
|
|
44
44
|
mergeOutcome?: "manual" | "auto" | "force" | "direct";
|
|
45
45
|
error?: string;
|
|
46
46
|
}
|
|
47
|
-
export interface SettingsResult {
|
|
48
|
-
success: boolean;
|
|
49
|
-
message: string;
|
|
50
|
-
skipped?: boolean;
|
|
47
|
+
export interface SettingsResult extends Pick<BaseProcessorResult, "success" | "message" | "skipped"> {
|
|
51
48
|
planOutput?: {
|
|
52
49
|
lines?: string[];
|
|
53
50
|
};
|
|
@@ -61,7 +58,7 @@ export interface ApplyRepoSettingsContext {
|
|
|
61
58
|
repoConfig: RepoConfig;
|
|
62
59
|
repoInfo: RepoInfo;
|
|
63
60
|
repoName: string;
|
|
64
|
-
|
|
61
|
+
repoNumber: number;
|
|
65
62
|
options: SyncOptions;
|
|
66
63
|
token: string | undefined;
|
|
67
64
|
settingsCollector: ResultsCollector;
|
package/dist/config/formatter.js
CHANGED
|
@@ -94,11 +94,6 @@ export function convertContentToString(content, fileName, options) {
|
|
|
94
94
|
defaultKeyType: "PLAIN",
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
// JSON5 format - output standard JSON (which is valid JSON5)
|
|
99
|
-
// Using JSON.stringify for standard JSON output that's compatible everywhere
|
|
100
|
-
return JSON.stringify(content, null, 2) + "\n";
|
|
101
|
-
}
|
|
102
|
-
// JSON format - comments not supported, ignore header/schemaUrl
|
|
97
|
+
// JSON and JSON5 — both use standard JSON.stringify (valid JSON5 superset)
|
|
103
98
|
return JSON.stringify(content, null, 2) + "\n";
|
|
104
99
|
}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type { MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, RepoVisibility, SquashMergeCommitTitle, SquashMergeCommitMessage, MergeCommitTitle, MergeCommitMessage, Label, RepoSettings,
|
|
1
|
+
export type { MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, RepoVisibility, SquashMergeCommitTitle, SquashMergeCommitMessage, MergeCommitTitle, MergeCommitMessage, Label, RepoSettings, RawFileConfig, RawRepoFileOverride, RawRepoSettings, RawRepoConfig, RawConfig, RepoConfig, Config, FileContent, ContentValue, } from "./types.js";
|
|
2
2
|
export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
3
3
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
4
4
|
export { convertContentToString } from "./formatter.js";
|
|
5
|
+
export { validateForSync } from "./validator.js";
|
package/dist/config/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
|
4
4
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
5
5
|
// Config formatting
|
|
6
6
|
export { convertContentToString } from "./formatter.js";
|
|
7
|
+
// Config validation
|
|
8
|
+
export { validateForSync } from "./validator.js";
|
package/dist/config/merge.d.ts
CHANGED
|
@@ -20,13 +20,7 @@ export declare function deepMerge(base: Record<string, unknown>, overlay: Record
|
|
|
20
20
|
* Standard $-prefixed keys ($schema, $id, $ref, etc.) are preserved.
|
|
21
21
|
*/
|
|
22
22
|
export declare function stripMergeDirectives(obj: Record<string, unknown>): Record<string, unknown>;
|
|
23
|
-
/**
|
|
24
|
-
* Create a default merge context.
|
|
25
|
-
*/
|
|
26
23
|
export declare function createMergeContext(defaultStrategy?: ArrayMergeStrategy): MergeContext;
|
|
27
|
-
/**
|
|
28
|
-
* Check if content is text type (string or string[]).
|
|
29
|
-
*/
|
|
30
24
|
export declare function isTextContent(content: unknown): content is string | string[];
|
|
31
25
|
/**
|
|
32
26
|
* Merge two text content values.
|
package/dist/config/merge.js
CHANGED
|
@@ -9,18 +9,11 @@ import { isPlainObject } from "../shared/type-guards.js";
|
|
|
9
9
|
* like $schema, $id, $ref, $generated are preserved.
|
|
10
10
|
*/
|
|
11
11
|
const XFG_DIRECTIVES = new Set(["$arrayMerge", "$values"]);
|
|
12
|
-
/**
|
|
13
|
-
* Strategy map for array merge operations.
|
|
14
|
-
* Extensible: add new strategies by adding to this map.
|
|
15
|
-
*/
|
|
16
12
|
const arrayMergeStrategies = new Map([
|
|
17
13
|
["replace", (_base, overlay) => overlay],
|
|
18
14
|
["append", (base, overlay) => [...base, ...overlay]],
|
|
19
15
|
["prepend", (base, overlay) => [...overlay, ...base]],
|
|
20
16
|
]);
|
|
21
|
-
/**
|
|
22
|
-
* Merge two arrays based on the specified strategy.
|
|
23
|
-
*/
|
|
24
17
|
function mergeArrays(base, overlay, strategy) {
|
|
25
18
|
const handler = arrayMergeStrategies.get(strategy);
|
|
26
19
|
if (handler) {
|
|
@@ -94,9 +87,6 @@ export function stripMergeDirectives(obj) {
|
|
|
94
87
|
}
|
|
95
88
|
return result;
|
|
96
89
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Create a default merge context.
|
|
99
|
-
*/
|
|
100
90
|
export function createMergeContext(defaultStrategy = "replace") {
|
|
101
91
|
return {
|
|
102
92
|
defaultArrayStrategy: defaultStrategy,
|
|
@@ -105,9 +95,6 @@ export function createMergeContext(defaultStrategy = "replace") {
|
|
|
105
95
|
// =============================================================================
|
|
106
96
|
// Text Content Utilities
|
|
107
97
|
// =============================================================================
|
|
108
|
-
/**
|
|
109
|
-
* Check if content is text type (string or string[]).
|
|
110
|
-
*/
|
|
111
98
|
export function isTextContent(content) {
|
|
112
99
|
return (typeof content === "string" ||
|
|
113
100
|
(Array.isArray(content) &&
|
package/dist/config/types.d.ts
CHANGED
|
@@ -8,21 +8,13 @@ export interface PRMergeOptions {
|
|
|
8
8
|
bypassReason?: string;
|
|
9
9
|
labels?: string[];
|
|
10
10
|
}
|
|
11
|
-
/** Ruleset target type */
|
|
12
11
|
export type RulesetTarget = "branch" | "tag";
|
|
13
|
-
/** Ruleset enforcement level */
|
|
14
12
|
export type RulesetEnforcement = "active" | "disabled" | "evaluate";
|
|
15
|
-
/** Bypass actor type */
|
|
16
13
|
export type BypassActorType = "Team" | "User" | "Integration";
|
|
17
|
-
/** Bypass mode - always bypass or only for PRs */
|
|
18
14
|
export type BypassMode = "always" | "pull_request";
|
|
19
|
-
/** Pattern operator for pattern-based rules */
|
|
20
15
|
type PatternOperator = "starts_with" | "ends_with" | "contains" | "regex";
|
|
21
|
-
/** Allowed merge methods */
|
|
22
16
|
export type MergeMethod = "merge" | "squash" | "rebase";
|
|
23
|
-
/** Code scanning alerts threshold */
|
|
24
17
|
export type AlertsThreshold = "none" | "errors" | "errors_and_warnings" | "all";
|
|
25
|
-
/** Security alerts threshold */
|
|
26
18
|
export type SecurityAlertsThreshold = "none" | "critical" | "high_or_higher" | "medium_or_higher" | "all";
|
|
27
19
|
export interface BypassActor {
|
|
28
20
|
actorId: number;
|
|
@@ -190,7 +182,6 @@ export interface MaxFileSizeRule {
|
|
|
190
182
|
type: "max_file_size";
|
|
191
183
|
parameters?: MaxFileSizeParameters;
|
|
192
184
|
}
|
|
193
|
-
/** Union of all rule types */
|
|
194
185
|
export type RulesetRule = PullRequestRule | RequiredStatusChecksRule | RequiredSignaturesRule | RequiredLinearHistoryRule | NonFastForwardRule | CreationRule | UpdateRule | DeletionRule | RequiredDeploymentsRule | CodeScanningRule | CodeQualityRule | WorkflowsRule | CommitAuthorEmailPatternRule | CommitMessagePatternRule | CommitterEmailPatternRule | BranchNamePatternRule | TagNamePatternRule | FilePathRestrictionRule | FileExtensionRestrictionRule | MaxFilePathLengthRule | MaxFileSizeRule;
|
|
195
186
|
/**
|
|
196
187
|
* GitHub Ruleset configuration.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RawConfig, RawRepoSettings, RawRootSettings } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Validates raw config structure before normalization.
|
|
4
|
-
* @throws
|
|
4
|
+
* @throws ValidationError if validation fails
|
|
5
5
|
*/
|
|
6
6
|
export declare function validateRawConfig(config: RawConfig): void;
|
|
7
7
|
/**
|
package/dist/config/validator.js
CHANGED
|
@@ -418,7 +418,7 @@ function hasGroupFiles(config) {
|
|
|
418
418
|
}
|
|
419
419
|
/**
|
|
420
420
|
* Validates raw config structure before normalization.
|
|
421
|
-
* @throws
|
|
421
|
+
* @throws ValidationError if validation fails
|
|
422
422
|
*/
|
|
423
423
|
export function validateRawConfig(config) {
|
|
424
424
|
validateConfigId(config);
|