@aspruyt/xfg 4.0.0 → 4.0.1

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 (173) hide show
  1. package/dist/cli/index.d.ts +1 -2
  2. package/dist/cli/index.js +0 -1
  3. package/dist/cli/program.js +7 -2
  4. package/dist/cli/{settings/results-collector.d.ts → results-collector.d.ts} +1 -1
  5. package/dist/cli/{settings/results-collector.js → results-collector.js} +2 -1
  6. package/dist/cli/settings-report-builder.d.ts +1 -3
  7. package/dist/cli/sync-command.d.ts +2 -24
  8. package/dist/cli/sync-command.js +295 -301
  9. package/dist/cli/types.d.ts +60 -40
  10. package/dist/cli/types.js +1 -12
  11. package/dist/config/errors.d.ts +9 -0
  12. package/dist/config/errors.js +11 -0
  13. package/dist/config/file-reference-resolver.d.ts +2 -1
  14. package/dist/config/file-reference-resolver.js +10 -8
  15. package/dist/config/formatter.d.ts +3 -2
  16. package/dist/config/index.d.ts +4 -6
  17. package/dist/config/index.js +4 -8
  18. package/dist/config/loader.js +4 -2
  19. package/dist/config/merge.d.ts +0 -9
  20. package/dist/config/merge.js +2 -7
  21. package/dist/config/normalizer.d.ts +4 -0
  22. package/dist/config/normalizer.js +61 -110
  23. package/dist/config/types.d.ts +15 -19
  24. package/dist/config/types.js +1 -1
  25. package/dist/config/validator.d.ts +0 -4
  26. package/dist/config/validator.js +286 -363
  27. package/dist/config/validators/file-validator.d.ts +2 -8
  28. package/dist/config/validators/file-validator.js +6 -17
  29. package/dist/config/validators/index.d.ts +3 -3
  30. package/dist/config/validators/index.js +3 -3
  31. package/dist/config/validators/repo-settings-validator.d.ts +0 -6
  32. package/dist/config/validators/repo-settings-validator.js +9 -9
  33. package/dist/config/validators/ruleset-validator.d.ts +0 -14
  34. package/dist/config/validators/ruleset-validator.js +28 -28
  35. package/dist/lifecycle/ado-migration-source.js +2 -1
  36. package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -5
  37. package/dist/lifecycle/github-lifecycle-provider.js +79 -90
  38. package/dist/lifecycle/index.d.ts +2 -6
  39. package/dist/lifecycle/index.js +0 -4
  40. package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
  41. package/dist/lifecycle/lifecycle-formatter.js +4 -0
  42. package/dist/lifecycle/lifecycle-helpers.d.ts +3 -2
  43. package/dist/lifecycle/repo-lifecycle-manager.js +4 -11
  44. package/dist/lifecycle/types.d.ts +0 -8
  45. package/dist/output/github-summary.d.ts +5 -0
  46. package/dist/output/github-summary.js +9 -2
  47. package/dist/output/index.d.ts +2 -2
  48. package/dist/output/index.js +1 -1
  49. package/dist/output/lifecycle-report.js +5 -23
  50. package/dist/output/settings-report.d.ts +14 -3
  51. package/dist/output/settings-report.js +137 -197
  52. package/dist/output/summary-utils.d.ts +1 -1
  53. package/dist/output/summary-utils.js +2 -1
  54. package/dist/output/sync-report.js +5 -8
  55. package/dist/output/unified-summary.d.ts +2 -1
  56. package/dist/output/unified-summary.js +71 -133
  57. package/dist/settings/base-processor.d.ts +67 -0
  58. package/dist/settings/base-processor.js +91 -0
  59. package/dist/settings/index.d.ts +4 -3
  60. package/dist/settings/index.js +3 -3
  61. package/dist/settings/labels/converter.d.ts +2 -1
  62. package/dist/settings/labels/github-labels-strategy.d.ts +9 -18
  63. package/dist/settings/labels/github-labels-strategy.js +17 -73
  64. package/dist/settings/labels/index.d.ts +2 -6
  65. package/dist/settings/labels/index.js +1 -9
  66. package/dist/settings/labels/processor.d.ts +6 -30
  67. package/dist/settings/labels/processor.js +62 -152
  68. package/dist/settings/labels/types.d.ts +5 -8
  69. package/dist/settings/repo-settings/formatter.d.ts +2 -2
  70. package/dist/settings/repo-settings/formatter.js +6 -6
  71. package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +11 -12
  72. package/dist/settings/repo-settings/github-repo-settings-strategy.js +32 -79
  73. package/dist/settings/repo-settings/index.d.ts +2 -5
  74. package/dist/settings/repo-settings/index.js +1 -9
  75. package/dist/settings/repo-settings/processor.d.ts +6 -27
  76. package/dist/settings/repo-settings/processor.js +51 -104
  77. package/dist/settings/repo-settings/types.d.ts +7 -9
  78. package/dist/settings/rulesets/diff-algorithm.d.ts +0 -4
  79. package/dist/settings/rulesets/diff-algorithm.js +1 -10
  80. package/dist/settings/rulesets/diff.d.ts +1 -1
  81. package/dist/settings/rulesets/diff.js +2 -21
  82. package/dist/settings/rulesets/formatter.d.ts +1 -3
  83. package/dist/settings/rulesets/formatter.js +1 -8
  84. package/dist/settings/rulesets/github-ruleset-strategy.d.ts +11 -51
  85. package/dist/settings/rulesets/github-ruleset-strategy.js +24 -85
  86. package/dist/settings/rulesets/index.d.ts +3 -6
  87. package/dist/settings/rulesets/index.js +5 -9
  88. package/dist/settings/rulesets/processor.d.ts +8 -33
  89. package/dist/settings/rulesets/processor.js +58 -151
  90. package/dist/settings/rulesets/types.d.ts +35 -6
  91. package/dist/shared/command-executor.d.ts +2 -22
  92. package/dist/shared/command-executor.js +8 -7
  93. package/dist/shared/env.d.ts +0 -8
  94. package/dist/shared/env.js +14 -70
  95. package/dist/shared/file-status.d.ts +2 -0
  96. package/dist/shared/file-status.js +13 -0
  97. package/dist/shared/gh-api-utils.d.ts +46 -0
  98. package/dist/shared/gh-api-utils.js +107 -0
  99. package/dist/shared/index.d.ts +5 -5
  100. package/dist/shared/index.js +3 -3
  101. package/dist/shared/interpolation-engine.d.ts +31 -0
  102. package/dist/shared/interpolation-engine.js +50 -0
  103. package/dist/shared/logger.d.ts +3 -7
  104. package/dist/shared/logger.js +4 -1
  105. package/dist/shared/repo-detector.d.ts +17 -2
  106. package/dist/shared/repo-detector.js +27 -0
  107. package/dist/shared/retry-utils.d.ts +9 -17
  108. package/dist/shared/retry-utils.js +22 -28
  109. package/dist/shared/sanitize-utils.d.ts +0 -7
  110. package/dist/shared/sanitize-utils.js +0 -7
  111. package/dist/shared/shell-utils.d.ts +1 -0
  112. package/dist/shared/shell-utils.js +3 -0
  113. package/dist/shared/string-utils.d.ts +4 -0
  114. package/dist/shared/string-utils.js +6 -0
  115. package/dist/shared/type-guards.d.ts +17 -0
  116. package/dist/shared/type-guards.js +26 -0
  117. package/dist/shared/workspace-utils.d.ts +0 -4
  118. package/dist/shared/workspace-utils.js +0 -4
  119. package/dist/{sync → shared}/xfg-template.d.ts +3 -2
  120. package/dist/{sync → shared}/xfg-template.js +13 -54
  121. package/dist/sync/auth-options-builder.d.ts +4 -5
  122. package/dist/sync/auth-options-builder.js +15 -26
  123. package/dist/sync/branch-manager.d.ts +5 -0
  124. package/dist/sync/branch-manager.js +12 -10
  125. package/dist/sync/commit-push-manager.d.ts +1 -1
  126. package/dist/sync/commit-push-manager.js +22 -18
  127. package/dist/sync/diff-utils.d.ts +4 -9
  128. package/dist/sync/diff-utils.js +2 -19
  129. package/dist/sync/file-sync-orchestrator.js +9 -8
  130. package/dist/sync/file-writer.d.ts +2 -1
  131. package/dist/sync/file-writer.js +3 -6
  132. package/dist/sync/index.d.ts +2 -15
  133. package/dist/sync/index.js +0 -19
  134. package/dist/sync/manifest-manager.d.ts +4 -0
  135. package/dist/sync/manifest-manager.js +5 -1
  136. package/dist/sync/manifest.d.ts +10 -41
  137. package/dist/sync/manifest.js +11 -56
  138. package/dist/sync/pr-merge-handler.d.ts +2 -6
  139. package/dist/sync/pr-merge-handler.js +6 -3
  140. package/dist/sync/repository-processor.d.ts +1 -2
  141. package/dist/sync/repository-processor.js +20 -12
  142. package/dist/sync/repository-session.js +5 -14
  143. package/dist/sync/sync-workflow.js +31 -38
  144. package/dist/sync/types.d.ts +43 -178
  145. package/dist/vcs/authenticated-git-ops.d.ts +27 -70
  146. package/dist/vcs/authenticated-git-ops.js +70 -96
  147. package/dist/vcs/azure-pr-strategy.d.ts +6 -4
  148. package/dist/vcs/azure-pr-strategy.js +34 -82
  149. package/dist/vcs/branch-utils.d.ts +6 -0
  150. package/dist/vcs/branch-utils.js +29 -0
  151. package/dist/vcs/commit-strategy-selector.d.ts +5 -0
  152. package/dist/vcs/commit-strategy-selector.js +10 -0
  153. package/dist/vcs/git-commit-strategy.js +1 -2
  154. package/dist/vcs/git-ops.d.ts +15 -59
  155. package/dist/vcs/git-ops.js +46 -110
  156. package/dist/vcs/github-app-token-manager.d.ts +0 -6
  157. package/dist/vcs/github-app-token-manager.js +5 -12
  158. package/dist/vcs/github-pr-strategy.d.ts +5 -5
  159. package/dist/vcs/github-pr-strategy.js +44 -122
  160. package/dist/vcs/gitlab-pr-strategy.d.ts +6 -4
  161. package/dist/vcs/gitlab-pr-strategy.js +39 -87
  162. package/dist/vcs/graphql-commit-strategy.d.ts +3 -4
  163. package/dist/vcs/graphql-commit-strategy.js +31 -63
  164. package/dist/vcs/index.d.ts +3 -16
  165. package/dist/vcs/index.js +2 -33
  166. package/dist/vcs/pr-creator.d.ts +9 -9
  167. package/dist/vcs/pr-creator.js +11 -10
  168. package/dist/vcs/pr-strategy-factory.d.ts +5 -0
  169. package/dist/vcs/pr-strategy-factory.js +17 -0
  170. package/dist/vcs/pr-strategy.d.ts +13 -26
  171. package/dist/vcs/pr-strategy.js +20 -25
  172. package/dist/vcs/types.d.ts +87 -21
  173. package/package.json +2 -1
