@aspruyt/xfg 5.1.3 → 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.
Files changed (81) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/lifecycle-report-builder.d.ts +2 -0
  3. package/dist/cli/lifecycle-report-builder.js +15 -0
  4. package/dist/cli/program.js +16 -11
  5. package/dist/cli/settings-report-builder.d.ts +1 -1
  6. package/dist/cli/settings-report-builder.js +20 -32
  7. package/dist/cli/sync-command.js +59 -64
  8. package/dist/cli/types.d.ts +3 -6
  9. package/dist/config/formatter.js +1 -6
  10. package/dist/config/index.d.ts +2 -1
  11. package/dist/config/index.js +2 -0
  12. package/dist/config/merge.d.ts +0 -6
  13. package/dist/config/merge.js +0 -13
  14. package/dist/config/types.d.ts +0 -9
  15. package/dist/config/validator.d.ts +1 -1
  16. package/dist/config/validator.js +1 -1
  17. package/dist/lifecycle/github-lifecycle-provider.js +28 -45
  18. package/dist/lifecycle/lifecycle-helpers.d.ts +1 -1
  19. package/dist/lifecycle/repo-lifecycle-manager.d.ts +1 -1
  20. package/dist/lifecycle/types.d.ts +4 -4
  21. package/dist/output/github-summary.d.ts +0 -50
  22. package/dist/output/github-summary.js +0 -201
  23. package/dist/output/lifecycle-report.d.ts +0 -1
  24. package/dist/output/lifecycle-report.js +0 -15
  25. package/dist/output/settings-report.d.ts +1 -1
  26. package/dist/output/settings-report.js +15 -77
  27. package/dist/output/sync-report.d.ts +1 -0
  28. package/dist/output/sync-report.js +19 -1
  29. package/dist/output/unified-summary.d.ts +1 -2
  30. package/dist/output/unified-summary.js +1 -19
  31. package/dist/settings/base-processor.d.ts +8 -0
  32. package/dist/settings/base-processor.js +7 -0
  33. package/dist/settings/index.d.ts +2 -2
  34. package/dist/settings/index.js +3 -1
  35. package/dist/settings/labels/converter.d.ts +1 -1
  36. package/dist/settings/labels/diff.d.ts +1 -1
  37. package/dist/settings/labels/formatter.d.ts +1 -1
  38. package/dist/settings/repo-settings/processor.js +9 -5
  39. package/dist/settings/rulesets/formatter.js +97 -85
  40. package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -2
  41. package/dist/settings/rulesets/github-ruleset-strategy.js +7 -4
  42. package/dist/settings/rulesets/index.d.ts +1 -1
  43. package/dist/settings/rulesets/index.js +0 -2
  44. package/dist/settings/rulesets/processor.js +5 -1
  45. package/dist/settings/rulesets/types.d.ts +6 -1
  46. package/dist/shared/gh-api-utils.d.ts +1 -1
  47. package/dist/shared/logger.d.ts +8 -8
  48. package/dist/shared/logger.js +9 -8
  49. package/dist/shared/repo-detector.js +0 -1
  50. package/dist/sync/branch-manager.js +2 -2
  51. package/dist/sync/commit-push-manager.js +2 -2
  52. package/dist/sync/diff-utils.d.ts +0 -9
  53. package/dist/sync/diff-utils.js +0 -9
  54. package/dist/sync/file-sync-orchestrator.js +1 -1
  55. package/dist/sync/file-sync-strategy.d.ts +1 -1
  56. package/dist/sync/file-writer.d.ts +1 -1
  57. package/dist/sync/file-writer.js +3 -1
  58. package/dist/sync/index.d.ts +1 -2
  59. package/dist/sync/index.js +1 -1
  60. package/dist/sync/manifest-manager.d.ts +1 -1
  61. package/dist/sync/manifest-manager.js +1 -1
  62. package/dist/sync/manifest.js +9 -1
  63. package/dist/sync/pr-merge-handler.js +2 -2
  64. package/dist/sync/sync-workflow.d.ts +1 -1
  65. package/dist/sync/types.d.ts +2 -2
  66. package/dist/vcs/authenticated-git-ops.d.ts +2 -1
  67. package/dist/vcs/commit-strategy-selector.d.ts +1 -1
  68. package/dist/vcs/commit-strategy-selector.js +1 -1
  69. package/dist/vcs/git-ops.d.ts +0 -1
  70. package/dist/vcs/git-ops.js +15 -8
  71. package/dist/vcs/github-pr-strategy.d.ts +0 -3
  72. package/dist/vcs/github-pr-strategy.js +4 -7
  73. package/dist/vcs/index.d.ts +3 -3
  74. package/dist/vcs/index.js +2 -2
  75. package/dist/vcs/pr-creator.js +9 -9
  76. package/dist/vcs/pr-strategy-factory.d.ts +1 -1
  77. package/dist/vcs/pr-strategy-factory.js +1 -1
  78. package/dist/vcs/pr-strategy.js +6 -11
  79. package/package.json +1 -1
  80. package/dist/output/summary-utils.d.ts +0 -20
  81. package/dist/output/summary-utils.js +0 -79
