@aspruyt/xfg 4.0.2 → 4.0.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/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +0 -6
- package/dist/cli/program.js +3 -2
- package/dist/cli/settings-report-builder.js +4 -4
- package/dist/cli/sync-command.js +72 -36
- package/dist/cli/sync-report-builder.d.ts +2 -6
- package/dist/cli/types.d.ts +2 -14
- package/dist/cli/types.js +1 -9
- package/dist/config/file-reference-resolver.js +13 -23
- package/dist/config/formatter.d.ts +0 -6
- package/dist/config/formatter.js +0 -9
- package/dist/config/index.d.ts +1 -2
- package/dist/config/index.js +0 -2
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +3 -3
- package/dist/config/normalizer.d.ts +1 -1
- package/dist/config/normalizer.js +44 -57
- package/dist/config/validator.d.ts +1 -1
- package/dist/config/validator.js +120 -121
- package/dist/config/validators/file-validator.d.ts +2 -4
- package/dist/config/validators/file-validator.js +3 -7
- package/dist/config/validators/repo-settings-validator.js +1 -1
- package/dist/config/validators/ruleset-validator.js +28 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +0 -1
- package/dist/lifecycle/ado-migration-source.d.ts +2 -1
- package/dist/lifecycle/ado-migration-source.js +7 -5
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -3
- package/dist/lifecycle/github-lifecycle-provider.js +29 -19
- package/dist/lifecycle/lifecycle-formatter.js +2 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +5 -1
- package/dist/lifecycle/lifecycle-helpers.js +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.js +12 -9
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +4 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +11 -7
- package/dist/lifecycle/types.d.ts +0 -15
- package/dist/output/github-summary.d.ts +6 -5
- package/dist/output/github-summary.js +36 -52
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.d.ts +2 -12
- package/dist/output/lifecycle-report.js +18 -35
- package/dist/output/settings-report.d.ts +4 -4
- package/dist/output/settings-report.js +6 -6
- package/dist/output/sync-report.d.ts +4 -6
- package/dist/output/sync-report.js +2 -2
- package/dist/output/unified-summary.d.ts +1 -0
- package/dist/output/unified-summary.js +8 -8
- package/dist/settings/base-processor.d.ts +1 -1
- package/dist/settings/base-processor.js +1 -1
- package/dist/settings/index.d.ts +3 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/diff.js +3 -2
- package/dist/settings/labels/formatter.js +3 -3
- package/dist/settings/labels/github-labels-strategy.d.ts +2 -23
- package/dist/settings/labels/github-labels-strategy.js +8 -28
- package/dist/settings/labels/index.d.ts +1 -0
- package/dist/settings/labels/index.js +2 -0
- package/dist/settings/labels/processor.d.ts +2 -2
- package/dist/settings/labels/processor.js +3 -4
- package/dist/settings/labels/types.d.ts +0 -3
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +2 -2
- package/dist/settings/repo-settings/formatter.d.ts +1 -1
- package/dist/settings/repo-settings/formatter.js +4 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -7
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +9 -17
- package/dist/settings/repo-settings/index.d.ts +1 -0
- package/dist/settings/repo-settings/index.js +2 -0
- package/dist/settings/repo-settings/processor.d.ts +2 -2
- package/dist/settings/repo-settings/processor.js +5 -6
- package/dist/settings/repo-settings/types.d.ts +9 -13
- package/dist/settings/repo-settings/types.js +1 -14
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -1
- package/dist/settings/rulesets/diff-algorithm.js +6 -8
- package/dist/settings/rulesets/formatter.js +15 -51
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -20
- package/dist/settings/rulesets/github-ruleset-strategy.js +6 -30
- package/dist/settings/rulesets/index.d.ts +2 -1
- package/dist/settings/rulesets/index.js +3 -1
- package/dist/settings/rulesets/processor.d.ts +2 -2
- package/dist/settings/rulesets/processor.js +3 -4
- package/dist/{vcs → shared}/branch-utils.js +5 -4
- package/dist/shared/command-executor.d.ts +2 -1
- package/dist/shared/command-executor.js +9 -5
- package/dist/shared/env.d.ts +6 -6
- package/dist/shared/env.js +10 -17
- package/dist/shared/errors.d.ts +26 -0
- package/dist/shared/errors.js +34 -0
- package/dist/shared/gh-api-utils.d.ts +21 -14
- package/dist/shared/gh-api-utils.js +33 -22
- package/dist/shared/index.d.ts +9 -2
- package/dist/shared/index.js +16 -2
- package/dist/shared/logger.d.ts +24 -1
- package/dist/shared/logger.js +8 -3
- package/dist/shared/repo-detector.js +9 -11
- package/dist/shared/retry-utils.d.ts +5 -7
- package/dist/shared/retry-utils.js +3 -10
- package/dist/shared/shell-utils.d.ts +0 -3
- package/dist/shared/shell-utils.js +2 -4
- package/dist/shared/type-guards.d.ts +2 -9
- package/dist/shared/type-guards.js +0 -6
- package/dist/shared/xfg-template.d.ts +2 -2
- package/dist/shared/xfg-template.js +2 -1
- package/dist/sync/auth-options-builder.d.ts +3 -2
- package/dist/sync/auth-options-builder.js +14 -10
- package/dist/sync/branch-manager.d.ts +12 -7
- package/dist/sync/branch-manager.js +4 -7
- package/dist/sync/commit-message.d.ts +1 -1
- package/dist/sync/commit-push-manager.d.ts +8 -2
- package/dist/sync/commit-push-manager.js +6 -5
- package/dist/sync/file-sync-orchestrator.js +17 -21
- package/dist/sync/file-writer.js +3 -5
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/manifest-manager.d.ts +1 -0
- package/dist/sync/manifest.d.ts +4 -7
- package/dist/sync/manifest.js +42 -45
- package/dist/sync/repository-processor.d.ts +5 -2
- package/dist/sync/repository-processor.js +11 -17
- package/dist/sync/repository-session.js +2 -1
- package/dist/sync/sync-workflow.d.ts +2 -2
- package/dist/sync/sync-workflow.js +16 -23
- package/dist/sync/types.d.ts +20 -25
- package/dist/vcs/authenticated-git-ops.d.ts +3 -4
- package/dist/vcs/authenticated-git-ops.js +5 -1
- package/dist/vcs/azure-pr-strategy.d.ts +6 -1
- package/dist/vcs/azure-pr-strategy.js +38 -31
- package/dist/vcs/commit-strategy-selector.d.ts +10 -19
- package/dist/vcs/commit-strategy-selector.js +8 -24
- package/dist/vcs/git-commit-strategy.d.ts +1 -1
- package/dist/vcs/git-commit-strategy.js +1 -3
- package/dist/vcs/git-ops.d.ts +4 -8
- package/dist/vcs/git-ops.js +18 -22
- package/dist/vcs/github-app-token-manager.js +9 -8
- package/dist/vcs/github-pr-strategy.js +18 -11
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +14 -7
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -7
- package/dist/vcs/graphql-commit-strategy.js +24 -32
- package/dist/vcs/index.d.ts +2 -1
- package/dist/vcs/pr-creator.d.ts +6 -9
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +2 -1
- package/dist/vcs/pr-strategy.d.ts +1 -1
- package/dist/vcs/pr-strategy.js +2 -3
- package/dist/vcs/types.d.ts +6 -10
- package/package.json +2 -2
- package/dist/config/errors.d.ts +0 -9
- package/dist/config/errors.js +0 -11
- /package/dist/{vcs → shared}/branch-utils.d.ts +0 -0
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { runSync } from "./sync-command.js";
|
|
2
|
-
export { type
|
|
2
|
+
export { type ProcessorFactory, type RulesetProcessorFactory, type RepoSettingsProcessorFactory, type LabelsProcessorFactory, } from "./types.js";
|
|
3
3
|
export type { SyncOptions, SharedOptions } from "./sync-command.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
// CLI command implementations
|
|
2
1
|
export { runSync } from "./sync-command.js";
|
|
3
|
-
// Export types - using 'export type' for type aliases, but interfaces need special handling
|
|
4
|
-
// For ESM compatibility, re-export everything from types.js
|
|
5
|
-
export {
|
|
6
|
-
// Runtime values
|
|
7
|
-
defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, defaultLabelsProcessorFactory, } from "./types.js";
|
package/dist/cli/program.js
CHANGED
|
@@ -2,6 +2,7 @@ import { program, Command } from "commander";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ValidationError } from "../shared/errors.js";
|
|
5
6
|
import { runSync } from "./sync-command.js";
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
@@ -40,14 +41,14 @@ const syncCommand = new Command("sync")
|
|
|
40
41
|
.option("-m, --merge <mode>", "PR merge mode: manual, auto (default, merge when checks pass), force (bypass requirements), direct (push to default branch, no PR)", (value) => {
|
|
41
42
|
const valid = ["manual", "auto", "force", "direct"];
|
|
42
43
|
if (!valid.includes(value)) {
|
|
43
|
-
throw new
|
|
44
|
+
throw new ValidationError(`Invalid merge mode: ${value}. Valid: ${valid.join(", ")}`);
|
|
44
45
|
}
|
|
45
46
|
return value;
|
|
46
47
|
})
|
|
47
48
|
.option("--merge-strategy <strategy>", "Merge strategy: merge, squash (default), rebase", (value) => {
|
|
48
49
|
const valid = ["merge", "squash", "rebase"];
|
|
49
50
|
if (!valid.includes(value)) {
|
|
50
|
-
throw new
|
|
51
|
+
throw new ValidationError(`Invalid merge strategy: ${value}. Valid: ${valid.join(", ")}`);
|
|
51
52
|
}
|
|
52
53
|
return value;
|
|
53
54
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function buildSettingsReport(results) {
|
|
2
2
|
const repos = [];
|
|
3
3
|
const totals = {
|
|
4
|
-
settings: {
|
|
4
|
+
settings: { create: 0, update: 0 },
|
|
5
5
|
rulesets: { create: 0, update: 0, delete: 0 },
|
|
6
6
|
labels: { create: 0, update: 0, delete: 0 },
|
|
7
7
|
};
|
|
@@ -26,11 +26,11 @@ export function buildSettingsReport(results) {
|
|
|
26
26
|
newValue: entry.newValue,
|
|
27
27
|
};
|
|
28
28
|
repoChanges.settings.push(settingChange);
|
|
29
|
-
if (entry.action === "
|
|
30
|
-
totals.settings.
|
|
29
|
+
if (entry.action === "create") {
|
|
30
|
+
totals.settings.create++;
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
totals.settings.
|
|
33
|
+
totals.settings.update++;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
package/dist/cli/sync-command.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { resolve, join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { loadRawConfig, normalizeConfig } from "../config/index.js";
|
|
4
|
+
import { ValidationError, SyncError } from "../shared/errors.js";
|
|
4
5
|
import { validateForSync } from "../config/validator.js";
|
|
5
6
|
import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../shared/repo-detector.js";
|
|
6
|
-
import { sanitizeBranchName, validateBranchName } from "../
|
|
7
|
+
import { sanitizeBranchName, validateBranchName, } from "../shared/branch-utils.js";
|
|
7
8
|
import { createTokenManager } from "../vcs/index.js";
|
|
8
|
-
import {
|
|
9
|
+
import { RepositoryProcessor } from "../sync/index.js";
|
|
10
|
+
import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, GitHubRulesetStrategy, GitHubRepoSettingsStrategy, GitHubLabelsStrategy, } from "../settings/index.js";
|
|
11
|
+
import { ShellCommandExecutor } from "../shared/command-executor.js";
|
|
12
|
+
import { Logger } from "../shared/logger.js";
|
|
9
13
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
10
|
-
|
|
14
|
+
const defaultExecutor = new ShellCommandExecutor(process.env);
|
|
15
|
+
const logger = new Logger(!!(process.env.DEBUG || process.env.XFG_DEBUG));
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const defaultRulesetProcessorFactory = () => new RulesetProcessor(new GitHubRulesetStrategy(defaultExecutor, { cwd }));
|
|
18
|
+
const defaultRepoSettingsProcessorFactory = () => new RepoSettingsProcessor(new GitHubRepoSettingsStrategy(defaultExecutor, { cwd }));
|
|
19
|
+
const defaultLabelsProcessorFactory = () => new LabelsProcessor(new GitHubLabelsStrategy(defaultExecutor, { cwd }));
|
|
11
20
|
import { ResultsCollector } from "./results-collector.js";
|
|
12
21
|
import { buildSettingsReport } from "./settings-report-builder.js";
|
|
13
22
|
import { formatSettingsReportCLI } from "../output/settings-report.js";
|
|
@@ -176,6 +185,7 @@ function displayReports(reportResults, lifecycleReportInputs, settingsCollector,
|
|
|
176
185
|
sync: report,
|
|
177
186
|
settings: settingsReport,
|
|
178
187
|
dryRun,
|
|
188
|
+
summaryPath: process.env.GITHUB_STEP_SUMMARY,
|
|
179
189
|
});
|
|
180
190
|
}
|
|
181
191
|
/**
|
|
@@ -219,11 +229,19 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
219
229
|
const repoToken = isGitHubRepo(repoInfo)
|
|
220
230
|
? (await resolveGitHubToken(repoInfo, ctx.tokenManager, repoName, logger, process.env.GH_TOKEN)).token
|
|
221
231
|
: undefined;
|
|
222
|
-
const
|
|
232
|
+
const repo = {
|
|
233
|
+
repoConfig,
|
|
234
|
+
repoInfo,
|
|
235
|
+
repoName,
|
|
236
|
+
index,
|
|
237
|
+
workDir,
|
|
238
|
+
token: repoToken,
|
|
239
|
+
};
|
|
240
|
+
const skipFileSync = await runLifecyclePhase(repo, ctx);
|
|
223
241
|
if (skipFileSync)
|
|
224
242
|
return;
|
|
225
243
|
// Sync files via processor
|
|
226
|
-
await runFileSyncPhase(
|
|
244
|
+
await runFileSyncPhase(repo, ctx);
|
|
227
245
|
// Apply settings via API (GitHub-only — ADO and GitLab repos are skipped)
|
|
228
246
|
await applyRepoSettings({
|
|
229
247
|
repoConfig,
|
|
@@ -242,24 +260,27 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
242
260
|
* Run lifecycle check (repo existence, creation, forking).
|
|
243
261
|
* Returns true if the main loop should skip file sync for this repo.
|
|
244
262
|
*/
|
|
245
|
-
async function runLifecyclePhase(
|
|
246
|
-
const current = index + 1;
|
|
263
|
+
async function runLifecyclePhase(repo, ctx) {
|
|
264
|
+
const current = repo.index + 1;
|
|
247
265
|
try {
|
|
248
|
-
const { outputLines, lifecycleResult } = await runLifecycleCheck(repoConfig, repoInfo,
|
|
266
|
+
const { outputLines, lifecycleResult } = await runLifecycleCheck(repo.repoConfig, repo.repoInfo, {
|
|
249
267
|
dryRun: ctx.options.dryRun ?? false,
|
|
250
|
-
resolvedWorkDir: workDir,
|
|
268
|
+
resolvedWorkDir: repo.workDir,
|
|
251
269
|
githubHosts: ctx.config.githubHosts,
|
|
252
|
-
token:
|
|
253
|
-
|
|
270
|
+
token: repo.token,
|
|
271
|
+
repoIndex: repo.index,
|
|
272
|
+
lifecycleManager: ctx.lifecycleManager,
|
|
273
|
+
repoSettings: ctx.config.settings?.repo,
|
|
274
|
+
});
|
|
254
275
|
for (const line of outputLines) {
|
|
255
276
|
logger.info(line);
|
|
256
277
|
}
|
|
257
278
|
const createSettings = toCreateRepoSettings(ctx.config.settings?.repo);
|
|
258
279
|
ctx.lifecycleReportInputs.push({
|
|
259
|
-
repoName,
|
|
280
|
+
repoName: repo.repoName,
|
|
260
281
|
action: lifecycleResult.action,
|
|
261
|
-
upstream: repoConfig.upstream,
|
|
262
|
-
source: repoConfig.source,
|
|
282
|
+
upstream: repo.repoConfig.upstream,
|
|
283
|
+
source: repo.repoConfig.source,
|
|
263
284
|
settings: createSettings
|
|
264
285
|
? {
|
|
265
286
|
visibility: createSettings.visibility,
|
|
@@ -270,7 +291,7 @@ async function runLifecyclePhase(repoConfig, repoInfo, repoName, index, workDir,
|
|
|
270
291
|
// In dry-run, skip processing repos that don't exist yet
|
|
271
292
|
if (ctx.options.dryRun && lifecycleResult.action !== "existed") {
|
|
272
293
|
ctx.reportResults.push({
|
|
273
|
-
repoName,
|
|
294
|
+
repoName: repo.repoName,
|
|
274
295
|
success: true,
|
|
275
296
|
fileChanges: [],
|
|
276
297
|
});
|
|
@@ -279,9 +300,9 @@ async function runLifecyclePhase(repoConfig, repoInfo, repoName, index, workDir,
|
|
|
279
300
|
return false;
|
|
280
301
|
}
|
|
281
302
|
catch (error) {
|
|
282
|
-
logger.error(current, repoName, `Lifecycle error: ${toErrorMessage(error)}`);
|
|
303
|
+
logger.error(current, repo.repoName, `Lifecycle error: ${toErrorMessage(error)}`);
|
|
283
304
|
ctx.reportResults.push({
|
|
284
|
-
repoName,
|
|
305
|
+
repoName: repo.repoName,
|
|
285
306
|
success: false,
|
|
286
307
|
fileChanges: [],
|
|
287
308
|
error: toErrorMessage(error),
|
|
@@ -292,23 +313,25 @@ async function runLifecyclePhase(repoConfig, repoInfo, repoName, index, workDir,
|
|
|
292
313
|
/**
|
|
293
314
|
* Run the file sync processor for a single repo and collect results.
|
|
294
315
|
*/
|
|
295
|
-
async function runFileSyncPhase(
|
|
316
|
+
async function runFileSyncPhase(repo, ctx) {
|
|
317
|
+
const current = repo.index + 1;
|
|
296
318
|
try {
|
|
297
|
-
logger.progress(current, repoName, "Processing...");
|
|
298
|
-
const result = await ctx.processor.process(repoConfig, repoInfo, {
|
|
319
|
+
logger.progress(current, repo.repoName, "Processing...");
|
|
320
|
+
const result = await ctx.processor.process(repo.repoConfig, repo.repoInfo, {
|
|
299
321
|
branchName: ctx.branchName,
|
|
300
|
-
workDir,
|
|
322
|
+
workDir: repo.workDir,
|
|
301
323
|
configId: ctx.config.id,
|
|
302
324
|
dryRun: ctx.options.dryRun,
|
|
303
325
|
retries: ctx.options.retries,
|
|
326
|
+
executor: defaultExecutor,
|
|
304
327
|
prTemplate: ctx.config.prTemplate,
|
|
305
328
|
noDelete: ctx.options.noDelete,
|
|
306
|
-
token,
|
|
307
|
-
|
|
329
|
+
token: repo.token,
|
|
330
|
+
hasAppCredentials: isGitHubRepo(repo.repoInfo) && ctx.tokenManager !== null,
|
|
308
331
|
});
|
|
309
332
|
const mergeOutcome = determineMergeOutcome(result);
|
|
310
333
|
ctx.reportResults.push({
|
|
311
|
-
repoName,
|
|
334
|
+
repoName: repo.repoName,
|
|
312
335
|
success: result.success,
|
|
313
336
|
fileChanges: (result.fileChanges ?? []).map((f) => ({
|
|
314
337
|
path: f.path,
|
|
@@ -319,19 +342,19 @@ async function runFileSyncPhase(repoConfig, repoInfo, repoName, current, workDir
|
|
|
319
342
|
error: result.success ? undefined : result.message,
|
|
320
343
|
});
|
|
321
344
|
if (result.skipped) {
|
|
322
|
-
logger.skip(current, repoName, result.message);
|
|
345
|
+
logger.skip(current, repo.repoName, result.message);
|
|
323
346
|
}
|
|
324
347
|
else if (result.success) {
|
|
325
|
-
logger.success(current, repoName, result.message);
|
|
348
|
+
logger.success(current, repo.repoName, result.message);
|
|
326
349
|
}
|
|
327
350
|
else {
|
|
328
|
-
logger.error(current, repoName, result.message);
|
|
351
|
+
logger.error(current, repo.repoName, result.message);
|
|
329
352
|
}
|
|
330
353
|
}
|
|
331
354
|
catch (error) {
|
|
332
|
-
logger.error(current, repoName, toErrorMessage(error));
|
|
355
|
+
logger.error(current, repo.repoName, toErrorMessage(error));
|
|
333
356
|
ctx.reportResults.push({
|
|
334
|
-
repoName,
|
|
357
|
+
repoName: repo.repoName,
|
|
335
358
|
success: false,
|
|
336
359
|
fileChanges: [],
|
|
337
360
|
error: toErrorMessage(error),
|
|
@@ -339,10 +362,10 @@ async function runFileSyncPhase(repoConfig, repoInfo, repoName, current, workDir
|
|
|
339
362
|
}
|
|
340
363
|
}
|
|
341
364
|
export async function runSync(options, deps = {}) {
|
|
342
|
-
const {
|
|
365
|
+
const { lifecycleManager, rulesetProcessorFactory = defaultRulesetProcessorFactory, repoSettingsProcessorFactory = defaultRepoSettingsProcessorFactory, labelsProcessorFactory = defaultLabelsProcessorFactory, } = deps;
|
|
343
366
|
const configPath = resolve(options.config);
|
|
344
367
|
if (!existsSync(configPath)) {
|
|
345
|
-
throw new
|
|
368
|
+
throw new ValidationError(`Config file not found: ${configPath}`);
|
|
346
369
|
}
|
|
347
370
|
logger.log(`Loading config from: ${configPath}`);
|
|
348
371
|
if (options.dryRun) {
|
|
@@ -350,7 +373,7 @@ export async function runSync(options, deps = {}) {
|
|
|
350
373
|
}
|
|
351
374
|
const rawConfig = loadRawConfig(configPath);
|
|
352
375
|
validateForSync(rawConfig);
|
|
353
|
-
const config = normalizeConfig(rawConfig);
|
|
376
|
+
const config = normalizeConfig(rawConfig, process.env);
|
|
354
377
|
const fileNames = getUniqueFileNames(config);
|
|
355
378
|
let branchName;
|
|
356
379
|
if (options.branch) {
|
|
@@ -364,13 +387,26 @@ export async function runSync(options, deps = {}) {
|
|
|
364
387
|
logger.log(`Found ${config.repos.length} repositories to process`);
|
|
365
388
|
logger.log(`Target files: ${formatFileNames(fileNames)}`);
|
|
366
389
|
logger.log(`Branch: ${branchName}\n`);
|
|
390
|
+
const tokenManager = createTokenManager(process.env.XFG_GITHUB_APP_ID && process.env.XFG_GITHUB_APP_PRIVATE_KEY
|
|
391
|
+
? {
|
|
392
|
+
appId: process.env.XFG_GITHUB_APP_ID,
|
|
393
|
+
privateKey: process.env.XFG_GITHUB_APP_PRIVATE_KEY,
|
|
394
|
+
}
|
|
395
|
+
: undefined);
|
|
396
|
+
const processor = deps.processorFactory
|
|
397
|
+
? deps.processorFactory()
|
|
398
|
+
: new RepositoryProcessor(undefined, logger, {
|
|
399
|
+
tokenManager,
|
|
400
|
+
envToken: process.env.GH_TOKEN,
|
|
401
|
+
});
|
|
367
402
|
const ctx = {
|
|
368
403
|
config,
|
|
369
404
|
options,
|
|
370
405
|
branchName,
|
|
371
|
-
processor
|
|
372
|
-
lifecycleManager: lifecycleManager ??
|
|
373
|
-
|
|
406
|
+
processor,
|
|
407
|
+
lifecycleManager: lifecycleManager ??
|
|
408
|
+
new RepoLifecycleManager(undefined, defaultExecutor, options.retries, cwd, logger),
|
|
409
|
+
tokenManager,
|
|
374
410
|
reportResults: [],
|
|
375
411
|
lifecycleReportInputs: [],
|
|
376
412
|
settingsCollector: new ResultsCollector(),
|
|
@@ -387,6 +423,6 @@ export async function runSync(options, deps = {}) {
|
|
|
387
423
|
const hasErrors = ctx.reportResults.some((r) => r.error);
|
|
388
424
|
const hasSettingsErrors = settingsResults.some((r) => r.error);
|
|
389
425
|
if (hasErrors || hasSettingsErrors) {
|
|
390
|
-
throw new
|
|
426
|
+
throw new SyncError("One or more repositories had errors during sync");
|
|
391
427
|
}
|
|
392
428
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import type { SyncReport } from "../output/sync-report.js";
|
|
2
|
-
interface FileChangeInput {
|
|
3
|
-
path: string;
|
|
4
|
-
action: "create" | "update" | "delete";
|
|
5
|
-
}
|
|
1
|
+
import type { SyncReport, ReportFileChange } from "../output/sync-report.js";
|
|
6
2
|
interface SyncResultInput {
|
|
7
3
|
repoName: string;
|
|
8
4
|
success: boolean;
|
|
9
|
-
fileChanges:
|
|
5
|
+
fileChanges: ReportFileChange[];
|
|
10
6
|
prUrl?: string;
|
|
11
7
|
mergeOutcome?: "manual" | "auto" | "force" | "direct";
|
|
12
8
|
error?: string;
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import type { MergeMode, MergeStrategy, RepoConfig } from "../config/index.js";
|
|
2
2
|
import type { IRepoLifecycleManager } from "../lifecycle/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import type { IRepositoryProcessor } from "../sync/index.js";
|
|
4
|
+
import type { ISettingsProcessor, IRulesetProcessor, IRepoSettingsProcessor, ILabelsProcessor } from "../settings/index.js";
|
|
5
5
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
6
6
|
import type { ResultsCollector } from "./results-collector.js";
|
|
7
|
-
export type { IRepositoryProcessor, IRulesetProcessor };
|
|
8
7
|
export type ProcessorFactory = () => IRepositoryProcessor;
|
|
9
|
-
/**
|
|
10
|
-
* Default factory that creates a real RepositoryProcessor.
|
|
11
|
-
*/
|
|
12
|
-
export declare const defaultProcessorFactory: ProcessorFactory;
|
|
13
|
-
/**
|
|
14
|
-
* Generic factory type for settings processors.
|
|
15
|
-
*/
|
|
16
8
|
export type SettingsProcessorFactory<T extends ISettingsProcessor> = () => T;
|
|
17
9
|
export type RulesetProcessorFactory = SettingsProcessorFactory<IRulesetProcessor>;
|
|
18
10
|
export type RepoSettingsProcessorFactory = SettingsProcessorFactory<IRepoSettingsProcessor>;
|
|
19
11
|
export type LabelsProcessorFactory = SettingsProcessorFactory<ILabelsProcessor>;
|
|
20
|
-
export declare const defaultRulesetProcessorFactory: RulesetProcessorFactory;
|
|
21
|
-
export declare const defaultRepoSettingsProcessorFactory: RepoSettingsProcessorFactory;
|
|
22
|
-
export declare const defaultLabelsProcessorFactory: LabelsProcessorFactory;
|
|
23
12
|
/**
|
|
24
13
|
* Dependencies for the sync command (dependency injection).
|
|
25
14
|
*/
|
|
@@ -79,4 +68,3 @@ export interface ApplyRepoSettingsContext {
|
|
|
79
68
|
repoSettingsProcessorFactory: NonNullable<SyncDependencies["repoSettingsProcessorFactory"]>;
|
|
80
69
|
labelsProcessorFactory: NonNullable<SyncDependencies["labelsProcessorFactory"]>;
|
|
81
70
|
}
|
|
82
|
-
export type { IRepoSettingsProcessor, ILabelsProcessor };
|
package/dist/cli/types.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, } from "../settings/index.js";
|
|
3
|
-
/**
|
|
4
|
-
* Default factory that creates a real RepositoryProcessor.
|
|
5
|
-
*/
|
|
6
|
-
export const defaultProcessorFactory = () => new RepositoryProcessor();
|
|
7
|
-
export const defaultRulesetProcessorFactory = () => new RulesetProcessor();
|
|
8
|
-
export const defaultRepoSettingsProcessorFactory = () => new RepoSettingsProcessor();
|
|
9
|
-
export const defaultLabelsProcessorFactory = () => new LabelsProcessor();
|
|
1
|
+
export {};
|
|
@@ -3,7 +3,7 @@ import { resolve, isAbsolute, normalize, extname, relative } from "node:path";
|
|
|
3
3
|
import JSON5 from "json5";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
5
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
6
|
-
import { ValidationError } from "
|
|
6
|
+
import { ValidationError } from "../shared/errors.js";
|
|
7
7
|
/**
|
|
8
8
|
* Check if a value is a file reference (string starting with @)
|
|
9
9
|
*/
|
|
@@ -44,40 +44,30 @@ export function resolveFileReference(reference, configDir) {
|
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
46
|
const msg = toErrorMessage(error);
|
|
47
|
-
throw new
|
|
47
|
+
throw new ValidationError(`Failed to load file reference "${reference}": ${msg}`);
|
|
48
48
|
}
|
|
49
49
|
// Parse based on extension
|
|
50
50
|
const ext = extname(relativePath).toLowerCase();
|
|
51
51
|
if (ext === ".json") {
|
|
52
|
-
|
|
53
|
-
return JSON.parse(content);
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
const msg = toErrorMessage(error);
|
|
57
|
-
throw new Error(`Invalid JSON in "${reference}": ${msg}`);
|
|
58
|
-
}
|
|
52
|
+
return parseWithContext(() => JSON.parse(content), `Invalid JSON in "${reference}"`);
|
|
59
53
|
}
|
|
60
54
|
if (ext === ".json5") {
|
|
61
|
-
|
|
62
|
-
return JSON5.parse(content);
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
const msg = toErrorMessage(error);
|
|
66
|
-
throw new Error(`Invalid JSON5 in "${reference}": ${msg}`);
|
|
67
|
-
}
|
|
55
|
+
return parseWithContext(() => JSON5.parse(content), `Invalid JSON5 in "${reference}"`);
|
|
68
56
|
}
|
|
69
57
|
if (ext === ".yaml" || ext === ".yml") {
|
|
70
|
-
|
|
71
|
-
return parseYaml(content);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
const msg = toErrorMessage(error);
|
|
75
|
-
throw new Error(`Invalid YAML in "${reference}": ${msg}`);
|
|
76
|
-
}
|
|
58
|
+
return parseWithContext(() => parseYaml(content), `Invalid YAML in "${reference}"`);
|
|
77
59
|
}
|
|
78
60
|
// Text file - return as string
|
|
79
61
|
return content;
|
|
80
62
|
}
|
|
63
|
+
function parseWithContext(fn, errorPrefix) {
|
|
64
|
+
try {
|
|
65
|
+
return fn();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw new ValidationError(`${errorPrefix}: ${toErrorMessage(error)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
81
71
|
/**
|
|
82
72
|
* Recursively resolve file references in a content value.
|
|
83
73
|
* Only string values starting with @ are resolved.
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
type OutputFormat = "json" | "json5" | "yaml";
|
|
2
|
-
/**
|
|
3
|
-
* Options for content conversion.
|
|
4
|
-
*/
|
|
5
2
|
interface ConvertOptions {
|
|
6
3
|
header?: string[];
|
|
7
4
|
schemaUrl?: string;
|
|
8
5
|
}
|
|
9
|
-
/**
|
|
10
|
-
* Detects output format from file extension.
|
|
11
|
-
*/
|
|
12
6
|
export declare function detectOutputFormat(fileName: string): OutputFormat;
|
|
13
7
|
/**
|
|
14
8
|
* Converts content to string in the appropriate format.
|
package/dist/config/formatter.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { Document, stringify } from "yaml";
|
|
2
|
-
/**
|
|
3
|
-
* Detects output format from file extension.
|
|
4
|
-
*/
|
|
5
2
|
export function detectOutputFormat(fileName) {
|
|
6
3
|
const ext = fileName.toLowerCase().split(".").pop();
|
|
7
4
|
if (ext === "yaml" || ext === "yml") {
|
|
@@ -54,7 +51,6 @@ function buildCommentOnlyYaml(header, schemaUrl) {
|
|
|
54
51
|
* Handles null content (empty files), text content (string/string[]), and object content (JSON/YAML).
|
|
55
52
|
*/
|
|
56
53
|
export function convertContentToString(content, fileName, options) {
|
|
57
|
-
// Handle empty file case
|
|
58
54
|
if (content === null) {
|
|
59
55
|
const format = detectOutputFormat(fileName);
|
|
60
56
|
if (format === "yaml" && options) {
|
|
@@ -65,18 +61,13 @@ export function convertContentToString(content, fileName, options) {
|
|
|
65
61
|
}
|
|
66
62
|
return "";
|
|
67
63
|
}
|
|
68
|
-
// Handle string content (text file)
|
|
69
64
|
if (typeof content === "string") {
|
|
70
|
-
// Ensure trailing newline for text files
|
|
71
65
|
return content.endsWith("\n") ? content : content + "\n";
|
|
72
66
|
}
|
|
73
|
-
// Handle string[] content (text file with lines)
|
|
74
67
|
if (Array.isArray(content)) {
|
|
75
|
-
// Join lines with newlines and ensure trailing newline
|
|
76
68
|
const text = content.join("\n");
|
|
77
69
|
return text.length > 0 ? text + "\n" : "";
|
|
78
70
|
}
|
|
79
|
-
// Handle object content (JSON/YAML)
|
|
80
71
|
const format = detectOutputFormat(fileName);
|
|
81
72
|
if (format === "yaml") {
|
|
82
73
|
// Use Document API for YAML to support comments
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export type { MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, Label, RepoSettings, ContentValue, RawFileConfig, RawRepoFileOverride, RawRepoSettings, RawRepoConfig, RawConfig, RepoConfig, Config, } from "./types.js";
|
|
1
|
+
export type { MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, RepoVisibility, SquashMergeCommitTitle, SquashMergeCommitMessage, MergeCommitTitle, MergeCommitMessage, Label, RepoSettings, ContentValue, RawFileConfig, RawRepoFileOverride, RawRepoSettings, RawRepoConfig, RawConfig, RepoConfig, Config, } from "./types.js";
|
|
2
2
|
export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
3
3
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
4
4
|
export { convertContentToString } from "./formatter.js";
|
|
5
|
-
export { ValidationError } from "./errors.js";
|
package/dist/config/index.js
CHANGED
|
@@ -4,5 +4,3 @@ export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
|
4
4
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
5
5
|
// Config formatting
|
|
6
6
|
export { convertContentToString } from "./formatter.js";
|
|
7
|
-
// Errors
|
|
8
|
-
export { ValidationError } from "./errors.js";
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export { normalizeConfigInternal as normalizeConfig };
|
|
|
6
6
|
* Use this when you need to perform command-specific validation before normalizing.
|
|
7
7
|
*/
|
|
8
8
|
export declare function loadRawConfig(filePath: string): RawConfig;
|
|
9
|
-
export declare function loadConfig(filePath: string): Config;
|
|
9
|
+
export declare function loadConfig(filePath: string, env: Record<string, string | undefined>): Config;
|
package/dist/config/loader.js
CHANGED
|
@@ -5,7 +5,7 @@ import { validateRawConfig } from "./validator.js";
|
|
|
5
5
|
import { normalizeConfig as normalizeConfigInternal } from "./normalizer.js";
|
|
6
6
|
import { resolveFileReferencesInConfig } from "./file-reference-resolver.js";
|
|
7
7
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
8
|
-
import { ValidationError } from "
|
|
8
|
+
import { ValidationError } from "../shared/errors.js";
|
|
9
9
|
export { normalizeConfigInternal as normalizeConfig };
|
|
10
10
|
/**
|
|
11
11
|
* Load and validate raw config without normalization.
|
|
@@ -27,7 +27,7 @@ export function loadRawConfig(filePath) {
|
|
|
27
27
|
validateRawConfig(rawConfig);
|
|
28
28
|
return rawConfig;
|
|
29
29
|
}
|
|
30
|
-
export function loadConfig(filePath) {
|
|
30
|
+
export function loadConfig(filePath, env) {
|
|
31
31
|
const rawConfig = loadRawConfig(filePath);
|
|
32
|
-
return normalizeConfigInternal(rawConfig);
|
|
32
|
+
return normalizeConfigInternal(rawConfig, env);
|
|
33
33
|
}
|
|
@@ -8,4 +8,4 @@ export declare function mergeSettings(root: RawRootSettings | undefined, perRepo
|
|
|
8
8
|
* Normalizes raw config into expanded, merged config.
|
|
9
9
|
* Pipeline: expand git arrays -> merge content -> interpolate env vars
|
|
10
10
|
*/
|
|
11
|
-
export declare function normalizeConfig(raw: RawConfig): Config;
|
|
11
|
+
export declare function normalizeConfig(raw: RawConfig, env: Record<string, string | undefined>): Config;
|
|
@@ -104,17 +104,12 @@ function mergeRuleset(root, perRepo) {
|
|
|
104
104
|
return structuredClone(perRepo ?? {});
|
|
105
105
|
if (!perRepo)
|
|
106
106
|
return structuredClone(root);
|
|
107
|
-
// Deep merge using the existing merge utility with replace strategy
|
|
107
|
+
// Deep merge using the existing merge utility with replace strategy.
|
|
108
|
+
// deepMerge operates on Record<string, unknown> — the cast is safe because
|
|
109
|
+
// merging two Ruleset-shaped objects preserves the Ruleset structure.
|
|
108
110
|
const ctx = createMergeContext("replace");
|
|
109
|
-
|
|
110
|
-
return merged;
|
|
111
|
+
return deepMerge(structuredClone(root), perRepo, ctx);
|
|
111
112
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Merges root and per-repo label configs.
|
|
114
|
-
* Per-repo labels override root labels by name.
|
|
115
|
-
* inherit: false skips all root labels.
|
|
116
|
-
* label: false opts out of a specific root label.
|
|
117
|
-
*/
|
|
118
113
|
function mergeLabels(rootLabels, repoLabels) {
|
|
119
114
|
if (!rootLabels && !repoLabels)
|
|
120
115
|
return undefined;
|
|
@@ -135,12 +130,12 @@ function mergeLabels(rootLabels, repoLabels) {
|
|
|
135
130
|
continue;
|
|
136
131
|
if (!inheritLabels && !repoLabel && rootLabel)
|
|
137
132
|
continue;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
merged
|
|
133
|
+
const base = rootLabel && typeof rootLabel === "object" ? rootLabel : {};
|
|
134
|
+
const overlay = repoLabel && typeof repoLabel === "object" ? repoLabel : {};
|
|
135
|
+
const color = (overlay.color ?? base.color ?? "")
|
|
136
|
+
.replace(/^#/, "")
|
|
137
|
+
.toLowerCase();
|
|
138
|
+
const merged = { ...base, ...overlay, color };
|
|
144
139
|
result[name] = merged;
|
|
145
140
|
}
|
|
146
141
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
@@ -363,11 +358,41 @@ function mergeGroupSettings(rootSettings, groupNames, groupDefs) {
|
|
|
363
358
|
}
|
|
364
359
|
return accumulated;
|
|
365
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Resolves a single file entry by merging root config with repo overrides.
|
|
363
|
+
* Returns null if the file should be skipped.
|
|
364
|
+
*/
|
|
365
|
+
function resolveFileEntry(fileName, fileConfig, repoOverride, inheritFiles, globalDeleteOrphaned, env) {
|
|
366
|
+
if (repoOverride === false)
|
|
367
|
+
return null;
|
|
368
|
+
if (!inheritFiles && !repoOverride)
|
|
369
|
+
return null;
|
|
370
|
+
const fileStrategy = fileConfig.mergeStrategy ?? "replace";
|
|
371
|
+
let mergedContent = resolveFileContent(fileConfig.content, repoOverride, fileStrategy);
|
|
372
|
+
if (mergedContent !== null) {
|
|
373
|
+
mergedContent = interpolateContent(mergedContent, { strict: true, env });
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
fileName,
|
|
377
|
+
content: mergedContent,
|
|
378
|
+
createOnly: repoOverride?.createOnly ?? fileConfig.createOnly,
|
|
379
|
+
executable: repoOverride?.executable ?? fileConfig.executable,
|
|
380
|
+
header: normalizeHeader(repoOverride?.header ?? fileConfig.header),
|
|
381
|
+
schemaUrl: repoOverride?.schemaUrl ?? fileConfig.schemaUrl,
|
|
382
|
+
template: repoOverride?.template ?? fileConfig.template,
|
|
383
|
+
vars: fileConfig.vars || repoOverride?.vars
|
|
384
|
+
? { ...fileConfig.vars, ...repoOverride?.vars }
|
|
385
|
+
: undefined,
|
|
386
|
+
deleteOrphaned: repoOverride?.deleteOrphaned ??
|
|
387
|
+
fileConfig.deleteOrphaned ??
|
|
388
|
+
globalDeleteOrphaned,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
366
391
|
/**
|
|
367
392
|
* Normalizes raw config into expanded, merged config.
|
|
368
393
|
* Pipeline: expand git arrays -> merge content -> interpolate env vars
|
|
369
394
|
*/
|
|
370
|
-
export function normalizeConfig(raw) {
|
|
395
|
+
export function normalizeConfig(raw, env) {
|
|
371
396
|
const expandedRepos = [];
|
|
372
397
|
for (const rawRepo of raw.repos) {
|
|
373
398
|
const gitUrls = Array.isArray(rawRepo.git) ? rawRepo.git : [rawRepo.git];
|
|
@@ -389,47 +414,9 @@ export function normalizeConfig(raw) {
|
|
|
389
414
|
// Skip reserved key
|
|
390
415
|
if (fileName === "inherit")
|
|
391
416
|
continue;
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
// Skip if inherit: false and no repo-specific override
|
|
398
|
-
if (!inheritFiles && !repoOverride) {
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
const fileConfig = effectiveRootFiles[fileName];
|
|
402
|
-
const fileStrategy = fileConfig.mergeStrategy ?? "replace";
|
|
403
|
-
let mergedContent = resolveFileContent(fileConfig.content, repoOverride, fileStrategy);
|
|
404
|
-
if (mergedContent !== null) {
|
|
405
|
-
mergedContent = interpolateContent(mergedContent, { strict: true });
|
|
406
|
-
}
|
|
407
|
-
// Resolve fields: per-repo overrides root level
|
|
408
|
-
const createOnly = repoOverride?.createOnly ?? fileConfig.createOnly;
|
|
409
|
-
const executable = repoOverride?.executable ?? fileConfig.executable;
|
|
410
|
-
const header = normalizeHeader(repoOverride?.header ?? fileConfig.header);
|
|
411
|
-
const schemaUrl = repoOverride?.schemaUrl ?? fileConfig.schemaUrl;
|
|
412
|
-
// Template: per-repo overrides root level
|
|
413
|
-
const template = repoOverride?.template ?? fileConfig.template;
|
|
414
|
-
// Vars: merge root + per-repo (per-repo takes precedence)
|
|
415
|
-
const vars = fileConfig.vars || repoOverride?.vars
|
|
416
|
-
? { ...fileConfig.vars, ...repoOverride?.vars }
|
|
417
|
-
: undefined;
|
|
418
|
-
// deleteOrphaned: per-repo overrides per-file overrides global
|
|
419
|
-
const deleteOrphaned = repoOverride?.deleteOrphaned ??
|
|
420
|
-
fileConfig.deleteOrphaned ??
|
|
421
|
-
raw.deleteOrphaned;
|
|
422
|
-
files.push({
|
|
423
|
-
fileName,
|
|
424
|
-
content: mergedContent,
|
|
425
|
-
createOnly,
|
|
426
|
-
executable,
|
|
427
|
-
header,
|
|
428
|
-
schemaUrl,
|
|
429
|
-
template,
|
|
430
|
-
vars,
|
|
431
|
-
deleteOrphaned,
|
|
432
|
-
});
|
|
417
|
+
const entry = resolveFileEntry(fileName, effectiveRootFiles[fileName], rawRepo.files?.[fileName], inheritFiles, raw.deleteOrphaned, env);
|
|
418
|
+
if (entry)
|
|
419
|
+
files.push(entry);
|
|
433
420
|
}
|
|
434
421
|
// Merge PR options: per-repo overrides effective (root + groups)
|
|
435
422
|
const prOptions = mergePROptions(effectivePROptions, rawRepo.prOptions);
|