@@ -1,16 +1,3 @@
1
- export { FileWriter, shouldBeExecutable } from "./file-writer.js";
2
- export { ManifestManager } from "./manifest-manager.js";
3
- export { BranchManager } from "./branch-manager.js";
4
- export { AuthOptionsBuilder } from "./auth-options-builder.js";
5
- export { RepositorySession } from "./repository-session.js";
6
- export { CommitPushManager } from "./commit-push-manager.js";
7
- export { formatCommitMessage } from "./commit-message.js";
8
- export { FileSyncOrchestrator } from "./file-sync-orchestrator.js";
9
- export { PRMergeHandler } from "./pr-merge-handler.js";
10
- export { FileSyncStrategy } from "./file-sync-strategy.js";
11
- export { SyncWorkflow } from "./sync-workflow.js";
12
- export type { IFileWriter, FileWriteContext, FileWriterDeps, FileWriteAllResult, FileWriteResult, IManifestManager, OrphanProcessResult, OrphanDeleteOptions, OrphanDeleteDeps, IBranchManager, BranchSetupOptions, IAuthOptionsBuilder, AuthResult, IRepositorySession, SessionOptions, SessionContext, ICommitPushManager, CommitPushOptions, CommitPushResult, GitOpsFactory, IRepositoryProcessor, ProcessorOptions, ProcessorResult, FileChangeDetail, IFileSyncOrchestrator, FileSyncResult, IPRMergeHandler, PRHandlerOptions, WorkResult, IWorkStrategy, ISyncWorkflow, } from "./types.js";
1
+ export type { DiffStats } from "./diff-utils.js";
2
+ export type { GitOpsFactory, IAuthOptionsBuilder, IBranchManager, ICommitPushManager, IFileSyncOrchestrator, IPRMergeHandler, IRepositoryProcessor, IRepositorySession, ISyncWorkflow, IWorkStrategy, ProcessorResult, SessionContext, WorkResult, } from "./types.js";
13
3
  export { RepositoryProcessor } from "./repository-processor.js";
