@aspruyt/xfg 4.0.0 → 4.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/README.md +1 -2
- package/dist/cli/index.d.ts +1 -2
- package/dist/cli/index.js +0 -1
- package/dist/cli/program.js +7 -2
- package/dist/cli/{settings/results-collector.d.ts → results-collector.d.ts} +1 -1
- package/dist/cli/{settings/results-collector.js → results-collector.js} +2 -1
- package/dist/cli/settings-report-builder.d.ts +1 -3
- package/dist/cli/sync-command.d.ts +2 -24
- package/dist/cli/sync-command.js +295 -301
- package/dist/cli/types.d.ts +60 -40
- package/dist/cli/types.js +1 -12
- package/dist/config/errors.d.ts +9 -0
- package/dist/config/errors.js +11 -0
- package/dist/config/file-reference-resolver.d.ts +2 -1
- package/dist/config/file-reference-resolver.js +10 -8
- package/dist/config/formatter.d.ts +3 -2
- package/dist/config/index.d.ts +4 -6
- package/dist/config/index.js +4 -8
- package/dist/config/loader.js +4 -2
- package/dist/config/merge.d.ts +0 -9
- package/dist/config/merge.js +2 -7
- package/dist/config/normalizer.d.ts +4 -0
- package/dist/config/normalizer.js +61 -110
- package/dist/config/types.d.ts +15 -19
- package/dist/config/types.js +1 -1
- package/dist/config/validator.d.ts +0 -4
- package/dist/config/validator.js +286 -363
- package/dist/config/validators/file-validator.d.ts +2 -8
- package/dist/config/validators/file-validator.js +6 -17
- package/dist/config/validators/index.d.ts +3 -3
- package/dist/config/validators/index.js +3 -3
- package/dist/config/validators/repo-settings-validator.d.ts +0 -6
- package/dist/config/validators/repo-settings-validator.js +9 -9
- package/dist/config/validators/ruleset-validator.d.ts +0 -14
- package/dist/config/validators/ruleset-validator.js +28 -28
- package/dist/lifecycle/ado-migration-source.js +2 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -5
- package/dist/lifecycle/github-lifecycle-provider.js +79 -90
- package/dist/lifecycle/index.d.ts +2 -6
- package/dist/lifecycle/index.js +0 -4
- package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
- package/dist/lifecycle/lifecycle-formatter.js +4 -0
- package/dist/lifecycle/lifecycle-helpers.d.ts +3 -2
- package/dist/lifecycle/repo-lifecycle-manager.js +4 -11
- package/dist/lifecycle/types.d.ts +0 -8
- package/dist/output/github-summary.d.ts +5 -0
- package/dist/output/github-summary.js +9 -2
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.js +5 -23
- package/dist/output/settings-report.d.ts +14 -3
- package/dist/output/settings-report.js +137 -197
- package/dist/output/summary-utils.d.ts +1 -1
- package/dist/output/summary-utils.js +2 -1
- package/dist/output/sync-report.js +5 -8
- package/dist/output/unified-summary.d.ts +2 -1
- package/dist/output/unified-summary.js +71 -133
- package/dist/settings/base-processor.d.ts +67 -0
- package/dist/settings/base-processor.js +91 -0
- package/dist/settings/index.d.ts +4 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/converter.d.ts +2 -1
- package/dist/settings/labels/github-labels-strategy.d.ts +9 -18
- package/dist/settings/labels/github-labels-strategy.js +17 -73
- package/dist/settings/labels/index.d.ts +2 -6
- package/dist/settings/labels/index.js +1 -9
- package/dist/settings/labels/processor.d.ts +6 -30
- package/dist/settings/labels/processor.js +62 -152
- package/dist/settings/labels/types.d.ts +5 -8
- package/dist/settings/repo-settings/formatter.d.ts +2 -2
- package/dist/settings/repo-settings/formatter.js +6 -6
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +11 -12
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +32 -79
- package/dist/settings/repo-settings/index.d.ts +2 -5
- package/dist/settings/repo-settings/index.js +1 -9
- package/dist/settings/repo-settings/processor.d.ts +6 -27
- package/dist/settings/repo-settings/processor.js +51 -104
- package/dist/settings/repo-settings/types.d.ts +7 -9
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -4
- package/dist/settings/rulesets/diff-algorithm.js +1 -10
- package/dist/settings/rulesets/diff.d.ts +1 -1
- package/dist/settings/rulesets/diff.js +2 -21
- package/dist/settings/rulesets/formatter.d.ts +1 -3
- package/dist/settings/rulesets/formatter.js +1 -8
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +11 -51
- package/dist/settings/rulesets/github-ruleset-strategy.js +24 -85
- package/dist/settings/rulesets/index.d.ts +3 -6
- package/dist/settings/rulesets/index.js +5 -9
- package/dist/settings/rulesets/processor.d.ts +8 -33
- package/dist/settings/rulesets/processor.js +58 -151
- package/dist/settings/rulesets/types.d.ts +35 -6
- package/dist/shared/command-executor.d.ts +2 -22
- package/dist/shared/command-executor.js +8 -7
- package/dist/shared/env.d.ts +0 -8
- package/dist/shared/env.js +14 -70
- package/dist/shared/file-status.d.ts +2 -0
- package/dist/shared/file-status.js +13 -0
- package/dist/shared/gh-api-utils.d.ts +46 -0
- package/dist/shared/gh-api-utils.js +107 -0
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +3 -3
- package/dist/shared/interpolation-engine.d.ts +31 -0
- package/dist/shared/interpolation-engine.js +50 -0
- package/dist/shared/logger.d.ts +3 -7
- package/dist/shared/logger.js +4 -1
- package/dist/shared/repo-detector.d.ts +17 -2
- package/dist/shared/repo-detector.js +27 -0
- package/dist/shared/retry-utils.d.ts +9 -17
- package/dist/shared/retry-utils.js +22 -28
- package/dist/shared/sanitize-utils.d.ts +0 -7
- package/dist/shared/sanitize-utils.js +0 -7
- package/dist/shared/shell-utils.d.ts +1 -0
- package/dist/shared/shell-utils.js +3 -0
- package/dist/shared/string-utils.d.ts +4 -0
- package/dist/shared/string-utils.js +6 -0
- package/dist/shared/type-guards.d.ts +17 -0
- package/dist/shared/type-guards.js +26 -0
- package/dist/shared/workspace-utils.d.ts +0 -4
- package/dist/shared/workspace-utils.js +0 -4
- package/dist/{sync → shared}/xfg-template.d.ts +3 -2
- package/dist/{sync → shared}/xfg-template.js +13 -54
- package/dist/sync/auth-options-builder.d.ts +4 -5
- package/dist/sync/auth-options-builder.js +15 -26
- package/dist/sync/branch-manager.d.ts +5 -0
- package/dist/sync/branch-manager.js +12 -10
- package/dist/sync/commit-push-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.js +22 -18
- package/dist/sync/diff-utils.d.ts +4 -9
- package/dist/sync/diff-utils.js +2 -19
- package/dist/sync/file-sync-orchestrator.js +9 -8
- package/dist/sync/file-writer.d.ts +2 -1
- package/dist/sync/file-writer.js +3 -6
- package/dist/sync/index.d.ts +2 -15
- package/dist/sync/index.js +0 -19
- package/dist/sync/manifest-manager.d.ts +4 -0
- package/dist/sync/manifest-manager.js +5 -1
- package/dist/sync/manifest.d.ts +10 -41
- package/dist/sync/manifest.js +11 -56
- package/dist/sync/pr-merge-handler.d.ts +2 -6
- package/dist/sync/pr-merge-handler.js +6 -3
- package/dist/sync/repository-processor.d.ts +1 -2
- package/dist/sync/repository-processor.js +20 -12
- package/dist/sync/repository-session.js +5 -14
- package/dist/sync/sync-workflow.js +31 -38
- package/dist/sync/types.d.ts +43 -178
- package/dist/vcs/authenticated-git-ops.d.ts +27 -70
- package/dist/vcs/authenticated-git-ops.js +70 -96
- package/dist/vcs/azure-pr-strategy.d.ts +6 -4
- package/dist/vcs/azure-pr-strategy.js +34 -82
- package/dist/vcs/branch-utils.d.ts +6 -0
- package/dist/vcs/branch-utils.js +29 -0
- package/dist/vcs/commit-strategy-selector.d.ts +5 -0
- package/dist/vcs/commit-strategy-selector.js +10 -0
- package/dist/vcs/git-commit-strategy.js +1 -2
- package/dist/vcs/git-ops.d.ts +15 -59
- package/dist/vcs/git-ops.js +46 -110
- package/dist/vcs/github-app-token-manager.d.ts +0 -6
- package/dist/vcs/github-app-token-manager.js +5 -12
- package/dist/vcs/github-pr-strategy.d.ts +5 -5
- package/dist/vcs/github-pr-strategy.js +44 -122
- package/dist/vcs/gitlab-pr-strategy.d.ts +6 -4
- package/dist/vcs/gitlab-pr-strategy.js +39 -87
- package/dist/vcs/graphql-commit-strategy.d.ts +3 -4
- package/dist/vcs/graphql-commit-strategy.js +31 -63
- package/dist/vcs/index.d.ts +3 -16
- package/dist/vcs/index.js +2 -33
- package/dist/vcs/pr-creator.d.ts +9 -9
- package/dist/vcs/pr-creator.js +11 -10
- package/dist/vcs/pr-strategy-factory.d.ts +5 -0
- package/dist/vcs/pr-strategy-factory.js +17 -0
- package/dist/vcs/pr-strategy.d.ts +13 -26
- package/dist/vcs/pr-strategy.js +20 -25
- package/dist/vcs/types.d.ts +87 -21
- package/package.json +2 -1
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,53 +1,24 @@
|
|
|
1
|
-
import { RepoConfig } from "../config/index.js";
|
|
2
|
-
import { RepoInfo } from "../shared/repo-detector.js";
|
|
1
|
+
import type { MergeMode, MergeStrategy, RepoConfig } from "../config/index.js";
|
|
3
2
|
import type { IRepoLifecycleManager } from "../lifecycle/index.js";
|
|
4
|
-
import { type
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
* Processor interface for dependency injection in tests.
|
|
10
|
-
*/
|
|
11
|
-
export interface IRepositoryProcessor {
|
|
12
|
-
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: ProcessorOptions): Promise<ProcessorResult>;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Factory function type for creating processors.
|
|
16
|
-
*/
|
|
3
|
+
import { type IRepositoryProcessor } from "../sync/index.js";
|
|
4
|
+
import { type ISettingsProcessor, type IRulesetProcessor, type IRepoSettingsProcessor, type ILabelsProcessor } from "../settings/index.js";
|
|
5
|
+
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
6
|
+
import type { ResultsCollector } from "./results-collector.js";
|
|
7
|
+
export type { IRepositoryProcessor, IRulesetProcessor };
|
|
17
8
|
export type ProcessorFactory = () => IRepositoryProcessor;
|
|
18
9
|
/**
|
|
19
10
|
* Default factory that creates a real RepositoryProcessor.
|
|
20
11
|
*/
|
|
21
12
|
export declare const defaultProcessorFactory: ProcessorFactory;
|
|
22
13
|
/**
|
|
23
|
-
*
|
|
24
|
-
*/
|
|
25
|
-
export interface IRulesetProcessor {
|
|
26
|
-
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RulesetProcessorOptions): Promise<RulesetProcessorResult>;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Factory function type for creating ruleset processors.
|
|
30
|
-
*/
|
|
31
|
-
export type RulesetProcessorFactory = () => IRulesetProcessor;
|
|
32
|
-
/**
|
|
33
|
-
* Default factory that creates a real RulesetProcessor.
|
|
14
|
+
* Generic factory type for settings processors.
|
|
34
15
|
*/
|
|
16
|
+
export type SettingsProcessorFactory<T extends ISettingsProcessor> = () => T;
|
|
17
|
+
export type RulesetProcessorFactory = SettingsProcessorFactory<IRulesetProcessor>;
|
|
18
|
+
export type RepoSettingsProcessorFactory = SettingsProcessorFactory<IRepoSettingsProcessor>;
|
|
19
|
+
export type LabelsProcessorFactory = SettingsProcessorFactory<ILabelsProcessor>;
|
|
35
20
|
export declare const defaultRulesetProcessorFactory: RulesetProcessorFactory;
|
|
36
|
-
/**
|
|
37
|
-
* Repo settings processor factory function type.
|
|
38
|
-
*/
|
|
39
|
-
export type RepoSettingsProcessorFactory = () => IRepoSettingsProcessor;
|
|
40
|
-
/**
|
|
41
|
-
* Default factory that creates a real RepoSettingsProcessor.
|
|
42
|
-
*/
|
|
43
21
|
export declare const defaultRepoSettingsProcessorFactory: RepoSettingsProcessorFactory;
|
|
44
|
-
/**
|
|
45
|
-
* Labels processor interface for dependency injection in tests.
|
|
46
|
-
*/
|
|
47
|
-
export type LabelsProcessorFactory = () => ILabelsProcessor;
|
|
48
|
-
/**
|
|
49
|
-
* Default factory that creates a real LabelsProcessor.
|
|
50
|
-
*/
|
|
51
22
|
export declare const defaultLabelsProcessorFactory: LabelsProcessorFactory;
|
|
52
23
|
/**
|
|
53
24
|
* Dependencies for the sync command (dependency injection).
|
|
@@ -59,4 +30,53 @@ export interface SyncDependencies {
|
|
|
59
30
|
repoSettingsProcessorFactory?: RepoSettingsProcessorFactory;
|
|
60
31
|
labelsProcessorFactory?: LabelsProcessorFactory;
|
|
61
32
|
}
|
|
33
|
+
export interface SharedOptions {
|
|
34
|
+
config: string;
|
|
35
|
+
dryRun?: boolean;
|
|
36
|
+
workDir?: string;
|
|
37
|
+
retries?: number;
|
|
38
|
+
noDelete?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface SyncOptions extends SharedOptions {
|
|
41
|
+
branch?: string;
|
|
42
|
+
merge?: MergeMode;
|
|
43
|
+
mergeStrategy?: MergeStrategy;
|
|
44
|
+
deleteBranch?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface SyncResultEntry {
|
|
47
|
+
repoName: string;
|
|
48
|
+
success: boolean;
|
|
49
|
+
fileChanges: Array<{
|
|
50
|
+
path: string;
|
|
51
|
+
action: "create" | "update" | "delete";
|
|
52
|
+
}>;
|
|
53
|
+
prUrl?: string;
|
|
54
|
+
mergeOutcome?: "manual" | "auto" | "force" | "direct";
|
|
55
|
+
error?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface SettingsResult {
|
|
58
|
+
success: boolean;
|
|
59
|
+
message: string;
|
|
60
|
+
skipped?: boolean;
|
|
61
|
+
planOutput?: {
|
|
62
|
+
lines?: string[];
|
|
63
|
+
};
|
|
64
|
+
warnings?: string[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Context for applying repo settings (rulesets, labels, repo config).
|
|
68
|
+
* Groups parameters that were previously passed individually.
|
|
69
|
+
*/
|
|
70
|
+
export interface ApplyRepoSettingsContext {
|
|
71
|
+
repoConfig: RepoConfig;
|
|
72
|
+
repoInfo: RepoInfo;
|
|
73
|
+
repoName: string;
|
|
74
|
+
current: number;
|
|
75
|
+
options: SyncOptions;
|
|
76
|
+
token: string | undefined;
|
|
77
|
+
settingsCollector: ResultsCollector;
|
|
78
|
+
rulesetProcessorFactory: NonNullable<SyncDependencies["rulesetProcessorFactory"]>;
|
|
79
|
+
repoSettingsProcessorFactory: NonNullable<SyncDependencies["repoSettingsProcessorFactory"]>;
|
|
80
|
+
labelsProcessorFactory: NonNullable<SyncDependencies["labelsProcessorFactory"]>;
|
|
81
|
+
}
|
|
62
82
|
export type { IRepoSettingsProcessor, ILabelsProcessor };
|
package/dist/cli/types.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { RepositoryProcessor, } from "../sync/index.js";
|
|
2
|
-
import { RulesetProcessor, } from "../settings/
|
|
3
|
-
import { RepoSettingsProcessor, } from "../settings/repo-settings/processor.js";
|
|
4
|
-
import { LabelsProcessor, } from "../settings/labels/processor.js";
|
|
2
|
+
import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, } from "../settings/index.js";
|
|
5
3
|
/**
|
|
6
4
|
* Default factory that creates a real RepositoryProcessor.
|
|
7
5
|
*/
|
|
8
6
|
export const defaultProcessorFactory = () => new RepositoryProcessor();
|
|
9
|
-
/**
|
|
10
|
-
* Default factory that creates a real RulesetProcessor.
|
|
11
|
-
*/
|
|
12
7
|
export const defaultRulesetProcessorFactory = () => new RulesetProcessor();
|
|
13
|
-
/**
|
|
14
|
-
* Default factory that creates a real RepoSettingsProcessor.
|
|
15
|
-
*/
|
|
16
8
|
export const defaultRepoSettingsProcessorFactory = () => new RepoSettingsProcessor();
|
|
17
|
-
/**
|
|
18
|
-
* Default factory that creates a real LabelsProcessor.
|
|
19
|
-
*/
|
|
20
9
|
export const defaultLabelsProcessorFactory = () => new LabelsProcessor();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when config validation fails.
|
|
3
|
+
* Distinguishable from I/O errors by type, so callers and retry logic
|
|
4
|
+
* can treat validation failures as permanent without message-parsing.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ValidationError extends Error {
|
|
7
|
+
readonly name = "ValidationError";
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when config validation fails.
|
|
3
|
+
* Distinguishable from I/O errors by type, so callers and retry logic
|
|
4
|
+
* can treat validation failures as permanent without message-parsing.
|
|
5
|
+
*/
|
|
6
|
+
export class ValidationError extends Error {
|
|
7
|
+
name = "ValidationError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContentValue, RawConfig } from "./types.js";
|
|
2
|
-
|
|
2
|
+
interface FileReferenceOptions {
|
|
3
3
|
configDir: string;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
@@ -18,3 +18,4 @@ export declare function resolveFileReference(reference: string, configDir: strin
|
|
|
18
18
|
* Walks through files at root level and per-repo level.
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveFileReferencesInConfig(raw: RawConfig, options: FileReferenceOptions): RawConfig;
|
|
21
|
+
export {};
|
|
@@ -2,6 +2,8 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve, isAbsolute, normalize, extname, relative } from "node:path";
|
|
3
3
|
import JSON5 from "json5";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
6
|
+
import { ValidationError } from "./errors.js";
|
|
5
7
|
/**
|
|
6
8
|
* Check if a value is a file reference (string starting with @)
|
|
7
9
|
*/
|
|
@@ -17,11 +19,11 @@ export function isFileReference(value) {
|
|
|
17
19
|
export function resolveFileReference(reference, configDir) {
|
|
18
20
|
const relativePath = reference.slice(1); // Remove @ prefix
|
|
19
21
|
if (relativePath.length === 0) {
|
|
20
|
-
throw new
|
|
22
|
+
throw new ValidationError(`Invalid file reference "${reference}": path is empty`);
|
|
21
23
|
}
|
|
22
24
|
// Security: block absolute paths
|
|
23
25
|
if (isAbsolute(relativePath)) {
|
|
24
|
-
throw new
|
|
26
|
+
throw new ValidationError(`File reference "${reference}" uses absolute path. Use relative paths only.`);
|
|
25
27
|
}
|
|
26
28
|
const resolvedPath = resolve(configDir, relativePath);
|
|
27
29
|
const normalizedResolved = normalize(resolvedPath);
|
|
@@ -32,7 +34,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
32
34
|
// where normalize() returns paths with backslash separators.
|
|
33
35
|
const pathFromConfig = relative(normalizedConfigDir, normalizedResolved);
|
|
34
36
|
if (pathFromConfig.startsWith("..") || isAbsolute(pathFromConfig)) {
|
|
35
|
-
throw new
|
|
37
|
+
throw new ValidationError(`File reference "${reference}" escapes config directory. ` +
|
|
36
38
|
`References must be within "${configDir}".`);
|
|
37
39
|
}
|
|
38
40
|
// Load file
|
|
@@ -41,7 +43,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
41
43
|
content = readFileSync(resolvedPath, "utf-8");
|
|
42
44
|
}
|
|
43
45
|
catch (error) {
|
|
44
|
-
const msg =
|
|
46
|
+
const msg = toErrorMessage(error);
|
|
45
47
|
throw new Error(`Failed to load file reference "${reference}": ${msg}`);
|
|
46
48
|
}
|
|
47
49
|
// Parse based on extension
|
|
@@ -51,7 +53,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
51
53
|
return JSON.parse(content);
|
|
52
54
|
}
|
|
53
55
|
catch (error) {
|
|
54
|
-
const msg =
|
|
56
|
+
const msg = toErrorMessage(error);
|
|
55
57
|
throw new Error(`Invalid JSON in "${reference}": ${msg}`);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -60,7 +62,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
60
62
|
return JSON5.parse(content);
|
|
61
63
|
}
|
|
62
64
|
catch (error) {
|
|
63
|
-
const msg =
|
|
65
|
+
const msg = toErrorMessage(error);
|
|
64
66
|
throw new Error(`Invalid JSON5 in "${reference}": ${msg}`);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -69,7 +71,7 @@ export function resolveFileReference(reference, configDir) {
|
|
|
69
71
|
return parseYaml(content);
|
|
70
72
|
}
|
|
71
73
|
catch (error) {
|
|
72
|
-
const msg =
|
|
74
|
+
const msg = toErrorMessage(error);
|
|
73
75
|
throw new Error(`Invalid YAML in "${reference}": ${msg}`);
|
|
74
76
|
}
|
|
75
77
|
}
|
|
@@ -103,7 +105,7 @@ export function resolveFileReferencesInConfig(raw, options) {
|
|
|
103
105
|
if (result.prTemplate && isFileReference(result.prTemplate)) {
|
|
104
106
|
const resolved = resolveFileReference(result.prTemplate, configDir);
|
|
105
107
|
if (typeof resolved !== "string") {
|
|
106
|
-
throw new
|
|
108
|
+
throw new ValidationError(`prTemplate file reference "${result.prTemplate}" must resolve to a text file, not JSON/YAML`);
|
|
107
109
|
}
|
|
108
110
|
result.prTemplate = resolved;
|
|
109
111
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
type OutputFormat = "json" | "json5" | "yaml";
|
|
2
2
|
/**
|
|
3
3
|
* Options for content conversion.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
interface ConvertOptions {
|
|
6
6
|
header?: string[];
|
|
7
7
|
schemaUrl?: string;
|
|
8
8
|
}
|
|
@@ -15,3 +15,4 @@ export declare function detectOutputFormat(fileName: string): OutputFormat;
|
|
|
15
15
|
* Handles null content (empty files), text content (string/string[]), and object content (JSON/YAML).
|
|
16
16
|
*/
|
|
17
17
|
export declare function convertContentToString(content: Record<string, unknown> | string | string[] | null, fileName: string, options?: ConvertOptions): string;
|
|
18
|
+
export {};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export type { MergeMode, MergeStrategy,
|
|
2
|
-
export {
|
|
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";
|
|
2
|
+
export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
3
3
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
4
|
-
export { convertContentToString
|
|
5
|
-
export {
|
|
6
|
-
export { arrayMergeStrategies, deepMerge, stripMergeDirectives, createMergeContext, isTextContent, mergeTextContent, type ArrayMergeStrategy, type ArrayMergeHandler, type MergeContext, } from "./merge.js";
|
|
7
|
-
export { validateRawConfig, validateSettings, validateForSync, hasActionableSettings, } from "./validator.js";
|
|
4
|
+
export { convertContentToString } from "./formatter.js";
|
|
5
|
+
export { ValidationError } from "./errors.js";
|
package/dist/config/index.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
// Re-export values (non-type exports)
|
|
2
|
-
export {
|
|
2
|
+
export { RULESET_COMPARABLE_FIELDS } from "./types.js";
|
|
3
3
|
// Re-export loading functions
|
|
4
4
|
export { loadRawConfig, loadConfig, normalizeConfig } from "./loader.js";
|
|
5
5
|
// Config formatting
|
|
6
|
-
export { convertContentToString
|
|
7
|
-
//
|
|
8
|
-
export {
|
|
9
|
-
// Deep merge utilities
|
|
10
|
-
export { arrayMergeStrategies, deepMerge, stripMergeDirectives, createMergeContext, isTextContent, mergeTextContent, } from "./merge.js";
|
|
11
|
-
// Validation
|
|
12
|
-
export { validateRawConfig, validateSettings, validateForSync, hasActionableSettings, } from "./validator.js";
|
|
6
|
+
export { convertContentToString } from "./formatter.js";
|
|
7
|
+
// Errors
|
|
8
|
+
export { ValidationError } from "./errors.js";
|
package/dist/config/loader.js
CHANGED
|
@@ -4,6 +4,8 @@ import { parse } from "yaml";
|
|
|
4
4
|
import { validateRawConfig } from "./validator.js";
|
|
5
5
|
import { normalizeConfig as normalizeConfigInternal } from "./normalizer.js";
|
|
6
6
|
import { resolveFileReferencesInConfig } from "./file-reference-resolver.js";
|
|
7
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
8
|
+
import { ValidationError } from "./errors.js";
|
|
7
9
|
export { normalizeConfigInternal as normalizeConfig };
|
|
8
10
|
/**
|
|
9
11
|
* Load and validate raw config without normalization.
|
|
@@ -17,8 +19,8 @@ export function loadRawConfig(filePath) {
|
|
|
17
19
|
rawConfig = parse(content);
|
|
18
20
|
}
|
|
19
21
|
catch (error) {
|
|
20
|
-
const message =
|
|
21
|
-
throw new
|
|
22
|
+
const message = toErrorMessage(error);
|
|
23
|
+
throw new ValidationError(`Failed to parse YAML config at ${filePath}: ${message}`);
|
|
22
24
|
}
|
|
23
25
|
// Resolve file references before validation so content type checking works
|
|
24
26
|
rawConfig = resolveFileReferencesInConfig(rawConfig, { configDir });
|
package/dist/config/merge.d.ts
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
* Supports configurable array merge strategies via $arrayMerge directive.
|
|
4
4
|
*/
|
|
5
5
|
export type ArrayMergeStrategy = "replace" | "append" | "prepend";
|
|
6
|
-
/**
|
|
7
|
-
* Handler function type for array merge strategies.
|
|
8
|
-
*/
|
|
9
|
-
export type ArrayMergeHandler = (base: unknown[], overlay: unknown[]) => unknown[];
|
|
10
|
-
/**
|
|
11
|
-
* Strategy map for array merge operations.
|
|
12
|
-
* Extensible: add new strategies by adding to this map.
|
|
13
|
-
*/
|
|
14
|
-
export declare const arrayMergeStrategies: Map<ArrayMergeStrategy, ArrayMergeHandler>;
|
|
15
6
|
export interface MergeContext {
|
|
16
7
|
arrayStrategies: Map<string, ArrayMergeStrategy>;
|
|
17
8
|
defaultArrayStrategy: ArrayMergeStrategy;
|
package/dist/config/merge.js
CHANGED
|
@@ -2,21 +2,16 @@
|
|
|
2
2
|
* Deep merge utilities for JSON configuration objects.
|
|
3
3
|
* Supports configurable array merge strategies via $arrayMerge directive.
|
|
4
4
|
*/
|
|
5
|
+
import { isPlainObject } from "../shared/type-guards.js";
|
|
5
6
|
/**
|
|
6
7
|
* Strategy map for array merge operations.
|
|
7
8
|
* Extensible: add new strategies by adding to this map.
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
+
const arrayMergeStrategies = new Map([
|
|
10
11
|
["replace", (_base, overlay) => overlay],
|
|
11
12
|
["append", (base, overlay) => [...base, ...overlay]],
|
|
12
13
|
["prepend", (base, overlay) => [...overlay, ...base]],
|
|
13
14
|
]);
|
|
14
|
-
/**
|
|
15
|
-
* Check if a value is a plain object (not null, not array).
|
|
16
|
-
*/
|
|
17
|
-
function isPlainObject(val) {
|
|
18
|
-
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
19
|
-
}
|
|
20
15
|
/**
|
|
21
16
|
* Merge two arrays based on the specified strategy.
|
|
22
17
|
*/
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { RawConfig, Config, RepoSettings, RawRootSettings, RawRepoSettings } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Merges settings: per-repo settings deep merge with root settings.
|
|
4
|
+
* Returns undefined if no settings are defined.
|
|
5
|
+
*/
|
|
2
6
|
export declare function mergeSettings(root: RawRootSettings | undefined, perRepo: RawRepoSettings | undefined): RepoSettings | undefined;
|
|
3
7
|
/**
|
|
4
8
|
* Normalizes raw config into expanded, merged config.
|
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
import { deepMerge, stripMergeDirectives, createMergeContext, isTextContent, mergeTextContent, } from "./merge.js";
|
|
2
2
|
import { interpolateContent } from "../shared/env.js";
|
|
3
|
+
/**
|
|
4
|
+
* Clone content, stripping merge directives from object content.
|
|
5
|
+
* Text content is cloned as-is since it has no merge directives.
|
|
6
|
+
*/
|
|
7
|
+
function cloneContent(content) {
|
|
8
|
+
if (isTextContent(content)) {
|
|
9
|
+
return structuredClone(content);
|
|
10
|
+
}
|
|
11
|
+
return stripMergeDirectives(structuredClone(content));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the final content for a file by applying override/inherit/merge rules.
|
|
15
|
+
*
|
|
16
|
+
* Returns null when the file should be empty (e.g. override with no content,
|
|
17
|
+
* or root file with no content and no repo override).
|
|
18
|
+
*/
|
|
19
|
+
function resolveFileContent(rootContent, repoOverride, mergeStrategy) {
|
|
20
|
+
// Override mode: use only repo content
|
|
21
|
+
if (repoOverride?.override) {
|
|
22
|
+
return repoOverride.content !== undefined
|
|
23
|
+
? cloneContent(repoOverride.content)
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
// Root has no content — use repo content if provided, otherwise empty
|
|
27
|
+
if (rootContent === undefined) {
|
|
28
|
+
return repoOverride?.content ? cloneContent(repoOverride.content) : null;
|
|
29
|
+
}
|
|
30
|
+
// No repo override — use root content as-is
|
|
31
|
+
if (!repoOverride?.content) {
|
|
32
|
+
return structuredClone(rootContent);
|
|
33
|
+
}
|
|
34
|
+
// Both exist — merge
|
|
35
|
+
return mergeContentPair(rootContent, repoOverride.content, mergeStrategy);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge two content values using the appropriate strategy.
|
|
39
|
+
* Handles text+text, object+object, and type mismatch cases.
|
|
40
|
+
*/
|
|
41
|
+
function mergeContentPair(base, overlay, strategy) {
|
|
42
|
+
if (isTextContent(base) && isTextContent(overlay)) {
|
|
43
|
+
return mergeTextContent(base, overlay, strategy);
|
|
44
|
+
}
|
|
45
|
+
if (!isTextContent(base) && !isTextContent(overlay)) {
|
|
46
|
+
const ctx = createMergeContext(strategy);
|
|
47
|
+
const merged = deepMerge(structuredClone(base), overlay, ctx);
|
|
48
|
+
return stripMergeDirectives(merged);
|
|
49
|
+
}
|
|
50
|
+
// Type mismatch — overlay wins
|
|
51
|
+
return overlay;
|
|
52
|
+
}
|
|
3
53
|
/**
|
|
4
54
|
* Checks whether an object's `inherit` property is not explicitly set to false.
|
|
5
55
|
* Replaces the repeated `(x )?.inherit !== false` pattern.
|
|
@@ -59,10 +109,6 @@ function mergeRuleset(root, perRepo) {
|
|
|
59
109
|
const merged = deepMerge(structuredClone(root), perRepo, ctx);
|
|
60
110
|
return merged;
|
|
61
111
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Merges settings: per-repo settings deep merge with root settings.
|
|
64
|
-
* Returns undefined if no settings are defined.
|
|
65
|
-
*/
|
|
66
112
|
/**
|
|
67
113
|
* Merges root and per-repo label configs.
|
|
68
114
|
* Per-repo labels override root labels by name.
|
|
@@ -99,6 +145,10 @@ function mergeLabels(rootLabels, repoLabels) {
|
|
|
99
145
|
}
|
|
100
146
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
101
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Merges settings: per-repo settings deep merge with root settings.
|
|
150
|
+
* Returns undefined if no settings are defined.
|
|
151
|
+
*/
|
|
102
152
|
export function mergeSettings(root, perRepo) {
|
|
103
153
|
if (!root && !perRepo)
|
|
104
154
|
return undefined;
|
|
@@ -106,7 +156,6 @@ export function mergeSettings(root, perRepo) {
|
|
|
106
156
|
// Merge rulesets by name - each ruleset is deep merged
|
|
107
157
|
const rootRulesets = root?.rulesets ?? {};
|
|
108
158
|
const repoRulesets = perRepo?.rulesets ?? {};
|
|
109
|
-
// Check if repo opts out of all inherited rulesets
|
|
110
159
|
const inheritRulesets = shouldInherit(repoRulesets);
|
|
111
160
|
const allRulesetNames = new Set([
|
|
112
161
|
...Object.keys(rootRulesets).filter((name) => name !== "inherit"),
|
|
@@ -194,19 +243,8 @@ function mergeGroupFiles(rootFiles, groupNames, groupDefs) {
|
|
|
194
243
|
// override:true or one side missing content — use overlay content
|
|
195
244
|
mergedContent = overlay.content ?? existing.content;
|
|
196
245
|
}
|
|
197
|
-
else if (isTextContent(existing.content) &&
|
|
198
|
-
isTextContent(overlay.content)) {
|
|
199
|
-
mergedContent = mergeTextContent(existing.content, overlay.content, existing.mergeStrategy ?? "replace");
|
|
200
|
-
}
|
|
201
|
-
else if (!isTextContent(existing.content) &&
|
|
202
|
-
!isTextContent(overlay.content)) {
|
|
203
|
-
const ctx = createMergeContext(existing.mergeStrategy ?? "replace");
|
|
204
|
-
mergedContent = deepMerge(structuredClone(existing.content), overlay.content, ctx);
|
|
205
|
-
mergedContent = stripMergeDirectives(mergedContent);
|
|
206
|
-
}
|
|
207
246
|
else {
|
|
208
|
-
|
|
209
|
-
mergedContent = overlay.content;
|
|
247
|
+
mergedContent = mergeContentPair(existing.content, overlay.content, existing.mergeStrategy ?? "replace");
|
|
210
248
|
}
|
|
211
249
|
const { override: _override, ...restFileConfig } = fileConfig;
|
|
212
250
|
accumulated[fileName] = {
|
|
@@ -332,7 +370,6 @@ function mergeGroupSettings(rootSettings, groupNames, groupDefs) {
|
|
|
332
370
|
export function normalizeConfig(raw) {
|
|
333
371
|
const expandedRepos = [];
|
|
334
372
|
for (const rawRepo of raw.repos) {
|
|
335
|
-
// Step 1: Expand git arrays
|
|
336
373
|
const gitUrls = Array.isArray(rawRepo.git) ? rawRepo.git : [rawRepo.git];
|
|
337
374
|
// Resolve groups: build effective root files/prOptions/settings by merging group layers
|
|
338
375
|
const effectiveRootFiles = rawRepo.groups?.length
|
|
@@ -347,9 +384,7 @@ export function normalizeConfig(raw) {
|
|
|
347
384
|
const fileNames = Object.keys(effectiveRootFiles);
|
|
348
385
|
for (const gitUrl of gitUrls) {
|
|
349
386
|
const files = [];
|
|
350
|
-
// Check if repo opts out of all inherited files
|
|
351
387
|
const inheritFiles = shouldInherit(rawRepo.files);
|
|
352
|
-
// Step 2: Process each file definition
|
|
353
388
|
for (const fileName of fileNames) {
|
|
354
389
|
// Skip reserved key
|
|
355
390
|
if (fileName === "inherit")
|
|
@@ -365,56 +400,7 @@ export function normalizeConfig(raw) {
|
|
|
365
400
|
}
|
|
366
401
|
const fileConfig = effectiveRootFiles[fileName];
|
|
367
402
|
const fileStrategy = fileConfig.mergeStrategy ?? "replace";
|
|
368
|
-
|
|
369
|
-
let mergedContent;
|
|
370
|
-
if (repoOverride?.override) {
|
|
371
|
-
// Override mode: use only repo file content (may be undefined for empty file)
|
|
372
|
-
if (repoOverride.content === undefined) {
|
|
373
|
-
mergedContent = null;
|
|
374
|
-
}
|
|
375
|
-
else if (isTextContent(repoOverride.content)) {
|
|
376
|
-
// Text content: use as-is (no merge directives to strip)
|
|
377
|
-
mergedContent = structuredClone(repoOverride.content);
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
mergedContent = stripMergeDirectives(structuredClone(repoOverride.content));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
else if (fileConfig.content === undefined) {
|
|
384
|
-
// Root file has no content = empty file (unless repo provides content)
|
|
385
|
-
if (repoOverride?.content) {
|
|
386
|
-
if (isTextContent(repoOverride.content)) {
|
|
387
|
-
mergedContent = structuredClone(repoOverride.content);
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
mergedContent = stripMergeDirectives(structuredClone(repoOverride.content));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
mergedContent = null;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
else if (!repoOverride?.content) {
|
|
398
|
-
// No repo override: use file base content as-is
|
|
399
|
-
mergedContent = structuredClone(fileConfig.content);
|
|
400
|
-
}
|
|
401
|
-
else {
|
|
402
|
-
// Merge mode: handle text vs object content
|
|
403
|
-
if (isTextContent(fileConfig.content)) {
|
|
404
|
-
// Text content merging - validate overlay is also text
|
|
405
|
-
if (!isTextContent(repoOverride.content)) {
|
|
406
|
-
throw new Error(`Expected text content for ${fileName}, got object`);
|
|
407
|
-
}
|
|
408
|
-
mergedContent = mergeTextContent(fileConfig.content, repoOverride.content, fileStrategy);
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
// Object content: deep merge file base + repo overlay
|
|
412
|
-
const ctx = createMergeContext(fileStrategy);
|
|
413
|
-
mergedContent = deepMerge(structuredClone(fileConfig.content), repoOverride.content, ctx);
|
|
414
|
-
mergedContent = stripMergeDirectives(mergedContent);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
// Step 4: Interpolate env vars (only if content exists)
|
|
403
|
+
let mergedContent = resolveFileContent(fileConfig.content, repoOverride, fileStrategy);
|
|
418
404
|
if (mergedContent !== null) {
|
|
419
405
|
mergedContent = interpolateContent(mergedContent, { strict: true });
|
|
420
406
|
}
|
|
@@ -459,46 +445,11 @@ export function normalizeConfig(raw) {
|
|
|
459
445
|
});
|
|
460
446
|
}
|
|
461
447
|
}
|
|
462
|
-
// Normalize root settings
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const filteredRulesets = {};
|
|
468
|
-
for (const [name, ruleset] of Object.entries(raw.settings.rulesets)) {
|
|
469
|
-
if (name === "inherit" || ruleset === false)
|
|
470
|
-
continue;
|
|
471
|
-
filteredRulesets[name] = ruleset;
|
|
472
|
-
}
|
|
473
|
-
if (Object.keys(filteredRulesets).length > 0) {
|
|
474
|
-
normalizedRootSettings.rulesets = filteredRulesets;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
if (raw.settings.repo) {
|
|
478
|
-
normalizedRootSettings.repo = raw.settings.repo;
|
|
479
|
-
}
|
|
480
|
-
if (raw.settings.labels) {
|
|
481
|
-
const filteredLabels = {};
|
|
482
|
-
for (const [name, label] of Object.entries(raw.settings.labels)) {
|
|
483
|
-
if (name === "inherit" || label === false)
|
|
484
|
-
continue;
|
|
485
|
-
const l = label;
|
|
486
|
-
filteredLabels[name] = {
|
|
487
|
-
...l,
|
|
488
|
-
color: l.color.replace(/^#/, "").toLowerCase(),
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
if (Object.keys(filteredLabels).length > 0) {
|
|
492
|
-
normalizedRootSettings.labels = filteredLabels;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (raw.settings.deleteOrphaned !== undefined) {
|
|
496
|
-
normalizedRootSettings.deleteOrphaned = raw.settings.deleteOrphaned;
|
|
497
|
-
}
|
|
498
|
-
if (Object.keys(normalizedRootSettings).length === 0) {
|
|
499
|
-
normalizedRootSettings = undefined;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
448
|
+
// Normalize root settings by reusing mergeSettings with no per-repo overlay.
|
|
449
|
+
// This filters out inherit/false entries from rulesets/labels, normalizes
|
|
450
|
+
// label colors, and handles deleteOrphaned — the same logic that per-repo
|
|
451
|
+
// merging already applies.
|
|
452
|
+
const normalizedRootSettings = mergeSettings(raw.settings, undefined);
|
|
502
453
|
return {
|
|
503
454
|
id: raw.id,
|
|
504
455
|
repos: expandedRepos,
|