@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
|
@@ -20,8 +20,10 @@ export class CodeScanningProcessor {
|
|
|
20
20
|
}
|
|
21
21
|
async applySettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
|
|
22
22
|
const { dryRun } = options;
|
|
23
|
-
const desiredSettings = repoConfig.settings
|
|
24
|
-
|
|
23
|
+
const desiredSettings = repoConfig.settings?.codeScanning;
|
|
24
|
+
if (!desiredSettings || typeof desiredSettings !== "object") {
|
|
25
|
+
throw new Error("applySettings called without codeScanning settings");
|
|
26
|
+
}
|
|
25
27
|
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
26
28
|
// Validate GHAS availability for private repos
|
|
27
29
|
const metadata = await this.metadataProvider.getMetadata(githubRepo, strategyOptions);
|
|
@@ -34,7 +36,7 @@ export class CodeScanningProcessor {
|
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
// Fetch current settings
|
|
37
|
-
const currentSettings = await this.strategy.
|
|
39
|
+
const currentSettings = await this.strategy.get(githubRepo, strategyOptions);
|
|
38
40
|
// Compute diff
|
|
39
41
|
const changes = diffCodeScanning(currentSettings, desiredSettings);
|
|
40
42
|
const changeCounts = countActions(changes);
|
|
@@ -61,7 +63,7 @@ export class CodeScanningProcessor {
|
|
|
61
63
|
if (desiredSettings.languages !== undefined) {
|
|
62
64
|
payload.languages = desiredSettings.languages;
|
|
63
65
|
}
|
|
64
|
-
await this.strategy.
|
|
66
|
+
await this.strategy.update(githubRepo, payload, strategyOptions);
|
|
65
67
|
const appliedCount = changes.filter((c) => c.action !== "unchanged").length;
|
|
66
68
|
return buildApplyResult(repoName, changeCounts, appliedCount, {
|
|
67
69
|
planOutput,
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { RepoInfo } from "../../repo/index.js";
|
|
2
2
|
import type { GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
3
|
+
import type { CodeScanningState, CodeScanningQuerySuite } from "../../config/types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Current code scanning default setup state from GitHub API.
|
|
5
6
|
*/
|
|
6
7
|
export interface CurrentCodeScanningSettings {
|
|
7
|
-
state:
|
|
8
|
-
query_suite?:
|
|
8
|
+
state: CodeScanningState;
|
|
9
|
+
query_suite?: CodeScanningQuerySuite;
|
|
10
|
+
languages?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface CodeScanningUpdateParams {
|
|
13
|
+
state: string;
|
|
14
|
+
query_suite?: string;
|
|
9
15
|
languages?: string[];
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
@@ -13,10 +19,6 @@ export interface CurrentCodeScanningSettings {
|
|
|
13
19
|
* Abstracts the GitHub API calls for testability.
|
|
14
20
|
*/
|
|
15
21
|
export interface ICodeScanningStrategy {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
state: string;
|
|
19
|
-
query_suite?: string;
|
|
20
|
-
languages?: string[];
|
|
21
|
-
}, options?: GhApiOptions): Promise<void>;
|
|
22
|
+
get(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentCodeScanningSettings>;
|
|
23
|
+
update(repoInfo: RepoInfo, settings: CodeScanningUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
22
24
|
}
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { type PropertyDiff, type RulesetPlanEntry, RulesetProcessor, type IRules
|
|
|
3
3
|
export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsPlanEntry, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
|
|
4
4
|
export { type LabelsPlanEntry, LabelsProcessor, type ILabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
|
|
5
5
|
export { type CodeScanningPlanEntry, CodeScanningProcessor, type ICodeScanningProcessor, GitHubCodeScanningStrategy, } from "./code-scanning/index.js";
|
|
6
|
+
export { type VariablesPlanEntry, VariablesProcessor, type IVariablesProcessor, GitHubVariablesStrategy, } from "./variables/index.js";
|
package/dist/settings/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export { RepoSettingsProcessor, GitHubRepoSettingsStrategy, } from "./repo-setti
|
|
|
8
8
|
export { LabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
|
|
9
9
|
// Code scanning
|
|
10
10
|
export { CodeScanningProcessor, GitHubCodeScanningStrategy, } from "./code-scanning/index.js";
|
|
11
|
+
// Variables
|
|
12
|
+
export { VariablesProcessor, GitHubVariablesStrategy, } from "./variables/index.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
2
|
import { type RepoInfo } from "../../repo/index.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
|
-
import type { ILabelsStrategy, GitHubLabel } from "./types.js";
|
|
4
|
+
import type { ILabelsStrategy, GitHubLabel, LabelCreateParams, LabelUpdateParams } from "./types.js";
|
|
5
5
|
interface GitHubLabelsStrategyOptions {
|
|
6
6
|
retries?: number;
|
|
7
7
|
cwd: string;
|
|
@@ -10,16 +10,8 @@ export declare class GitHubLabelsStrategy implements ILabelsStrategy {
|
|
|
10
10
|
private api;
|
|
11
11
|
constructor(executor: ICommandExecutor, options: GitHubLabelsStrategyOptions);
|
|
12
12
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubLabel[]>;
|
|
13
|
-
create(repoInfo: RepoInfo, label:
|
|
14
|
-
|
|
15
|
-
color: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
}, options?: GhApiOptions): Promise<void>;
|
|
18
|
-
update(repoInfo: RepoInfo, currentName: string, label: {
|
|
19
|
-
new_name?: string;
|
|
20
|
-
color?: string;
|
|
21
|
-
description?: string;
|
|
22
|
-
}, options?: GhApiOptions): Promise<void>;
|
|
13
|
+
create(repoInfo: RepoInfo, label: LabelCreateParams, options?: GhApiOptions): Promise<void>;
|
|
14
|
+
update(repoInfo: RepoInfo, currentName: string, label: LabelUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
23
15
|
delete(repoInfo: RepoInfo, name: string, options?: GhApiOptions): Promise<void>;
|
|
24
16
|
}
|
|
25
17
|
export {};
|
|
@@ -11,17 +11,19 @@ export interface GitHubLabel {
|
|
|
11
11
|
* Strategy interface for label operations.
|
|
12
12
|
* Abstracts platform-specific API calls.
|
|
13
13
|
*/
|
|
14
|
+
export interface LabelCreateParams {
|
|
15
|
+
name: string;
|
|
16
|
+
color: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LabelUpdateParams {
|
|
20
|
+
new_name?: string;
|
|
21
|
+
color?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|
|
14
24
|
export interface ILabelsStrategy {
|
|
15
25
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubLabel[]>;
|
|
16
|
-
create(repoInfo: RepoInfo, label:
|
|
17
|
-
|
|
18
|
-
color: string;
|
|
19
|
-
description?: string;
|
|
20
|
-
}, options?: GhApiOptions): Promise<void>;
|
|
21
|
-
update(repoInfo: RepoInfo, currentName: string, label: {
|
|
22
|
-
new_name?: string;
|
|
23
|
-
color?: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
}, options?: GhApiOptions): Promise<void>;
|
|
26
|
+
create(repoInfo: RepoInfo, label: LabelCreateParams, options?: GhApiOptions): Promise<void>;
|
|
27
|
+
update(repoInfo: RepoInfo, currentName: string, label: LabelUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
26
28
|
delete(repoInfo: RepoInfo, name: string, options?: GhApiOptions): Promise<void>;
|
|
27
29
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
2
2
|
import type { SettingsAction } from "../base-processor.js";
|
|
3
3
|
import type { CurrentRepoSettings } from "./types.js";
|
|
4
|
-
export type RepoSettingsAction = Exclude<SettingsAction, "delete">;
|
|
4
|
+
export type RepoSettingsAction = Exclude<SettingsAction, "delete" | "unchanged">;
|
|
5
5
|
export interface RepoSettingsChange {
|
|
6
6
|
property: keyof GitHubRepoSettings;
|
|
7
7
|
action: RepoSettingsAction;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
+
import { type PlanEntry } from "../base-processor.js";
|
|
1
2
|
import type { RepoSettingsChange } from "./diff.js";
|
|
2
|
-
export
|
|
3
|
-
property: string;
|
|
4
|
-
action: "create" | "update";
|
|
5
|
-
oldValue?: unknown;
|
|
6
|
-
newValue?: unknown;
|
|
7
|
-
}
|
|
3
|
+
export type RepoSettingsPlanEntry = PlanEntry;
|
|
8
4
|
export interface RepoSettingsPlanResult {
|
|
9
5
|
lines: string[];
|
|
10
6
|
creates: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { formatScalarValue } from "../../shared/string-utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatChangeLines } from "../base-processor.js";
|
|
4
4
|
/**
|
|
5
5
|
* Format a value for display.
|
|
6
6
|
*/
|
|
@@ -32,37 +32,18 @@ function getWarning(change) {
|
|
|
32
32
|
* Formats repo settings changes as Terraform-style plan output.
|
|
33
33
|
*/
|
|
34
34
|
export function formatRepoSettingsPlan(changes) {
|
|
35
|
-
const lines = [];
|
|
36
35
|
const warnings = [];
|
|
37
|
-
const { create: creates, update: updates } = countActions(changes);
|
|
38
|
-
const entries = [];
|
|
39
36
|
if (changes.length === 0) {
|
|
40
|
-
return { lines, creates, updates, warnings, entries };
|
|
37
|
+
return { lines: [], creates: 0, updates: 0, warnings, entries: [] };
|
|
41
38
|
}
|
|
42
39
|
for (const change of changes) {
|
|
43
40
|
const warning = getWarning(change);
|
|
44
41
|
if (warning) {
|
|
45
42
|
warnings.push(warning);
|
|
46
43
|
}
|
|
47
|
-
if (change.action === "create") {
|
|
48
|
-
lines.push(chalk.green(` + ${change.property}: ${formatValue(change.newValue)}`));
|
|
49
|
-
entries.push({
|
|
50
|
-
property: change.property,
|
|
51
|
-
action: "create",
|
|
52
|
-
newValue: change.newValue,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
else if (change.action === "update") {
|
|
56
|
-
lines.push(chalk.yellow(` ~ ${change.property}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`));
|
|
57
|
-
entries.push({
|
|
58
|
-
property: change.property,
|
|
59
|
-
action: "update",
|
|
60
|
-
oldValue: change.oldValue,
|
|
61
|
-
newValue: change.newValue,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
44
|
}
|
|
65
|
-
|
|
45
|
+
const result = formatChangeLines(changes, formatValue);
|
|
46
|
+
return { ...result, warnings };
|
|
66
47
|
}
|
|
67
48
|
/**
|
|
68
49
|
* Formats warnings for display.
|
|
@@ -13,8 +13,8 @@ export declare class GitHubRepoSettingsStrategy implements IRepoSettingsStrategy
|
|
|
13
13
|
get(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentRepoSettings>;
|
|
14
14
|
update(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: GhApiOptions): Promise<void>;
|
|
15
15
|
updateVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
updateAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
17
|
+
updatePrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
18
18
|
branchExists(repoInfo: RepoInfo, branch: string, options?: GhApiOptions): Promise<boolean>;
|
|
19
19
|
private getVulnerabilityAlerts;
|
|
20
20
|
private getAutomatedSecurityFixes;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertGitHubRepo, } from "../../repo/index.js";
|
|
2
|
-
import { GhApiClient
|
|
2
|
+
import { GhApiClient } from "../../shared/gh-api-utils.js";
|
|
3
|
+
import { isHttp404Error } from "../../shared/gh-token-utils.js";
|
|
3
4
|
import { parseApiJson } from "../../shared/json-utils.js";
|
|
4
5
|
import { camelToSnake } from "../../shared/string-utils.js";
|
|
5
6
|
/**
|
|
@@ -71,7 +72,7 @@ export class GitHubRepoSettingsStrategy {
|
|
|
71
72
|
settings.owner_type = parsed.owner?.type;
|
|
72
73
|
settings.vulnerability_alerts = await this.getVulnerabilityAlerts(repoInfo, options);
|
|
73
74
|
// Pass vulnerability_alerts state - automated security fixes requires it enabled
|
|
74
|
-
settings.automated_security_fixes = await this.getAutomatedSecurityFixes(repoInfo, options
|
|
75
|
+
settings.automated_security_fixes = await this.getAutomatedSecurityFixes(repoInfo, options);
|
|
75
76
|
settings.private_vulnerability_reporting =
|
|
76
77
|
await this.getPrivateVulnerabilityReporting(repoInfo, options);
|
|
77
78
|
return settings;
|
|
@@ -92,13 +93,13 @@ export class GitHubRepoSettingsStrategy {
|
|
|
92
93
|
const method = enable ? "PUT" : "DELETE";
|
|
93
94
|
await this.api.call(method, endpoint, { options });
|
|
94
95
|
}
|
|
95
|
-
async
|
|
96
|
+
async updateAutomatedSecurityFixes(repoInfo, enable, options) {
|
|
96
97
|
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
97
98
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/automated-security-fixes`;
|
|
98
99
|
const method = enable ? "PUT" : "DELETE";
|
|
99
100
|
await this.api.call(method, endpoint, { options });
|
|
100
101
|
}
|
|
101
|
-
async
|
|
102
|
+
async updatePrivateVulnerabilityReporting(repoInfo, enable, options) {
|
|
102
103
|
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
103
104
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/private-vulnerability-reporting`;
|
|
104
105
|
const method = enable ? "PUT" : "DELETE";
|
|
@@ -128,10 +129,10 @@ export class GitHubRepoSettingsStrategy {
|
|
|
128
129
|
if (isHttp404Error(error)) {
|
|
129
130
|
return false; // 404 = disabled
|
|
130
131
|
}
|
|
131
|
-
throw error;
|
|
132
|
+
throw error;
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
|
-
async getAutomatedSecurityFixes(github, options
|
|
135
|
+
async getAutomatedSecurityFixes(github, options) {
|
|
135
136
|
// Note: GitHub returns JSON with {enabled: boolean} for this endpoint
|
|
136
137
|
const endpoint = `/repos/${github.owner}/${github.repo}/automated-security-fixes`;
|
|
137
138
|
try {
|
|
@@ -162,7 +163,7 @@ export class GitHubRepoSettingsStrategy {
|
|
|
162
163
|
if (isHttp404Error(error)) {
|
|
163
164
|
return false; // 404 = not available (e.g. private repos)
|
|
164
165
|
}
|
|
165
|
-
throw error;
|
|
166
|
+
throw error;
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -20,7 +20,10 @@ export class RepoSettingsProcessor {
|
|
|
20
20
|
}
|
|
21
21
|
async applySettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
|
|
22
22
|
const { dryRun } = options;
|
|
23
|
-
const desiredSettings = repoConfig.settings
|
|
23
|
+
const desiredSettings = repoConfig.settings?.repo;
|
|
24
|
+
if (!desiredSettings || typeof desiredSettings !== "object") {
|
|
25
|
+
throw new Error("applySettings called without repo settings");
|
|
26
|
+
}
|
|
24
27
|
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
25
28
|
// Fetch current settings and metadata in parallel
|
|
26
29
|
const [currentSettings, metadata] = await Promise.all([
|
|
@@ -39,16 +42,15 @@ export class RepoSettingsProcessor {
|
|
|
39
42
|
// Compute diff
|
|
40
43
|
const changes = diffRepoSettings(currentSettings, desiredSettings);
|
|
41
44
|
if (!hasRepoSettingsChanges(changes)) {
|
|
42
|
-
const unchangedCount = changes.filter((c) => c.action === "unchanged").length;
|
|
43
45
|
return {
|
|
44
46
|
success: true,
|
|
45
47
|
repoName,
|
|
46
48
|
message: "No changes needed",
|
|
47
|
-
changes: { create: 0, update: 0, delete: 0, unchanged:
|
|
49
|
+
changes: { create: 0, update: 0, delete: 0, unchanged: 0 },
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
// Validate defaultBranch target exists before attempting to apply
|
|
51
|
-
const defaultBranchChange = changes.find((c) => c.property === "defaultBranch"
|
|
53
|
+
const defaultBranchChange = changes.find((c) => c.property === "defaultBranch");
|
|
52
54
|
if (defaultBranchChange) {
|
|
53
55
|
const targetBranch = String(defaultBranchChange.newValue);
|
|
54
56
|
const exists = await this.strategy.branchExists(githubRepo, targetBranch, strategyOptions);
|
|
@@ -69,7 +71,7 @@ export class RepoSettingsProcessor {
|
|
|
69
71
|
create: planOutput.creates,
|
|
70
72
|
update: planOutput.updates,
|
|
71
73
|
delete: 0,
|
|
72
|
-
unchanged:
|
|
74
|
+
unchanged: 0,
|
|
73
75
|
};
|
|
74
76
|
if (dryRun) {
|
|
75
77
|
return buildDryRunResult(repoName, changeCounts, {
|
|
@@ -80,10 +82,8 @@ export class RepoSettingsProcessor {
|
|
|
80
82
|
// Apply changes - only send settings that actually changed
|
|
81
83
|
const changedSettings = {};
|
|
82
84
|
for (const change of changes) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
change.newValue;
|
|
86
|
-
}
|
|
85
|
+
changedSettings[change.property] =
|
|
86
|
+
change.newValue;
|
|
87
87
|
}
|
|
88
88
|
await this.applyChanges(githubRepo, changedSettings, strategyOptions);
|
|
89
89
|
const appliedCount = Object.keys(changedSettings).length;
|
|
@@ -106,12 +106,12 @@ export class RepoSettingsProcessor {
|
|
|
106
106
|
}
|
|
107
107
|
// Handle private vulnerability reporting (separate endpoint)
|
|
108
108
|
if (privateVulnerabilityReporting !== undefined) {
|
|
109
|
-
await this.strategy.
|
|
109
|
+
await this.strategy.updatePrivateVulnerabilityReporting(repoInfo, privateVulnerabilityReporting, options);
|
|
110
110
|
}
|
|
111
111
|
// Handle automated security fixes (separate endpoint)
|
|
112
112
|
// Done last to ensure vulnerability alerts have been fully processed
|
|
113
113
|
if (automatedSecurityFixes !== undefined) {
|
|
114
|
-
await this.strategy.
|
|
114
|
+
await this.strategy.updateAutomatedSecurityFixes(repoInfo, automatedSecurityFixes, options);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
validateSecuritySettings(desiredSettings, metadata) {
|
|
@@ -58,11 +58,11 @@ export interface IRepoSettingsStrategy {
|
|
|
58
58
|
/**
|
|
59
59
|
* Enables or disables automated security fixes.
|
|
60
60
|
*/
|
|
61
|
-
|
|
61
|
+
updateAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
62
62
|
/**
|
|
63
63
|
* Enables or disables private vulnerability reporting.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
updatePrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
66
66
|
/**
|
|
67
67
|
* Checks whether a branch exists in the repository.
|
|
68
68
|
*/
|
|
@@ -78,14 +78,16 @@ function diffObjectArrays(currentArr, desiredArr, parentPath) {
|
|
|
78
78
|
const currentByType = new Map();
|
|
79
79
|
for (let i = 0; i < currentArr.length; i++) {
|
|
80
80
|
const item = currentArr[i];
|
|
81
|
-
const type = item.type;
|
|
81
|
+
const type = typeof item.type === "string" ? item.type : String(item.type ?? "");
|
|
82
82
|
if (type)
|
|
83
83
|
currentByType.set(type, { item, index: i });
|
|
84
84
|
}
|
|
85
85
|
const matchedTypes = new Set();
|
|
86
86
|
for (let i = 0; i < desiredArr.length; i++) {
|
|
87
87
|
const desiredItem = desiredArr[i];
|
|
88
|
-
const type = desiredItem.type
|
|
88
|
+
const type = typeof desiredItem.type === "string"
|
|
89
|
+
? desiredItem.type
|
|
90
|
+
: String(desiredItem.type ?? "");
|
|
89
91
|
const label = `[${i}] (${type})`;
|
|
90
92
|
const currentEntry = currentByType.get(type);
|
|
91
93
|
if (currentEntry) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { RULESET_COMPARABLE_FIELDS } from "../../config/index.js";
|
|
2
|
+
import { findMatchKey } from "../../config/merge.js";
|
|
2
3
|
import { isPlainObject } from "../../shared/type-guards.js";
|
|
3
4
|
import { camelToSnake } from "../../shared/string-utils.js";
|
|
4
5
|
import { countActions } from "../base-processor.js";
|
|
6
|
+
import { deepEqual } from "./diff-algorithm.js";
|
|
5
7
|
/**
|
|
6
8
|
* Normalizes a value recursively, converting keys to a consistent format (snake_case).
|
|
7
9
|
* This allows comparing GitHub API responses (snake_case) with config (camelCase).
|
|
@@ -54,37 +56,6 @@ function normalizeConfigRuleset(ruleset) {
|
|
|
54
56
|
};
|
|
55
57
|
return normalizeRuleset(withDefaults);
|
|
56
58
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Performs deep equality comparison of two normalized values.
|
|
59
|
-
*/
|
|
60
|
-
function deepEqual(a, b) {
|
|
61
|
-
if (a === b) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
if (a === null || b === null || a === undefined || b === undefined) {
|
|
65
|
-
return a === b;
|
|
66
|
-
}
|
|
67
|
-
if (typeof a !== typeof b) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
71
|
-
if (a.length !== b.length) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
return a.every((val, i) => deepEqual(val, b[i]));
|
|
75
|
-
}
|
|
76
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
77
|
-
const objA = a;
|
|
78
|
-
const objB = b;
|
|
79
|
-
const keysA = Object.keys(objA);
|
|
80
|
-
const keysB = Object.keys(objB);
|
|
81
|
-
if (keysA.length !== keysB.length) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
return keysA.every((key) => deepEqual(objA[key], objB[key]));
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
59
|
/**
|
|
89
60
|
* Projects `current` onto the shape of `desired`.
|
|
90
61
|
* Only keeps keys/structure present in `desired`, filtering out API noise.
|
|
@@ -129,26 +100,6 @@ function projectObjects(current, desired) {
|
|
|
129
100
|
}
|
|
130
101
|
return result;
|
|
131
102
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Candidate keys for matching array items by identity rather than index.
|
|
134
|
-
* Order matters — first key found across all items wins.
|
|
135
|
-
*/
|
|
136
|
-
const MATCH_KEY_CANDIDATES = ["type", "actor_id"];
|
|
137
|
-
/**
|
|
138
|
-
* Finds a key that uniquely identifies items in both arrays.
|
|
139
|
-
* Returns the first candidate key present in every item of both arrays, or undefined.
|
|
140
|
-
*/
|
|
141
|
-
function findMatchKey(current, desired) {
|
|
142
|
-
const allItems = [...current, ...desired];
|
|
143
|
-
if (allItems.length === 0)
|
|
144
|
-
return undefined;
|
|
145
|
-
for (const candidate of MATCH_KEY_CANDIDATES) {
|
|
146
|
-
const everyItemHasKey = allItems.every((item) => isPlainObject(item) && candidate in item);
|
|
147
|
-
if (everyItemHasKey)
|
|
148
|
-
return candidate;
|
|
149
|
-
}
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
103
|
function projectArrays(current, desired) {
|
|
153
104
|
// Primitive arrays — return current as-is
|
|
154
105
|
if (desired.length === 0 || !isPlainObject(desired[0])) {
|
|
@@ -114,6 +114,10 @@ function getActionStyle(action) {
|
|
|
114
114
|
return { symbol: "-", color: chalk.red };
|
|
115
115
|
case "change":
|
|
116
116
|
return { symbol: "~", color: chalk.yellow };
|
|
117
|
+
default: {
|
|
118
|
+
const _ = action;
|
|
119
|
+
throw new Error(`Unknown DiffAction: ${String(_)}`);
|
|
120
|
+
}
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
function hasComplexValue(value) {
|
|
@@ -2,7 +2,7 @@ import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
|
2
2
|
import { type RepoInfo } from "../../repo/index.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { Ruleset } from "../../config/index.js";
|
|
5
|
-
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetUpdateParams } from "./types.js";
|
|
5
|
+
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetCreateParams, RulesetUpdateParams } from "./types.js";
|
|
6
6
|
/**
|
|
7
7
|
* Converts camelCase config ruleset to snake_case GitHub API format.
|
|
8
8
|
*/
|
|
@@ -24,8 +24,8 @@ export declare class GitHubRulesetStrategy implements IRulesetStrategy {
|
|
|
24
24
|
constructor(executor: ICommandExecutor, options: GitHubRulesetStrategyOptions);
|
|
25
25
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
26
26
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
27
|
-
create(repoInfo: RepoInfo,
|
|
28
|
-
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<
|
|
27
|
+
create(repoInfo: RepoInfo, params: RulesetCreateParams, options?: GhApiOptions): Promise<void>;
|
|
28
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
29
29
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
30
30
|
}
|
|
31
31
|
export {};
|
|
@@ -111,22 +111,20 @@ export class GitHubRulesetStrategy {
|
|
|
111
111
|
const result = await this.api.call("GET", endpoint, { options });
|
|
112
112
|
return parseApiJson(result, "ruleset response");
|
|
113
113
|
}
|
|
114
|
-
async create(repoInfo,
|
|
114
|
+
async create(repoInfo, params, options) {
|
|
115
115
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
116
116
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`;
|
|
117
|
-
const payload = configToGitHub(name, ruleset);
|
|
118
|
-
|
|
119
|
-
return parseApiJson(result, "ruleset response");
|
|
117
|
+
const payload = configToGitHub(params.name, params.ruleset);
|
|
118
|
+
await this.api.call("POST", endpoint, { payload, options });
|
|
120
119
|
}
|
|
121
120
|
async update(repoInfo, params, options) {
|
|
122
121
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
123
122
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${params.rulesetId}`;
|
|
124
123
|
const payload = configToGitHub(params.name, params.ruleset);
|
|
125
|
-
|
|
124
|
+
await this.api.call("PUT", endpoint, {
|
|
126
125
|
payload,
|
|
127
126
|
options,
|
|
128
127
|
});
|
|
129
|
-
return parseApiJson(result, "ruleset response");
|
|
130
128
|
}
|
|
131
129
|
async delete(repoInfo, rulesetId, options) {
|
|
132
130
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { type PropertyDiff } from "./diff-algorithm.js";
|
|
2
2
|
export { type RulesetPlanEntry } from "./formatter.js";
|
|
3
3
|
export { RulesetProcessor, type IRulesetProcessor } from "./processor.js";
|
|
4
4
|
export { GitHubRulesetStrategy } from "./github-ruleset-strategy.js";
|
|
@@ -50,7 +50,7 @@ export class RulesetProcessor {
|
|
|
50
50
|
switch (change.action) {
|
|
51
51
|
case "create":
|
|
52
52
|
if (change.desired) {
|
|
53
|
-
await this.strategy.create(githubRepo, change.name, change.desired, strategyOptions);
|
|
53
|
+
await this.strategy.create(githubRepo, { name: change.name, ruleset: change.desired }, strategyOptions);
|
|
54
54
|
appliedCount++;
|
|
55
55
|
}
|
|
56
56
|
break;
|
|
@@ -30,6 +30,10 @@ export interface GitHubRule {
|
|
|
30
30
|
type: string;
|
|
31
31
|
parameters?: Record<string, unknown>;
|
|
32
32
|
}
|
|
33
|
+
export interface RulesetCreateParams {
|
|
34
|
+
name: string;
|
|
35
|
+
ruleset: Ruleset;
|
|
36
|
+
}
|
|
33
37
|
export interface RulesetUpdateParams {
|
|
34
38
|
rulesetId: number;
|
|
35
39
|
name: string;
|
|
@@ -38,7 +42,7 @@ export interface RulesetUpdateParams {
|
|
|
38
42
|
export interface IRulesetStrategy {
|
|
39
43
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
40
44
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
41
|
-
create(repoInfo: RepoInfo,
|
|
42
|
-
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<
|
|
45
|
+
create(repoInfo: RepoInfo, params: RulesetCreateParams, options?: GhApiOptions): Promise<void>;
|
|
46
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
43
47
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
44
48
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GitHubVariable } from "./types.js";
|
|
2
|
+
import type { SettingsAction } from "../base-processor.js";
|
|
3
|
+
export type VariableAction = SettingsAction;
|
|
4
|
+
export interface VariableChange {
|
|
5
|
+
action: VariableAction;
|
|
6
|
+
name: string;
|
|
7
|
+
oldValue?: string;
|
|
8
|
+
newValue?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function diffVariables(current: GitHubVariable[], desired: Record<string, string>, deleteOrphaned: boolean): VariableChange[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function diffVariables(current, desired, deleteOrphaned) {
|
|
2
|
+
const changes = [];
|
|
3
|
+
const currentByName = new Map();
|
|
4
|
+
for (const v of current) {
|
|
5
|
+
currentByName.set(v.name.toUpperCase(), v);
|
|
6
|
+
}
|
|
7
|
+
const desiredUpper = new Set(Object.keys(desired).map((n) => n.toUpperCase()));
|
|
8
|
+
for (const [name, desiredValue] of Object.entries(desired)) {
|
|
9
|
+
const currentVar = currentByName.get(name.toUpperCase());
|
|
10
|
+
if (!currentVar) {
|
|
11
|
+
changes.push({ action: "create", name, newValue: desiredValue });
|
|
12
|
+
}
|
|
13
|
+
else if (currentVar.value !== desiredValue) {
|
|
14
|
+
changes.push({
|
|
15
|
+
action: "update",
|
|
16
|
+
name: currentVar.name,
|
|
17
|
+
oldValue: currentVar.value,
|
|
18
|
+
newValue: desiredValue,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
changes.push({ action: "unchanged", name: currentVar.name });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (deleteOrphaned) {
|
|
26
|
+
for (const [nameUpper, currentVar] of currentByName) {
|
|
27
|
+
if (!desiredUpper.has(nameUpper)) {
|
|
28
|
+
changes.push({ action: "delete", name: currentVar.name });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const actionOrder = {
|
|
33
|
+
delete: 0,
|
|
34
|
+
update: 1,
|
|
35
|
+
create: 2,
|
|
36
|
+
unchanged: 3,
|
|
37
|
+
};
|
|
38
|
+
return changes.sort((a, b) => actionOrder[a.action] - actionOrder[b.action]);
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VariableChange, VariableAction } from "./diff.js";
|
|
2
|
+
export interface VariablesPlanEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
action: VariableAction;
|
|
5
|
+
oldValue?: string;
|
|
6
|
+
newValue?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface VariablesPlanResult {
|
|
9
|
+
lines: string[];
|
|
10
|
+
creates: number;
|
|
11
|
+
updates: number;
|
|
12
|
+
deletes: number;
|
|
13
|
+
unchanged: number;
|
|
14
|
+
entries: VariablesPlanEntry[];
|
|
15
|
+
}
|
|
16
|
+
export declare function formatVariablesPlan(changes: VariableChange[]): VariablesPlanResult;
|