@aspruyt/xfg 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +70 -0
- package/dist/cli/settings-command.d.ts +10 -0
- package/dist/cli/settings-command.js +228 -0
- package/dist/cli/sync-command.d.ts +25 -0
- package/dist/cli/sync-command.js +155 -0
- package/dist/cli/types.d.ts +45 -0
- package/dist/cli/types.js +15 -0
- package/dist/cli.js +2 -19
- package/dist/{file-reference-resolver.d.ts → config/file-reference-resolver.d.ts} +1 -1
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +12 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/{config.js → config/loader.js} +3 -24
- package/dist/{config-normalizer.d.ts → config/normalizer.d.ts} +1 -1
- package/dist/{config-normalizer.js → config/normalizer.js} +1 -1
- package/dist/{config.d.ts → config/types.d.ts} +5 -9
- package/dist/config/types.js +16 -0
- package/dist/{config-validator.d.ts → config/validator.d.ts} +5 -5
- package/dist/{config-validator.js → config/validator.js} +60 -372
- package/dist/config/validators/file-validator.d.ts +22 -0
- package/dist/config/validators/file-validator.js +46 -0
- package/dist/config/validators/index.d.ts +3 -0
- package/dist/config/validators/index.js +6 -0
- package/dist/config/validators/repo-settings-validator.d.ts +10 -0
- package/dist/config/validators/repo-settings-validator.js +71 -0
- package/dist/config/validators/ruleset-validator.d.ts +18 -0
- package/dist/config/validators/ruleset-validator.js +201 -0
- package/dist/index.d.ts +3 -66
- package/dist/index.js +3 -474
- package/dist/output/index.d.ts +4 -0
- package/dist/output/index.js +8 -0
- package/dist/{summary-utils.d.ts → output/summary-utils.d.ts} +3 -3
- package/dist/settings/index.d.ts +3 -0
- package/dist/settings/index.js +6 -0
- package/dist/{repo-settings-diff.d.ts → settings/repo-settings/diff.d.ts} +2 -2
- package/dist/{repo-settings-plan-formatter.d.ts → settings/repo-settings/formatter.d.ts} +1 -1
- package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.d.ts +4 -4
- package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.js +3 -3
- package/dist/settings/repo-settings/index.d.ts +5 -0
- package/dist/settings/repo-settings/index.js +10 -0
- package/dist/{repo-settings-processor.d.ts → settings/repo-settings/processor.d.ts} +4 -4
- package/dist/{repo-settings-processor.js → settings/repo-settings/processor.js} +6 -6
- package/dist/{strategies/repo-settings-strategy.d.ts → settings/repo-settings/types.d.ts} +2 -2
- package/dist/{resource-converters.d.ts → settings/resource-converters.d.ts} +4 -4
- package/dist/settings/rulesets/diff-algorithm.d.ts +18 -0
- package/dist/settings/rulesets/diff-algorithm.js +166 -0
- package/dist/{ruleset-diff.d.ts → settings/rulesets/diff.d.ts} +2 -2
- package/dist/{ruleset-diff.js → settings/rulesets/diff.js} +1 -1
- package/dist/{ruleset-plan-formatter.d.ts → settings/rulesets/formatter.d.ts} +4 -12
- package/dist/{ruleset-plan-formatter.js → settings/rulesets/formatter.js} +5 -166
- package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.d.ts +4 -4
- package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.js +3 -3
- package/dist/settings/rulesets/index.d.ts +6 -0
- package/dist/settings/rulesets/index.js +10 -0
- package/dist/{ruleset-processor.d.ts → settings/rulesets/processor.d.ts} +4 -4
- package/dist/{ruleset-processor.js → settings/rulesets/processor.js} +6 -6
- package/dist/{strategies/ruleset-strategy.d.ts → settings/rulesets/types.d.ts} +2 -2
- package/dist/{command-executor.d.ts → shared/command-executor.d.ts} +10 -2
- package/dist/{command-executor.js → shared/command-executor.js} +2 -1
- package/dist/shared/index.d.ts +8 -0
- package/dist/shared/index.js +16 -0
- package/dist/{logger.d.ts → shared/logger.d.ts} +1 -1
- package/dist/{logger.js → shared/logger.js} +1 -1
- package/dist/sync/auth-options-builder.d.ts +12 -0
- package/dist/sync/auth-options-builder.js +54 -0
- package/dist/sync/branch-manager.d.ts +7 -0
- package/dist/sync/branch-manager.js +36 -0
- package/dist/sync/commit-message.d.ts +11 -0
- package/dist/sync/commit-message.js +27 -0
- package/dist/sync/commit-push-manager.d.ts +8 -0
- package/dist/sync/commit-push-manager.js +71 -0
- package/dist/sync/file-sync-orchestrator.d.ts +11 -0
- package/dist/sync/file-sync-orchestrator.js +58 -0
- package/dist/sync/file-writer.d.ts +18 -0
- package/dist/sync/file-writer.js +101 -0
- package/dist/sync/index.d.ts +14 -0
- package/dist/sync/index.js +17 -0
- package/dist/sync/manifest-manager.d.ts +10 -0
- package/dist/sync/manifest-manager.js +64 -0
- package/dist/sync/pr-merge-handler.d.ts +11 -0
- package/dist/sync/pr-merge-handler.js +62 -0
- package/dist/sync/repository-processor.d.ts +30 -0
- package/dist/sync/repository-processor.js +278 -0
- package/dist/sync/repository-session.d.ts +9 -0
- package/dist/sync/repository-session.js +35 -0
- package/dist/sync/types.d.ts +296 -0
- package/dist/{xfg-template.d.ts → sync/xfg-template.d.ts} +2 -2
- package/dist/{authenticated-git-ops.js → vcs/authenticated-git-ops.js} +3 -3
- package/dist/{strategies → vcs}/azure-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/azure-pr-strategy.js +5 -5
- package/dist/{strategies → vcs}/commit-strategy-selector.d.ts +3 -3
- package/dist/{strategies → vcs}/commit-strategy-selector.js +1 -1
- package/dist/{strategies → vcs}/git-commit-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/git-commit-strategy.js +3 -3
- package/dist/{git-ops.d.ts → vcs/git-ops.d.ts} +1 -1
- package/dist/{git-ops.js → vcs/git-ops.js} +4 -4
- package/dist/{github-app-token-manager.d.ts → vcs/github-app-token-manager.d.ts} +1 -1
- package/dist/{github-app-token-manager.js → vcs/github-app-token-manager.js} +1 -1
- package/dist/{strategies → vcs}/github-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/github-pr-strategy.js +30 -33
- package/dist/{strategies → vcs}/gitlab-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/gitlab-pr-strategy.js +5 -5
- package/dist/{strategies → vcs}/graphql-commit-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/graphql-commit-strategy.js +3 -3
- package/dist/vcs/index.d.ts +16 -0
- package/dist/{strategies → vcs}/index.js +15 -10
- package/dist/{pr-creator.d.ts → vcs/pr-creator.d.ts} +4 -4
- package/dist/{pr-creator.js → vcs/pr-creator.js} +3 -3
- package/dist/vcs/pr-strategy.d.ts +41 -0
- package/dist/{strategies → vcs}/pr-strategy.js +1 -1
- package/dist/{strategies/pr-strategy.d.ts → vcs/types.d.ts} +32 -35
- package/dist/vcs/types.js +1 -0
- package/package.json +2 -2
- package/dist/repository-processor.d.ts +0 -79
- package/dist/repository-processor.js +0 -659
- package/dist/strategies/commit-strategy.d.ts +0 -36
- package/dist/strategies/index.d.ts +0 -18
- /package/dist/{file-reference-resolver.js → config/file-reference-resolver.js} +0 -0
- /package/dist/{config-formatter.d.ts → config/formatter.d.ts} +0 -0
- /package/dist/{config-formatter.js → config/formatter.js} +0 -0
- /package/dist/{merge.d.ts → config/merge.d.ts} +0 -0
- /package/dist/{merge.js → config/merge.js} +0 -0
- /package/dist/{github-summary.d.ts → output/github-summary.d.ts} +0 -0
- /package/dist/{github-summary.js → output/github-summary.js} +0 -0
- /package/dist/{plan-formatter.d.ts → output/plan-formatter.d.ts} +0 -0
- /package/dist/{plan-formatter.js → output/plan-formatter.js} +0 -0
- /package/dist/{plan-summary.d.ts → output/plan-summary.d.ts} +0 -0
- /package/dist/{plan-summary.js → output/plan-summary.js} +0 -0
- /package/dist/{summary-utils.js → output/summary-utils.js} +0 -0
- /package/dist/{repo-settings-diff.js → settings/repo-settings/diff.js} +0 -0
- /package/dist/{repo-settings-plan-formatter.js → settings/repo-settings/formatter.js} +0 -0
- /package/dist/{strategies/repo-settings-strategy.js → settings/repo-settings/types.js} +0 -0
- /package/dist/{resource-converters.js → settings/resource-converters.js} +0 -0
- /package/dist/{strategies/commit-strategy.js → settings/rulesets/types.js} +0 -0
- /package/dist/{env.d.ts → shared/env.d.ts} +0 -0
- /package/dist/{env.js → shared/env.js} +0 -0
- /package/dist/{repo-detector.d.ts → shared/repo-detector.d.ts} +0 -0
- /package/dist/{repo-detector.js → shared/repo-detector.js} +0 -0
- /package/dist/{retry-utils.d.ts → shared/retry-utils.d.ts} +0 -0
- /package/dist/{retry-utils.js → shared/retry-utils.js} +0 -0
- /package/dist/{sanitize-utils.d.ts → shared/sanitize-utils.d.ts} +0 -0
- /package/dist/{sanitize-utils.js → shared/sanitize-utils.js} +0 -0
- /package/dist/{shell-utils.d.ts → shared/shell-utils.d.ts} +0 -0
- /package/dist/{shell-utils.js → shared/shell-utils.js} +0 -0
- /package/dist/{workspace-utils.d.ts → shared/workspace-utils.d.ts} +0 -0
- /package/dist/{workspace-utils.js → shared/workspace-utils.js} +0 -0
- /package/dist/{diff-utils.d.ts → sync/diff-utils.d.ts} +0 -0
- /package/dist/{diff-utils.js → sync/diff-utils.js} +0 -0
- /package/dist/{manifest.d.ts → sync/manifest.d.ts} +0 -0
- /package/dist/{manifest.js → sync/manifest.js} +0 -0
- /package/dist/{strategies/ruleset-strategy.js → sync/types.js} +0 -0
- /package/dist/{xfg-template.js → sync/xfg-template.js} +0 -0
- /package/dist/{authenticated-git-ops.d.ts → vcs/authenticated-git-ops.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,474 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import { loadRawConfig, normalizeConfig, } from "./config.js";
|
|
8
|
-
import { validateForSync, validateForSettings } from "./config-validator.js";
|
|
9
|
-
// Get version from package.json
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = dirname(__filename);
|
|
12
|
-
const packageJson = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
13
|
-
import { parseGitUrl, getRepoDisplayName } from "./repo-detector.js";
|
|
14
|
-
import { sanitizeBranchName, validateBranchName } from "./git-ops.js";
|
|
15
|
-
import { logger } from "./logger.js";
|
|
16
|
-
import { generateWorkspaceName } from "./workspace-utils.js";
|
|
17
|
-
import { RepositoryProcessor, } from "./repository-processor.js";
|
|
18
|
-
import { buildRepoResult, buildErrorResult } from "./summary-utils.js";
|
|
19
|
-
import { RulesetProcessor, } from "./ruleset-processor.js";
|
|
20
|
-
import { getManagedRulesets } from "./manifest.js";
|
|
21
|
-
import { isGitHubRepo } from "./repo-detector.js";
|
|
22
|
-
import { RepoSettingsProcessor, } from "./repo-settings-processor.js";
|
|
23
|
-
import { printPlan } from "./plan-formatter.js";
|
|
24
|
-
import { writePlanSummary } from "./plan-summary.js";
|
|
25
|
-
import { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
|
|
26
|
-
/**
|
|
27
|
-
* Default factory that creates a real RepositoryProcessor.
|
|
28
|
-
*/
|
|
29
|
-
export const defaultProcessorFactory = () => new RepositoryProcessor();
|
|
30
|
-
/**
|
|
31
|
-
* Default factory that creates a real RulesetProcessor.
|
|
32
|
-
*/
|
|
33
|
-
export const defaultRulesetProcessorFactory = () => new RulesetProcessor();
|
|
34
|
-
/**
|
|
35
|
-
* Default factory that creates a real RepoSettingsProcessor.
|
|
36
|
-
*/
|
|
37
|
-
export const defaultRepoSettingsProcessorFactory = () => new RepoSettingsProcessor();
|
|
38
|
-
/**
|
|
39
|
-
* Adds shared options to a command.
|
|
40
|
-
*/
|
|
41
|
-
function addSharedOptions(cmd) {
|
|
42
|
-
return cmd
|
|
43
|
-
.requiredOption("-c, --config <path>", "Path to YAML config file")
|
|
44
|
-
.option("-d, --dry-run", "Show what would be done without making changes")
|
|
45
|
-
.option("-w, --work-dir <path>", "Temporary directory for cloning", "./tmp")
|
|
46
|
-
.option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (v) => parseInt(v, 10), 3)
|
|
47
|
-
.option("--no-delete", "Skip deletion of orphaned resources even if deleteOrphaned is configured");
|
|
48
|
-
}
|
|
49
|
-
// =============================================================================
|
|
50
|
-
// Sync Command
|
|
51
|
-
// =============================================================================
|
|
52
|
-
/**
|
|
53
|
-
* Get unique file names from all repos in the config
|
|
54
|
-
*/
|
|
55
|
-
function getUniqueFileNames(config) {
|
|
56
|
-
const fileNames = new Set();
|
|
57
|
-
for (const repo of config.repos) {
|
|
58
|
-
for (const file of repo.files) {
|
|
59
|
-
fileNames.add(file.fileName);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return Array.from(fileNames);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Generate default branch name based on files being synced
|
|
66
|
-
*/
|
|
67
|
-
function generateBranchName(fileNames) {
|
|
68
|
-
if (fileNames.length === 1) {
|
|
69
|
-
return `chore/sync-${sanitizeBranchName(fileNames[0])}`;
|
|
70
|
-
}
|
|
71
|
-
return "chore/sync-config";
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Format file names for display
|
|
75
|
-
*/
|
|
76
|
-
function formatFileNames(fileNames) {
|
|
77
|
-
if (fileNames.length === 1) {
|
|
78
|
-
return fileNames[0];
|
|
79
|
-
}
|
|
80
|
-
if (fileNames.length <= 3) {
|
|
81
|
-
return fileNames.join(", ");
|
|
82
|
-
}
|
|
83
|
-
return `${fileNames.length} files`;
|
|
84
|
-
}
|
|
85
|
-
export async function runSync(options, processorFactory = defaultProcessorFactory) {
|
|
86
|
-
const configPath = resolve(options.config);
|
|
87
|
-
if (!existsSync(configPath)) {
|
|
88
|
-
console.error(`Config file not found: ${configPath}`);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
console.log(`Loading config from: ${configPath}`);
|
|
92
|
-
if (options.dryRun) {
|
|
93
|
-
console.log("Running in DRY RUN mode - no changes will be made\n");
|
|
94
|
-
}
|
|
95
|
-
const rawConfig = loadRawConfig(configPath);
|
|
96
|
-
// Validate config is suitable for sync command
|
|
97
|
-
try {
|
|
98
|
-
validateForSync(rawConfig);
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
const config = normalizeConfig(rawConfig);
|
|
105
|
-
const fileNames = getUniqueFileNames(config);
|
|
106
|
-
let branchName;
|
|
107
|
-
if (options.branch) {
|
|
108
|
-
validateBranchName(options.branch);
|
|
109
|
-
branchName = options.branch;
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
branchName = generateBranchName(fileNames);
|
|
113
|
-
}
|
|
114
|
-
logger.setTotal(config.repos.length);
|
|
115
|
-
console.log(`Found ${config.repos.length} repositories to process`);
|
|
116
|
-
console.log(`Target files: ${formatFileNames(fileNames)}`);
|
|
117
|
-
console.log(`Branch: ${branchName}\n`);
|
|
118
|
-
const processor = processorFactory();
|
|
119
|
-
const results = [];
|
|
120
|
-
// Build plan for Terraform-style output
|
|
121
|
-
const plan = { resources: [], errors: [] };
|
|
122
|
-
for (let i = 0; i < config.repos.length; i++) {
|
|
123
|
-
const repoConfig = config.repos[i];
|
|
124
|
-
// Apply CLI merge overrides to repo config
|
|
125
|
-
if (options.merge || options.mergeStrategy || options.deleteBranch) {
|
|
126
|
-
repoConfig.prOptions = {
|
|
127
|
-
...repoConfig.prOptions,
|
|
128
|
-
merge: options.merge ?? repoConfig.prOptions?.merge,
|
|
129
|
-
mergeStrategy: options.mergeStrategy ?? repoConfig.prOptions?.mergeStrategy,
|
|
130
|
-
deleteBranch: options.deleteBranch ?? repoConfig.prOptions?.deleteBranch,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const current = i + 1;
|
|
134
|
-
let repoInfo;
|
|
135
|
-
try {
|
|
136
|
-
repoInfo = parseGitUrl(repoConfig.git, {
|
|
137
|
-
githubHosts: config.githubHosts,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
logger.error(current, repoConfig.git, String(error));
|
|
142
|
-
results.push(buildErrorResult(repoConfig.git, error));
|
|
143
|
-
plan.errors.push({
|
|
144
|
-
repo: repoConfig.git,
|
|
145
|
-
message: error instanceof Error ? error.message : String(error),
|
|
146
|
-
});
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const repoName = getRepoDisplayName(repoInfo);
|
|
150
|
-
const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(i)));
|
|
151
|
-
try {
|
|
152
|
-
logger.progress(current, repoName, "Processing...");
|
|
153
|
-
const result = await processor.process(repoConfig, repoInfo, {
|
|
154
|
-
branchName,
|
|
155
|
-
workDir,
|
|
156
|
-
configId: config.id,
|
|
157
|
-
dryRun: options.dryRun,
|
|
158
|
-
retries: options.retries,
|
|
159
|
-
prTemplate: config.prTemplate,
|
|
160
|
-
noDelete: options.noDelete,
|
|
161
|
-
});
|
|
162
|
-
const repoResult = buildRepoResult(repoName, repoConfig, result);
|
|
163
|
-
results.push(repoResult);
|
|
164
|
-
if (result.skipped) {
|
|
165
|
-
logger.skip(current, repoName, result.message);
|
|
166
|
-
}
|
|
167
|
-
else if (result.success) {
|
|
168
|
-
logger.success(current, repoName, repoResult.message);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
logger.error(current, repoName, result.message);
|
|
172
|
-
}
|
|
173
|
-
// Collect resources for plan output
|
|
174
|
-
plan.resources.push(...syncResultToResources(repoName, repoConfig, result));
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
logger.error(current, repoName, String(error));
|
|
178
|
-
results.push(buildErrorResult(repoName, error));
|
|
179
|
-
plan.errors.push({
|
|
180
|
-
repo: repoName,
|
|
181
|
-
message: error instanceof Error ? error.message : String(error),
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Print Terraform-style plan summary
|
|
186
|
-
console.log("");
|
|
187
|
-
printPlan(plan);
|
|
188
|
-
// Write GitHub Actions job summary
|
|
189
|
-
writePlanSummary(plan, {
|
|
190
|
-
title: "Config Sync Summary",
|
|
191
|
-
dryRun: options.dryRun ?? false,
|
|
192
|
-
});
|
|
193
|
-
if (plan.errors && plan.errors.length > 0) {
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// =============================================================================
|
|
198
|
-
// Settings Command
|
|
199
|
-
// =============================================================================
|
|
200
|
-
export async function runSettings(options, processorFactory = defaultRulesetProcessorFactory, repoProcessorFactory = defaultProcessorFactory, repoSettingsProcessorFactory = defaultRepoSettingsProcessorFactory) {
|
|
201
|
-
const configPath = resolve(options.config);
|
|
202
|
-
if (!existsSync(configPath)) {
|
|
203
|
-
console.error(`Config file not found: ${configPath}`);
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
console.log(`Loading config from: ${configPath}`);
|
|
207
|
-
if (options.dryRun) {
|
|
208
|
-
console.log("Running in DRY RUN mode - no changes will be made\n");
|
|
209
|
-
}
|
|
210
|
-
const rawConfig = loadRawConfig(configPath);
|
|
211
|
-
// Validate config is suitable for settings command
|
|
212
|
-
try {
|
|
213
|
-
validateForSettings(rawConfig);
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
const config = normalizeConfig(rawConfig);
|
|
220
|
-
// Check if any repos have rulesets configured or have managed rulesets to clean up
|
|
221
|
-
const reposWithRulesets = config.repos.filter((r) => r.settings?.rulesets && Object.keys(r.settings.rulesets).length > 0);
|
|
222
|
-
// Check if any repos have repo settings configured
|
|
223
|
-
const reposWithRepoSettings = config.repos.filter((r) => r.settings?.repo && Object.keys(r.settings.repo).length > 0);
|
|
224
|
-
if (reposWithRulesets.length === 0 && reposWithRepoSettings.length === 0) {
|
|
225
|
-
console.log("No settings configured. Add settings.rulesets or settings.repo to your config.");
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (reposWithRulesets.length > 0) {
|
|
229
|
-
console.log(`Found ${reposWithRulesets.length} repositories with rulesets`);
|
|
230
|
-
}
|
|
231
|
-
if (reposWithRepoSettings.length > 0) {
|
|
232
|
-
console.log(`Found ${reposWithRepoSettings.length} repositories with repo settings`);
|
|
233
|
-
}
|
|
234
|
-
console.log("");
|
|
235
|
-
logger.setTotal(reposWithRulesets.length + reposWithRepoSettings.length);
|
|
236
|
-
const processor = processorFactory();
|
|
237
|
-
const repoProcessor = repoProcessorFactory();
|
|
238
|
-
const results = [];
|
|
239
|
-
// Build plan for Terraform-style output
|
|
240
|
-
const plan = { resources: [], errors: [] };
|
|
241
|
-
for (let i = 0; i < reposWithRulesets.length; i++) {
|
|
242
|
-
const repoConfig = reposWithRulesets[i];
|
|
243
|
-
let repoInfo;
|
|
244
|
-
try {
|
|
245
|
-
repoInfo = parseGitUrl(repoConfig.git, {
|
|
246
|
-
githubHosts: config.githubHosts,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
logger.error(i + 1, repoConfig.git, String(error));
|
|
251
|
-
results.push(buildErrorResult(repoConfig.git, error));
|
|
252
|
-
plan.errors.push({
|
|
253
|
-
repo: repoConfig.git,
|
|
254
|
-
message: error instanceof Error ? error.message : String(error),
|
|
255
|
-
});
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const repoName = getRepoDisplayName(repoInfo);
|
|
259
|
-
// Skip non-GitHub repos
|
|
260
|
-
if (!isGitHubRepo(repoInfo)) {
|
|
261
|
-
logger.skip(i + 1, repoName, "GitHub Rulesets only supported for GitHub repos");
|
|
262
|
-
// Mark all rulesets from this repo as skipped
|
|
263
|
-
if (repoConfig.settings?.rulesets) {
|
|
264
|
-
for (const rulesetName of Object.keys(repoConfig.settings.rulesets)) {
|
|
265
|
-
plan.resources.push({
|
|
266
|
-
type: "ruleset",
|
|
267
|
-
repo: repoName,
|
|
268
|
-
name: rulesetName,
|
|
269
|
-
action: "skipped",
|
|
270
|
-
skipReason: "GitHub Rulesets only supported for GitHub repos",
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
// Note: For settings command, we don't clone repos - we work with the API directly.
|
|
277
|
-
// Manifest handling for tracking managed rulesets would require cloning.
|
|
278
|
-
// For now, use an empty list - orphan deletion requires the sync command first.
|
|
279
|
-
const managedRulesets = getManagedRulesets(null, config.id);
|
|
280
|
-
try {
|
|
281
|
-
logger.progress(i + 1, repoName, "Processing rulesets...");
|
|
282
|
-
const result = await processor.process(repoConfig, repoInfo, {
|
|
283
|
-
configId: config.id,
|
|
284
|
-
dryRun: options.dryRun,
|
|
285
|
-
managedRulesets,
|
|
286
|
-
noDelete: options.noDelete,
|
|
287
|
-
});
|
|
288
|
-
// Print detailed ruleset plan output
|
|
289
|
-
if (result.planOutput && result.planOutput.lines.length > 0) {
|
|
290
|
-
logger.info("");
|
|
291
|
-
logger.info(chalk.bold(`${repoName} - Rulesets:`));
|
|
292
|
-
for (const line of result.planOutput.lines) {
|
|
293
|
-
logger.info(line);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (result.skipped) {
|
|
297
|
-
logger.skip(i + 1, repoName, result.message);
|
|
298
|
-
}
|
|
299
|
-
else if (result.success) {
|
|
300
|
-
logger.success(i + 1, repoName, result.message);
|
|
301
|
-
// Update manifest with ruleset tracking if there are rulesets to track
|
|
302
|
-
if (result.manifestUpdate &&
|
|
303
|
-
result.manifestUpdate.rulesets.length > 0) {
|
|
304
|
-
const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(i)));
|
|
305
|
-
logger.progress(i + 1, repoName, "Updating manifest...");
|
|
306
|
-
const manifestResult = await repoProcessor.updateManifestOnly(repoInfo, repoConfig, {
|
|
307
|
-
branchName: "chore/sync-rulesets",
|
|
308
|
-
workDir,
|
|
309
|
-
configId: config.id,
|
|
310
|
-
dryRun: options.dryRun,
|
|
311
|
-
retries: options.retries,
|
|
312
|
-
}, result.manifestUpdate);
|
|
313
|
-
if (!manifestResult.success && !manifestResult.skipped) {
|
|
314
|
-
logger.info(`Warning: Failed to update manifest for ${repoName}: ${manifestResult.message}`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
logger.error(i + 1, repoName, result.message);
|
|
320
|
-
}
|
|
321
|
-
results.push({
|
|
322
|
-
repoName,
|
|
323
|
-
status: result.skipped
|
|
324
|
-
? "skipped"
|
|
325
|
-
: result.success
|
|
326
|
-
? "succeeded"
|
|
327
|
-
: "failed",
|
|
328
|
-
message: result.message,
|
|
329
|
-
rulesetPlanDetails: result.planOutput?.entries,
|
|
330
|
-
});
|
|
331
|
-
// Collect resources for plan output
|
|
332
|
-
plan.resources.push(...rulesetResultToResources(repoName, result));
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
logger.error(i + 1, repoName, String(error));
|
|
336
|
-
results.push(buildErrorResult(repoName, error));
|
|
337
|
-
plan.errors.push({
|
|
338
|
-
repo: repoName,
|
|
339
|
-
message: error instanceof Error ? error.message : String(error),
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
// Process repo settings
|
|
344
|
-
if (reposWithRepoSettings.length > 0) {
|
|
345
|
-
const repoSettingsProcessor = repoSettingsProcessorFactory();
|
|
346
|
-
console.log(`\nProcessing repo settings for ${reposWithRepoSettings.length} repositories\n`);
|
|
347
|
-
for (let i = 0; i < reposWithRepoSettings.length; i++) {
|
|
348
|
-
const repoConfig = reposWithRepoSettings[i];
|
|
349
|
-
let repoInfo;
|
|
350
|
-
try {
|
|
351
|
-
repoInfo = parseGitUrl(repoConfig.git, {
|
|
352
|
-
githubHosts: config.githubHosts,
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
console.error(`Failed to parse ${repoConfig.git}: ${error}`);
|
|
357
|
-
plan.errors.push({
|
|
358
|
-
repo: repoConfig.git,
|
|
359
|
-
message: error instanceof Error ? error.message : String(error),
|
|
360
|
-
});
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
const repoName = getRepoDisplayName(repoInfo);
|
|
364
|
-
try {
|
|
365
|
-
const result = await repoSettingsProcessor.process(repoConfig, repoInfo, {
|
|
366
|
-
dryRun: options.dryRun,
|
|
367
|
-
});
|
|
368
|
-
if (result.planOutput && result.planOutput.lines.length > 0) {
|
|
369
|
-
console.log(`\n ${chalk.bold(repoName)}:`);
|
|
370
|
-
console.log(" Repo Settings:");
|
|
371
|
-
for (const line of result.planOutput.lines) {
|
|
372
|
-
console.log(line);
|
|
373
|
-
}
|
|
374
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
375
|
-
for (const warning of result.warnings) {
|
|
376
|
-
console.log(chalk.yellow(` ⚠️ Warning: ${warning}`));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (result.skipped) {
|
|
381
|
-
// Silent skip for repos without repo settings
|
|
382
|
-
}
|
|
383
|
-
else if (result.success) {
|
|
384
|
-
console.log(chalk.green(` ✓ ${repoName}: ${result.message}`));
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
console.log(chalk.red(` ✗ ${repoName}: ${result.message}`));
|
|
388
|
-
}
|
|
389
|
-
// Merge repo settings plan details into existing result or push new
|
|
390
|
-
if (!result.skipped) {
|
|
391
|
-
const existing = results.find((r) => r.repoName === repoName);
|
|
392
|
-
if (existing) {
|
|
393
|
-
existing.repoSettingsPlanDetails = result.planOutput?.entries;
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
results.push({
|
|
397
|
-
repoName,
|
|
398
|
-
status: result.success ? "succeeded" : "failed",
|
|
399
|
-
message: result.message,
|
|
400
|
-
repoSettingsPlanDetails: result.planOutput?.entries,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Collect resources for plan output
|
|
405
|
-
plan.resources.push(...repoSettingsResultToResources(repoName, result));
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
console.error(` ✗ ${repoName}: ${error}`);
|
|
409
|
-
plan.errors.push({
|
|
410
|
-
repo: repoName,
|
|
411
|
-
message: error instanceof Error ? error.message : String(error),
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// Print Terraform-style plan summary
|
|
417
|
-
console.log("");
|
|
418
|
-
printPlan(plan);
|
|
419
|
-
// Write GitHub Actions job summary
|
|
420
|
-
writePlanSummary(plan, {
|
|
421
|
-
title: "Repository Settings Summary",
|
|
422
|
-
dryRun: options.dryRun ?? false,
|
|
423
|
-
});
|
|
424
|
-
if (plan.errors && plan.errors.length > 0) {
|
|
425
|
-
process.exit(1);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// =============================================================================
|
|
429
|
-
// CLI Program
|
|
430
|
-
// =============================================================================
|
|
431
|
-
program
|
|
432
|
-
.name("xfg")
|
|
433
|
-
.description("Sync files and manage settings across repositories")
|
|
434
|
-
.version(packageJson.version);
|
|
435
|
-
// Sync command (file synchronization)
|
|
436
|
-
const syncCommand = new Command("sync")
|
|
437
|
-
.description("Sync configuration files across repositories (default command)")
|
|
438
|
-
.option("-b, --branch <name>", "Override the branch name (default: chore/sync-{filename} or chore/sync-config)")
|
|
439
|
-
.option("-m, --merge <mode>", "PR merge mode: manual, auto (default, merge when checks pass), force (bypass requirements), direct (push to default branch, no PR)", (value) => {
|
|
440
|
-
const valid = ["manual", "auto", "force", "direct"];
|
|
441
|
-
if (!valid.includes(value)) {
|
|
442
|
-
throw new Error(`Invalid merge mode: ${value}. Valid: ${valid.join(", ")}`);
|
|
443
|
-
}
|
|
444
|
-
return value;
|
|
445
|
-
})
|
|
446
|
-
.option("--merge-strategy <strategy>", "Merge strategy: merge, squash (default), rebase", (value) => {
|
|
447
|
-
const valid = ["merge", "squash", "rebase"];
|
|
448
|
-
if (!valid.includes(value)) {
|
|
449
|
-
throw new Error(`Invalid merge strategy: ${value}. Valid: ${valid.join(", ")}`);
|
|
450
|
-
}
|
|
451
|
-
return value;
|
|
452
|
-
})
|
|
453
|
-
.option("--delete-branch", "Delete source branch after merge")
|
|
454
|
-
.action((opts) => {
|
|
455
|
-
runSync(opts).catch((error) => {
|
|
456
|
-
console.error("Fatal error:", error);
|
|
457
|
-
process.exit(1);
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
addSharedOptions(syncCommand);
|
|
461
|
-
program.addCommand(syncCommand);
|
|
462
|
-
// Settings command (ruleset management)
|
|
463
|
-
const settingsCommand = new Command("settings")
|
|
464
|
-
.description("Manage GitHub Rulesets for repositories")
|
|
465
|
-
.action((opts) => {
|
|
466
|
-
runSettings(opts).catch((error) => {
|
|
467
|
-
console.error("Fatal error:", error);
|
|
468
|
-
process.exit(1);
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
addSharedOptions(settingsCommand);
|
|
472
|
-
program.addCommand(settingsCommand);
|
|
473
|
-
// Export program for CLI entry point
|
|
474
|
-
export { program };
|
|
1
|
+
// Public API for library consumers
|
|
2
|
+
export { runSync, runSettings } from "./cli/index.js";
|
|
3
|
+
export { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./cli/index.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { formatResourceId, formatResourceLine, formatPlanSummary, formatPlan, printPlan, type ResourceType, type ResourceAction, type Resource, type ResourceDetails, type PropertyChange, type PlanCounts, type Plan, type RepoError, } from "./plan-formatter.js";
|
|
2
|
+
export { formatPlanMarkdown, writePlanSummary, type PlanMarkdownOptions, } from "./plan-summary.js";
|
|
3
|
+
export { formatSummary, isGitHubActions, writeSummary, type MergeOutcome, type FileChanges, type RulesetPlanDetail, type RepoSettingsPlanDetail, type RepoResult, type SummaryData, } from "./github-summary.js";
|
|
4
|
+
export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Plan formatting (console output with chalk)
|
|
2
|
+
export { formatResourceId, formatResourceLine, formatPlanSummary, formatPlan, printPlan, } from "./plan-formatter.js";
|
|
3
|
+
// Plan summary (markdown output for GitHub)
|
|
4
|
+
export { formatPlanMarkdown, writePlanSummary, } from "./plan-summary.js";
|
|
5
|
+
// GitHub Actions summary
|
|
6
|
+
export { formatSummary, isGitHubActions, writeSummary, } from "./github-summary.js";
|
|
7
|
+
// Summary utilities
|
|
8
|
+
export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ProcessorResult } from "
|
|
2
|
-
import { RepoConfig } from "
|
|
1
|
+
import type { ProcessorResult } from "../sync/index.js";
|
|
2
|
+
import { RepoConfig } from "../config/index.js";
|
|
3
3
|
import { MergeOutcome, FileChanges, RepoResult } from "./github-summary.js";
|
|
4
|
-
import { DiffStats } from "
|
|
4
|
+
import { DiffStats } from "../sync/diff-utils.js";
|
|
5
5
|
/**
|
|
6
6
|
* Determine merge outcome from repo config and processor result
|
|
7
7
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { GitHubRepoSettings } from "
|
|
2
|
-
import type { CurrentRepoSettings } from "./
|
|
1
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
2
|
+
import type { CurrentRepoSettings } from "./types.js";
|
|
3
3
|
export type RepoSettingsAction = "add" | "change" | "unchanged";
|
|
4
4
|
export interface RepoSettingsChange {
|
|
5
5
|
property: keyof GitHubRepoSettings;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ICommandExecutor } from "
|
|
2
|
-
import { RepoInfo } from "
|
|
3
|
-
import type { GitHubRepoSettings } from "
|
|
4
|
-
import type { IRepoSettingsStrategy, RepoSettingsStrategyOptions, CurrentRepoSettings } from "./
|
|
1
|
+
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
+
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
4
|
+
import type { IRepoSettingsStrategy, RepoSettingsStrategyOptions, CurrentRepoSettings } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* GitHub Repository Settings Strategy.
|
|
7
7
|
* Manages repository settings via GitHub REST API using `gh api` CLI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defaultExecutor } from "
|
|
2
|
-
import { isGitHubRepo } from "
|
|
3
|
-
import { escapeShellArg } from "
|
|
1
|
+
import { defaultExecutor, } from "../../shared/command-executor.js";
|
|
2
|
+
import { isGitHubRepo, } from "../../shared/repo-detector.js";
|
|
3
|
+
import { escapeShellArg } from "../../shared/shell-utils.js";
|
|
4
4
|
/**
|
|
5
5
|
* Converts camelCase to snake_case.
|
|
6
6
|
*/
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { isRepoSettingsStrategy, type IRepoSettingsStrategy, type RepoSettingsStrategyOptions, type CurrentRepoSettings, } from "./types.js";
|
|
2
|
+
export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsProcessorOptions, type RepoSettingsProcessorResult, } from "./processor.js";
|
|
3
|
+
export { diffRepoSettings, hasChanges, type RepoSettingsAction, type RepoSettingsChange, } from "./diff.js";
|
|
4
|
+
export { formatRepoSettingsPlan, type RepoSettingsPlanResult, type RepoSettingsPlanEntry, } from "./formatter.js";
|
|
5
|
+
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export { isRepoSettingsStrategy, } from "./types.js";
|
|
3
|
+
// Repo settings processor
|
|
4
|
+
export { RepoSettingsProcessor, } from "./processor.js";
|
|
5
|
+
// Repo settings diff
|
|
6
|
+
export { diffRepoSettings, hasChanges, } from "./diff.js";
|
|
7
|
+
// Repo settings formatter
|
|
8
|
+
export { formatRepoSettingsPlan, } from "./formatter.js";
|
|
9
|
+
// Repo settings strategies
|
|
10
|
+
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { RepoConfig } from "
|
|
2
|
-
import type { RepoInfo } from "
|
|
3
|
-
import type { IRepoSettingsStrategy } from "./
|
|
4
|
-
import { RepoSettingsPlanResult } from "./
|
|
1
|
+
import type { RepoConfig } from "../../config/index.js";
|
|
2
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import type { IRepoSettingsStrategy } from "./types.js";
|
|
4
|
+
import { RepoSettingsPlanResult } from "./formatter.js";
|
|
5
5
|
export interface IRepoSettingsProcessor {
|
|
6
6
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
|
|
7
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { isGitHubRepo, getRepoDisplayName } from "
|
|
2
|
-
import { GitHubRepoSettingsStrategy } from "./
|
|
3
|
-
import { diffRepoSettings, hasChanges } from "./
|
|
4
|
-
import { formatRepoSettingsPlan
|
|
5
|
-
import { hasGitHubAppCredentials } from "
|
|
6
|
-
import { GitHubAppTokenManager } from "
|
|
1
|
+
import { isGitHubRepo, getRepoDisplayName, } from "../../shared/repo-detector.js";
|
|
2
|
+
import { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
3
|
+
import { diffRepoSettings, hasChanges } from "./diff.js";
|
|
4
|
+
import { formatRepoSettingsPlan } from "./formatter.js";
|
|
5
|
+
import { hasGitHubAppCredentials } from "../../vcs/index.js";
|
|
6
|
+
import { GitHubAppTokenManager } from "../../vcs/github-app-token-manager.js";
|
|
7
7
|
export class RepoSettingsProcessor {
|
|
8
8
|
strategy;
|
|
9
9
|
tokenManager;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RepoInfo } from "
|
|
2
|
-
import type { GitHubRepoSettings } from "
|
|
1
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
2
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
3
3
|
export interface RepoSettingsStrategyOptions {
|
|
4
4
|
token?: string;
|
|
5
5
|
host?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Resource } from "
|
|
2
|
-
import type { RulesetProcessorResult } from "./
|
|
3
|
-
import type { ProcessorResult } from "
|
|
4
|
-
import type { RepoConfig } from "
|
|
1
|
+
import type { Resource } from "../output/plan-formatter.js";
|
|
2
|
+
import type { RulesetProcessorResult } from "./rulesets/processor.js";
|
|
3
|
+
import type { ProcessorResult } from "../sync/index.js";
|
|
4
|
+
import type { RepoConfig } from "../config/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* Convert RulesetProcessorResult planOutput entries to Resource objects.
|
|
7
7
|
* Includes the detailed plan lines in the first resource's details for display.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type DiffAction = "add" | "change" | "remove";
|
|
2
|
+
export interface PropertyDiff {
|
|
3
|
+
path: string[];
|
|
4
|
+
action: DiffAction;
|
|
5
|
+
oldValue?: unknown;
|
|
6
|
+
newValue?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function isObject(val: unknown): val is Record<string, unknown>;
|
|
9
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
10
|
+
export declare function isArrayOfObjects(arr: unknown[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Recursively compute property-level diffs between two objects.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computePropertyDiffs(current: Record<string, unknown>, desired: Record<string, unknown>, parentPath?: string[]): PropertyDiff[];
|
|
15
|
+
/**
|
|
16
|
+
* Diff two arrays of objects by matching items on `type` field (or by index).
|
|
17
|
+
*/
|
|
18
|
+
export declare function diffObjectArrays(currentArr: unknown[], desiredArr: unknown[], parentPath: string[]): PropertyDiff[];
|