@aspruyt/xfg 6.0.0 → 6.0.2
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/sync-command.js +33 -35
- package/dist/cli/types.d.ts +12 -11
- package/dist/{output → cli}/unified-summary.d.ts +3 -3
- package/dist/{output → cli}/unified-summary.js +4 -4
- package/dist/config/file-reference-resolver.js +24 -56
- package/dist/config/normalizer.js +29 -40
- package/dist/config/validator.js +94 -102
- package/dist/lifecycle/ado-migration-source.d.ts +1 -1
- package/dist/lifecycle/ado-migration-source.js +1 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +5 -6
- package/dist/lifecycle/github-lifecycle-provider.js +50 -20
- package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
- package/dist/lifecycle/lifecycle-formatter.js +1 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +16 -6
- package/dist/lifecycle/types.d.ts +30 -8
- package/dist/output/lifecycle-report.d.ts +4 -2
- package/dist/output/settings-report.d.ts +4 -4
- package/dist/repo/detector.d.ts +8 -0
- package/dist/{shared/repo-detector.js → repo/detector.js} +1 -4
- package/dist/repo/index.d.ts +4 -0
- package/dist/repo/index.js +3 -0
- package/dist/{shared/repo-metadata-provider.d.ts → repo/metadata-provider.d.ts} +3 -3
- package/dist/{shared/repo-metadata-provider.js → repo/metadata-provider.js} +3 -3
- package/dist/{shared/repo-detector.d.ts → repo/types.d.ts} +1 -7
- package/dist/repo/types.js +1 -0
- package/dist/{shared/repo-info-utils.d.ts → repo/utils.d.ts} +1 -1
- package/dist/{shared/repo-info-utils.js → repo/utils.js} +1 -1
- package/dist/settings/base-processor.d.ts +1 -1
- package/dist/settings/base-processor.js +1 -1
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +1 -1
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +1 -1
- package/dist/settings/code-scanning/processor.d.ts +2 -2
- package/dist/settings/code-scanning/types.d.ts +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/labels/formatter.js +16 -11
- package/dist/settings/labels/github-labels-strategy.d.ts +1 -1
- package/dist/settings/labels/github-labels-strategy.js +1 -1
- package/dist/settings/labels/processor.d.ts +1 -1
- package/dist/settings/labels/types.d.ts +1 -1
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +1 -1
- package/dist/settings/repo-settings/formatter.js +2 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +4 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +4 -4
- package/dist/settings/repo-settings/processor.d.ts +2 -2
- package/dist/settings/repo-settings/processor.js +5 -5
- package/dist/settings/repo-settings/types.d.ts +4 -4
- package/dist/settings/rulesets/diff-algorithm.js +1 -1
- package/dist/settings/rulesets/formatter.js +0 -3
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +1 -1
- package/dist/settings/rulesets/github-ruleset-strategy.js +1 -1
- package/dist/settings/rulesets/processor.d.ts +1 -1
- package/dist/settings/rulesets/types.d.ts +1 -1
- package/dist/shared/command-executor.js +3 -3
- package/dist/shared/gh-api-utils.d.ts +7 -4
- package/dist/shared/gh-api-utils.js +2 -2
- package/dist/shared/retry-utils.js +1 -1
- package/dist/shared/xfg-template.d.ts +22 -2
- package/dist/sync/auth-options-builder.d.ts +1 -1
- package/dist/sync/auth-options-builder.js +1 -1
- package/dist/sync/branch-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.d.ts +2 -2
- package/dist/sync/commit-push-manager.js +5 -3
- package/dist/sync/file-sync-orchestrator.d.ts +1 -1
- package/dist/sync/file-sync-strategy.d.ts +1 -1
- package/dist/sync/file-writer.js +44 -10
- package/dist/sync/repository-processor.d.ts +1 -1
- package/dist/sync/repository-session.d.ts +1 -1
- package/dist/sync/sync-workflow.d.ts +1 -1
- package/dist/sync/sync-workflow.js +2 -1
- package/dist/sync/types.d.ts +7 -4
- package/dist/vcs/{azure-pr-strategy.d.ts → ado-pr-strategy.d.ts} +2 -2
- package/dist/vcs/{azure-pr-strategy.js → ado-pr-strategy.js} +4 -4
- package/dist/vcs/authenticated-git-ops.d.ts +2 -0
- package/dist/vcs/authenticated-git-ops.js +6 -0
- package/dist/vcs/commit-strategy-selector.d.ts +1 -1
- package/dist/vcs/commit-strategy-selector.js +1 -1
- package/dist/vcs/file-mode-fixup-commit-strategy.d.ts +8 -6
- package/dist/vcs/file-mode-fixup-commit-strategy.js +79 -30
- package/dist/vcs/git-ops.d.ts +15 -3
- package/dist/vcs/git-ops.js +57 -24
- package/dist/vcs/github-app-token-manager.d.ts +1 -1
- package/dist/vcs/github-pr-strategy.d.ts +1 -1
- package/dist/vcs/github-pr-strategy.js +4 -4
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +4 -4
- package/dist/vcs/graphql-commit-strategy.js +8 -3
- package/dist/vcs/index.d.ts +1 -1
- package/dist/vcs/pr-creator.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +3 -3
- package/dist/vcs/pr-strategy.d.ts +1 -1
- package/dist/vcs/pr-strategy.js +1 -1
- package/dist/vcs/types.d.ts +10 -3
- package/package.json +3 -3
- /package/dist/{shared → vcs}/sanitize-utils.d.ts +0 -0
- /package/dist/{shared → vcs}/sanitize-utils.js +0 -0
package/dist/cli/sync-command.js
CHANGED
|
@@ -2,12 +2,12 @@ import { resolve, join } from "node:path";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { loadRawConfig, normalizeConfig, validateForSync, } from "../config/index.js";
|
|
4
4
|
import { ValidationError, SyncError } from "../shared/errors.js";
|
|
5
|
-
import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../
|
|
5
|
+
import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../repo/index.js";
|
|
6
6
|
import { sanitizeBranchName, validateBranchName } from "./branch-utils.js";
|
|
7
7
|
import { createTokenManager } from "../vcs/index.js";
|
|
8
8
|
import { RepositoryProcessor } from "../sync/index.js";
|
|
9
9
|
import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, CodeScanningProcessor, GitHubRulesetStrategy, GitHubRepoSettingsStrategy, GitHubLabelsStrategy, GitHubCodeScanningStrategy, } from "../settings/index.js";
|
|
10
|
-
import { GitHubRepoMetadataProvider } from "../
|
|
10
|
+
import { GitHubRepoMetadataProvider } from "../repo/index.js";
|
|
11
11
|
import { ShellCommandExecutor } from "../shared/command-executor.js";
|
|
12
12
|
import { Logger } from "../shared/logger.js";
|
|
13
13
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
@@ -44,7 +44,7 @@ import { buildSyncReport } from "./sync-report-builder.js";
|
|
|
44
44
|
import { formatSyncReportCLI } from "../output/sync-report.js";
|
|
45
45
|
import { buildLifecycleReport } from "./lifecycle-report-builder.js";
|
|
46
46
|
import { formatLifecycleReportCLI, hasLifecycleChanges, } from "../output/lifecycle-report.js";
|
|
47
|
-
import { writeUnifiedSummary } from "
|
|
47
|
+
import { writeUnifiedSummary } from "./unified-summary.js";
|
|
48
48
|
import { toErrorMessage } from "../shared/type-guards.js";
|
|
49
49
|
import { resolveGitHubToken } from "../shared/gh-api-utils.js";
|
|
50
50
|
import { RepoLifecycleManager, runLifecycleCheck, } from "../lifecycle/index.js";
|
|
@@ -116,7 +116,8 @@ async function runAndStoreResult(factory, repoConfig, repoInfo, opts, repoName,
|
|
|
116
116
|
return result;
|
|
117
117
|
}
|
|
118
118
|
function buildSettingsDescriptors(ctx) {
|
|
119
|
-
const { repoConfig, repoInfo, options, token, repoName, settingsCollector
|
|
119
|
+
const { repoConfig, repoInfo, options, token, repoName, settingsCollector } = ctx;
|
|
120
|
+
const { factories } = ctx;
|
|
120
121
|
const sharedOpts = {
|
|
121
122
|
dryRun: options.dryRun,
|
|
122
123
|
noDelete: options.noDelete,
|
|
@@ -126,28 +127,28 @@ function buildSettingsDescriptors(ctx) {
|
|
|
126
127
|
{
|
|
127
128
|
key: "rulesets",
|
|
128
129
|
label: "Rulesets",
|
|
129
|
-
run: () => runAndStoreResult(
|
|
130
|
+
run: () => runAndStoreResult(factories.rulesets, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
|
|
130
131
|
e.rulesetResult = r;
|
|
131
132
|
}),
|
|
132
133
|
},
|
|
133
134
|
{
|
|
134
135
|
key: "labels",
|
|
135
136
|
label: "Labels",
|
|
136
|
-
run: () => runAndStoreResult(
|
|
137
|
+
run: () => runAndStoreResult(factories.labels, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
|
|
137
138
|
e.labelsResult = r;
|
|
138
139
|
}),
|
|
139
140
|
},
|
|
140
141
|
{
|
|
141
142
|
key: "repo",
|
|
142
143
|
label: "Repo Settings",
|
|
143
|
-
run: () => runAndStoreResult(
|
|
144
|
+
run: () => runAndStoreResult(factories.repo, repoConfig, repoInfo, { dryRun: options.dryRun, token }, repoName, settingsCollector, (e, r) => {
|
|
144
145
|
e.settingsResult = r;
|
|
145
146
|
}),
|
|
146
147
|
},
|
|
147
148
|
{
|
|
148
149
|
key: "codeScanning",
|
|
149
150
|
label: "Code Scanning",
|
|
150
|
-
run: () => runAndStoreResult(
|
|
151
|
+
run: () => runAndStoreResult(factories.codeScanning, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
|
|
151
152
|
e.codeScanningResult = r;
|
|
152
153
|
}),
|
|
153
154
|
},
|
|
@@ -211,6 +212,14 @@ function displayReports(reportResults, lifecycleReportInputs, settingsCollector,
|
|
|
211
212
|
summaryPath: process.env.GITHUB_STEP_SUMMARY,
|
|
212
213
|
});
|
|
213
214
|
}
|
|
215
|
+
function pushFailure(results, repoName, error) {
|
|
216
|
+
results.push({
|
|
217
|
+
repoName,
|
|
218
|
+
success: false,
|
|
219
|
+
fileChanges: [],
|
|
220
|
+
error: toErrorMessage(error),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
214
223
|
/**
|
|
215
224
|
* Process a single repository: resolve URL, run lifecycle check, sync files, apply settings.
|
|
216
225
|
* Pushes results into ctx.reportResults, ctx.lifecycleReportInputs, and ctx.settingsCollector.
|
|
@@ -239,12 +248,7 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
239
248
|
}
|
|
240
249
|
catch (error) {
|
|
241
250
|
getLogger().error(repoNumber, repoConfig.git, toErrorMessage(error));
|
|
242
|
-
ctx.reportResults.
|
|
243
|
-
repoName: repoConfig.git,
|
|
244
|
-
success: false,
|
|
245
|
-
fileChanges: [],
|
|
246
|
-
error: toErrorMessage(error),
|
|
247
|
-
});
|
|
251
|
+
pushFailure(ctx.reportResults, repoConfig.git, error);
|
|
248
252
|
return;
|
|
249
253
|
}
|
|
250
254
|
const repoName = getRepoDisplayName(repoInfo);
|
|
@@ -280,10 +284,7 @@ async function processSingleRepo(repoConfig, index, ctx) {
|
|
|
280
284
|
options,
|
|
281
285
|
token: repoToken,
|
|
282
286
|
settingsCollector: ctx.settingsCollector,
|
|
283
|
-
|
|
284
|
-
repoSettingsProcessorFactory: ctx.repoSettingsProcessorFactory,
|
|
285
|
-
labelsProcessorFactory: ctx.labelsProcessorFactory,
|
|
286
|
-
codeScanningProcessorFactory: ctx.codeScanningProcessorFactory,
|
|
287
|
+
factories: ctx.factories,
|
|
287
288
|
});
|
|
288
289
|
}
|
|
289
290
|
/**
|
|
@@ -330,12 +331,7 @@ async function runLifecyclePhase(repo, ctx) {
|
|
|
330
331
|
}
|
|
331
332
|
catch (error) {
|
|
332
333
|
getLogger().error(repoNumber, repo.repoName, `Lifecycle error: ${toErrorMessage(error)}`);
|
|
333
|
-
ctx.reportResults.
|
|
334
|
-
repoName: repo.repoName,
|
|
335
|
-
success: false,
|
|
336
|
-
fileChanges: [],
|
|
337
|
-
error: toErrorMessage(error),
|
|
338
|
-
});
|
|
334
|
+
pushFailure(ctx.reportResults, repo.repoName, error);
|
|
339
335
|
return true;
|
|
340
336
|
}
|
|
341
337
|
}
|
|
@@ -383,19 +379,24 @@ async function runFileSyncPhase(repo, ctx) {
|
|
|
383
379
|
}
|
|
384
380
|
catch (error) {
|
|
385
381
|
getLogger().error(repoNumber, repo.repoName, toErrorMessage(error));
|
|
386
|
-
ctx.reportResults.
|
|
387
|
-
repoName: repo.repoName,
|
|
388
|
-
success: false,
|
|
389
|
-
fileChanges: [],
|
|
390
|
-
error: toErrorMessage(error),
|
|
391
|
-
});
|
|
382
|
+
pushFailure(ctx.reportResults, repo.repoName, error);
|
|
392
383
|
}
|
|
393
384
|
}
|
|
394
385
|
export async function runSync(options, deps = {}) {
|
|
395
386
|
// Reset module-level singletons to ensure fresh state per invocation
|
|
396
387
|
_defaultExecutor = undefined;
|
|
397
388
|
_logger = undefined;
|
|
398
|
-
const { lifecycleManager,
|
|
389
|
+
const { lifecycleManager, settingsProcessorFactories } = deps;
|
|
390
|
+
const factories = {
|
|
391
|
+
rulesets: settingsProcessorFactories?.rulesets ??
|
|
392
|
+
createDefaultRulesetProcessorFactory(),
|
|
393
|
+
labels: settingsProcessorFactories?.labels ??
|
|
394
|
+
createDefaultLabelsProcessorFactory(),
|
|
395
|
+
repo: settingsProcessorFactories?.repo ??
|
|
396
|
+
createDefaultRepoSettingsProcessorFactory(),
|
|
397
|
+
codeScanning: settingsProcessorFactories?.codeScanning ??
|
|
398
|
+
createDefaultCodeScanningProcessorFactory(),
|
|
399
|
+
};
|
|
399
400
|
const configPath = resolve(options.config);
|
|
400
401
|
if (!existsSync(configPath)) {
|
|
401
402
|
throw new ValidationError(`Config path not found: ${configPath}`);
|
|
@@ -443,10 +444,7 @@ export async function runSync(options, deps = {}) {
|
|
|
443
444
|
reportResults: [],
|
|
444
445
|
lifecycleReportInputs: [],
|
|
445
446
|
settingsCollector: new ResultsCollector(),
|
|
446
|
-
|
|
447
|
-
repoSettingsProcessorFactory,
|
|
448
|
-
labelsProcessorFactory,
|
|
449
|
-
codeScanningProcessorFactory,
|
|
447
|
+
factories,
|
|
450
448
|
};
|
|
451
449
|
for (let i = 0; i < config.repos.length; i++) {
|
|
452
450
|
await processSingleRepo(config.repos[i], i, ctx);
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { MergeMode, MergeStrategy, RepoConfig } from "../config/index.js";
|
|
2
2
|
import type { IRepoLifecycleManager } from "../lifecycle/index.js";
|
|
3
3
|
import type { IRepositoryProcessor } from "../sync/index.js";
|
|
4
|
-
import type { ISettingsProcessor, IRulesetProcessor, IRepoSettingsProcessor, ILabelsProcessor, ICodeScanningProcessor, BaseProcessorResult } from "../settings/index.js";
|
|
5
|
-
import type { RepoInfo } from "../
|
|
4
|
+
import type { ISettingsProcessor, IRulesetProcessor, IRepoSettingsProcessor, ILabelsProcessor, ICodeScanningProcessor, BaseProcessorResult, ActiveAction } from "../settings/index.js";
|
|
5
|
+
import type { RepoInfo } from "../repo/index.js";
|
|
6
6
|
import type { ResultsCollector } from "./results-collector.js";
|
|
7
7
|
export type ProcessorFactory = () => IRepositoryProcessor;
|
|
8
8
|
export type SettingsProcessorFactory<T extends ISettingsProcessor> = () => T;
|
|
@@ -10,16 +10,20 @@ export type RulesetProcessorFactory = SettingsProcessorFactory<IRulesetProcessor
|
|
|
10
10
|
export type RepoSettingsProcessorFactory = SettingsProcessorFactory<IRepoSettingsProcessor>;
|
|
11
11
|
export type LabelsProcessorFactory = SettingsProcessorFactory<ILabelsProcessor>;
|
|
12
12
|
export type CodeScanningProcessorFactory = SettingsProcessorFactory<ICodeScanningProcessor>;
|
|
13
|
+
export type SettingsKind = "rulesets" | "labels" | "repo" | "codeScanning";
|
|
14
|
+
export interface SettingsProcessorFactories {
|
|
15
|
+
rulesets: RulesetProcessorFactory;
|
|
16
|
+
labels: LabelsProcessorFactory;
|
|
17
|
+
repo: RepoSettingsProcessorFactory;
|
|
18
|
+
codeScanning: CodeScanningProcessorFactory;
|
|
19
|
+
}
|
|
13
20
|
/**
|
|
14
21
|
* Dependencies for the sync command (dependency injection).
|
|
15
22
|
*/
|
|
16
23
|
export interface SyncDependencies {
|
|
17
24
|
processorFactory?: ProcessorFactory;
|
|
18
25
|
lifecycleManager?: IRepoLifecycleManager;
|
|
19
|
-
|
|
20
|
-
repoSettingsProcessorFactory?: RepoSettingsProcessorFactory;
|
|
21
|
-
labelsProcessorFactory?: LabelsProcessorFactory;
|
|
22
|
-
codeScanningProcessorFactory?: CodeScanningProcessorFactory;
|
|
26
|
+
settingsProcessorFactories?: Partial<SettingsProcessorFactories>;
|
|
23
27
|
}
|
|
24
28
|
export interface SharedOptions {
|
|
25
29
|
config: string;
|
|
@@ -39,7 +43,7 @@ export interface SyncResultEntry {
|
|
|
39
43
|
success: boolean;
|
|
40
44
|
fileChanges: Array<{
|
|
41
45
|
path: string;
|
|
42
|
-
action:
|
|
46
|
+
action: ActiveAction;
|
|
43
47
|
diffLines?: string[];
|
|
44
48
|
}>;
|
|
45
49
|
prUrl?: string;
|
|
@@ -64,8 +68,5 @@ export interface ApplyRepoSettingsContext {
|
|
|
64
68
|
options: SyncOptions;
|
|
65
69
|
token: string | undefined;
|
|
66
70
|
settingsCollector: ResultsCollector;
|
|
67
|
-
|
|
68
|
-
repoSettingsProcessorFactory: NonNullable<SyncDependencies["repoSettingsProcessorFactory"]>;
|
|
69
|
-
labelsProcessorFactory: NonNullable<SyncDependencies["labelsProcessorFactory"]>;
|
|
70
|
-
codeScanningProcessorFactory: NonNullable<SyncDependencies["codeScanningProcessorFactory"]>;
|
|
71
|
+
factories: SettingsProcessorFactories;
|
|
71
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { LifecycleReport } from "
|
|
2
|
-
import type { SyncReport } from "
|
|
3
|
-
import type { SettingsReport } from "
|
|
1
|
+
import type { LifecycleReport } from "../output/lifecycle-report.js";
|
|
2
|
+
import type { SyncReport } from "../output/types.js";
|
|
3
|
+
import type { SettingsReport } from "../output/settings-report.js";
|
|
4
4
|
interface UnifiedSummaryInput {
|
|
5
5
|
lifecycle?: LifecycleReport;
|
|
6
6
|
sync?: SyncReport;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { hasLifecycleChanges } from "
|
|
2
|
-
import { writeGitHubStepSummary } from "
|
|
3
|
-
import { renderSyncLines } from "
|
|
4
|
-
import { renderRepoSettingsDiffLines, formatCountEntry, } from "
|
|
1
|
+
import { hasLifecycleChanges } from "../output/lifecycle-report.js";
|
|
2
|
+
import { writeGitHubStepSummary } from "../output/github-summary.js";
|
|
3
|
+
import { renderSyncLines } from "../output/sync-report.js";
|
|
4
|
+
import { renderRepoSettingsDiffLines, formatCountEntry, } from "../output/settings-report.js";
|
|
5
5
|
// =============================================================================
|
|
6
6
|
// Helpers
|
|
7
7
|
// =============================================================================
|
|
@@ -83,6 +83,25 @@ function resolveContentValue(value, configDir) {
|
|
|
83
83
|
// Otherwise return as-is (objects, arrays, plain strings)
|
|
84
84
|
return value;
|
|
85
85
|
}
|
|
86
|
+
function resolveContentInFilesMap(filesMap, configDir) {
|
|
87
|
+
if (!filesMap) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const [fileName, fileConfig] of Object.entries(filesMap)) {
|
|
91
|
+
if (fileConfig === false) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (fileConfig &&
|
|
95
|
+
typeof fileConfig === "object" &&
|
|
96
|
+
"content" in fileConfig) {
|
|
97
|
+
const typed = fileConfig;
|
|
98
|
+
const resolved = resolveContentValue(typed.content, configDir);
|
|
99
|
+
if (resolved !== undefined) {
|
|
100
|
+
filesMap[fileName] = { ...fileConfig, content: resolved };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
86
105
|
/**
|
|
87
106
|
* Resolve all file references in a raw config.
|
|
88
107
|
* Walks through files at root level and per-repo level.
|
|
@@ -100,74 +119,23 @@ export function resolveFileReferencesInConfig(raw, options) {
|
|
|
100
119
|
result.prTemplate = resolved;
|
|
101
120
|
}
|
|
102
121
|
// Resolve root-level file content
|
|
103
|
-
|
|
104
|
-
for (const [fileName, fileConfig] of Object.entries(result.files)) {
|
|
105
|
-
if (fileConfig &&
|
|
106
|
-
typeof fileConfig === "object" &&
|
|
107
|
-
"content" in fileConfig) {
|
|
108
|
-
const resolved = resolveContentValue(fileConfig.content, configDir);
|
|
109
|
-
if (resolved !== undefined) {
|
|
110
|
-
result.files[fileName] = { ...fileConfig, content: resolved };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
122
|
+
resolveContentInFilesMap(result.files, configDir);
|
|
115
123
|
// Resolve group-level file content
|
|
116
124
|
if (result.groups) {
|
|
117
|
-
for (const
|
|
118
|
-
|
|
119
|
-
for (const [fileName, fileConfig] of Object.entries(group.files)) {
|
|
120
|
-
if (fileConfig &&
|
|
121
|
-
typeof fileConfig === "object" &&
|
|
122
|
-
"content" in fileConfig) {
|
|
123
|
-
const resolved = resolveContentValue(fileConfig.content, configDir);
|
|
124
|
-
if (resolved !== undefined) {
|
|
125
|
-
result.groups[groupName].files[fileName] = {
|
|
126
|
-
...fileConfig,
|
|
127
|
-
content: resolved,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
125
|
+
for (const group of Object.values(result.groups)) {
|
|
126
|
+
resolveContentInFilesMap(group.files, configDir);
|
|
133
127
|
}
|
|
134
128
|
}
|
|
135
129
|
// Resolve conditional group file content
|
|
136
130
|
if (result.conditionalGroups) {
|
|
137
131
|
for (const cg of result.conditionalGroups) {
|
|
138
|
-
|
|
139
|
-
for (const [fileName, fileConfig] of Object.entries(cg.files)) {
|
|
140
|
-
if (fileConfig &&
|
|
141
|
-
typeof fileConfig === "object" &&
|
|
142
|
-
"content" in fileConfig) {
|
|
143
|
-
const resolved = resolveContentValue(fileConfig.content, configDir);
|
|
144
|
-
if (resolved !== undefined) {
|
|
145
|
-
cg.files[fileName] = { ...fileConfig, content: resolved };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
132
|
+
resolveContentInFilesMap(cg.files, configDir);
|
|
150
133
|
}
|
|
151
134
|
}
|
|
152
135
|
// Resolve per-repo file content
|
|
153
136
|
if (result.repos) {
|
|
154
137
|
for (const repo of result.repos) {
|
|
155
|
-
|
|
156
|
-
for (const [fileName, fileOverride] of Object.entries(repo.files)) {
|
|
157
|
-
// Skip false (exclusion) entries
|
|
158
|
-
if (fileOverride === false) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (fileOverride &&
|
|
162
|
-
typeof fileOverride === "object" &&
|
|
163
|
-
"content" in fileOverride) {
|
|
164
|
-
const resolved = resolveContentValue(fileOverride.content, configDir);
|
|
165
|
-
if (resolved !== undefined) {
|
|
166
|
-
repo.files[fileName] = { ...fileOverride, content: resolved };
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
138
|
+
resolveContentInFilesMap(repo.files, configDir);
|
|
171
139
|
}
|
|
172
140
|
}
|
|
173
141
|
return result;
|
|
@@ -294,6 +294,28 @@ function mergeGroupPROptions(rootPR, groupNames, groupDefs) {
|
|
|
294
294
|
}
|
|
295
295
|
return accumulated;
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Merges a named-entry map (e.g. rulesets, labels) where overlay entries
|
|
299
|
+
* extend or replace base entries. `inherit: false` in the overlay discards
|
|
300
|
+
* the base; `false` values mark explicit opt-outs. The merge callback runs
|
|
301
|
+
* when both base and overlay have an entry with the same name.
|
|
302
|
+
*/
|
|
303
|
+
function mergeNamedEntries(base, overlay, merge) {
|
|
304
|
+
const inherit = shouldInherit(overlay);
|
|
305
|
+
const result = inherit ? { ...(base ?? {}) } : {};
|
|
306
|
+
for (const [name, entry] of Object.entries(overlay)) {
|
|
307
|
+
if (name === "inherit")
|
|
308
|
+
continue;
|
|
309
|
+
if (entry === false) {
|
|
310
|
+
result[name] = false;
|
|
311
|
+
}
|
|
312
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
313
|
+
const existing = result[name];
|
|
314
|
+
result[name] = merge(existing, entry);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
297
319
|
/**
|
|
298
320
|
* Merges two raw settings layers (root/group into accumulated).
|
|
299
321
|
* Unlike mergeSettings(), this operates on raw types and returns raw types,
|
|
@@ -308,26 +330,9 @@ function mergeRawSettings(base, overlay) {
|
|
|
308
330
|
const result = base ? structuredClone(base) : {};
|
|
309
331
|
// Merge rulesets
|
|
310
332
|
if (overlay.rulesets) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
result.rulesets = {};
|
|
315
|
-
}
|
|
316
|
-
if (!result.rulesets)
|
|
317
|
-
result.rulesets = {};
|
|
318
|
-
for (const [name, ruleset] of Object.entries(overlay.rulesets)) {
|
|
319
|
-
if (name === "inherit")
|
|
320
|
-
continue;
|
|
321
|
-
if (ruleset === false) {
|
|
322
|
-
result.rulesets[name] = false;
|
|
323
|
-
}
|
|
324
|
-
else if (typeof ruleset === "object") {
|
|
325
|
-
const existing = result.rulesets[name];
|
|
326
|
-
result.rulesets[name] = existing
|
|
327
|
-
? mergeRuleset(existing, ruleset)
|
|
328
|
-
: structuredClone(ruleset);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
333
|
+
result.rulesets = mergeNamedEntries(result.rulesets, overlay.rulesets, (existing, entry) => existing && typeof existing === "object"
|
|
334
|
+
? mergeRuleset(existing, entry)
|
|
335
|
+
: structuredClone(entry));
|
|
331
336
|
}
|
|
332
337
|
// Merge repo settings: overlay replaces base (shallow merge, same as mergeSettings)
|
|
333
338
|
if (overlay.repo !== undefined) {
|
|
@@ -343,26 +348,10 @@ function mergeRawSettings(base, overlay) {
|
|
|
343
348
|
}
|
|
344
349
|
// Merge labels
|
|
345
350
|
if (overlay.labels) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
if (!result.labels)
|
|
351
|
-
result.labels = {};
|
|
352
|
-
for (const [name, label] of Object.entries(overlay.labels)) {
|
|
353
|
-
if (name === "inherit")
|
|
354
|
-
continue;
|
|
355
|
-
if (label === false) {
|
|
356
|
-
result.labels[name] = false;
|
|
357
|
-
}
|
|
358
|
-
else if (typeof label === "object") {
|
|
359
|
-
const existing = result.labels[name];
|
|
360
|
-
result.labels[name] = {
|
|
361
|
-
...(existing && typeof existing === "object" ? existing : {}),
|
|
362
|
-
...label,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
351
|
+
result.labels = mergeNamedEntries(result.labels, overlay.labels, (existing, entry) => ({
|
|
352
|
+
...(existing && typeof existing === "object" ? existing : {}),
|
|
353
|
+
...entry,
|
|
354
|
+
}));
|
|
366
355
|
}
|
|
367
356
|
// Merge code scanning: overlay fully replaces base (same semantics as mergeSettings)
|
|
368
357
|
if (overlay.codeScanning !== undefined) {
|