@aspruyt/xfg 5.1.3 → 5.1.5
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 +20 -78
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hasLifecycleChanges } from "./lifecycle-report.js";
|
|
2
2
|
import { writeGitHubStepSummary } from "./github-summary.js";
|
|
3
|
+
import { renderSyncLines } from "./sync-report.js";
|
|
3
4
|
import { renderRepoSettingsDiffLines, formatCountEntry, } from "./settings-report.js";
|
|
4
5
|
// =============================================================================
|
|
5
6
|
// Helpers
|
|
@@ -130,25 +131,6 @@ function renderLifecycleLines(lcAction, diffLines) {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
|
-
export function renderSyncLines(syncRepo, diffLines) {
|
|
134
|
-
for (const file of syncRepo.files) {
|
|
135
|
-
if (file.action === "create") {
|
|
136
|
-
diffLines.push(`+ ${file.path}`);
|
|
137
|
-
}
|
|
138
|
-
else if (file.action === "update") {
|
|
139
|
-
diffLines.push(`! ${file.path}`);
|
|
140
|
-
}
|
|
141
|
-
else if (file.action === "delete") {
|
|
142
|
-
diffLines.push(`- ${file.path}`);
|
|
143
|
-
}
|
|
144
|
-
if (file.diffLines) {
|
|
145
|
-
diffLines.push(...file.diffLines);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (syncRepo.error) {
|
|
149
|
-
diffLines.push(`- Error: ${syncRepo.error}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
134
|
// =============================================================================
|
|
153
135
|
// Markdown Formatter
|
|
154
136
|
// =============================================================================
|
|
@@ -35,6 +35,14 @@ interface SettingsGuards<TOptions extends BaseProcessorOptions, TResult extends
|
|
|
35
35
|
export declare function withGitHubGuards<TOptions extends BaseProcessorOptions, TResult extends BaseProcessorResult>(repoConfig: RepoConfig, repoInfo: RepoInfo, options: TOptions, guards: SettingsGuards<TOptions, TResult>): Promise<TResult>;
|
|
36
36
|
/** Common action literals shared by all settings processors. */
|
|
37
37
|
export type SettingsAction = "create" | "update" | "delete" | "unchanged";
|
|
38
|
+
/** Actions that represent an actual change (excludes "unchanged"). */
|
|
39
|
+
export type ActiveAction = Exclude<SettingsAction, "unchanged">;
|
|
40
|
+
/** Type predicate that narrows entries with an active (non-"unchanged") action. */
|
|
41
|
+
export declare function isActiveAction<T extends {
|
|
42
|
+
action: SettingsAction;
|
|
43
|
+
}>(entry: T): entry is T & {
|
|
44
|
+
action: ActiveAction;
|
|
45
|
+
};
|
|
38
46
|
export interface ChangeCounts {
|
|
39
47
|
create: number;
|
|
40
48
|
update: number;
|
|
@@ -6,6 +6,9 @@ import { toErrorMessage } from "../shared/type-guards.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export async function withGitHubGuards(repoConfig, repoInfo, options, guards) {
|
|
8
8
|
const repoName = getRepoDisplayName(repoInfo);
|
|
9
|
+
// Safe cast: all TResult subtypes (RulesetProcessorResult, LabelsProcessorResult,
|
|
10
|
+
// RepoSettingsProcessorResult) only extend BaseProcessorResult with optional fields.
|
|
11
|
+
// If adding a new processor result type, ensure all extension fields are optional.
|
|
9
12
|
if (!isGitHubRepo(repoInfo)) {
|
|
10
13
|
return {
|
|
11
14
|
success: true,
|
|
@@ -34,6 +37,10 @@ export async function withGitHubGuards(repoConfig, repoInfo, options, guards) {
|
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
}
|
|
40
|
+
/** Type predicate that narrows entries with an active (non-"unchanged") action. */
|
|
41
|
+
export function isActiveAction(entry) {
|
|
42
|
+
return entry.action !== "unchanged";
|
|
43
|
+
}
|
|
37
44
|
/**
|
|
38
45
|
* Count actions from a diff result array.
|
|
39
46
|
* Works with any change type that has an `action` field.
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type
|
|
2
|
-
export { type PropertyDiff,
|
|
1
|
+
export { type BaseProcessorResult, type ISettingsProcessor, countActions, isActiveAction, } from "./base-processor.js";
|
|
2
|
+
export { type PropertyDiff, type RulesetPlanEntry, RulesetProcessor, type IRulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
|
|
3
3
|
export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsPlanEntry, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
|
|
4
4
|
export { type LabelsPlanEntry, LabelsProcessor, type ILabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
|
package/dist/settings/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// Base processor
|
|
2
|
+
export { countActions, isActiveAction, } from "./base-processor.js";
|
|
1
3
|
// Rulesets
|
|
2
|
-
export {
|
|
4
|
+
export { RulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
|
|
3
5
|
// Repo settings
|
|
4
6
|
export { RepoSettingsProcessor, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
|
|
5
7
|
// Labels
|
|
@@ -57,12 +57,13 @@ export class RepoSettingsProcessor {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
// Apply changes - only send settings that actually changed
|
|
60
|
-
const changedSettings =
|
|
60
|
+
const changedSettings = {};
|
|
61
|
+
for (const change of changes) {
|
|
61
62
|
if (change.action !== "unchanged") {
|
|
62
|
-
|
|
63
|
+
changedSettings[change.property] =
|
|
64
|
+
change.newValue;
|
|
63
65
|
}
|
|
64
|
-
|
|
65
|
-
}, {});
|
|
66
|
+
}
|
|
66
67
|
await this.applyChanges(githubRepo, changedSettings, strategyOptions);
|
|
67
68
|
const appliedCount = Object.keys(changedSettings).length;
|
|
68
69
|
return buildApplyResult(repoName, changeCounts, appliedCount, {
|
|
@@ -94,7 +95,10 @@ export class RepoSettingsProcessor {
|
|
|
94
95
|
}
|
|
95
96
|
validateSecuritySettings(desiredSettings, currentSettings) {
|
|
96
97
|
const errors = [];
|
|
97
|
-
|
|
98
|
+
// Use desired visibility if specified (repo may be transitioning to public),
|
|
99
|
+
// otherwise fall back to current visibility
|
|
100
|
+
const effectiveVisibility = desiredSettings.visibility ?? currentSettings.visibility;
|
|
101
|
+
const isPublic = effectiveVisibility === "public";
|
|
98
102
|
// privateVulnerabilityReporting is only available on public repos
|
|
99
103
|
if (desiredSettings.privateVulnerabilityReporting === true && !isPublic) {
|
|
100
104
|
errors.push("privateVulnerabilityReporting is only available for public repositories");
|
|
@@ -121,46 +121,52 @@ function getActionStyle(action) {
|
|
|
121
121
|
/**
|
|
122
122
|
* Render a leaf tree node (no children) with its value.
|
|
123
123
|
*/
|
|
124
|
-
function
|
|
124
|
+
function hasComplexValue(value) {
|
|
125
|
+
return (isPlainObject(value) ||
|
|
126
|
+
(Array.isArray(value) && value.some((v) => isPlainObject(v))));
|
|
127
|
+
}
|
|
128
|
+
function renderComplexLeaf(child, style, indentStr, indent) {
|
|
125
129
|
const lines = [];
|
|
126
|
-
|
|
127
|
-
(Array.isArray(child.newValue) &&
|
|
128
|
-
child.newValue.some((v) => isPlainObject(v)));
|
|
129
|
-
const hasComplexOld = isPlainObject(child.oldValue) ||
|
|
130
|
-
(Array.isArray(child.oldValue) &&
|
|
131
|
-
child.oldValue.some((v) => isPlainObject(v)));
|
|
132
|
-
if (child.action === "add" && hasComplexNew) {
|
|
130
|
+
if (child.action === "add") {
|
|
133
131
|
lines.push(style.color(`${indentStr}${style.symbol} ${child.name}:`));
|
|
134
132
|
lines.push(...renderNestedValue(child.newValue, child.action, indent + 1));
|
|
135
133
|
}
|
|
136
|
-
else if (child.action === "remove"
|
|
134
|
+
else if (child.action === "remove") {
|
|
137
135
|
lines.push(style.color(`${indentStr}${style.symbol} ${child.name} (removed):`));
|
|
138
136
|
lines.push(...renderNestedValue(child.oldValue, child.action, indent + 1));
|
|
139
137
|
}
|
|
140
|
-
else
|
|
138
|
+
else {
|
|
141
139
|
lines.push(style.color(`${indentStr}${style.symbol} ${child.name}:`));
|
|
142
|
-
if (
|
|
140
|
+
if (hasComplexValue(child.oldValue)) {
|
|
143
141
|
lines.push(...renderNestedValue(child.oldValue, "remove", indent + 1));
|
|
144
142
|
}
|
|
145
|
-
if (
|
|
143
|
+
if (hasComplexValue(child.newValue)) {
|
|
146
144
|
lines.push(...renderNestedValue(child.newValue, "add", indent + 1));
|
|
147
145
|
}
|
|
148
146
|
}
|
|
149
|
-
else {
|
|
150
|
-
let valuePart = "";
|
|
151
|
-
if (child.action === "change") {
|
|
152
|
-
valuePart = `: ${formatValue(child.oldValue)} → ${formatValue(child.newValue)}`;
|
|
153
|
-
}
|
|
154
|
-
else if (child.action === "add") {
|
|
155
|
-
valuePart = `: ${formatValue(child.newValue)}`;
|
|
156
|
-
}
|
|
157
|
-
else if (child.action === "remove") {
|
|
158
|
-
valuePart = ` (was: ${formatValue(child.oldValue)})`;
|
|
159
|
-
}
|
|
160
|
-
lines.push(style.color(`${indentStr}${style.symbol} ${child.name}${valuePart}`));
|
|
161
|
-
}
|
|
162
147
|
return lines;
|
|
163
148
|
}
|
|
149
|
+
function renderSimpleLeaf(child, style, indentStr) {
|
|
150
|
+
let valuePart = "";
|
|
151
|
+
if (child.action === "change") {
|
|
152
|
+
valuePart = `: ${formatValue(child.oldValue)} → ${formatValue(child.newValue)}`;
|
|
153
|
+
}
|
|
154
|
+
else if (child.action === "add") {
|
|
155
|
+
valuePart = `: ${formatValue(child.newValue)}`;
|
|
156
|
+
}
|
|
157
|
+
else if (child.action === "remove") {
|
|
158
|
+
valuePart = ` (was: ${formatValue(child.oldValue)})`;
|
|
159
|
+
}
|
|
160
|
+
return style.color(`${indentStr}${style.symbol} ${child.name}${valuePart}`);
|
|
161
|
+
}
|
|
162
|
+
function renderLeafNode(child, style, indentStr, indent) {
|
|
163
|
+
const isComplex = (child.action !== "remove" && hasComplexValue(child.newValue)) ||
|
|
164
|
+
(child.action !== "add" && hasComplexValue(child.oldValue));
|
|
165
|
+
if (isComplex) {
|
|
166
|
+
return renderComplexLeaf(child, style, indentStr, indent);
|
|
167
|
+
}
|
|
168
|
+
return [renderSimpleLeaf(child, style, indentStr)];
|
|
169
|
+
}
|
|
164
170
|
/**
|
|
165
171
|
* Recursively render tree nodes to formatted lines.
|
|
166
172
|
*/
|
|
@@ -198,85 +204,91 @@ export function formatPropertyTree(diffs) {
|
|
|
198
204
|
* Delegates to renderNestedObject which handles recursive rendering.
|
|
199
205
|
*/
|
|
200
206
|
function formatFullConfig(ruleset, indent = 2) {
|
|
201
|
-
|
|
207
|
+
// Object.entries works on any object; the cast avoids a double assertion
|
|
208
|
+
const entries = Object.entries(ruleset);
|
|
209
|
+
return renderNestedObject(Object.fromEntries(entries), "add", indent);
|
|
202
210
|
}
|
|
203
211
|
/**
|
|
204
212
|
* Format ruleset changes as a Terraform-style plan.
|
|
205
213
|
*/
|
|
206
214
|
export function formatRulesetPlan(changes) {
|
|
207
215
|
const lines = [];
|
|
208
|
-
let creates = 0;
|
|
209
|
-
let updates = 0;
|
|
210
|
-
let deletes = 0;
|
|
211
|
-
let unchanged = 0;
|
|
212
216
|
const entries = [];
|
|
213
|
-
// Group by action
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
lines.push(chalk.
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
// Group by action in a single pass
|
|
218
|
+
const grouped = {
|
|
219
|
+
create: [],
|
|
220
|
+
update: [],
|
|
221
|
+
delete: [],
|
|
222
|
+
unchanged: [],
|
|
223
|
+
};
|
|
224
|
+
for (const c of changes) {
|
|
225
|
+
grouped[c.action].push(c);
|
|
226
|
+
}
|
|
227
|
+
for (const change of grouped.create) {
|
|
228
|
+
if (grouped.create.indexOf(change) === 0) {
|
|
229
|
+
lines.push(chalk.bold(" Create:"));
|
|
230
|
+
}
|
|
231
|
+
lines.push(chalk.green(` + ruleset "${change.name}"`));
|
|
232
|
+
if (change.desired) {
|
|
233
|
+
lines.push(...formatFullConfig(change.desired, 2));
|
|
234
|
+
}
|
|
235
|
+
const propertyCount = change.desired
|
|
236
|
+
? Object.keys(change.desired).length
|
|
237
|
+
: 0;
|
|
238
|
+
entries.push({
|
|
239
|
+
name: change.name,
|
|
240
|
+
action: "create",
|
|
241
|
+
propertyCount,
|
|
242
|
+
config: change.desired,
|
|
243
|
+
});
|
|
244
|
+
lines.push("");
|
|
245
|
+
}
|
|
246
|
+
for (const change of grouped.update) {
|
|
247
|
+
if (grouped.update.indexOf(change) === 0) {
|
|
248
|
+
lines.push(chalk.bold(" Update:"));
|
|
249
|
+
}
|
|
250
|
+
lines.push(chalk.yellow(` ~ ruleset "${change.name}"`));
|
|
251
|
+
if (change.current && change.desired) {
|
|
252
|
+
const currentNorm = normalizeRuleset(change.current);
|
|
253
|
+
const desiredNorm = normalizeRuleset(change.desired);
|
|
254
|
+
const projectedCurrent = projectToDesiredShape(currentNorm, desiredNorm);
|
|
255
|
+
const diffs = computePropertyDiffs(projectedCurrent, desiredNorm);
|
|
256
|
+
const treeLines = formatPropertyTree(diffs);
|
|
257
|
+
for (const line of treeLines) {
|
|
258
|
+
lines.push(` ${line}`);
|
|
228
259
|
}
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
260
|
+
const added = diffs.filter((d) => d.action === "add").length;
|
|
261
|
+
const changed = diffs.filter((d) => d.action === "change").length;
|
|
262
|
+
const removed = diffs.filter((d) => d.action === "remove").length;
|
|
232
263
|
entries.push({
|
|
233
264
|
name: change.name,
|
|
234
|
-
action: "
|
|
235
|
-
|
|
236
|
-
|
|
265
|
+
action: "update",
|
|
266
|
+
propertyChanges: { added, changed, removed },
|
|
267
|
+
propertyDiffs: diffs,
|
|
237
268
|
});
|
|
238
|
-
lines.push(""); // Blank line between rulesets
|
|
239
269
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
lines.push(chalk.bold(" Update:"));
|
|
243
|
-
for (const change of updateChanges) {
|
|
244
|
-
lines.push(chalk.yellow(` ~ ruleset "${change.name}"`));
|
|
245
|
-
if (change.current && change.desired) {
|
|
246
|
-
const currentNorm = normalizeRuleset(change.current);
|
|
247
|
-
const desiredNorm = normalizeRuleset(change.desired);
|
|
248
|
-
const projectedCurrent = projectToDesiredShape(currentNorm, desiredNorm);
|
|
249
|
-
const diffs = computePropertyDiffs(projectedCurrent, desiredNorm);
|
|
250
|
-
const treeLines = formatPropertyTree(diffs);
|
|
251
|
-
for (const line of treeLines) {
|
|
252
|
-
lines.push(` ${line}`);
|
|
253
|
-
}
|
|
254
|
-
const added = diffs.filter((d) => d.action === "add").length;
|
|
255
|
-
const changed = diffs.filter((d) => d.action === "change").length;
|
|
256
|
-
const removed = diffs.filter((d) => d.action === "remove").length;
|
|
257
|
-
entries.push({
|
|
258
|
-
name: change.name,
|
|
259
|
-
action: "update",
|
|
260
|
-
propertyChanges: { added, changed, removed },
|
|
261
|
-
propertyDiffs: diffs,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
entries.push({ name: change.name, action: "update" });
|
|
266
|
-
}
|
|
267
|
-
lines.push(""); // Blank line between rulesets
|
|
270
|
+
else {
|
|
271
|
+
entries.push({ name: change.name, action: "update" });
|
|
268
272
|
}
|
|
273
|
+
lines.push("");
|
|
269
274
|
}
|
|
270
|
-
if (
|
|
275
|
+
if (grouped.delete.length > 0) {
|
|
271
276
|
lines.push(chalk.bold(" Delete:"));
|
|
272
|
-
for (const change of
|
|
277
|
+
for (const change of grouped.delete) {
|
|
273
278
|
lines.push(chalk.red(` - ruleset "${change.name}"`));
|
|
274
279
|
entries.push({ name: change.name, action: "delete" });
|
|
275
280
|
}
|
|
276
|
-
lines.push("");
|
|
281
|
+
lines.push("");
|
|
277
282
|
}
|
|
278
|
-
for (const change of
|
|
283
|
+
for (const change of grouped.unchanged) {
|
|
279
284
|
entries.push({ name: change.name, action: "unchanged" });
|
|
280
285
|
}
|
|
281
|
-
return {
|
|
286
|
+
return {
|
|
287
|
+
lines,
|
|
288
|
+
creates: grouped.create.length,
|
|
289
|
+
updates: grouped.update.length,
|
|
290
|
+
deletes: grouped.delete.length,
|
|
291
|
+
unchanged: grouped.unchanged.length,
|
|
292
|
+
entries,
|
|
293
|
+
};
|
|
282
294
|
}
|
|
@@ -2,7 +2,7 @@ import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
|
2
2
|
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { Ruleset } from "../../config/index.js";
|
|
5
|
-
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule } from "./types.js";
|
|
5
|
+
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetUpdateParams } from "./types.js";
|
|
6
6
|
/**
|
|
7
7
|
* Converts camelCase config ruleset to snake_case GitHub API format.
|
|
8
8
|
*/
|
|
@@ -25,7 +25,7 @@ export declare class GitHubRulesetStrategy implements IRulesetStrategy {
|
|
|
25
25
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
26
26
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
27
27
|
create(repoInfo: RepoInfo, name: string, ruleset: Ruleset, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
28
|
-
update(repoInfo: RepoInfo,
|
|
28
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
29
29
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
30
30
|
}
|
|
31
31
|
export {};
|
|
@@ -118,11 +118,14 @@ export class GitHubRulesetStrategy {
|
|
|
118
118
|
const result = await this.api.call("POST", endpoint, { payload, options });
|
|
119
119
|
return parseApiJson(result, "ruleset response");
|
|
120
120
|
}
|
|
121
|
-
async update(repoInfo,
|
|
121
|
+
async update(repoInfo, params, options) {
|
|
122
122
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
123
|
-
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${rulesetId}`;
|
|
124
|
-
const payload = configToGitHub(name, ruleset);
|
|
125
|
-
const result = await this.api.call("PUT", endpoint, {
|
|
123
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${params.rulesetId}`;
|
|
124
|
+
const payload = configToGitHub(params.name, params.ruleset);
|
|
125
|
+
const result = await this.api.call("PUT", endpoint, {
|
|
126
|
+
payload,
|
|
127
|
+
options,
|
|
128
|
+
});
|
|
126
129
|
return parseApiJson(result, "ruleset response");
|
|
127
130
|
}
|
|
128
131
|
async delete(repoInfo, rulesetId, options) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { computePropertyDiffs, deepEqual, isArrayOfObjects, type PropertyDiff, } from "./diff-algorithm.js";
|
|
2
|
-
export {
|
|
2
|
+
export { type RulesetPlanEntry } from "./formatter.js";
|
|
3
3
|
export { RulesetProcessor, type IRulesetProcessor } from "./processor.js";
|
|
4
4
|
export { GitHubRulesetStrategy } from "./github-ruleset-strategy.js";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// Diff algorithm - property-level diffing for ruleset comparisons
|
|
2
2
|
export { computePropertyDiffs, deepEqual, isArrayOfObjects, } from "./diff-algorithm.js";
|
|
3
|
-
// Formatter
|
|
4
|
-
export { formatPropertyTree } from "./formatter.js";
|
|
5
3
|
// Processor
|
|
6
4
|
export { RulesetProcessor } from "./processor.js";
|
|
7
5
|
// Strategy
|
|
@@ -56,7 +56,11 @@ export class RulesetProcessor {
|
|
|
56
56
|
break;
|
|
57
57
|
case "update":
|
|
58
58
|
if (change.rulesetId !== undefined && change.desired) {
|
|
59
|
-
await this.strategy.update(githubRepo,
|
|
59
|
+
await this.strategy.update(githubRepo, {
|
|
60
|
+
rulesetId: change.rulesetId,
|
|
61
|
+
name: change.name,
|
|
62
|
+
ruleset: change.desired,
|
|
63
|
+
}, strategyOptions);
|
|
60
64
|
appliedCount++;
|
|
61
65
|
}
|
|
62
66
|
break;
|
|
@@ -30,10 +30,15 @@ export interface GitHubRule {
|
|
|
30
30
|
type: string;
|
|
31
31
|
parameters?: Record<string, unknown>;
|
|
32
32
|
}
|
|
33
|
+
export interface RulesetUpdateParams {
|
|
34
|
+
rulesetId: number;
|
|
35
|
+
name: string;
|
|
36
|
+
ruleset: Ruleset;
|
|
37
|
+
}
|
|
33
38
|
export interface IRulesetStrategy {
|
|
34
39
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
35
40
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
36
41
|
create(repoInfo: RepoInfo, name: string, ruleset: Ruleset, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
37
|
-
update(repoInfo: RepoInfo,
|
|
42
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
38
43
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
39
44
|
}
|
|
@@ -31,7 +31,7 @@ export declare class GhApiClient {
|
|
|
31
31
|
constructor(executor: ICommandExecutor, retries: number, cwd: string);
|
|
32
32
|
call(method: HttpMethod, endpoint: string, params?: GhApiCallParams): Promise<string>;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
interface ResolveGitHubTokenOptions {
|
|
35
35
|
repoInfo: GitHubRepoInfo;
|
|
36
36
|
tokenManager: ITokenManager | null;
|
|
37
37
|
context: string;
|
package/dist/shared/logger.d.ts
CHANGED
|
@@ -27,10 +27,10 @@ export interface ILogger {
|
|
|
27
27
|
fileDiff(fileName: string, status: FileStatus, diffLines: string[]): void;
|
|
28
28
|
diffSummary(newCount: number, modifiedCount: number, unchangedCount: number, deletedCount?: number): void;
|
|
29
29
|
setTotal(total: number): void;
|
|
30
|
-
progress(
|
|
31
|
-
success(
|
|
32
|
-
skip(
|
|
33
|
-
error(
|
|
30
|
+
progress(repoNumber: number, repoName: string, message: string): void;
|
|
31
|
+
success(repoNumber: number, repoName: string, message: string): void;
|
|
32
|
+
skip(repoNumber: number, repoName: string, reason: string): void;
|
|
33
|
+
error(repoNumber: number, repoName: string, error: string): void;
|
|
34
34
|
}
|
|
35
35
|
export declare class Logger implements ILogger {
|
|
36
36
|
private readonly debugEnabled;
|
|
@@ -38,13 +38,13 @@ export declare class Logger implements ILogger {
|
|
|
38
38
|
constructor(debugEnabled?: boolean);
|
|
39
39
|
log(message: string): void;
|
|
40
40
|
setTotal(total: number): void;
|
|
41
|
-
progress(
|
|
41
|
+
progress(repoNumber: number, repoName: string, message: string): void;
|
|
42
42
|
info(message: string): void;
|
|
43
43
|
warn(message: string): void;
|
|
44
44
|
debug(message: string): void;
|
|
45
|
-
success(
|
|
46
|
-
skip(
|
|
47
|
-
error(
|
|
45
|
+
success(repoNumber: number, repoName: string, message: string): void;
|
|
46
|
+
skip(repoNumber: number, repoName: string, reason: string): void;
|
|
47
|
+
error(repoNumber: number, repoName: string, error: string): void;
|
|
48
48
|
/**
|
|
49
49
|
* Display a file diff with status badge.
|
|
50
50
|
* Used in dry-run mode to show what would change.
|
package/dist/shared/logger.js
CHANGED
|
@@ -17,8 +17,8 @@ export class Logger {
|
|
|
17
17
|
setTotal(total) {
|
|
18
18
|
this.stats.total = total;
|
|
19
19
|
}
|
|
20
|
-
progress(
|
|
21
|
-
console.log(chalk.blue(`[${
|
|
20
|
+
progress(repoNumber, repoName, message) {
|
|
21
|
+
console.log(chalk.blue(`[${repoNumber}/${this.stats.total}]`) +
|
|
22
22
|
` ${repoName}: ${message}`);
|
|
23
23
|
}
|
|
24
24
|
info(message) {
|
|
@@ -32,19 +32,20 @@ export class Logger {
|
|
|
32
32
|
console.log(chalk.dim(` [debug] ${message}`));
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
success(
|
|
35
|
+
success(repoNumber, repoName, message) {
|
|
36
36
|
this.stats.succeeded++;
|
|
37
|
-
console.log(chalk.green(`[${
|
|
37
|
+
console.log(chalk.green(`[${repoNumber}/${this.stats.total}] ✓`) +
|
|
38
38
|
` ${repoName}: ${message}`);
|
|
39
39
|
}
|
|
40
|
-
skip(
|
|
40
|
+
skip(repoNumber, repoName, reason) {
|
|
41
41
|
this.stats.skipped++;
|
|
42
|
-
console.log(chalk.yellow(`[${
|
|
42
|
+
console.log(chalk.yellow(`[${repoNumber}/${this.stats.total}] ⊘`) +
|
|
43
43
|
` ${repoName}: Skipped - ${reason}`);
|
|
44
44
|
}
|
|
45
|
-
error(
|
|
45
|
+
error(repoNumber, repoName, error) {
|
|
46
46
|
this.stats.failed++;
|
|
47
|
-
console.log(chalk.red(`[${
|
|
47
|
+
console.log(chalk.red(`[${repoNumber}/${this.stats.total}] ✗`) +
|
|
48
|
+
` ${repoName}: ${error}`);
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
51
|
* Display a file diff with status badge.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPRStrategy } from "../vcs/index.js";
|
|
2
2
|
export class BranchManager {
|
|
3
3
|
log;
|
|
4
4
|
prStrategyFactory;
|
|
5
|
-
constructor(log, prStrategyFactory =
|
|
5
|
+
constructor(log, prStrategyFactory = createPRStrategy) {
|
|
6
6
|
this.log = log;
|
|
7
7
|
this.prStrategyFactory = prStrategyFactory;
|
|
8
8
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCommitStrategy } from "../vcs/index.js";
|
|
2
2
|
import { getRepoDisplayName } from "../shared/repo-detector.js";
|
|
3
3
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
4
4
|
export class CommitPushManager {
|
|
5
5
|
log;
|
|
6
6
|
commitStrategyFactory;
|
|
7
|
-
constructor(log, commitStrategyFactory =
|
|
7
|
+
constructor(log, commitStrategyFactory = createCommitStrategy) {
|
|
8
8
|
this.log = log;
|
|
9
9
|
this.commitStrategyFactory = commitStrategyFactory;
|
|
10
10
|
}
|
|
@@ -2,9 +2,6 @@ export type { FileStatus } from "../shared/file-status.js";
|
|
|
2
2
|
export { formatStatusBadge } from "../shared/file-status.js";
|
|
3
3
|
import type { FileStatus } from "../shared/file-status.js";
|
|
4
4
|
export declare function getFileStatus(exists: boolean, changed: boolean): FileStatus;
|
|
5
|
-
/**
|
|
6
|
-
* Format a single diff line with appropriate color.
|
|
7
|
-
*/
|
|
8
5
|
export declare function formatDiffLine(line: string): string;
|
|
9
6
|
/**
|
|
10
7
|
* Check if a file is likely binary based on its extension.
|
|
@@ -30,11 +27,5 @@ export interface DiffStats {
|
|
|
30
27
|
unchangedCount: number;
|
|
31
28
|
deletedCount: number;
|
|
32
29
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Create an empty diff stats object.
|
|
35
|
-
*/
|
|
36
30
|
export declare function createDiffStats(): DiffStats;
|
|
37
|
-
/**
|
|
38
|
-
* Increment the appropriate counter in diff stats.
|
|
39
|
-
*/
|
|
40
31
|
export declare function incrementDiffStats(stats: DiffStats, status: FileStatus): void;
|
package/dist/sync/diff-utils.js
CHANGED
|
@@ -5,9 +5,6 @@ export function getFileStatus(exists, changed) {
|
|
|
5
5
|
return "NEW";
|
|
6
6
|
return changed ? "MODIFIED" : "UNCHANGED";
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Format a single diff line with appropriate color.
|
|
10
|
-
*/
|
|
11
8
|
export function formatDiffLine(line) {
|
|
12
9
|
if (line.startsWith("+"))
|
|
13
10
|
return chalk.green(line);
|
|
@@ -261,15 +258,9 @@ function groupIntoHunks(ops, oldLines, newLines, contextLines) {
|
|
|
261
258
|
}
|
|
262
259
|
return hunks;
|
|
263
260
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Create an empty diff stats object.
|
|
266
|
-
*/
|
|
267
261
|
export function createDiffStats() {
|
|
268
262
|
return { newCount: 0, modifiedCount: 0, unchangedCount: 0, deletedCount: 0 };
|
|
269
263
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Increment the appropriate counter in diff stats.
|
|
272
|
-
*/
|
|
273
264
|
export function incrementDiffStats(stats, status) {
|
|
274
265
|
switch (status) {
|
|
275
266
|
case "NEW":
|
|
@@ -22,7 +22,7 @@ export class FileSyncOrchestrator {
|
|
|
22
22
|
hasAppCredentials: options.hasAppCredentials,
|
|
23
23
|
}, { gitOps: session.gitOps, log: this.log });
|
|
24
24
|
const filesWithDeleteOrphaned = new Map(repoConfig.files.map((f) => [f.fileName, f.deleteOrphaned]));
|
|
25
|
-
const { manifest: newManifest, existingManifest, filesToDelete, } = this.manifestManager.
|
|
25
|
+
const { manifest: newManifest, existingManifest, filesToDelete, } = this.manifestManager.detectOrphans(workDir, configId, filesWithDeleteOrphaned);
|
|
26
26
|
this.manifestManager.deleteOrphans(filesToDelete, { dryRun: dryRun, noDelete: noDelete }, { gitOps: session.gitOps, log: this.log, fileChanges });
|
|
27
27
|
// Save manifest (may add to fileChanges)
|
|
28
28
|
this.manifestManager.saveUpdatedManifest(workDir, newManifest, existingManifest, dryRun, fileChanges);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RepoConfig } from "../config/
|
|
1
|
+
import type { RepoConfig } from "../config/index.js";
|
|
2
2
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
3
3
|
import type { IWorkStrategy, WorkResult, SessionContext, ProcessorOptions, IFileSyncOrchestrator } from "./types.js";
|
|
4
4
|
/**
|