@aspruyt/xfg 6.1.0 → 6.3.0
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/lifecycle-report-builder.d.ts +2 -2
- package/dist/cli/lifecycle-report-builder.js +3 -11
- package/dist/cli/program.d.ts +2 -1
- package/dist/cli/program.js +43 -6
- package/dist/cli/repo-sync-runner.d.ts +24 -0
- package/dist/cli/repo-sync-runner.js +156 -0
- package/dist/cli/results-collector.d.ts +1 -1
- package/dist/cli/results-collector.js +2 -2
- package/dist/cli/secrets-command.d.ts +25 -0
- package/dist/cli/secrets-command.js +75 -0
- package/dist/cli/settings-factories.d.ts +8 -0
- package/dist/cli/settings-factories.js +32 -0
- package/dist/cli/settings-report-builder.d.ts +7 -2
- package/dist/cli/settings-report-builder.js +28 -20
- package/dist/cli/settings-runner.d.ts +2 -0
- package/dist/cli/settings-runner.js +94 -0
- package/dist/cli/sync-command.d.ts +1 -1
- package/dist/cli/sync-command.js +31 -372
- package/dist/cli/sync-report-builder.d.ts +1 -1
- package/dist/cli/sync-utils.d.ts +8 -0
- package/dist/cli/sync-utils.js +36 -0
- package/dist/cli/types.d.ts +8 -8
- package/dist/cli/unified-summary.d.ts +1 -3
- package/dist/cli/unified-summary.js +7 -5
- package/dist/cli.js +2 -1
- package/dist/{shared → config}/env.js +2 -2
- package/dist/config/extends-resolver.js +4 -3
- package/dist/config/file-reference-resolver.js +4 -2
- package/dist/config/formatter.js +1 -0
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/config/loader.js +30 -6
- package/dist/config/merge.d.ts +11 -1
- package/dist/config/merge.js +78 -6
- package/dist/config/normalizer.js +129 -49
- package/dist/config/types.d.ts +20 -0
- package/dist/config/validator.d.ts +5 -4
- package/dist/config/validator.js +187 -614
- package/dist/config/validators/file-validator.d.ts +2 -1
- package/dist/config/validators/file-validator.js +9 -1
- package/dist/config/validators/group-validator.d.ts +3 -0
- package/dist/config/validators/group-validator.js +167 -0
- package/dist/config/validators/repo-entry-validator.d.ts +2 -0
- package/dist/config/validators/repo-entry-validator.js +165 -0
- package/dist/config/validators/repo-settings-validator.js +18 -7
- package/dist/config/validators/ruleset-validator.js +2 -5
- package/dist/config/validators/shared.d.ts +11 -0
- package/dist/config/validators/shared.js +242 -0
- package/dist/lifecycle/ado-migration-source.js +2 -4
- package/dist/lifecycle/github-lifecycle-provider.d.ts +7 -11
- package/dist/lifecycle/github-lifecycle-provider.js +125 -136
- package/dist/lifecycle/{lifecycle-helpers.d.ts → helpers.d.ts} +5 -1
- package/dist/lifecycle/{lifecycle-helpers.js → helpers.js} +9 -8
- package/dist/lifecycle/index.d.ts +2 -2
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +2 -2
- package/dist/output/github-summary.js +2 -3
- package/dist/output/index.d.ts +4 -0
- package/dist/output/index.js +4 -0
- package/dist/output/lifecycle-report.d.ts +1 -1
- package/dist/output/lifecycle-report.js +5 -0
- package/dist/output/settings-report.d.ts +11 -0
- package/dist/output/settings-report.js +24 -0
- package/dist/output/sync-report.d.ts +25 -3
- package/dist/output/sync-report.js +11 -11
- package/dist/secrets/encryption.d.ts +9 -0
- package/dist/secrets/encryption.js +29 -0
- package/dist/secrets/github-secrets-strategy.d.ts +17 -0
- package/dist/secrets/github-secrets-strategy.js +38 -0
- package/dist/secrets/index.d.ts +5 -0
- package/dist/secrets/index.js +3 -0
- package/dist/secrets/processor.d.ts +31 -0
- package/dist/secrets/processor.js +115 -0
- package/dist/secrets/types.d.ts +21 -0
- package/dist/settings/base-processor.d.ts +18 -7
- package/dist/settings/base-processor.js +26 -5
- package/dist/settings/code-scanning/diff.js +2 -2
- package/dist/settings/code-scanning/formatter.d.ts +2 -6
- package/dist/settings/code-scanning/formatter.js +2 -25
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +3 -7
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +2 -2
- package/dist/settings/code-scanning/processor.js +6 -4
- package/dist/settings/code-scanning/types.d.ts +10 -8
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -0
- package/dist/settings/labels/github-labels-strategy.d.ts +3 -11
- package/dist/settings/labels/types.d.ts +12 -10
- 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.d.ts +2 -6
- package/dist/settings/repo-settings/formatter.js +4 -23
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -2
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +8 -7
- package/dist/settings/repo-settings/processor.js +11 -11
- package/dist/settings/repo-settings/types.d.ts +2 -2
- package/dist/settings/rulesets/diff-algorithm.js +4 -2
- package/dist/settings/rulesets/diff.js +2 -51
- package/dist/settings/rulesets/formatter.js +4 -0
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +3 -3
- package/dist/settings/rulesets/github-ruleset-strategy.js +4 -6
- package/dist/settings/rulesets/index.d.ts +1 -1
- package/dist/settings/rulesets/index.js +0 -2
- package/dist/settings/rulesets/processor.js +1 -1
- package/dist/settings/rulesets/types.d.ts +6 -2
- package/dist/settings/variables/diff.d.ts +10 -0
- package/dist/settings/variables/diff.js +39 -0
- package/dist/settings/variables/formatter.d.ts +16 -0
- package/dist/settings/variables/formatter.js +70 -0
- package/dist/settings/variables/github-variables-strategy.d.ts +17 -0
- package/dist/settings/variables/github-variables-strategy.js +40 -0
- package/dist/settings/variables/index.d.ts +4 -0
- package/dist/settings/variables/index.js +2 -0
- package/dist/settings/variables/processor.d.ts +19 -0
- package/dist/settings/variables/processor.js +60 -0
- package/dist/settings/variables/types.d.ts +18 -0
- package/dist/settings/variables/types.js +1 -0
- package/dist/shared/command-executor.d.ts +4 -4
- package/dist/shared/command-executor.js +9 -7
- package/dist/shared/diff-format.d.ts +1 -0
- package/dist/shared/diff-format.js +10 -0
- package/dist/shared/env-resolver.d.ts +16 -0
- package/dist/shared/env-resolver.js +33 -0
- package/dist/shared/errors.d.ts +7 -4
- package/dist/shared/errors.js +8 -8
- package/dist/shared/gh-api-utils.d.ts +3 -34
- package/dist/shared/gh-api-utils.js +23 -53
- package/dist/shared/gh-token-utils.d.ts +26 -0
- package/dist/shared/gh-token-utils.js +32 -0
- package/dist/shared/json-utils.js +1 -1
- package/dist/shared/regex-utils.d.ts +1 -0
- package/dist/shared/regex-utils.js +3 -0
- package/dist/shared/retry-utils.d.ts +1 -0
- package/dist/shared/retry-utils.js +13 -7
- package/dist/sync/auth-options-builder.js +1 -1
- package/dist/sync/branch-manager.js +5 -3
- package/dist/sync/commit-push-manager.js +2 -3
- package/dist/sync/diff-utils.d.ts +0 -1
- package/dist/sync/diff-utils.js +5 -10
- package/dist/sync/file-sync-orchestrator.js +0 -2
- package/dist/sync/file-writer.d.ts +3 -0
- package/dist/sync/file-writer.js +84 -81
- package/dist/sync/index.d.ts +0 -1
- package/dist/sync/index.js +0 -1
- package/dist/sync/manifest.js +1 -1
- package/dist/sync/pr-merge-handler.js +6 -6
- package/dist/sync/sync-workflow.js +1 -1
- package/dist/sync/types.d.ts +2 -2
- package/dist/vcs/ado-pr-strategy.d.ts +3 -5
- package/dist/vcs/ado-pr-strategy.js +131 -33
- package/dist/vcs/authenticated-git-ops.js +45 -23
- package/dist/vcs/git-commit-strategy.js +10 -6
- package/dist/vcs/git-ops.js +30 -24
- package/dist/vcs/github-pr-strategy.d.ts +3 -2
- package/dist/vcs/github-pr-strategy.js +80 -30
- package/dist/vcs/gitlab-pr-strategy.d.ts +2 -5
- package/dist/vcs/gitlab-pr-strategy.js +88 -87
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -5
- package/dist/vcs/graphql-commit-strategy.js +21 -37
- package/dist/vcs/pr-creator.js +9 -2
- package/dist/vcs/pr-strategy.d.ts +2 -3
- package/dist/vcs/pr-strategy.js +0 -1
- package/dist/vcs/types.d.ts +9 -5
- package/package.json +7 -5
- package/dist/config/validators/index.d.ts +0 -3
- package/dist/config/validators/index.js +0 -6
- package/dist/output/types.d.ts +0 -20
- package/dist/shared/shell-utils.d.ts +0 -6
- package/dist/shared/shell-utils.js +0 -17
- /package/dist/{shared → config}/env.d.ts +0 -0
- /package/dist/lifecycle/{lifecycle-formatter.d.ts → formatter.d.ts} +0 -0
- /package/dist/lifecycle/{lifecycle-formatter.js → formatter.js} +0 -0
- /package/dist/{output → secrets}/types.js +0 -0
- /package/dist/{vcs → shared}/sanitize-utils.d.ts +0 -0
- /package/dist/{vcs → shared}/sanitize-utils.js +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { SyncReport } from "../output/types.js";
|
|
3
|
-
import type { SettingsReport } from "../output/settings-report.js";
|
|
1
|
+
import { type LifecycleReport, type SyncReport, type SettingsReport } from "../output/index.js";
|
|
4
2
|
interface UnifiedSummaryInput {
|
|
5
3
|
lifecycle?: LifecycleReport;
|
|
6
4
|
sync?: SyncReport;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { hasLifecycleChanges } from "../output/
|
|
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";
|
|
1
|
+
import { hasLifecycleChanges, writeGitHubStepSummary, renderSyncLines, renderRepoSettingsDiffLines, formatCountEntry, } from "../output/index.js";
|
|
5
2
|
// =============================================================================
|
|
6
3
|
// Helpers
|
|
7
4
|
// =============================================================================
|
|
@@ -121,6 +118,11 @@ function renderLifecycleLines(lcAction, diffLines) {
|
|
|
121
118
|
case "migrated":
|
|
122
119
|
diffLines.push(`+ MIGRATE ${lcAction.source ?? "source"} -> ${lcAction.repoName}`);
|
|
123
120
|
break;
|
|
121
|
+
/* c8 ignore next 4 */
|
|
122
|
+
default: {
|
|
123
|
+
const _exhaustive = lcAction.action;
|
|
124
|
+
throw new Error(`Unexpected lifecycle action: ${_exhaustive}`);
|
|
125
|
+
}
|
|
124
126
|
}
|
|
125
127
|
if (lcAction.settings) {
|
|
126
128
|
if (lcAction.settings.visibility) {
|
|
@@ -188,7 +190,7 @@ export function formatUnifiedSummaryMarkdown(input) {
|
|
|
188
190
|
if (hasLcChange && hasSyncChanges)
|
|
189
191
|
diffLines.push("");
|
|
190
192
|
if (syncRepo)
|
|
191
|
-
renderSyncLines(syncRepo
|
|
193
|
+
diffLines.push(...renderSyncLines(syncRepo));
|
|
192
194
|
// Blank line between files and settings sections
|
|
193
195
|
if (hasSyncChanges && hasSettingsChanges)
|
|
194
196
|
diffLines.push("");
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Supports ${VAR}, ${VAR:-default}, and ${VAR:?message} syntax.
|
|
4
4
|
* Use $${VAR} to escape and output literal ${VAR}.
|
|
5
5
|
*/
|
|
6
|
-
import { interpolateString, interpolateValue, } from "
|
|
7
|
-
import { ValidationError } from "
|
|
6
|
+
import { interpolateString, interpolateValue, } from "../shared/interpolation-engine.js";
|
|
7
|
+
import { ValidationError } from "../shared/errors.js";
|
|
8
8
|
/**
|
|
9
9
|
* Regex to match environment variable placeholders.
|
|
10
10
|
* Captures:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "../shared/errors.js";
|
|
1
2
|
const MAX_EXTENDS_DEPTH = 100;
|
|
2
3
|
/**
|
|
3
4
|
* Resolves a single group's extends chain into an ordered list of group names.
|
|
@@ -7,16 +8,16 @@ const MAX_EXTENDS_DEPTH = 100;
|
|
|
7
8
|
export function resolveExtendsChain(groupName, groupDefs) {
|
|
8
9
|
function walk(name, visited, depth) {
|
|
9
10
|
if (depth > MAX_EXTENDS_DEPTH) {
|
|
10
|
-
throw new
|
|
11
|
+
throw new ValidationError(`Extends chain exceeds maximum depth of ${MAX_EXTENDS_DEPTH} — likely misconfigured`);
|
|
11
12
|
}
|
|
12
13
|
if (visited.has(name)) {
|
|
13
14
|
const cycle = [...visited, name].join(" -> ");
|
|
14
|
-
throw new
|
|
15
|
+
throw new ValidationError(`Circular extends detected: ${cycle}`);
|
|
15
16
|
}
|
|
16
17
|
visited.add(name);
|
|
17
18
|
const group = groupDefs[name];
|
|
18
19
|
if (!group) {
|
|
19
|
-
throw new
|
|
20
|
+
throw new ValidationError(`Group '${name}' referenced in extends chain does not exist`);
|
|
20
21
|
}
|
|
21
22
|
if (!group.extends) {
|
|
22
23
|
return [name];
|
|
@@ -44,7 +44,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
46
|
const msg = toErrorMessage(error);
|
|
47
|
-
throw new ValidationError(`Failed to load file reference "${reference}": ${msg}
|
|
47
|
+
throw new ValidationError(`Failed to load file reference "${reference}": ${msg}`, { cause: error });
|
|
48
48
|
}
|
|
49
49
|
// Parse based on extension
|
|
50
50
|
const ext = extname(relativePath).toLowerCase();
|
|
@@ -65,7 +65,9 @@ function parseWithContext(fn, errorPrefix) {
|
|
|
65
65
|
return fn();
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
throw new ValidationError(`${errorPrefix}: ${toErrorMessage(error)}
|
|
68
|
+
throw new ValidationError(`${errorPrefix}: ${toErrorMessage(error)}`, {
|
|
69
|
+
cause: error,
|
|
70
|
+
});
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
/**
|
package/dist/config/formatter.js
CHANGED
package/dist/config/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, RepoVisibility, SquashMergeCommitTitle, SquashMergeCommitMessage, MergeCommitTitle, MergeCommitMessage, Label, CodeScanningSettings, CodeScanningState, CodeScanningQuerySuite, CodeScanningLanguage, RepoSettings, RawFileConfig, RawRepoFileOverride, RawGroupConfig, RawRepoSettings, RawRepoConfig, RawConfig, RawConditionalGroupWhen, RawConditionalGroupConfig, RepoConfig, Config, FileContent, ContentValue, } from "./types.js";
|
|
1
|
+
export type { PRMergeOptions, MergeMode, MergeStrategy, BypassActor, StatusCheckConfig, CodeScanningTool, PullRequestRuleParameters, RulesetRule, Ruleset, GitHubRepoSettings, RepoVisibility, SquashMergeCommitTitle, SquashMergeCommitMessage, MergeCommitTitle, MergeCommitMessage, Label, CodeScanningSettings, CodeScanningState, CodeScanningQuerySuite, CodeScanningLanguage, RepoSettings, RawFileConfig, RawRepoFileOverride, RawGroupConfig, SecretConfig, RawRootSettings, RawRepoSettings, RawRepoConfig, RawConfig, RawConditionalGroupWhen, RawConditionalGroupConfig, RepoConfig, Config, FileContent, ContentValue, } 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 { validateForSync } from "./validator.js";
|
|
5
|
+
export { validateForSync, validateSecretsConfig } from "./validator.js";
|
package/dist/config/index.js
CHANGED
|
@@ -5,4 +5,4 @@ export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
|
5
5
|
// Config formatting
|
|
6
6
|
export { convertContentToString } from "./formatter.js";
|
|
7
7
|
// Config validation
|
|
8
|
-
export { validateForSync } from "./validator.js";
|
|
8
|
+
export { validateForSync, validateSecretsConfig } from "./validator.js";
|
package/dist/config/loader.js
CHANGED
|
@@ -13,7 +13,13 @@ export { normalizeConfigInternal as normalizeConfig };
|
|
|
13
13
|
* Use this when you need to perform command-specific validation before normalizing.
|
|
14
14
|
*/
|
|
15
15
|
export function loadRawConfig(configPath) {
|
|
16
|
-
|
|
16
|
+
let stat;
|
|
17
|
+
try {
|
|
18
|
+
stat = statSync(configPath);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new ValidationError(`Failed to read config at ${configPath}: ${toErrorMessage(error)}`, { cause: error });
|
|
22
|
+
}
|
|
17
23
|
if (stat.isDirectory()) {
|
|
18
24
|
return loadRawConfigFromDirectory(configPath);
|
|
19
25
|
}
|
|
@@ -24,7 +30,13 @@ export function loadConfig(configPath, env) {
|
|
|
24
30
|
return normalizeConfigInternal(rawConfig, env);
|
|
25
31
|
}
|
|
26
32
|
function loadRawConfigFromFile(filePath) {
|
|
27
|
-
|
|
33
|
+
let content;
|
|
34
|
+
try {
|
|
35
|
+
content = readFileSync(filePath, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new ValidationError(`Failed to read config file ${filePath}: ${toErrorMessage(error)}`, { cause: error });
|
|
39
|
+
}
|
|
28
40
|
const configDir = dirname(filePath);
|
|
29
41
|
let rawConfig;
|
|
30
42
|
try {
|
|
@@ -32,7 +44,7 @@ function loadRawConfigFromFile(filePath) {
|
|
|
32
44
|
}
|
|
33
45
|
catch (error) {
|
|
34
46
|
const message = toErrorMessage(error);
|
|
35
|
-
throw new ValidationError(`Failed to parse YAML config at ${filePath}: ${message}
|
|
47
|
+
throw new ValidationError(`Failed to parse YAML config at ${filePath}: ${message}`, { cause: error });
|
|
36
48
|
}
|
|
37
49
|
// Resolve file references before validation so content type checking works
|
|
38
50
|
rawConfig = resolveFileReferencesInConfig(rawConfig, { configDir });
|
|
@@ -40,7 +52,13 @@ function loadRawConfigFromFile(filePath) {
|
|
|
40
52
|
return rawConfig;
|
|
41
53
|
}
|
|
42
54
|
function loadRawConfigFromDirectory(dirPath) {
|
|
43
|
-
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new ValidationError(`Failed to read config directory ${dirPath}: ${toErrorMessage(error)}`, { cause: error });
|
|
61
|
+
}
|
|
44
62
|
const yamlFiles = entries
|
|
45
63
|
.filter((entry) => entry.isFile() &&
|
|
46
64
|
[".yaml", ".yml"].includes(extname(entry.name).toLowerCase()))
|
|
@@ -51,7 +69,13 @@ function loadRawConfigFromDirectory(dirPath) {
|
|
|
51
69
|
}
|
|
52
70
|
const fragments = yamlFiles.map((fileName) => {
|
|
53
71
|
const filePath = join(dirPath, fileName);
|
|
54
|
-
|
|
72
|
+
let content;
|
|
73
|
+
try {
|
|
74
|
+
content = readFileSync(filePath, "utf-8");
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new ValidationError(`Failed to read config file ${filePath}: ${toErrorMessage(error)}`, { cause: error });
|
|
78
|
+
}
|
|
55
79
|
const configDir = dirname(filePath);
|
|
56
80
|
let config;
|
|
57
81
|
try {
|
|
@@ -59,7 +83,7 @@ function loadRawConfigFromDirectory(dirPath) {
|
|
|
59
83
|
}
|
|
60
84
|
catch (error) {
|
|
61
85
|
const message = toErrorMessage(error);
|
|
62
|
-
throw new ValidationError(`Failed to parse YAML config at ${filePath}: ${message}
|
|
86
|
+
throw new ValidationError(`Failed to parse YAML config at ${filePath}: ${message}`, { cause: error });
|
|
63
87
|
}
|
|
64
88
|
if (!config || typeof config !== "object") {
|
|
65
89
|
throw new ValidationError(`Config file ${fileName} is empty or invalid — expected a YAML mapping`);
|
package/dist/config/merge.d.ts
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
* Deep merge utilities for JSON configuration objects.
|
|
3
3
|
* Supports per-field array merge strategies via $arrayMerge + $values directives.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Candidate keys for matching array items by identity rather than index.
|
|
7
|
+
* Order matters — first key found across all items wins.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MATCH_KEY_CANDIDATES: readonly ["type", "actor_id"];
|
|
10
|
+
/**
|
|
11
|
+
* Finds a key that uniquely identifies items in both arrays.
|
|
12
|
+
* Returns the first candidate key present in every item of both arrays, or undefined.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findMatchKey(base: unknown[], overlay: unknown[]): string | undefined;
|
|
15
|
+
export type ArrayMergeStrategy = "replace" | "append" | "prepend" | "merge";
|
|
6
16
|
export interface MergeContext {
|
|
7
17
|
defaultArrayStrategy: ArrayMergeStrategy;
|
|
8
18
|
}
|
package/dist/config/merge.js
CHANGED
|
@@ -3,16 +3,87 @@
|
|
|
3
3
|
* Supports per-field array merge strategies via $arrayMerge + $values directives.
|
|
4
4
|
*/
|
|
5
5
|
import { isPlainObject } from "../shared/type-guards.js";
|
|
6
|
+
/**
|
|
7
|
+
* Candidate keys for matching array items by identity rather than index.
|
|
8
|
+
* Order matters — first key found across all items wins.
|
|
9
|
+
*/
|
|
10
|
+
export const MATCH_KEY_CANDIDATES = ["type", "actor_id"];
|
|
11
|
+
/**
|
|
12
|
+
* Finds a key that uniquely identifies items in both arrays.
|
|
13
|
+
* Returns the first candidate key present in every item of both arrays, or undefined.
|
|
14
|
+
*/
|
|
15
|
+
export function findMatchKey(base, overlay) {
|
|
16
|
+
if (base.length === 0 && overlay.length === 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
const hasKey = (item, key) => isPlainObject(item) && key in item;
|
|
19
|
+
for (const candidate of MATCH_KEY_CANDIDATES) {
|
|
20
|
+
if (base.every((item) => hasKey(item, candidate)) &&
|
|
21
|
+
overlay.every((item) => hasKey(item, candidate))) {
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
6
27
|
/**
|
|
7
28
|
* Keys reserved for xfg merge directives.
|
|
8
29
|
* Only these are stripped during merge — standard $-prefixed keys
|
|
9
30
|
* like $schema, $id, $ref, $generated are preserved.
|
|
10
31
|
*/
|
|
11
32
|
const XFG_DIRECTIVES = new Set(["$arrayMerge", "$values"]);
|
|
33
|
+
function mergeByKey(base, overlay, matchKey, ctx) {
|
|
34
|
+
const baseByKey = new Map();
|
|
35
|
+
// findMatchKey guarantees every item in both arrays is a plain object with matchKey
|
|
36
|
+
for (let i = 0; i < base.length; i++) {
|
|
37
|
+
const item = base[i];
|
|
38
|
+
const keyValue = item[matchKey];
|
|
39
|
+
if (keyValue !== undefined && !baseByKey.has(keyValue)) {
|
|
40
|
+
baseByKey.set(keyValue, { item, index: i });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const appended = [];
|
|
44
|
+
for (const overlayItem of overlay) {
|
|
45
|
+
const item = overlayItem;
|
|
46
|
+
const keyValue = item[matchKey];
|
|
47
|
+
const baseEntry = baseByKey.get(keyValue);
|
|
48
|
+
if (baseEntry) {
|
|
49
|
+
baseByKey.set(keyValue, {
|
|
50
|
+
item: deepMerge(baseEntry.item, item, ctx),
|
|
51
|
+
index: baseEntry.index,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
appended.push(overlayItem);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const result = [];
|
|
59
|
+
for (let i = 0; i < base.length; i++) {
|
|
60
|
+
const item = base[i];
|
|
61
|
+
const keyValue = item[matchKey];
|
|
62
|
+
const entry = baseByKey.get(keyValue);
|
|
63
|
+
if (entry && entry.index === i) {
|
|
64
|
+
result.push(entry.item);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
result.push(item);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
result.push(...appended);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
12
73
|
const arrayMergeStrategies = new Map([
|
|
13
74
|
["replace", (_base, overlay) => overlay],
|
|
14
75
|
["append", (base, overlay) => [...base, ...overlay]],
|
|
15
76
|
["prepend", (base, overlay) => [...overlay, ...base]],
|
|
77
|
+
[
|
|
78
|
+
"merge",
|
|
79
|
+
(base, overlay, ctx) => {
|
|
80
|
+
const matchKey = findMatchKey(base, overlay);
|
|
81
|
+
if (!matchKey) {
|
|
82
|
+
return [...base, ...overlay];
|
|
83
|
+
}
|
|
84
|
+
return mergeByKey(base, overlay, matchKey, ctx);
|
|
85
|
+
},
|
|
86
|
+
],
|
|
16
87
|
]);
|
|
17
88
|
/**
|
|
18
89
|
* Checks if a value is an unresolved $arrayMerge directive object
|
|
@@ -28,12 +99,11 @@ function isUnresolvedDirective(value) {
|
|
|
28
99
|
arrayMergeStrategies.has(value.$arrayMerge) &&
|
|
29
100
|
Array.isArray(value.$values));
|
|
30
101
|
}
|
|
31
|
-
function mergeArrays(base, overlay, strategy) {
|
|
102
|
+
function mergeArrays(base, overlay, strategy, ctx) {
|
|
32
103
|
const handler = arrayMergeStrategies.get(strategy);
|
|
33
104
|
if (handler) {
|
|
34
|
-
return handler(base, overlay);
|
|
105
|
+
return handler(base, overlay, ctx);
|
|
35
106
|
}
|
|
36
|
-
// Fallback to replace for unknown strategies
|
|
37
107
|
return overlay;
|
|
38
108
|
}
|
|
39
109
|
/**
|
|
@@ -61,16 +131,17 @@ export function deepMerge(base, overlay, ctx) {
|
|
|
61
131
|
const values = overlayValue.$values;
|
|
62
132
|
if ((strategy === "replace" ||
|
|
63
133
|
strategy === "append" ||
|
|
64
|
-
strategy === "prepend"
|
|
134
|
+
strategy === "prepend" ||
|
|
135
|
+
strategy === "merge") &&
|
|
65
136
|
Array.isArray(values) &&
|
|
66
137
|
Array.isArray(resolvedBase)) {
|
|
67
|
-
result[key] = mergeArrays(resolvedBase, values, strategy);
|
|
138
|
+
result[key] = mergeArrays(resolvedBase, values, strategy, ctx);
|
|
68
139
|
continue;
|
|
69
140
|
}
|
|
70
141
|
}
|
|
71
142
|
// Both are arrays — use default strategy
|
|
72
143
|
if (Array.isArray(resolvedBase) && Array.isArray(overlayValue)) {
|
|
73
|
-
result[key] = mergeArrays(resolvedBase, overlayValue, ctx.defaultArrayStrategy);
|
|
144
|
+
result[key] = mergeArrays(resolvedBase, overlayValue, ctx.defaultArrayStrategy, ctx);
|
|
74
145
|
continue;
|
|
75
146
|
}
|
|
76
147
|
// Both are plain objects — recurse
|
|
@@ -144,6 +215,7 @@ export function mergeTextContent(base, overlay, strategy = "replace") {
|
|
|
144
215
|
if (Array.isArray(base)) {
|
|
145
216
|
switch (strategy) {
|
|
146
217
|
case "append":
|
|
218
|
+
case "merge":
|
|
147
219
|
return [...base, ...overlay];
|
|
148
220
|
case "prepend":
|
|
149
221
|
return [...overlay, ...base];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deepMerge, stripMergeDirectives, createMergeContext, isTextContent, mergeTextContent, } from "./merge.js";
|
|
2
|
-
import { interpolateContent } from "
|
|
2
|
+
import { interpolateContent } from "./env.js";
|
|
3
3
|
import { expandRepoGroups } from "./extends-resolver.js";
|
|
4
4
|
/**
|
|
5
5
|
* Clone content, stripping merge directives from object content.
|
|
@@ -79,22 +79,8 @@ function mergePROptions(global, perRepo) {
|
|
|
79
79
|
return perRepo;
|
|
80
80
|
if (!perRepo)
|
|
81
81
|
return global;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const mergeStrategy = perRepo.mergeStrategy ?? global.mergeStrategy;
|
|
85
|
-
const deleteBranch = perRepo.deleteBranch ?? global.deleteBranch;
|
|
86
|
-
const bypassReason = perRepo.bypassReason ?? global.bypassReason;
|
|
87
|
-
const labels = perRepo.labels ?? global.labels;
|
|
88
|
-
if (merge !== undefined)
|
|
89
|
-
result.merge = merge;
|
|
90
|
-
if (mergeStrategy !== undefined)
|
|
91
|
-
result.mergeStrategy = mergeStrategy;
|
|
92
|
-
if (deleteBranch !== undefined)
|
|
93
|
-
result.deleteBranch = deleteBranch;
|
|
94
|
-
if (bypassReason !== undefined)
|
|
95
|
-
result.bypassReason = bypassReason;
|
|
96
|
-
if (labels !== undefined)
|
|
97
|
-
result.labels = labels;
|
|
82
|
+
const merged = { ...global, ...perRepo };
|
|
83
|
+
const result = Object.fromEntries(Object.entries(merged).filter(([, v]) => v !== undefined));
|
|
98
84
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
99
85
|
}
|
|
100
86
|
/**
|
|
@@ -141,6 +127,23 @@ function mergeLabels(rootLabels, repoLabels) {
|
|
|
141
127
|
}
|
|
142
128
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
143
129
|
}
|
|
130
|
+
// GitHub treats variable names case-insensitively; overlay keys win over base keys with same name but different casing.
|
|
131
|
+
function mergeVariablesCaseInsensitive(base, overlay) {
|
|
132
|
+
const overlayUpper = new Map();
|
|
133
|
+
for (const key of Object.keys(overlay)) {
|
|
134
|
+
overlayUpper.set(key.toUpperCase(), key);
|
|
135
|
+
}
|
|
136
|
+
const result = {};
|
|
137
|
+
for (const [key, value] of Object.entries(base)) {
|
|
138
|
+
if (!overlayUpper.has(key.toUpperCase())) {
|
|
139
|
+
result[key] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
143
|
+
result[key] = value;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
144
147
|
/**
|
|
145
148
|
* Merges settings: per-repo settings deep merge with root settings.
|
|
146
149
|
* Returns undefined if no settings are defined.
|
|
@@ -179,7 +182,9 @@ export function mergeSettings(root, perRepo) {
|
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
// deleteOrphaned: per-repo overrides root
|
|
182
|
-
const deleteOrphaned = perRepo?.deleteOrphaned
|
|
185
|
+
const deleteOrphaned = perRepo?.deleteOrphaned !== undefined
|
|
186
|
+
? perRepo.deleteOrphaned
|
|
187
|
+
: root?.deleteOrphaned;
|
|
183
188
|
if (deleteOrphaned !== undefined) {
|
|
184
189
|
result.deleteOrphaned = deleteOrphaned;
|
|
185
190
|
}
|
|
@@ -223,6 +228,37 @@ export function mergeSettings(root, perRepo) {
|
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
}
|
|
231
|
+
// Variables merging — deleteOrphaned is a peer key (like secrets' deleteOrphaned)
|
|
232
|
+
if (root?.variables || perRepo?.variables) {
|
|
233
|
+
const rootVars = root?.variables ?? {};
|
|
234
|
+
const repoVars = perRepo?.variables ?? {};
|
|
235
|
+
const rootDeleteOrphaned = rootVars
|
|
236
|
+
.deleteOrphaned;
|
|
237
|
+
const repoDeleteOrphaned = repoVars
|
|
238
|
+
.deleteOrphaned;
|
|
239
|
+
const effectiveDeleteOrphaned = repoDeleteOrphaned !== undefined
|
|
240
|
+
? repoDeleteOrphaned
|
|
241
|
+
: rootDeleteOrphaned;
|
|
242
|
+
const inherit = repoVars.inherit;
|
|
243
|
+
if (inherit === false) {
|
|
244
|
+
const { inherit: _, deleteOrphaned: _d, ...rest } = repoVars;
|
|
245
|
+
result.variables = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== false));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const combined = mergeVariablesCaseInsensitive(rootVars, repoVars);
|
|
249
|
+
const { inherit: _, deleteOrphaned: _d, ...rest } = combined;
|
|
250
|
+
result.variables = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== false));
|
|
251
|
+
}
|
|
252
|
+
if (effectiveDeleteOrphaned !== undefined) {
|
|
253
|
+
result.variables.deleteOrphaned =
|
|
254
|
+
effectiveDeleteOrphaned;
|
|
255
|
+
}
|
|
256
|
+
// Only delete if no variable entries remain (deleteOrphaned alone is not actionable)
|
|
257
|
+
const { deleteOrphaned: _check, ...varEntries } = result.variables;
|
|
258
|
+
if (Object.keys(varEntries).length === 0 && !effectiveDeleteOrphaned) {
|
|
259
|
+
delete result.variables;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
226
262
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
227
263
|
}
|
|
228
264
|
/**
|
|
@@ -362,6 +398,40 @@ function mergeRawSettings(base, overlay) {
|
|
|
362
398
|
result.codeScanning = structuredClone(overlay.codeScanning);
|
|
363
399
|
}
|
|
364
400
|
}
|
|
401
|
+
// Variables: simple string values with false opt-outs (mergeNamedEntries won't work for strings)
|
|
402
|
+
if (overlay.variables) {
|
|
403
|
+
const overlayVars = overlay.variables;
|
|
404
|
+
const inherit = overlayVars.inherit !== false;
|
|
405
|
+
const baseVars = inherit
|
|
406
|
+
? { ...(result.variables ?? {}) }
|
|
407
|
+
: {};
|
|
408
|
+
// Build overlay entries (excluding meta keys)
|
|
409
|
+
const overlayEntries = {};
|
|
410
|
+
for (const [name, entry] of Object.entries(overlay.variables)) {
|
|
411
|
+
if (name === "inherit" || name === "deleteOrphaned")
|
|
412
|
+
continue;
|
|
413
|
+
overlayEntries[name] = entry;
|
|
414
|
+
}
|
|
415
|
+
// Case-insensitive merge: overlay keys replace base keys with same name
|
|
416
|
+
const merged = mergeVariablesCaseInsensitive(baseVars, overlayEntries);
|
|
417
|
+
// Apply false opt-outs
|
|
418
|
+
const cleaned = {};
|
|
419
|
+
for (const [name, value] of Object.entries(merged)) {
|
|
420
|
+
if (name === "inherit" || name === "deleteOrphaned")
|
|
421
|
+
continue;
|
|
422
|
+
if (value !== false) {
|
|
423
|
+
cleaned[name] = value;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const baseDelete = baseVars.deleteOrphaned;
|
|
427
|
+
const effectiveDelete = overlayVars.deleteOrphaned !== undefined
|
|
428
|
+
? overlayVars.deleteOrphaned
|
|
429
|
+
: baseDelete;
|
|
430
|
+
if (effectiveDelete !== undefined) {
|
|
431
|
+
cleaned.deleteOrphaned = effectiveDelete;
|
|
432
|
+
}
|
|
433
|
+
result.variables = cleaned;
|
|
434
|
+
}
|
|
365
435
|
// deleteOrphaned: overlay wins
|
|
366
436
|
if (overlay.deleteOrphaned !== undefined) {
|
|
367
437
|
result.deleteOrphaned = overlay.deleteOrphaned;
|
|
@@ -454,6 +524,35 @@ function resolveFileEntry(fileName, fileConfig, repoOverride, inheritFiles, glob
|
|
|
454
524
|
globalDeleteOrphaned,
|
|
455
525
|
};
|
|
456
526
|
}
|
|
527
|
+
function normalizeRepoEntry(ctx) {
|
|
528
|
+
const files = [];
|
|
529
|
+
const inheritFiles = shouldInherit(ctx.rawRepo.files);
|
|
530
|
+
for (const fileName of ctx.fileNames) {
|
|
531
|
+
if (fileName === "inherit")
|
|
532
|
+
continue;
|
|
533
|
+
const entry = resolveFileEntry(fileName, ctx.effectiveRootFiles[fileName], ctx.rawRepo.files?.[fileName], inheritFiles, ctx.globalDeleteOrphaned, ctx.env);
|
|
534
|
+
if (entry)
|
|
535
|
+
files.push(entry);
|
|
536
|
+
}
|
|
537
|
+
for (const fileName of ctx.repoOnlyFileNames) {
|
|
538
|
+
const repoOverride = ctx.rawRepo.files[fileName];
|
|
539
|
+
if (repoOverride === false)
|
|
540
|
+
continue;
|
|
541
|
+
const entry = resolveFileEntry(fileName, {}, repoOverride, true, ctx.globalDeleteOrphaned, ctx.env);
|
|
542
|
+
if (entry)
|
|
543
|
+
files.push(entry);
|
|
544
|
+
}
|
|
545
|
+
const prOptions = mergePROptions(ctx.effectivePROptions, ctx.rawRepo.prOptions);
|
|
546
|
+
const settings = mergeSettings(ctx.effectiveSettings, ctx.rawRepo.settings);
|
|
547
|
+
return {
|
|
548
|
+
git: ctx.gitUrl,
|
|
549
|
+
files,
|
|
550
|
+
prOptions,
|
|
551
|
+
settings,
|
|
552
|
+
upstream: ctx.rawRepo.upstream,
|
|
553
|
+
source: ctx.rawRepo.source,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
457
556
|
/**
|
|
458
557
|
* Normalizes raw config into expanded, merged config.
|
|
459
558
|
* Pipeline: expand git arrays -> merge content -> interpolate env vars
|
|
@@ -497,37 +596,17 @@ export function normalizeConfig(raw, env) {
|
|
|
497
596
|
}
|
|
498
597
|
}
|
|
499
598
|
for (const gitUrl of gitUrls) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
for (const fileName of repoOnlyFileNames) {
|
|
512
|
-
const repoOverride = rawRepo.files[fileName];
|
|
513
|
-
if (repoOverride === false)
|
|
514
|
-
continue;
|
|
515
|
-
const entry = resolveFileEntry(fileName, {}, repoOverride, true, raw.deleteOrphaned, env);
|
|
516
|
-
if (entry)
|
|
517
|
-
files.push(entry);
|
|
518
|
-
}
|
|
519
|
-
// Merge PR options: per-repo overrides effective (root + groups)
|
|
520
|
-
const prOptions = mergePROptions(effectivePROptions, rawRepo.prOptions);
|
|
521
|
-
// Merge settings: per-repo deep merges with effective (root + groups)
|
|
522
|
-
const settings = mergeSettings(effectiveSettings, rawRepo.settings);
|
|
523
|
-
expandedRepos.push({
|
|
524
|
-
git: gitUrl,
|
|
525
|
-
files,
|
|
526
|
-
prOptions,
|
|
527
|
-
settings,
|
|
528
|
-
upstream: rawRepo.upstream,
|
|
529
|
-
source: rawRepo.source,
|
|
530
|
-
});
|
|
599
|
+
expandedRepos.push(normalizeRepoEntry({
|
|
600
|
+
gitUrl,
|
|
601
|
+
rawRepo,
|
|
602
|
+
effectiveRootFiles,
|
|
603
|
+
fileNames,
|
|
604
|
+
repoOnlyFileNames,
|
|
605
|
+
effectivePROptions,
|
|
606
|
+
effectiveSettings,
|
|
607
|
+
globalDeleteOrphaned: raw.deleteOrphaned,
|
|
608
|
+
env,
|
|
609
|
+
}));
|
|
531
610
|
}
|
|
532
611
|
}
|
|
533
612
|
// Normalize root settings by reusing mergeSettings with no per-repo overlay.
|
|
@@ -542,5 +621,6 @@ export function normalizeConfig(raw, env) {
|
|
|
542
621
|
githubHosts: raw.githubHosts,
|
|
543
622
|
deleteOrphaned: raw.deleteOrphaned,
|
|
544
623
|
settings: normalizedRootSettings,
|
|
624
|
+
secrets: raw.secrets,
|
|
545
625
|
};
|
|
546
626
|
}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -267,6 +267,9 @@ export interface CodeScanningSettings {
|
|
|
267
267
|
querySuite?: CodeScanningQuerySuite;
|
|
268
268
|
languages?: CodeScanningLanguage[];
|
|
269
269
|
}
|
|
270
|
+
export interface SecretConfig {
|
|
271
|
+
env: string;
|
|
272
|
+
}
|
|
270
273
|
export interface RepoSettings {
|
|
271
274
|
/** GitHub rulesets keyed by name */
|
|
272
275
|
rulesets?: Record<string, Ruleset>;
|
|
@@ -276,6 +279,10 @@ export interface RepoSettings {
|
|
|
276
279
|
labels?: Record<string, Label>;
|
|
277
280
|
/** GitHub code scanning default setup */
|
|
278
281
|
codeScanning?: CodeScanningSettings;
|
|
282
|
+
/** GitHub Actions variables keyed by name */
|
|
283
|
+
variables?: Record<string, string> & {
|
|
284
|
+
deleteOrphaned?: boolean;
|
|
285
|
+
};
|
|
279
286
|
deleteOrphaned?: boolean;
|
|
280
287
|
}
|
|
281
288
|
export type ContentValue = Record<string, unknown> | string | string[];
|
|
@@ -336,6 +343,9 @@ export interface RawRootSettings {
|
|
|
336
343
|
repo?: GitHubRepoSettings | false;
|
|
337
344
|
labels?: Record<string, Label | false>;
|
|
338
345
|
codeScanning?: CodeScanningSettings | false;
|
|
346
|
+
variables?: Record<string, string | false> & {
|
|
347
|
+
deleteOrphaned?: boolean;
|
|
348
|
+
};
|
|
339
349
|
deleteOrphaned?: boolean;
|
|
340
350
|
}
|
|
341
351
|
export interface RawRepoSettings {
|
|
@@ -347,6 +357,10 @@ export interface RawRepoSettings {
|
|
|
347
357
|
inherit?: boolean;
|
|
348
358
|
};
|
|
349
359
|
codeScanning?: CodeScanningSettings | false;
|
|
360
|
+
variables?: Record<string, string | false> & {
|
|
361
|
+
inherit?: boolean;
|
|
362
|
+
deleteOrphaned?: boolean;
|
|
363
|
+
};
|
|
350
364
|
deleteOrphaned?: boolean;
|
|
351
365
|
}
|
|
352
366
|
export interface RawRepoConfig {
|
|
@@ -373,6 +387,9 @@ export interface RawConfig {
|
|
|
373
387
|
githubHosts?: string[];
|
|
374
388
|
deleteOrphaned?: boolean;
|
|
375
389
|
settings?: RawRootSettings;
|
|
390
|
+
secrets?: Record<string, SecretConfig | boolean> & {
|
|
391
|
+
deleteOrphaned?: boolean;
|
|
392
|
+
};
|
|
376
393
|
}
|
|
377
394
|
export interface FileContent {
|
|
378
395
|
fileName: string;
|
|
@@ -402,5 +419,8 @@ export interface Config {
|
|
|
402
419
|
githubHosts?: string[];
|
|
403
420
|
deleteOrphaned?: boolean;
|
|
404
421
|
settings?: RepoSettings;
|
|
422
|
+
secrets?: Record<string, SecretConfig | boolean> & {
|
|
423
|
+
deleteOrphaned?: boolean;
|
|
424
|
+
};
|
|
405
425
|
}
|
|
406
426
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RawConfig,
|
|
1
|
+
import type { RawConfig, RawRootSettings, RawRepoSettings } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Validates raw config structure before normalization.
|
|
4
4
|
* @throws ValidationError if validation fails
|
|
@@ -9,7 +9,8 @@ export declare function validateRawConfig(config: RawConfig): void;
|
|
|
9
9
|
* @throws ValidationError if neither files nor settings are present
|
|
10
10
|
*/
|
|
11
11
|
export declare function validateForSync(config: RawConfig): void;
|
|
12
|
-
|
|
13
|
-
* Checks if settings contain actionable configuration.
|
|
14
|
-
*/
|
|
12
|
+
export declare function validateVariableSecretOverlaps(config: RawConfig): void;
|
|
15
13
|
export declare function hasActionableSettings(settings: RawRootSettings | RawRepoSettings | undefined): boolean;
|
|
14
|
+
export declare function validateVariableName(name: string): void;
|
|
15
|
+
export declare function validateSecretName(name: string): void;
|
|
16
|
+
export declare function validateSecretsConfig(config: RawConfig): void;
|