14
- export { createEmptyManifest, loadManifest, saveManifest, getManagedFiles, updateManifest, MANIFEST_FILENAME, type XfgManifest, type XfgManifestConfigEntry, } from "./manifest.js";
15
- export { getFileStatus, formatStatusBadge, formatDiffLine, generateDiff, createDiffStats, incrementDiffStats, type FileStatus, type DiffStats, } from "./diff-utils.js";
16
- export { interpolateXfgContent, type XfgTemplateContext, type XfgInterpolationOptions, } from "./xfg-template.js";
@@ -1,20 +1 @@
1
- export { FileWriter, shouldBeExecutable } from "./file-writer.js";
2
- export { ManifestManager } from "./manifest-manager.js";
3
- export { BranchManager } from "./branch-manager.js";
4
- export { AuthOptionsBuilder } from "./auth-options-builder.js";
5
- export { RepositorySession } from "./repository-session.js";
6
- export { CommitPushManager } from "./commit-push-manager.js";
7
- export { formatCommitMessage } from "./commit-message.js";
8
- export { FileSyncOrchestrator } from "./file-sync-orchestrator.js";
9
- export { PRMergeHandler } from "./pr-merge-handler.js";
10
- // Strategy pattern components
11
- export { FileSyncStrategy } from "./file-sync-strategy.js";
12
- export { SyncWorkflow } from "./sync-workflow.js";
13
- // Repository processor
14
1
  export { RepositoryProcessor } from "./repository-processor.js";