@@ -35,6 +35,26 @@ const POST_CREATE_PERMANENT_PATTERNS = DEFAULT_PERMANENT_ERROR_PATTERNS.filter((
35
35
  * Interval between fork readiness checks (2 seconds).
36
36
  */
37
37
  const FORK_POLL_INTERVAL_MS = 2_000;
38
+ function buildRepoCreateFlags(parts, settings) {
39
+ if (settings?.visibility === "public") {
40
+ parts.push("--public");
41
+ }
42
+ else if (settings?.visibility === "internal") {
43
+ parts.push("--internal");
44
+ }
45
+ else {
46
+ parts.push("--private");
47
+ }
48
+ if (settings?.description) {
49
+ parts.push("--description", escapeShellArg(settings.description));
50
+ }
51
+ if (settings?.hasIssues === false) {
52
+ parts.push("--disable-issues");
53
+ }
54
+ if (settings?.hasWiki === false) {
55
+ parts.push("--disable-wiki");
56
+ }
57
+ }
38
58
  export class GitHubLifecycleProvider {
39
59
  platform = "github";
40
60
  executor;
@@ -127,27 +147,7 @@ export class GitHubLifecycleProvider {
127
147
  "gh repo create",
128
148
  escapeShellArg(`${repoInfo.owner}/${repoInfo.repo}`),
129
149
  ];
130
- // Visibility flag (default to private for safety)
131
- if (settings?.visibility === "public") {
132
- parts.push("--public");
133
- }
134
- else if (settings?.visibility === "internal") {
135
- parts.push("--internal");
136
- }
137
- else {
138
- parts.push("--private");
139
- }
140
- // Description
141
- if (settings?.description) {
142
- parts.push("--description", escapeShellArg(settings.description));
143
- }
144
- // Disable features if specified
145
- if (settings?.hasIssues === false) {
146
- parts.push("--disable-issues");
147
- }
148
- if (settings?.hasWiki === false) {
149
- parts.push("--disable-wiki");
150
- }
150
+ buildRepoCreateFlags(parts, settings);
151
151
  // Add --add-readme to establish the default branch via an initial commit.
152
152
  // This avoids empty repos where HEAD doesn't resolve.
153
153
  parts.push("--add-readme");
@@ -312,27 +312,7 @@ export class GitHubLifecycleProvider {
312
312
  escapeShellArg(sourceDir),
313
313
  "--push",
314
314
  ];
315
- // Visibility flag (default to private for safety)
316
- if (settings?.visibility === "public") {
317
- parts.push("--public");
318
- }
319
- else if (settings?.visibility === "internal") {
320
- parts.push("--internal");
321
- }
322
- else {
323
- parts.push("--private");
324
- }
325
- // Description
326
- if (settings?.description) {
327
- parts.push("--description", escapeShellArg(settings.description));
328
- }
329
- // Disable features if specified
330
- if (settings?.hasIssues === false) {
331
- parts.push("--disable-issues");
332
- }
333
- if (settings?.hasWiki === false) {
334
- parts.push("--disable-wiki");
335
- }
315
+ buildRepoCreateFlags(parts, settings);
336
316
  const command = parts.join(" ");
337
317
  await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), {
338
318
  retries: this.retries,
@@ -361,8 +341,8 @@ export class GitHubLifecycleProvider {
361
341
  const timeoutMs = options?.timeoutMs ?? 15000;
362
342
  const pollMs = options?.pollMs ?? 1000;
363
343
  const { tokenEnv, prefix, apiPath } = this.buildGhApiPrefix(repoInfo, options?.token);
364
- const startTime = Date.now();
365
- while (Date.now() - startTime < timeoutMs) {
344
+ const deadline = Date.now() + timeoutMs;
345
+ while (Date.now() < deadline) {
366
346
  try {
367
347
  const branch = (await this.executor.exec(`${prefix}${apiPath} --jq '.default_branch'`, this.cwd, { env: tokenEnv })).trim();
368
348
  if (branch === expectedBranch) {
@@ -372,7 +352,10 @@ export class GitHubLifecycleProvider {
372
352
  catch (error) {
373
353
  this.log?.debug(`Polling default branch: ${toErrorMessage(error)}`);
374
354
  }
375
- await new Promise((resolve) => setTimeout(resolve, pollMs));
355
+ const remaining = deadline - Date.now();
356
+ if (remaining <= 0)
357
+ break;
358
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollMs, remaining)));
376
359
  }
377
360
  // Don't throw — rename succeeded, this is just a best-effort wait
378
361
  }
@@ -1,4 +1,4 @@
1
- import type { RepoConfig, GitHubRepoSettings } from "../config/types.js";
1
+ import type { RepoConfig, GitHubRepoSettings } from "../config/index.js";
2
2
  import type { RepoInfo } from "../shared/repo-detector.js";
3
3
  import type { IRepoLifecycleManager, CreateRepoSettings, LifecycleResult } from "./types.js";
4
4
  interface LifecycleCheckOptions {
@@ -1,7 +1,7 @@
1
1
  import { type RepoInfo } from "../shared/repo-detector.js";
2
2
  import { type DebugInfoWarnLog } from "../shared/logger.js";
3
3
  import type { ICommandExecutor } from "../shared/command-executor.js";
4
- import type { RepoConfig } from "../config/types.js";
4
+ import type { RepoConfig } from "../config/index.js";
5
5
  import type { IRepoLifecycleManager, IRepoLifecycleFactory, LifecycleResult, LifecycleOptions, CreateRepoSettings } from "./types.js";
6
6
  /**
7
7
  * Orchestrates repo lifecycle operations before sync.
@@ -1,5 +1,5 @@
1
1
  import type { RepoInfo } from "../shared/repo-detector.js";
2
- import type { RepoConfig } from "../config/types.js";
2
+ import type { RepoConfig } from "../config/index.js";
3
3
  export type LifecyclePlatform = "github" | "azure-devops" | "gitlab";
4
4
  export interface LifecycleResult {
5
5
  repoInfo: RepoInfo;
@@ -32,7 +32,7 @@ export interface IRepoLifecycleProvider {
32
32
  readonly platform: LifecyclePlatform;
33
33
  /**
34
34
  * Check if a repository exists on this platform.
35
- * @throws Error on network/auth failures (NOT for "repo not found")
35
+ * @throws LifecycleError on network/auth failures (NOT for "repo not found")
36
36
  */
37
37
  exists(repoInfo: RepoInfo, token?: string): Promise<boolean>;
38
38
  /**
@@ -64,12 +64,12 @@ export interface IMigrationSource {
64
64
  export interface IRepoLifecycleFactory {
65
65
  /**
66
66
  * Get lifecycle provider for a platform.
67
- * @throws Error if platform not supported as target
67
+ * @throws LifecycleError if platform not supported as target
68
68
  */
69
69
  getProvider(platform: LifecyclePlatform): IRepoLifecycleProvider;
70
70
  /**
71
71
  * Get migration source for a platform.
72
- * @throws Error if platform not supported as source
72
+ * @throws LifecycleError if platform not supported as source
73
73
  */
74
74
  getMigrationSource(platform: LifecyclePlatform): IMigrationSource;
75
75
  }
@@ -1,56 +1,6 @@
1
1
  import type { DebugLog } from "../shared/logger.js";
2
- import type { SettingsAction } from "../settings/index.js";
3
- export type MergeOutcome = "manual" | "auto" | "force" | "direct";
4
- export interface FileChanges {
5
- added: number;
6
- modified: number;
7
- deleted: number;
8
- unchanged: number;
9
- }
10
- export interface RulesetPlanDetail {
11
- name: string;
12
- action: SettingsAction;
13
- propertyCount?: number;
14
- propertyChanges?: {
15
- added: number;
16
- changed: number;
17
- removed: number;
18
- };
19
- }
20
- export interface RepoSettingsPlanDetail {
21
- property: string;
22
- action: "create" | "update";
23
- }
24
- export interface LabelsPlanDetail {
25
- name: string;
26
- action: SettingsAction;
27
- newName?: string;
28
- }
29
- export interface RepoResult {
30
- repoName: string;
31
- status: "succeeded" | "skipped" | "failed";
32
- message: string;
33
- prUrl?: string;
34
- mergeOutcome?: MergeOutcome;
35
- fileChanges?: FileChanges;
36
- rulesetPlanDetails?: RulesetPlanDetail[];
37
- repoSettingsPlanDetails?: RepoSettingsPlanDetail[];
38
- labelsPlanDetails?: LabelsPlanDetail[];
39
- }
40
- export interface SummaryData {
41
- title: string;
42
- dryRun?: boolean;
43
- total: number;
44
- succeeded: number;
45
- skipped: number;
46
- failed: number;
47
- results: RepoResult[];
48
- }
49
- export declare function formatSummary(data: SummaryData): string;
50
- export declare function shouldWriteSummary(summaryPath: string | undefined): boolean;
51
2
  /**
52
3
  * Append markdown content to GITHUB_STEP_SUMMARY.
53
4
  * No-op if summaryPath is not provided.
54
5
  */
55
6
  export declare function writeGitHubStepSummary(markdown: string, summaryPath: string | undefined, log?: DebugLog): void;
56
- export declare function writeSummary(data: SummaryData, summaryPath: string | undefined): void;
@@ -1,202 +1,5 @@
1
1
  import { appendFileSync } from "node:fs";
2
2
  import { toErrorMessage } from "../shared/type-guards.js";
3
- function escapeMarkdown(text) {
4
- // Escape backslashes first, then pipes (order matters to prevent double-escaping)
5
- return text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
6
- }
7
- function formatFileChanges(changes) {
8
- if (!changes)
9
- return "-";
10
- return `+${changes.added} ~${changes.modified} -${changes.deleted}`;
11
- }
12
- function formatChangesColumn(result) {
13
- if (result.fileChanges) {
14
- return formatFileChanges(result.fileChanges);
15
- }
16
- // For settings results, derive changes from plan details
17
- const parts = [];
18
- if (result.rulesetPlanDetails && result.rulesetPlanDetails.length > 0) {
19
- parts.push(formatPlanSummary(result.rulesetPlanDetails));
20
- }
21
- if (result.repoSettingsPlanDetails &&
22
- result.repoSettingsPlanDetails.length > 0) {
23
- parts.push(formatPlanSummary(result.repoSettingsPlanDetails));
24
- }
25
- if (result.labelsPlanDetails && result.labelsPlanDetails.length > 0) {
26
- parts.push(formatPlanSummary(result.labelsPlanDetails));
27
- }
28
- return parts.length > 0 ? parts.join("; ") : "-";
29
- }
30
- function formatStatus(result, dryRun) {
31
- if (result.status === "skipped")
32
- return dryRun ? "⏭️ Would Skip" : "⏭️ Skipped";
33
- if (result.status === "failed")
34
- return dryRun ? "❌ Would Fail" : "❌ Failed";
35
- // Succeeded - format based on merge outcome
36
- switch (result.mergeOutcome) {
37
- case "manual":
38
- return dryRun ? "✅ Would Open" : "✅ Open";
39
- case "auto":
40
- return dryRun ? "✅ Would Auto-merge" : "✅ Auto-merge";
41
- case "force":
42
- return dryRun ? "✅ Would Merge" : "✅ Merged";
43
- case "direct":
44
- return dryRun ? "✅ Would Push" : "✅ Pushed";
45
- default:
46
- return dryRun ? "✅ Would Succeed" : "✅ Succeeded";
47
- }
48
- }
49
- function formatResult(result) {
50
- if (result.prUrl) {
51
- // Extract PR number from URL
52
- const prMatch = result.prUrl.match(/\/pull\/(\d+)/);
53
- const prNum = prMatch ? prMatch[1] : "PR";
54
- return `[PR #${prNum}](${result.prUrl})`;
55
- }
56
- if (result.mergeOutcome === "direct") {
57
- return "Direct to main";
58
- }
59
- return escapeMarkdown(result.message);
60
- }
61
- function formatRulesetAction(action) {
62
- switch (action) {
63
- case "create":
64
- return "+ Create";
65
- case "update":
66
- return "~ Update";
67
- case "delete":
68
- return "- Delete";
69
- case "unchanged":
70
- return "= Unchanged";
71
- default:
72
- return action;
73
- }
74
- }
75
- function formatRulesetProperties(detail) {
76
- if (detail.propertyChanges) {
77
- return `+${detail.propertyChanges.added} ~${detail.propertyChanges.changed} -${detail.propertyChanges.removed}`;
78
- }
79
- if (detail.propertyCount !== undefined) {
80
- return `${detail.propertyCount} properties`;
81
- }
82
- return "-";
83
- }
84
- function formatPlanSummary(details) {
85
- const counts = {};
86
- for (const d of details) {
87
- counts[d.action] = (counts[d.action] ?? 0) + 1;
88
- }
89
- const parts = [];
90
- if (counts.create)
91
- parts.push(`${counts.create} to create`);
92
- if (counts.update)
93
- parts.push(`${counts.update} to update`);
94
- if (counts.delete)
95
- parts.push(`${counts.delete} to delete`);
96
- return parts.join(", ") || "no changes";
97
- }
98
- function formatSettingsAction(action) {
99
- switch (action) {
100
- case "create":
101
- return "+ Create";
102
- case "update":
103
- return "~ Update";
104
- default:
105
- return action;
106
- }
107
- }
108
- export function formatSummary(data) {
109
- const lines = [];
110
- // Header
111
- const titleSuffix = data.dryRun ? " (Dry Run)" : "";
112
- lines.push(`## ${data.title}${titleSuffix}`);
113
- lines.push("");
114
- // Dry-run warning banner
115
- if (data.dryRun) {
116
- lines.push("> [!WARNING]");
117
- lines.push("> This was a dry run — no changes were applied");
118
- lines.push("");
119
- }
120
- // Stats table
121
- const succeededLabel = data.dryRun ? "✅ Would Succeed" : "✅ Succeeded";
122
- const skippedLabel = data.dryRun ? "⏭️ Would Skip" : "⏭️ Skipped";
123
- const failedLabel = data.dryRun ? "❌ Would Fail" : "❌ Failed";
124
- lines.push("| Status | Count |");
125
- lines.push("|--------|-------|");
126
- lines.push(`| ${succeededLabel} | ${data.succeeded} |`);
127
- lines.push(`| ${skippedLabel} | ${data.skipped} |`);
128
- lines.push(`| ${failedLabel} | ${data.failed} |`);
129
- lines.push(`| **Total** | **${data.total}** |`);
130
- // Repo details table (only if there are results)
131
- if (data.results.length > 0) {
132
- lines.push("");
133
- lines.push("<details>");
134
- lines.push("<summary>Repository Details</summary>");
135
- lines.push("");
136
- lines.push("| Repository | Status | Changes | Result |");
137
- lines.push("|------------|--------|---------|--------|");
138
- for (const result of data.results) {
139
- const repo = result.repoName;
140
- const status = formatStatus(result, data.dryRun);
141
- const changes = formatChangesColumn(result);
142
- const resultText = formatResult(result);
143
- lines.push(`| ${repo} | ${status} | ${changes} | ${resultText} |`);
144
- }
145
- // Plan details nested sections
146
- for (const result of data.results) {
147
- if (result.rulesetPlanDetails && result.rulesetPlanDetails.length > 0) {
148
- lines.push("");
149
- lines.push("<details>");
150
- lines.push(`<summary>${result.repoName} — Rulesets: ${formatPlanSummary(result.rulesetPlanDetails)}</summary>`);
151
- lines.push("");
152
- lines.push("| Ruleset | Action | Properties |");
153
- lines.push("|---------|--------|------------|");
154
- for (const detail of result.rulesetPlanDetails) {
155
- lines.push(`| ${detail.name} | ${formatRulesetAction(detail.action)} | ${formatRulesetProperties(detail)} |`);
156
- }
157
- lines.push("");
158
- lines.push("</details>");
159
- }
160
- if (result.repoSettingsPlanDetails &&
161
- result.repoSettingsPlanDetails.length > 0) {
162
- lines.push("");
163
- lines.push("<details>");
164
- lines.push(`<summary>${result.repoName} — Repo Settings: ${formatPlanSummary(result.repoSettingsPlanDetails)}</summary>`);
165
- lines.push("");
166
- lines.push("| Setting | Action |");
167
- lines.push("|---------|--------|");
168
- for (const detail of result.repoSettingsPlanDetails) {
169
- lines.push(`| ${detail.property} | ${formatSettingsAction(detail.action)} |`);
170
- }
171
- lines.push("");
172
- lines.push("</details>");
173
- }
174
- if (result.labelsPlanDetails && result.labelsPlanDetails.length > 0) {
175
- lines.push("");
176
- lines.push("<details>");
177
- lines.push(`<summary>${result.repoName} — Labels: ${formatPlanSummary(result.labelsPlanDetails)}</summary>`);
178
- lines.push("");
179
- lines.push("| Label | Action |");
180
- lines.push("|-------|--------|");
181
- for (const detail of result.labelsPlanDetails) {
182
- const action = formatRulesetAction(detail.action);
183
- const name = detail.newName
184
- ? `${detail.name} \u2192 ${detail.newName}`
185
- : detail.name;
186
- lines.push(`| ${name} | ${action} |`);
187
- }
188
- lines.push("");
189
- lines.push("</details>");
190
- }
191
- }
192
- lines.push("");
193
- lines.push("</details>");
194
- }
195
- return lines.join("\n");
196
- }
197
- export function shouldWriteSummary(summaryPath) {
198
- return !!summaryPath;
199
- }
200
3
  /**
201
4
  * Append markdown content to GITHUB_STEP_SUMMARY.
202
5
  * No-op if summaryPath is not provided.
@@ -212,7 +15,3 @@ export function writeGitHubStepSummary(markdown, summaryPath, log) {
212
15
  log?.debug(`Failed to write GitHub step summary: ${toErrorMessage(error)}`);
213
16
  }
214
17
  }
215
- export function writeSummary(data, summaryPath) {
216
- const markdown = formatSummary(data);
217
- writeGitHubStepSummary(markdown, summaryPath);
218
- }
@@ -17,7 +17,6 @@ export interface LifecycleAction {
17
17
  description?: string;
18
18
  };
19
19
  }
20
- export declare function buildLifecycleReport(results: LifecycleAction[]): LifecycleReport;
21
20
  /**
22
21
  * Returns true if the report has any non-"existed" actions worth displaying.
23
22
  */
@@ -1,21 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { writeGitHubStepSummary } from "./github-summary.js";
3
3
  import { formatCountEntry } from "./settings-report.js";
4
- export function buildLifecycleReport(results) {
5
- const actions = [];
6
- const totals = { created: 0, forked: 0, migrated: 0, existed: 0 };
7
- for (const result of results) {
8
- actions.push({
9
- repoName: result.repoName,
10
- action: result.action,
11
- upstream: result.upstream,
12
- source: result.source,
13
- settings: result.settings,
14
- });
15
- totals[result.action]++;
16
- }
17
- return { actions, totals };
18
- }
19
4
  function formatLifecycleSummary(totals) {
20
5
  const entry = formatCountEntry("repo", "repos", [
21
6
  { label: "to create", value: totals.created },
@@ -1,4 +1,4 @@
1
- import { type PropertyDiff } from "../settings/index.js";
1
+ import type { PropertyDiff } from "../settings/index.js";
2
2
  import type { Ruleset, Label } from "../config/index.js";
3
3
  export interface SettingsReport {
4
4
  repos: RepoChanges[];
@@ -1,5 +1,4 @@
1
1
  import chalk from "chalk";
2
- import { formatPropertyTree } from "../settings/index.js";
3
2
  import { writeGitHubStepSummary } from "./github-summary.js";
4
3
  /**
5
4
  * Shared recursive renderer for ruleset config objects.
@@ -54,9 +53,6 @@ function renderRulesetConfig(config, startDepth, formatLine) {
54
53
  }
55
54
  return lines;
56
55
  }
57
- function formatRulesetConfig(config, indent) {
58
- return renderRulesetConfig(config, indent, (depth, text) => chalk.green(`${" ".repeat(depth)}${text}`));
59
- }
60
56
  /**
61
57
  * Formats a summary entry like "3 files (1 to create, 2 to update)".
62
58
  * Returns null if total is 0.
@@ -98,6 +94,17 @@ function formatSettingsSummary(totals) {
98
94
  }
99
95
  return `Plan: ${parts.join(", ")}`;
100
96
  }
97
+ function colorizeDiffLine(line) {
98
+ const prefix = line.charAt(0);
99
+ const indented = ` ${line}`;
100
+ if (prefix === "+")
101
+ return chalk.green(indented);
102
+ if (prefix === "!")
103
+ return chalk.yellow(indented);
104
+ if (prefix === "-")
105
+ return chalk.red(indented);
106
+ return indented;
107
+ }
101
108
  export function formatSettingsReportCLI(report) {
102
109
  const lines = [];
103
110
  for (const repo of report.repos) {
@@ -107,80 +114,11 @@ export function formatSettingsReportCLI(report) {
107
114
  !repo.error) {
108
115
  continue;
109
116
  }
110
- // Repo header
111
117
  lines.push(chalk.yellow(`~ ${repo.repoName}`));
112
- // Settings
113
- for (const setting of repo.settings) {
114
- // Skip settings where both values are undefined
115
- if (setting.oldValue === undefined && setting.newValue === undefined) {
116
- continue;
117
- }
118
- if (setting.action === "create") {
119
- lines.push(chalk.green(` + ${setting.name}: ${formatValuePlain(setting.newValue)}`));
120
- }
121
- else {
122
- lines.push(chalk.yellow(` ~ ${setting.name}: ${formatValuePlain(setting.oldValue)} → ${formatValuePlain(setting.newValue)}`));
123
- }
124
- }
125
- // Rulesets
126
- for (const ruleset of repo.rulesets) {
127
- if (ruleset.action === "create") {
128
- lines.push(chalk.green(` + ruleset "${ruleset.name}"`));
129
- if (ruleset.config) {
130
- lines.push(...formatRulesetConfig(ruleset.config, 2));
131
- }
132
- }
133
- else if (ruleset.action === "update") {
134
- lines.push(chalk.yellow(` ~ ruleset "${ruleset.name}"`));
135
- if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
136
- const treeLines = formatPropertyTree(ruleset.propertyDiffs);
137
- for (const line of treeLines) {
138
- lines.push(` ${line}`);
139
- }
140
- }
141
- }
142
- else if (ruleset.action === "delete") {
143
- lines.push(chalk.red(` - ruleset "${ruleset.name}"`));
144
- }
145
- }
146
- // Labels
147
- for (const label of repo.labels) {
148
- if (label.action === "create") {
149
- lines.push(chalk.green(` + label "${label.name}"`));
150
- if (label.config) {
151
- lines.push(chalk.green(` color: "${label.config.color}"`));
152
- if (label.config.description !== undefined) {
153
- lines.push(chalk.green(` description: "${label.config.description}"`));
154
- }
155
- }
156
- }
157
- else if (label.action === "update") {
158
- if (label.newName) {
159
- lines.push(chalk.yellow(` ~ label "${label.name}" \u2192 "${label.newName}"`));
160
- }
161
- else {
162
- lines.push(chalk.yellow(` ~ label "${label.name}"`));
163
- }
164
- if (label.propertyChanges) {
165
- for (const prop of label.propertyChanges) {
166
- if (prop.property === "new_name")
167
- continue;
168
- if (prop.oldValue !== undefined) {
169
- lines.push(chalk.yellow(` ${prop.property}: "${prop.oldValue}" \u2192 "${prop.newValue}"`));
170
- }
171
- else {
172
- lines.push(chalk.yellow(` ${prop.property}: "${prop.newValue}"`));
173
- }
174
- }
175
- }
176
- }
177
- else if (label.action === "delete") {
178
- lines.push(chalk.red(` - label "${label.name}"`));
179
- }
180
- }
181
- // Error
182
- if (repo.error) {
183
- lines.push(chalk.red(` Error: ${repo.error}`));
118
+ const diffLines = [];
119
+ renderRepoSettingsDiffLines(repo, diffLines);
120
+ for (const diffLine of diffLines) {
121
+ lines.push(colorizeDiffLine(diffLine));
184
122
  }
185
123
  lines.push(""); // Blank line between repos
186
124
  }
@@ -2,4 +2,5 @@ import type { SyncReport, RepoFileChanges, ReportFileChange } from "./types.js";
2
2
  export type { SyncReport, RepoFileChanges, ReportFileChange };
3
3
  export declare function formatSyncReportCLI(report: SyncReport): string[];
4
4
  export declare function formatSyncReportMarkdown(report: SyncReport, dryRun: boolean): string;
5
+ export declare function renderSyncLines(syncRepo: RepoFileChanges, diffLines: string[]): void;
5
6
  export declare function writeSyncReportSummary(report: SyncReport, dryRun: boolean, summaryPath: string | undefined): void;
@@ -1,7 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { writeGitHubStepSummary } from "./github-summary.js";
3
3
  import { formatCountEntry } from "./settings-report.js";
4
- import { renderSyncLines } from "./unified-summary.js";
5
4
  import { formatDiffLine } from "../sync/index.js";
6
5
  function formatSyncSummary(totals) {
7
6
  const entry = formatCountEntry("file", "files", [
@@ -78,6 +77,25 @@ export function formatSyncReportMarkdown(report, dryRun) {
78
77
  lines.push(`**${formatSyncSummary(report.totals)}**`);
79
78
  return lines.join("\n");
80
79
  }
80
+ export function renderSyncLines(syncRepo, diffLines) {
81
+ for (const file of syncRepo.files) {
82
+ if (file.action === "create") {
83
+ diffLines.push(`+ ${file.path}`);
84
+ }
85
+ else if (file.action === "update") {
86
+ diffLines.push(`! ${file.path}`);
87
+ }
88
+ else if (file.action === "delete") {
89
+ diffLines.push(`- ${file.path}`);
90
+ }
91
+ if (file.diffLines) {
92
+ diffLines.push(...file.diffLines);
93
+ }
94
+ }
95
+ if (syncRepo.error) {
96
+ diffLines.push(`- Error: ${syncRepo.error}`);
97
+ }
98
+ }
81
99
  export function writeSyncReportSummary(report, dryRun, summaryPath) {
82
100
  const markdown = formatSyncReportMarkdown(report, dryRun);
83
101
  writeGitHubStepSummary(markdown, summaryPath);
@@ -1,5 +1,5 @@
1
1
  import type { LifecycleReport } from "./lifecycle-report.js";
2
- import type { SyncReport, RepoFileChanges } from "./types.js";
2
+ import type { SyncReport } from "./types.js";
3
3
  import type { SettingsReport } from "./settings-report.js";
4
4
  interface UnifiedSummaryInput {
5
5
  lifecycle?: LifecycleReport;
@@ -8,7 +8,6 @@ interface UnifiedSummaryInput {
8
8
  dryRun: boolean;
9
9
  summaryPath?: string | undefined;
10
10
  }
11
- export declare function renderSyncLines(syncRepo: RepoFileChanges, diffLines: string[]): void;
12
11
  export declare function formatUnifiedSummaryMarkdown(input: UnifiedSummaryInput): string;
13
12
  export declare function writeUnifiedSummary(input: UnifiedSummaryInput): void;
14
13
  export {};
@@ -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
  // =============================================================================