15
- // Manifest handling
16
- export { createEmptyManifest, loadManifest, saveManifest, getManagedFiles, updateManifest, MANIFEST_FILENAME, } from "./manifest.js";
17
- // Diff utilities
18
- export { getFileStatus, formatStatusBadge, formatDiffLine, generateDiff, createDiffStats, incrementDiffStats, } from "./diff-utils.js";
19
- // XFG templating
20
- export { interpolateXfgContent, } from "./xfg-template.js";
@@ -4,6 +4,10 @@ import type { IManifestManager, OrphanProcessResult, OrphanDeleteOptions, Orphan
4
4
  * Handles manifest loading, saving, and orphan detection.
5
5
  */
6
6
  export declare class ManifestManager implements IManifestManager {
7
+ private readonly log?;
8
+ constructor(log?: {
9
+ debug(msg: string): void;
10
+ } | undefined);
7
11
  processOrphans(workDir: string, configId: string, filesWithDeleteOrphaned: Map<string, boolean | undefined>): OrphanProcessResult;
8
12
  deleteOrphans(filesToDelete: string[], options: OrphanDeleteOptions, deps: OrphanDeleteDeps): Promise<void>;
9
13
  saveUpdatedManifest(workDir: string, manifest: XfgManifest, existingManifest: XfgManifest | null, dryRun: boolean, fileChanges: Map<string, FileWriteResult>): void;
@@ -5,8 +5,12 @@ import { loadManifest, saveManifest, updateManifest, MANIFEST_FILENAME, } from "
5
5
  * Handles manifest loading, saving, and orphan detection.
6
6
  */
7
7
  export class ManifestManager {
8
+ log;
9
+ constructor(log) {
10
+ this.log = log;
11
+ }
8
12
  processOrphans(workDir, configId, filesWithDeleteOrphaned) {
9
- const existingManifest = loadManifest(workDir);
13
+ const existingManifest = loadManifest(workDir, this.log);
10
14
  const { manifest, filesToDelete } = updateManifest(existingManifest, configId, filesWithDeleteOrphaned);
11
15
  return { manifest, filesToDelete };
12
16
  }
@@ -6,57 +6,26 @@ export interface XfgManifest {
6
6
  version: 4;
7
7
  configs: Record<string, XfgManifestConfigEntry>;
8
8
  }
9
- /**
10
- * Creates an empty manifest with the current version.
11
- */
12
9
  export declare function createEmptyManifest(): XfgManifest;
13
10
  /**
14
- * Loads the xfg manifest from a repository's working directory.
15
- * Returns null if the manifest file doesn't exist or is v1 format.
16
- *
17
- * V1 manifests are treated as non-existent because they lack the config ID
18
- * namespace required for multi-config support. The next run will create
19
- * a fresh v4 manifest.
20
- *
21
- * V2/V3 manifests are automatically migrated to V4 format.
22
- *
23
- * @param workDir - The repository working directory
24
- * @returns The manifest or null if not found or incompatible
11
+ * Loads and migrates manifest from workDir. V1 returns null (no config-ID namespace);
12
+ * V2/V3 are auto-migrated to V4.
25
13
  */
26
- export declare function loadManifest(workDir: string): XfgManifest | null;
14
+ export declare function loadManifest(workDir: string, log?: {
15
+ debug(msg: string): void;
16
+ }): XfgManifest | null;
27
17
  /**
28
18
  * Parses manifest content from a string (e.g., fetched from a remote API).
29
19
  * Handles V2→V3 migration, returns null for V1/unknown/invalid formats.
30
20
  */
31
- export declare function parseManifestContent(content: string): XfgManifest | null;
32
- /**
33
- * Saves the xfg manifest to a repository's working directory.
34
- *
35
- * @param workDir - The repository working directory
36
- * @param manifest - The manifest to save
37
- */
21
+ export declare function parseManifestContent(content: string, log?: {
22
+ debug(msg: string): void;
23
+ }): XfgManifest | null;
38
24
  export declare function saveManifest(workDir: string, manifest: XfgManifest): void;
39
- /**
40
- * Gets the list of managed files for a specific config from a manifest.
41
- * Returns an empty array if the manifest is null or the config isn't found.
42
- *
43
- * @param manifest - The manifest or null
44
- * @param configId - The config ID to get files for
45
- * @returns Array of managed file names for the given config
46
- */
47
25
  export declare function getManagedFiles(manifest: XfgManifest | null, configId: string): string[];
48
26
  /**
49
- * Updates the manifest with the current set of files that have deleteOrphaned enabled
50
- * for a specific config. Only modifies that config's files namespace - other configs are untouched.
51
- *
52
- * Files with deleteOrphaned: true are added to managedFiles.
53
- * Files with deleteOrphaned: false (explicit) are removed from managedFiles.
54
- * Files not in the config but in managedFiles for this configId are candidates for deletion.
55
- *
56
- * @param manifest - The existing manifest (or null for new repos)
57
- * @param configId - The config ID to update
58
- * @param filesWithDeleteOrphaned - Map of fileName to deleteOrphaned value (true/false/undefined)
59
- * @returns Updated manifest and list of files to delete
27
+ * Updates manifest tracking for a config. Files with deleteOrphaned: true are tracked;
28
+ * files previously tracked but no longer in config are returned as filesToDelete.
60
29
  */
61
30
  export declare function updateManifest(manifest: XfgManifest | null, configId: string, filesWithDeleteOrphaned: Map<string, boolean | undefined>): {
62
31
  manifest: XfgManifest;
@@ -1,18 +1,12 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  export const MANIFEST_FILENAME = ".xfg.json";
4
- /**
5
- * Type guard to check if a manifest is v1 format.
6
- */
7
4
  function isV1Manifest(manifest) {
8
5
  return (typeof manifest === "object" &&
9
6
  manifest !== null &&
10
7
  manifest.version === 1 &&
11
8
  Array.isArray(manifest.managedFiles));
12
9
  }
13
- /**
14
- * Type guard to check if a manifest is v2 format.
15
- */
16
10
  function isV2Manifest(manifest) {
17
11
  return (typeof manifest === "object" &&
18
12
  manifest !== null &&
@@ -20,9 +14,6 @@ function isV2Manifest(manifest) {
20
14
  typeof manifest.configs === "object" &&
21
15
  manifest.configs !== null);
22
16
  }
23
- /**
24
- * Type guard to check if a manifest is v3 format.
25
- */
26
17
  function isV3Manifest(manifest) {
27
18
  return (typeof manifest === "object" &&
28
19
  manifest !== null &&
@@ -30,9 +21,6 @@ function isV3Manifest(manifest) {
30
21
  typeof manifest.configs === "object" &&
31
22
  manifest.configs !== null);
32
23
  }
33
- /**
34
- * Type guard to check if a manifest is v4 format.
35
- */
36
24
  function isV4Manifest(manifest) {
37
25
  return (typeof manifest === "object" &&
38
26
  manifest !== null &&
@@ -72,9 +60,6 @@ function migrateV3ToV4(v3) {
72
60
  }
73
61
  return { version: 4, configs: v4Configs };
74
62
  }
75
- /**
76
- * Creates an empty manifest with the current version.
77
- */
78
63
  export function createEmptyManifest() {
79
64
  return {
80
65
  version: 4,
@@ -82,19 +67,10 @@ export function createEmptyManifest() {
82
67
  };
83
68
  }
84
69
  /**
85
- * Loads the xfg manifest from a repository's working directory.
86
- * Returns null if the manifest file doesn't exist or is v1 format.
87
- *
88
- * V1 manifests are treated as non-existent because they lack the config ID
89
- * namespace required for multi-config support. The next run will create
90
- * a fresh v4 manifest.
91
- *
92
- * V2/V3 manifests are automatically migrated to V4 format.
93
- *
94
- * @param workDir - The repository working directory
95
- * @returns The manifest or null if not found or incompatible
70
+ * Loads and migrates manifest from workDir. V1 returns null (no config-ID namespace);
71
+ * V2/V3 are auto-migrated to V4.
96
72
  */
97
- export function loadManifest(workDir) {
73
+ export function loadManifest(workDir, log) {
98
74
  const manifestPath = join(workDir, MANIFEST_FILENAME);
99
75
  if (!existsSync(manifestPath)) {
100
76
  return null;
@@ -121,7 +97,8 @@ export function loadManifest(workDir) {
121
97
  // Unknown format - treat as no manifest
122
98
  return null;
123
99
  }
124
- catch {
100
+ catch (error) {
101
+ log?.debug(`Failed to load manifest from ${manifestPath}: ${error}`);
125
102
  return null;
126
103
  }
127
104
  }
@@ -129,7 +106,7 @@ export function loadManifest(workDir) {
129
106
  * Parses manifest content from a string (e.g., fetched from a remote API).
130
107
  * Handles V2→V3 migration, returns null for V1/unknown/invalid formats.
131
108
  */
132
- export function parseManifestContent(content) {
109
+ export function parseManifestContent(content, log) {
133
110
  try {
134
111
  const parsed = JSON.parse(content);
135
112
  if (isV4Manifest(parsed)) {
@@ -143,29 +120,16 @@ export function parseManifestContent(content) {
143
120
  }
144
121
  return null;
145
122
  }
146
- catch {
123
+ catch (error) {
124
+ log?.debug(`Failed to parse manifest content: ${error}`);
147
125
  return null;
148
126
  }
149
127
  }
150
- /**
151
- * Saves the xfg manifest to a repository's working directory.
152
- *
153
- * @param workDir - The repository working directory
154
- * @param manifest - The manifest to save
155
- */
156
128
  export function saveManifest(workDir, manifest) {
157
129
  const manifestPath = join(workDir, MANIFEST_FILENAME);
158
130
  const content = JSON.stringify(manifest, null, 2) + "\n";
159
131
  writeFileSync(manifestPath, content, "utf-8");
160
132
  }
161
- /**
162
- * Gets the list of managed files for a specific config from a manifest.
163
- * Returns an empty array if the manifest is null or the config isn't found.
164
- *
165
- * @param manifest - The manifest or null
166
- * @param configId - The config ID to get files for
167
- * @returns Array of managed file names for the given config
168
- */
169
133
  export function getManagedFiles(manifest, configId) {
170
134
  if (!manifest) {
171
135
  return [];
@@ -173,17 +137,8 @@ export function getManagedFiles(manifest, configId) {
173
137
  return [...(manifest.configs[configId]?.files ?? [])];
174
138
  }
175
139
  /**
176
- * Updates the manifest with the current set of files that have deleteOrphaned enabled
177
- * for a specific config. Only modifies that config's files namespace - other configs are untouched.
178
- *
179
- * Files with deleteOrphaned: true are added to managedFiles.
180
- * Files with deleteOrphaned: false (explicit) are removed from managedFiles.
181
- * Files not in the config but in managedFiles for this configId are candidates for deletion.
182
- *
183
- * @param manifest - The existing manifest (or null for new repos)
184
- * @param configId - The config ID to update
185
- * @param filesWithDeleteOrphaned - Map of fileName to deleteOrphaned value (true/false/undefined)
186
- * @returns Updated manifest and list of files to delete
140
+ * Updates manifest tracking for a config. Files with deleteOrphaned: true are tracked;
141
+ * files previously tracked but no longer in config are returned as filesToDelete.
187
142
  */
188
143
  export function updateManifest(manifest, configId, filesWithDeleteOrphaned) {
189
144
  // Get existing managed files for this config only
@@ -211,7 +166,7 @@ export function updateManifest(manifest, configId, filesWithDeleteOrphaned) {
211
166
  ...(manifest?.configs ?? {}),
212
167
  };
213
168
  // Update this config's managed files
214
- const sortedManaged = Array.from(newManaged).sort();
169
+ const sortedManaged = Array.from(newManaged).sort((a, b) => a.localeCompare(b));
215
170
  if (sortedManaged.length > 0) {
216
171
  updatedConfigs[configId] = { files: sortedManaged };
217
172
  }
@@ -1,11 +1,7 @@
1
- import type { RepoConfig } from "../config/index.js";
2
- import type { RepoInfo } from "../shared/repo-detector.js";
3
1
  import type { ILogger } from "../shared/logger.js";
4
- import { type FileAction } from "../vcs/pr-creator.js";
5
- import type { DiffStats } from "./diff-utils.js";
6
- import type { ProcessorResult, PRHandlerOptions, IPRMergeHandler, FileChangeDetail } from "./types.js";
2
+ import type { ProcessorResult, IPRMergeHandler, CreateAndMergeInput } from "./types.js";
7
3
  export declare class PRMergeHandler implements IPRMergeHandler {
8
4
  private readonly log;
9
5
  constructor(log: ILogger);
10
- createAndMerge(repoInfo: RepoInfo, repoConfig: RepoConfig, options: PRHandlerOptions, changedFiles: FileAction[], repoName: string, diffStats?: DiffStats, fileChanges?: FileChangeDetail[]): Promise<ProcessorResult>;
6
+ createAndMerge(input: CreateAndMergeInput): Promise<ProcessorResult>;
11
7
  }
@@ -1,10 +1,11 @@
1
- import { createPR, mergePR, } from "../vcs/pr-creator.js";
1
+ import { createPR, mergePR } from "../vcs/pr-creator.js";
2
2
  export class PRMergeHandler {
3
3
  log;
4
4
  constructor(log) {
5
5
  this.log = log;
6
6
  }
7
- async createAndMerge(repoInfo, repoConfig, options, changedFiles, repoName, diffStats, fileChanges) {
7
+ async createAndMerge(input) {
8
+ const { repoInfo, repoConfig, options, changedFiles, repoName, diffStats, fileChanges, } = input;
8
9
  this.log.info("Creating pull request...");
9
10
  const prResult = await createPR({
10
11
  repoInfo,
@@ -18,6 +19,7 @@ export class PRMergeHandler {
18
19
  executor: options.executor,
19
20
  token: options.token,
20
21
  labels: repoConfig.prOptions?.labels,
22
+ log: this.log,
21
23
  });
22
24
  const mergeMode = repoConfig.prOptions?.merge ?? "auto";
23
25
  let mergeResult;
@@ -38,6 +40,7 @@ export class PRMergeHandler {
38
40
  retries: options.retries,
39
41
  executor: options.executor,
40
42
  token: options.token,
43
+ log: this.log,
41
44
  });
42
45
  mergeResult = {
43
46
  merged: result.merged ?? false,
@@ -45,7 +48,7 @@ export class PRMergeHandler {
45
48
  message: result.message,
46
49
  };
47
50
  if (!result.success) {
48
- this.log.info(`Warning: Merge operation failed - ${result.message}`);
51
+ this.log.warn(`Merge operation failed - ${result.message}`);
49
52
  }
50
53
  else {
51
54
  this.log.info(result.message);
@@ -1,14 +1,13 @@
1
1
  import type { RepoConfig } from "../config/index.js";
2
2
  import type { RepoInfo } from "../shared/repo-detector.js";
3
3
  import { ILogger } from "../shared/logger.js";
4
- import { type IFileWriter, type IManifestManager, type IBranchManager, type IAuthOptionsBuilder, type IRepositorySession, type ICommitPushManager, type IFileSyncOrchestrator, type IPRMergeHandler, type ISyncWorkflow, type IRepositoryProcessor, type GitOpsFactory, type ProcessorOptions, type ProcessorResult } from "./index.js";
4
+ import type { IFileWriter, IManifestManager, IBranchManager, IAuthOptionsBuilder, IRepositorySession, ICommitPushManager, IFileSyncOrchestrator, IPRMergeHandler, ISyncWorkflow, IRepositoryProcessor, GitOpsFactory, ProcessorOptions, ProcessorResult } from "./types.js";
5
5
  /**
6
6
  * Thin facade that delegates to SyncWorkflow with FileSyncStrategy.
7
7
  */
8
8
  export declare class RepositoryProcessor implements IRepositoryProcessor {
9
9
  private readonly syncWorkflow;
10
10
  private readonly fileSyncOrchestrator;
11
- private readonly log;
12
11
  constructor(gitOpsFactory?: GitOpsFactory, log?: ILogger, components?: {
13
12
  fileWriter?: IFileWriter;
14
13
  manifestManager?: IManifestManager;
@@ -1,28 +1,36 @@
1
1
  import { GitOps } from "../vcs/git-ops.js";
2
2
  import { AuthenticatedGitOps } from "../vcs/authenticated-git-ops.js";
3
+ import { defaultExecutor } from "../shared/command-executor.js";
3
4
  import { logger } from "../shared/logger.js";
4
- import { hasGitHubAppCredentials } from "../vcs/index.js";
5
- import { GitHubAppTokenManager } from "../vcs/github-app-token-manager.js";
6
- import { FileWriter, ManifestManager, BranchManager, AuthOptionsBuilder, RepositorySession, CommitPushManager, FileSyncOrchestrator, PRMergeHandler, FileSyncStrategy, SyncWorkflow, } from "./index.js";
5
+ import { createTokenManager } from "../vcs/index.js";
6
+ import { FileWriter } from "./file-writer.js";
7
+ import { ManifestManager } from "./manifest-manager.js";
8
+ import { BranchManager } from "./branch-manager.js";
9
+ import { AuthOptionsBuilder } from "./auth-options-builder.js";
10
+ import { RepositorySession } from "./repository-session.js";
11
+ import { CommitPushManager } from "./commit-push-manager.js";
12
+ import { FileSyncOrchestrator } from "./file-sync-orchestrator.js";
13
+ import { PRMergeHandler } from "./pr-merge-handler.js";
14
+ import { FileSyncStrategy } from "./file-sync-strategy.js";
15
+ import { SyncWorkflow } from "./sync-workflow.js";
7
16
  /**
8
17
  * Thin facade that delegates to SyncWorkflow with FileSyncStrategy.
9
18
  */
10
19
  export class RepositoryProcessor {
11
20
  syncWorkflow;
12
21
  fileSyncOrchestrator;
13
- log;
14
22
  constructor(gitOpsFactory, log, components) {
15
- const factory = gitOpsFactory ??
16
- ((opts, auth) => new AuthenticatedGitOps(new GitOps(opts), auth));
17
23
  const logInstance = log ?? logger;
18
- this.log = logInstance;
24
+ const factory = gitOpsFactory ??
25
+ ((opts, auth, retries) => {
26
+ const gitOps = new GitOps({ ...opts, log: logInstance });
27
+ return new AuthenticatedGitOps(gitOps, opts.executor ?? defaultExecutor, opts.workDir, retries ?? 3, auth, logInstance);
28
+ });
19
29
  // Initialize token manager for auth builder
20
- const tokenManager = hasGitHubAppCredentials()
21
- ? new GitHubAppTokenManager(process.env.XFG_GITHUB_APP_ID, process.env.XFG_GITHUB_APP_PRIVATE_KEY)
22
- : null;
30
+ const tokenManager = createTokenManager();
23
31
  const fileWriter = components?.fileWriter ?? new FileWriter();
24
- const manifestManager = components?.manifestManager ?? new ManifestManager();
25
- const branchManager = components?.branchManager ?? new BranchManager();
32
+ const manifestManager = components?.manifestManager ?? new ManifestManager(logInstance);
33
+ const branchManager = components?.branchManager ?? new BranchManager(logInstance);
26
34
  const authOptionsBuilder = components?.authOptionsBuilder ??
27
35
  new AuthOptionsBuilder(tokenManager, logInstance);
28
36
  const repositorySession = components?.repositorySession ??
@@ -1,3 +1,4 @@
1
+ import { safeCleanup } from "../shared/type-guards.js";
1
2
  export class RepositorySession {
2
3
  gitOpsFactory;
3
4
  log;
@@ -7,28 +8,18 @@ export class RepositorySession {
7
8
  }
8
9
  async setup(repoInfo, options) {
9
10
  const { workDir, dryRun, retries, authOptions } = options;
10
- // Create gitOps instance
11
- const gitOps = this.gitOpsFactory({ workDir, dryRun, retries }, authOptions);
12
- // Clean workspace
13
- this.log.info("Cleaning workspace...");
11
+ const gitOps = this.gitOpsFactory({ workDir, dryRun }, authOptions, retries);
12
+ this.log.debug("Cleaning workspace...");
14
13
  gitOps.cleanWorkspace();
15
- // Clone repository
16
- this.log.info("Cloning repository...");
14
+ this.log.debug("Cloning repository...");
17
15
  await gitOps.clone(repoInfo.gitUrl);
18
- // Detect default branch
19
16
  const { branch: baseBranch, method: detectionMethod } = await gitOps.getDefaultBranch();
20
17
  this.log.info(`Default branch: ${baseBranch} (detected via ${detectionMethod})`);
21
- // Return context with cleanup function
22
18
  return {
23
19
  gitOps,
24
20
  baseBranch,
25
21
  cleanup: () => {
26
- try {
27
- gitOps.cleanWorkspace();
28
- }
29
- catch {
30
- // Ignore cleanup errors - best effort
31
- }
22
+ safeCleanup(() => gitOps.cleanWorkspace(), "workspace removal failed", this.log);
32
23
  },
33
24
  };
34
25
  }
@@ -1,4 +1,5 @@
1
1
  import { getRepoDisplayName } from "../shared/repo-detector.js";
2
+ import { safeCleanup } from "../shared/type-guards.js";
2
3
  import { defaultExecutor } from "../shared/command-executor.js";
3
4
  /**
4
5
  * Orchestrates the common sync workflow steps.
@@ -21,47 +22,37 @@ export class SyncWorkflow {
21
22
  }
22
23
  async execute(repoConfig, repoInfo, options, workStrategy) {
23
24
  const repoName = getRepoDisplayName(repoInfo);
24
- const { branchName, workDir, dryRun } = options;
25
+ const { branchName, workDir } = options;
26
+ const dryRun = options.dryRun ?? false;
25
27
  const retries = options.retries ?? 3;
26
28
  const executor = options.executor ?? defaultExecutor;
27
- // Step 1: Resolve auth
28
- const authResult = await this.authOptionsBuilder.resolve(repoInfo, repoName);
29
- if (authResult.skipResult) {
29
+ const authResult = await this.authOptionsBuilder.resolve(repoInfo, repoName, options.token);
30
+ if (!authResult.ok) {
30
31
  return authResult.skipResult;
31
32
  }
32
- // Step 2: Determine merge mode
33
33
  const mergeMode = repoConfig.prOptions?.merge ?? "auto";
34
34
  const isDirectMode = mergeMode === "direct";
35
- // Warn if mergeStrategy is set but ignored in direct mode
36
- if (isDirectMode && repoConfig.prOptions?.mergeStrategy) {
37
- this.log.info(`Warning: mergeStrategy '${repoConfig.prOptions.mergeStrategy}' is ignored in direct mode`);
38
- }
39
35
  let session = null;
40
36
  try {
41
- // Step 3: Setup session
42
37
  session = await this.repositorySession.setup(repoInfo, {
43
38
  workDir,
44
- dryRun: dryRun ?? false,
39
+ dryRun,
45
40
  retries,
46
41
  authOptions: authResult.authOptions,
47
42
  });
48
- // Step 4: Setup branch
49
43
  await this.branchManager.setupBranch({
50
44
  repoInfo,
51
45
  branchName,
52
46
  baseBranch: session.baseBranch,
53
47
  workDir,
54
48
  isDirectMode,
55
- dryRun: dryRun ?? false,
49
+ dryRun,
56
50
  retries,
57
51
  token: authResult.token,
58
52
  gitOps: session.gitOps,
59
- log: this.log,
60
53
  executor,
61
54
  });
62
- // Step 5: Execute work strategy
63
55
  const workResult = await workStrategy.execute(repoConfig, repoInfo, session, options);
64
- // Step 6: No changes - skip
65
56
  if (!workResult) {
66
57
  return {
67
58
  success: true,
@@ -70,7 +61,6 @@ export class SyncWorkflow {
70
61
  skipped: true,
71
62
  };
72
63
  }
73
- // Step 7: Commit and push
74
64
  const pushBranch = isDirectMode ? session.baseBranch : branchName;
75
65
  const commitResult = await this.commitPushManager.commitAndPush({
76
66
  repoInfo,
@@ -80,13 +70,12 @@ export class SyncWorkflow {
80
70
  commitMessage: workResult.commitMessage,
81
71
  pushBranch,
82
72
  isDirectMode,
83
- dryRun: dryRun ?? false,
73
+ dryRun,
84
74
  retries,
85
75
  token: authResult.token,
86
76
  executor,
87
- }, repoName);
88
- // Step 8: Handle commit errors
89
- if (!commitResult.success && commitResult.errorResult) {
77
+ });
78
+ if (!commitResult.success) {
90
79
  return commitResult.errorResult;
91
80
  }
92
81
  if (commitResult.skipped) {
@@ -99,7 +88,6 @@ export class SyncWorkflow {
99
88
  fileChanges: workResult.fileChangeDetails,
100
89
  };
101
90
  }
102
- // Step 9: Direct mode - done
103
91
  if (isDirectMode) {
104
92
  this.log.info(`Changes pushed directly to ${session.baseBranch}`);
105
93
  return {
@@ -110,24 +98,29 @@ export class SyncWorkflow {
110
98
  fileChanges: workResult.fileChangeDetails,
111
99
  };
112
100
  }
113
- // Step 10: Create and merge PR
114
- return await this.prMergeHandler.createAndMerge(repoInfo, repoConfig, {
115
- branchName,
116
- baseBranch: session.baseBranch,
117
- workDir,
118
- dryRun: dryRun ?? false,
119
- retries,
120
- prTemplate: options.prTemplate,
121
- token: authResult.token,
122
- executor,
123
- }, workResult.changedFiles, repoName, workResult.diffStats, workResult.fileChangeDetails);
101
+ return await this.prMergeHandler.createAndMerge({
102
+ repoInfo,
103
+ repoConfig,
104
+ options: {
105
+ branchName,
106
+ baseBranch: session.baseBranch,
107
+ workDir,
108
+ dryRun,
109
+ retries,
110
+ prTemplate: options.prTemplate,
111
+ token: authResult.token,
112
+ executor,
113
+ },
114
+ changedFiles: workResult.changedFiles,
115
+ repoName,
116
+ diffStats: workResult.diffStats,
117
+ fileChanges: workResult.fileChangeDetails,
118
+ });
124
119
  }
125
120
  finally {
126
- try {
127
- session?.cleanup();
128
- }
129
- catch {
130
- // Ignore cleanup errors - best effort
121
+ if (session) {
122
+ const s = session;
123
+ safeCleanup(() => s.cleanup(), "session teardown failed", this.log);
131
124
  }
132
125
  }
133
126
  }