@aspruyt/xfg 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/lifecycle-report-builder.d.ts +2 -2
- package/dist/cli/lifecycle-report-builder.js +3 -11
- package/dist/cli/program.d.ts +2 -1
- package/dist/cli/program.js +43 -6
- package/dist/cli/repo-sync-runner.d.ts +24 -0
- package/dist/cli/repo-sync-runner.js +156 -0
- package/dist/cli/results-collector.d.ts +1 -1
- package/dist/cli/results-collector.js +2 -2
- package/dist/cli/secrets-command.d.ts +25 -0
- package/dist/cli/secrets-command.js +75 -0
- package/dist/cli/settings-factories.d.ts +8 -0
- package/dist/cli/settings-factories.js +32 -0
- package/dist/cli/settings-report-builder.d.ts +7 -2
- package/dist/cli/settings-report-builder.js +28 -20
- package/dist/cli/settings-runner.d.ts +2 -0
- package/dist/cli/settings-runner.js +94 -0
- package/dist/cli/sync-command.d.ts +1 -1
- package/dist/cli/sync-command.js +31 -372
- package/dist/cli/sync-report-builder.d.ts +1 -1
- package/dist/cli/sync-utils.d.ts +8 -0
- package/dist/cli/sync-utils.js +36 -0
- package/dist/cli/types.d.ts +8 -8
- package/dist/cli/unified-summary.d.ts +1 -3
- package/dist/cli/unified-summary.js +7 -5
- package/dist/cli.js +2 -1
- package/dist/{shared → config}/env.js +2 -2
- package/dist/config/extends-resolver.js +4 -3
- package/dist/config/file-reference-resolver.js +4 -2
- package/dist/config/formatter.js +1 -0
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/config/loader.js +30 -6
- package/dist/config/merge.d.ts +11 -1
- package/dist/config/merge.js +78 -6
- package/dist/config/normalizer.js +129 -49
- package/dist/config/types.d.ts +20 -0
- package/dist/config/validator.d.ts +5 -4
- package/dist/config/validator.js +187 -614
- package/dist/config/validators/file-validator.d.ts +2 -1
- package/dist/config/validators/file-validator.js +9 -1
- package/dist/config/validators/group-validator.d.ts +3 -0
- package/dist/config/validators/group-validator.js +167 -0
- package/dist/config/validators/repo-entry-validator.d.ts +2 -0
- package/dist/config/validators/repo-entry-validator.js +165 -0
- package/dist/config/validators/repo-settings-validator.js +18 -7
- package/dist/config/validators/ruleset-validator.js +2 -5
- package/dist/config/validators/shared.d.ts +11 -0
- package/dist/config/validators/shared.js +242 -0
- package/dist/lifecycle/ado-migration-source.js +2 -4
- package/dist/lifecycle/github-lifecycle-provider.d.ts +7 -11
- package/dist/lifecycle/github-lifecycle-provider.js +125 -136
- package/dist/lifecycle/{lifecycle-helpers.d.ts → helpers.d.ts} +5 -1
- package/dist/lifecycle/{lifecycle-helpers.js → helpers.js} +9 -8
- package/dist/lifecycle/index.d.ts +2 -2
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +2 -2
- package/dist/output/github-summary.js +2 -3
- package/dist/output/index.d.ts +4 -0
- package/dist/output/index.js +4 -0
- package/dist/output/lifecycle-report.d.ts +1 -1
- package/dist/output/lifecycle-report.js +5 -0
- package/dist/output/settings-report.d.ts +11 -0
- package/dist/output/settings-report.js +24 -0
- package/dist/output/sync-report.d.ts +25 -3
- package/dist/output/sync-report.js +11 -11
- package/dist/secrets/encryption.d.ts +9 -0
- package/dist/secrets/encryption.js +29 -0
- package/dist/secrets/github-secrets-strategy.d.ts +17 -0
- package/dist/secrets/github-secrets-strategy.js +38 -0
- package/dist/secrets/index.d.ts +5 -0
- package/dist/secrets/index.js +3 -0
- package/dist/secrets/processor.d.ts +31 -0
- package/dist/secrets/processor.js +115 -0
- package/dist/secrets/types.d.ts +21 -0
- package/dist/settings/base-processor.d.ts +18 -7
- package/dist/settings/base-processor.js +26 -5
- package/dist/settings/code-scanning/diff.js +2 -2
- package/dist/settings/code-scanning/formatter.d.ts +2 -6
- package/dist/settings/code-scanning/formatter.js +2 -25
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +3 -7
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +2 -2
- package/dist/settings/code-scanning/processor.js +6 -4
- package/dist/settings/code-scanning/types.d.ts +10 -8
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -0
- package/dist/settings/labels/github-labels-strategy.d.ts +3 -11
- package/dist/settings/labels/types.d.ts +12 -10
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +1 -1
- package/dist/settings/repo-settings/formatter.d.ts +2 -6
- package/dist/settings/repo-settings/formatter.js +4 -23
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -2
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +8 -7
- package/dist/settings/repo-settings/processor.js +11 -11
- package/dist/settings/repo-settings/types.d.ts +2 -2
- package/dist/settings/rulesets/diff-algorithm.js +4 -2
- package/dist/settings/rulesets/diff.js +2 -51
- package/dist/settings/rulesets/formatter.js +4 -0
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +3 -3
- package/dist/settings/rulesets/github-ruleset-strategy.js +4 -6
- package/dist/settings/rulesets/index.d.ts +1 -1
- package/dist/settings/rulesets/index.js +0 -2
- package/dist/settings/rulesets/processor.js +1 -1
- package/dist/settings/rulesets/types.d.ts +6 -2
- package/dist/settings/variables/diff.d.ts +10 -0
- package/dist/settings/variables/diff.js +39 -0
- package/dist/settings/variables/formatter.d.ts +16 -0
- package/dist/settings/variables/formatter.js +70 -0
- package/dist/settings/variables/github-variables-strategy.d.ts +17 -0
- package/dist/settings/variables/github-variables-strategy.js +40 -0
- package/dist/settings/variables/index.d.ts +4 -0
- package/dist/settings/variables/index.js +2 -0
- package/dist/settings/variables/processor.d.ts +19 -0
- package/dist/settings/variables/processor.js +60 -0
- package/dist/settings/variables/types.d.ts +18 -0
- package/dist/settings/variables/types.js +1 -0
- package/dist/shared/command-executor.d.ts +4 -4
- package/dist/shared/command-executor.js +9 -7
- package/dist/shared/diff-format.d.ts +1 -0
- package/dist/shared/diff-format.js +10 -0
- package/dist/shared/env-resolver.d.ts +16 -0
- package/dist/shared/env-resolver.js +33 -0
- package/dist/shared/errors.d.ts +7 -4
- package/dist/shared/errors.js +8 -8
- package/dist/shared/gh-api-utils.d.ts +3 -34
- package/dist/shared/gh-api-utils.js +23 -53
- package/dist/shared/gh-token-utils.d.ts +26 -0
- package/dist/shared/gh-token-utils.js +32 -0
- package/dist/shared/json-utils.js +1 -1
- package/dist/shared/regex-utils.d.ts +1 -0
- package/dist/shared/regex-utils.js +3 -0
- package/dist/shared/retry-utils.d.ts +1 -0
- package/dist/shared/retry-utils.js +13 -7
- package/dist/sync/auth-options-builder.js +1 -1
- package/dist/sync/branch-manager.js +5 -3
- package/dist/sync/commit-push-manager.js +2 -3
- package/dist/sync/diff-utils.d.ts +0 -1
- package/dist/sync/diff-utils.js +5 -10
- package/dist/sync/file-sync-orchestrator.js +0 -2
- package/dist/sync/file-writer.d.ts +3 -0
- package/dist/sync/file-writer.js +84 -81
- package/dist/sync/index.d.ts +0 -1
- package/dist/sync/index.js +0 -1
- package/dist/sync/manifest.js +1 -1
- package/dist/sync/pr-merge-handler.js +6 -6
- package/dist/sync/sync-workflow.js +1 -1
- package/dist/sync/types.d.ts +2 -2
- package/dist/vcs/ado-pr-strategy.d.ts +3 -5
- package/dist/vcs/ado-pr-strategy.js +131 -33
- package/dist/vcs/authenticated-git-ops.js +45 -23
- package/dist/vcs/git-commit-strategy.js +10 -6
- package/dist/vcs/git-ops.js +30 -24
- package/dist/vcs/github-pr-strategy.d.ts +3 -2
- package/dist/vcs/github-pr-strategy.js +80 -30
- package/dist/vcs/gitlab-pr-strategy.d.ts +2 -5
- package/dist/vcs/gitlab-pr-strategy.js +88 -87
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -5
- package/dist/vcs/graphql-commit-strategy.js +21 -37
- package/dist/vcs/pr-creator.js +9 -2
- package/dist/vcs/pr-strategy.d.ts +2 -3
- package/dist/vcs/pr-strategy.js +0 -1
- package/dist/vcs/types.d.ts +9 -5
- package/package.json +7 -5
- package/dist/config/validators/index.d.ts +0 -3
- package/dist/config/validators/index.js +0 -6
- package/dist/output/types.d.ts +0 -20
- package/dist/shared/shell-utils.d.ts +0 -6
- package/dist/shared/shell-utils.js +0 -17
- /package/dist/{shared → config}/env.d.ts +0 -0
- /package/dist/lifecycle/{lifecycle-formatter.d.ts → formatter.d.ts} +0 -0
- /package/dist/lifecycle/{lifecycle-formatter.js → formatter.js} +0 -0
- /package/dist/{output → secrets}/types.js +0 -0
- /package/dist/{vcs → shared}/sanitize-utils.d.ts +0 -0
- /package/dist/{vcs → shared}/sanitize-utils.js +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { LifecycleReport, LifecycleAction } from "../output/
|
|
2
|
-
export declare function buildLifecycleReport(
|
|
1
|
+
import type { LifecycleReport, LifecycleAction } from "../output/index.js";
|
|
2
|
+
export declare function buildLifecycleReport(actions: LifecycleAction[]): LifecycleReport;
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
export function buildLifecycleReport(
|
|
2
|
-
const actions = [];
|
|
1
|
+
export function buildLifecycleReport(actions) {
|
|
3
2
|
const totals = { created: 0, forked: 0, migrated: 0, existed: 0 };
|
|
4
|
-
for (const
|
|
5
|
-
|
|
6
|
-
repoName: result.repoName,
|
|
7
|
-
action: result.action,
|
|
8
|
-
upstream: result.upstream,
|
|
9
|
-
source: result.source,
|
|
10
|
-
settings: result.settings,
|
|
11
|
-
});
|
|
12
|
-
totals[result.action]++;
|
|
3
|
+
for (const action of actions) {
|
|
4
|
+
totals[action.action]++;
|
|
13
5
|
}
|
|
14
6
|
return { actions, totals };
|
|
15
7
|
}
|
package/dist/cli/program.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { program } from "commander";
|
|
2
2
|
import { MergeMode, MergeStrategy } from "../config/index.js";
|
|
3
|
+
declare function getVersion(): string;
|
|
3
4
|
export declare function parseMergeMode(value: string): MergeMode;
|
|
4
5
|
export declare function parseMergeStrategy(value: string): MergeStrategy;
|
|
5
|
-
export { program };
|
|
6
|
+
export { program, getVersion };
|
package/dist/cli/program.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { program, Command } from "commander";
|
|
1
|
+
import { program, Command, InvalidArgumentError } from "commander";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { ValidationError } from "../shared/errors.js";
|
|
6
6
|
import { runSync } from "./sync-command.js";
|
|
7
|
+
import { runSecretsSync } from "./secrets-command.js";
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = dirname(__filename);
|
|
9
10
|
function getVersion() {
|
|
@@ -26,7 +27,12 @@ function addSharedOptions(cmd) {
|
|
|
26
27
|
.requiredOption("-c, --config <path>", "Path to YAML config file")
|
|
27
28
|
.option("-d, --dry-run", "Show what would be done without making changes")
|
|
28
29
|
.option("-w, --work-dir <path>", "Temporary directory for cloning", "./tmp")
|
|
29
|
-
.option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (v) =>
|
|
30
|
+
.option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (v) => {
|
|
31
|
+
const n = parseInt(v, 10);
|
|
32
|
+
if (isNaN(n) || n < 0)
|
|
33
|
+
throw new InvalidArgumentError("Must be a non-negative number.");
|
|
34
|
+
return n;
|
|
35
|
+
}, 3)
|
|
30
36
|
.option("--no-delete", "Skip deletion of orphaned resources even if deleteOrphaned is configured");
|
|
31
37
|
}
|
|
32
38
|
// =============================================================================
|
|
@@ -51,8 +57,7 @@ export function parseMergeStrategy(value) {
|
|
|
51
57
|
// =============================================================================
|
|
52
58
|
program
|
|
53
59
|
.name("xfg")
|
|
54
|
-
.description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab")
|
|
55
|
-
.version(getVersion());
|
|
60
|
+
.description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab");
|
|
56
61
|
// Sync command (file synchronization)
|
|
57
62
|
const syncCommand = new Command("sync")
|
|
58
63
|
.description("Sync configuration files across repositories")
|
|
@@ -62,7 +67,11 @@ const syncCommand = new Command("sync")
|
|
|
62
67
|
.option("--delete-branch", "Delete source branch after merge")
|
|
63
68
|
.action(async (opts) => {
|
|
64
69
|
try {
|
|
65
|
-
|
|
70
|
+
const options = {
|
|
71
|
+
...opts,
|
|
72
|
+
noDelete: opts.delete === false,
|
|
73
|
+
};
|
|
74
|
+
await runSync(options);
|
|
66
75
|
}
|
|
67
76
|
catch (error) {
|
|
68
77
|
console.error("Fatal error:", error);
|
|
@@ -71,4 +80,32 @@ const syncCommand = new Command("sync")
|
|
|
71
80
|
});
|
|
72
81
|
addSharedOptions(syncCommand);
|
|
73
82
|
program.addCommand(syncCommand);
|
|
74
|
-
|
|
83
|
+
const secretsCommand = new Command("secrets").description("Manage repository secrets");
|
|
84
|
+
const secretsSyncCommand = new Command("sync")
|
|
85
|
+
.description("Sync secrets to target repositories")
|
|
86
|
+
.requiredOption("-c, --config <path>", "Path to xfg config file")
|
|
87
|
+
.option("-d, --dry-run", "Show what would be done without making changes")
|
|
88
|
+
.option("--no-delete", "Skip deletion of orphaned secrets")
|
|
89
|
+
.option("-w, --work-dir <path>", "Temporary directory for cloning", "./tmp")
|
|
90
|
+
.option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (value) => {
|
|
91
|
+
const n = parseInt(value, 10);
|
|
92
|
+
if (isNaN(n) || n < 0)
|
|
93
|
+
throw new InvalidArgumentError("Must be a non-negative number.");
|
|
94
|
+
return n;
|
|
95
|
+
}, 3)
|
|
96
|
+
.action(async (opts) => {
|
|
97
|
+
try {
|
|
98
|
+
const options = {
|
|
99
|
+
...opts,
|
|
100
|
+
noDelete: opts.delete === false,
|
|
101
|
+
};
|
|
102
|
+
await runSecretsSync(options);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Fatal error:", error);
|
|
106
|
+
return process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
secretsCommand.addCommand(secretsSyncCommand);
|
|
110
|
+
program.addCommand(secretsCommand);
|
|
111
|
+
export { program, getVersion };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Config, RepoConfig } from "../config/index.js";
|
|
2
|
+
import type { createTokenManager } from "../vcs/index.js";
|
|
3
|
+
import type { IRepositoryProcessor } from "../sync/index.js";
|
|
4
|
+
import type { ProcessExecutor } from "../shared/command-executor.js";
|
|
5
|
+
import type { Logger } from "../shared/logger.js";
|
|
6
|
+
import { type IRepoLifecycleManager } from "../lifecycle/index.js";
|
|
7
|
+
import type { SyncOptions, SyncResultEntry, SettingsProcessorFactories } from "./types.js";
|
|
8
|
+
import type { ResultsCollector } from "./results-collector.js";
|
|
9
|
+
import type { LifecycleAction } from "../output/index.js";
|
|
10
|
+
export interface RepoIterationContext {
|
|
11
|
+
config: Config;
|
|
12
|
+
options: SyncOptions;
|
|
13
|
+
branchName: string;
|
|
14
|
+
processor: IRepositoryProcessor;
|
|
15
|
+
lifecycleManager: IRepoLifecycleManager;
|
|
16
|
+
tokenManager: ReturnType<typeof createTokenManager>;
|
|
17
|
+
reportResults: SyncResultEntry[];
|
|
18
|
+
lifecycleReportInputs: LifecycleAction[];
|
|
19
|
+
settingsCollector: ResultsCollector;
|
|
20
|
+
factories: SettingsProcessorFactories;
|
|
21
|
+
logger: Logger;
|
|
22
|
+
executor: ProcessExecutor;
|
|
23
|
+
}
|
|
24
|
+
export declare function runSingleRepo(repoConfig: RepoConfig, index: number, ctx: RepoIterationContext): Promise<void>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { resolve, join } from "node:path";
|
|
2
|
+
import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../repo/index.js";
|
|
3
|
+
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
4
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
5
|
+
import { resolveGitHubToken } from "../shared/gh-token-utils.js";
|
|
6
|
+
import { runLifecycleCheck, } from "../lifecycle/index.js";
|
|
7
|
+
import { applyRepoSettings } from "./settings-runner.js";
|
|
8
|
+
import { determineMergeOutcome } from "./sync-utils.js";
|
|
9
|
+
function pushFailure(results, repoName, error) {
|
|
10
|
+
results.push({
|
|
11
|
+
repoName,
|
|
12
|
+
success: false,
|
|
13
|
+
fileChanges: [],
|
|
14
|
+
error: toErrorMessage(error),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function runLifecyclePhase(repo, ctx) {
|
|
18
|
+
const repoNumber = repo.index + 1;
|
|
19
|
+
try {
|
|
20
|
+
const { outputLines, lifecycleResult, reportSettings } = await runLifecycleCheck(repo.repoConfig, repo.repoInfo, {
|
|
21
|
+
dryRun: ctx.options.dryRun ?? false,
|
|
22
|
+
resolvedWorkDir: repo.workDir,
|
|
23
|
+
githubHosts: ctx.config.githubHosts,
|
|
24
|
+
token: repo.token,
|
|
25
|
+
repoIndex: repo.index,
|
|
26
|
+
lifecycleManager: ctx.lifecycleManager,
|
|
27
|
+
repoSettings: ctx.config.settings?.repo,
|
|
28
|
+
});
|
|
29
|
+
for (const line of outputLines) {
|
|
30
|
+
ctx.logger.info(line);
|
|
31
|
+
}
|
|
32
|
+
ctx.lifecycleReportInputs.push({
|
|
33
|
+
repoName: repo.repoName,
|
|
34
|
+
action: lifecycleResult.action,
|
|
35
|
+
upstream: repo.repoConfig.upstream,
|
|
36
|
+
source: repo.repoConfig.source,
|
|
37
|
+
settings: reportSettings,
|
|
38
|
+
});
|
|
39
|
+
if (ctx.options.dryRun && lifecycleResult.action !== "existed") {
|
|
40
|
+
ctx.reportResults.push({
|
|
41
|
+
repoName: repo.repoName,
|
|
42
|
+
success: true,
|
|
43
|
+
fileChanges: [],
|
|
44
|
+
});
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
ctx.logger.error(repoNumber, repo.repoName, `Lifecycle error: ${toErrorMessage(error)}`);
|
|
51
|
+
pushFailure(ctx.reportResults, repo.repoName, error);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runFileSyncPhase(repo, ctx) {
|
|
56
|
+
const repoNumber = repo.index + 1;
|
|
57
|
+
try {
|
|
58
|
+
ctx.logger.progress(repoNumber, repo.repoName, "Processing...");
|
|
59
|
+
const result = await ctx.processor.process(repo.repoConfig, repo.repoInfo, {
|
|
60
|
+
branchName: ctx.branchName,
|
|
61
|
+
workDir: repo.workDir,
|
|
62
|
+
configId: ctx.config.id,
|
|
63
|
+
dryRun: ctx.options.dryRun,
|
|
64
|
+
retries: ctx.options.retries,
|
|
65
|
+
executor: ctx.executor,
|
|
66
|
+
prTemplate: ctx.config.prTemplate,
|
|
67
|
+
noDelete: ctx.options.noDelete,
|
|
68
|
+
token: repo.token,
|
|
69
|
+
hasAppCredentials: isGitHubRepo(repo.repoInfo) && ctx.tokenManager !== null,
|
|
70
|
+
});
|
|
71
|
+
const mergeOutcome = determineMergeOutcome(result);
|
|
72
|
+
ctx.reportResults.push({
|
|
73
|
+
repoName: repo.repoName,
|
|
74
|
+
success: result.success,
|
|
75
|
+
fileChanges: result.fileChanges ?? [],
|
|
76
|
+
prUrl: result.prUrl,
|
|
77
|
+
mergeOutcome,
|
|
78
|
+
error: result.success ? undefined : result.message,
|
|
79
|
+
});
|
|
80
|
+
if (result.skipped) {
|
|
81
|
+
ctx.logger.skip(repoNumber, repo.repoName, result.message);
|
|
82
|
+
}
|
|
83
|
+
else if (result.success) {
|
|
84
|
+
ctx.logger.success(repoNumber, repo.repoName, result.message);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
ctx.logger.error(repoNumber, repo.repoName, result.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
ctx.logger.error(repoNumber, repo.repoName, toErrorMessage(error));
|
|
92
|
+
pushFailure(ctx.reportResults, repo.repoName, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export async function runSingleRepo(repoConfig, index, ctx) {
|
|
96
|
+
const { config, options, logger } = ctx;
|
|
97
|
+
const repoNumber = index + 1;
|
|
98
|
+
const effectivePrOptions = options.merge || options.mergeStrategy || options.deleteBranch
|
|
99
|
+
? {
|
|
100
|
+
...repoConfig.prOptions,
|
|
101
|
+
merge: options.merge ?? repoConfig.prOptions?.merge,
|
|
102
|
+
mergeStrategy: options.mergeStrategy ?? repoConfig.prOptions?.mergeStrategy,
|
|
103
|
+
deleteBranch: options.deleteBranch ?? repoConfig.prOptions?.deleteBranch,
|
|
104
|
+
}
|
|
105
|
+
: repoConfig.prOptions;
|
|
106
|
+
const effectiveRepoConfig = { ...repoConfig, prOptions: effectivePrOptions };
|
|
107
|
+
const mergeMode = effectivePrOptions?.merge ?? "auto";
|
|
108
|
+
if (mergeMode === "direct" && effectivePrOptions?.mergeStrategy) {
|
|
109
|
+
logger.warn(`mergeStrategy '${effectivePrOptions.mergeStrategy}' is ignored in direct mode for ${repoConfig.git}`);
|
|
110
|
+
}
|
|
111
|
+
let repoInfo;
|
|
112
|
+
try {
|
|
113
|
+
repoInfo = parseGitUrl(repoConfig.git, {
|
|
114
|
+
githubHosts: config.githubHosts,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.error(repoNumber, repoConfig.git, toErrorMessage(error));
|
|
119
|
+
pushFailure(ctx.reportResults, repoConfig.git, error);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const repoName = getRepoDisplayName(repoInfo);
|
|
123
|
+
const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(index)));
|
|
124
|
+
const repoToken = isGitHubRepo(repoInfo)
|
|
125
|
+
? (await resolveGitHubToken({
|
|
126
|
+
repoInfo: repoInfo,
|
|
127
|
+
tokenManager: ctx.tokenManager,
|
|
128
|
+
context: repoName,
|
|
129
|
+
log: logger,
|
|
130
|
+
envToken: process.env.GH_TOKEN,
|
|
131
|
+
})).token
|
|
132
|
+
: undefined;
|
|
133
|
+
const repo = {
|
|
134
|
+
repoConfig: effectiveRepoConfig,
|
|
135
|
+
repoInfo,
|
|
136
|
+
repoName,
|
|
137
|
+
index,
|
|
138
|
+
workDir,
|
|
139
|
+
token: repoToken,
|
|
140
|
+
};
|
|
141
|
+
const skipFileSync = await runLifecyclePhase(repo, ctx);
|
|
142
|
+
if (skipFileSync)
|
|
143
|
+
return;
|
|
144
|
+
await runFileSyncPhase(repo, ctx);
|
|
145
|
+
await applyRepoSettings({
|
|
146
|
+
repoConfig: effectiveRepoConfig,
|
|
147
|
+
repoInfo,
|
|
148
|
+
repoName,
|
|
149
|
+
repoNumber,
|
|
150
|
+
options,
|
|
151
|
+
token: repoToken,
|
|
152
|
+
settingsCollector: ctx.settingsCollector,
|
|
153
|
+
factories: ctx.factories,
|
|
154
|
+
logger,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { ProcessorResults } from "./settings-report-builder.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class ResultsCollector {
|
|
7
7
|
private readonly results;
|
|
8
|
-
|
|
8
|
+
findOrCreate(repoName: string): ProcessorResults;
|
|
9
9
|
appendError(repoName: string, error: unknown): void;
|
|
10
10
|
getAll(): ProcessorResults[];
|
|
11
11
|
}
|
|
@@ -5,7 +5,7 @@ import { toErrorMessage } from "../shared/type-guards.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export class ResultsCollector {
|
|
7
7
|
results = [];
|
|
8
|
-
|
|
8
|
+
findOrCreate(repoName) {
|
|
9
9
|
let result = this.results.find((r) => r.repoName === repoName);
|
|
10
10
|
if (!result) {
|
|
11
11
|
result = { repoName };
|
|
@@ -14,7 +14,7 @@ export class ResultsCollector {
|
|
|
14
14
|
return result;
|
|
15
15
|
}
|
|
16
16
|
appendError(repoName, error) {
|
|
17
|
-
const existing = this.
|
|
17
|
+
const existing = this.findOrCreate(repoName);
|
|
18
18
|
const errorMsg = toErrorMessage(error);
|
|
19
19
|
if (existing.error) {
|
|
20
20
|
existing.error += `; ${errorMsg}`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SecretsProcessorResult } from "../secrets/processor.js";
|
|
2
|
+
import type { SecretConfig, Config } from "../config/index.js";
|
|
3
|
+
import type { RepoInfo } from "../repo/index.js";
|
|
4
|
+
type SecretsConfig = Record<string, SecretConfig | boolean> & {
|
|
5
|
+
deleteOrphaned?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export interface ISecretsProcessorAdapter {
|
|
8
|
+
process(secretsConfig: SecretsConfig, repoInfo: RepoInfo, options: {
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
token?: string;
|
|
11
|
+
noDelete?: boolean;
|
|
12
|
+
}): Promise<SecretsProcessorResult>;
|
|
13
|
+
}
|
|
14
|
+
export interface SecretsSyncDependencies {
|
|
15
|
+
processorFactory?: (config: Config, cwd: string, retries: number) => ISecretsProcessorAdapter;
|
|
16
|
+
}
|
|
17
|
+
export interface SecretsSyncOptions {
|
|
18
|
+
config: string;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
noDelete?: boolean;
|
|
21
|
+
workDir?: string;
|
|
22
|
+
retries?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function runSecretsSync(options: SecretsSyncOptions, deps?: SecretsSyncDependencies): Promise<void>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { loadRawConfig } from "../config/index.js";
|
|
2
|
+
import { normalizeConfig } from "../config/normalizer.js";
|
|
3
|
+
import { validateRawConfig, validateSecretsConfig, validateVariableSecretOverlaps, } from "../config/validator.js";
|
|
4
|
+
import { SecretsProcessor, GitHubSecretsStrategy, SodiumEncryptor, } from "../secrets/index.js";
|
|
5
|
+
import { EnvResolver } from "../shared/env-resolver.js";
|
|
6
|
+
import { ProcessExecutor } from "../shared/command-executor.js";
|
|
7
|
+
import { parseGitUrl } from "../repo/index.js";
|
|
8
|
+
import { Logger } from "../shared/logger.js";
|
|
9
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
10
|
+
function createDefaultProcessor(_config, cwd, retries) {
|
|
11
|
+
const executor = new ProcessExecutor(process.env);
|
|
12
|
+
const encryptor = new SodiumEncryptor();
|
|
13
|
+
const envResolver = new EnvResolver(process.env);
|
|
14
|
+
const strategy = new GitHubSecretsStrategy(executor, {
|
|
15
|
+
cwd,
|
|
16
|
+
retries,
|
|
17
|
+
});
|
|
18
|
+
return new SecretsProcessor(strategy, encryptor, envResolver);
|
|
19
|
+
}
|
|
20
|
+
export async function runSecretsSync(options, deps = {}) {
|
|
21
|
+
const logger = new Logger(!!(process.env.DEBUG || process.env.XFG_DEBUG));
|
|
22
|
+
const { config: configPath, dryRun, workDir, retries, noDelete } = options;
|
|
23
|
+
const cwd = workDir ?? "./tmp";
|
|
24
|
+
const rawConfig = loadRawConfig(configPath);
|
|
25
|
+
validateRawConfig(rawConfig);
|
|
26
|
+
validateSecretsConfig(rawConfig);
|
|
27
|
+
validateVariableSecretOverlaps(rawConfig);
|
|
28
|
+
const config = normalizeConfig(rawConfig, process.env);
|
|
29
|
+
if (!config.secrets) {
|
|
30
|
+
logger.info("No secrets configured. Nothing to do.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const { deleteOrphaned, ...secretEntries } = config.secrets;
|
|
34
|
+
const secretNames = Object.keys(secretEntries).filter((k) => typeof secretEntries[k] !== "boolean");
|
|
35
|
+
if (secretNames.length === 0 && !deleteOrphaned) {
|
|
36
|
+
logger.info("No secrets configured. Nothing to do.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const processorFactory = deps.processorFactory ?? createDefaultProcessor;
|
|
40
|
+
const processor = processorFactory(config, cwd, retries ?? 3);
|
|
41
|
+
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
42
|
+
let hasErrors = false;
|
|
43
|
+
logger.setTotal(config.repos.length);
|
|
44
|
+
for (let i = 0; i < config.repos.length; i++) {
|
|
45
|
+
const repoConfig = config.repos[i];
|
|
46
|
+
const repoName = repoConfig.git;
|
|
47
|
+
try {
|
|
48
|
+
const repoInfo = parseGitUrl(repoConfig.git, {
|
|
49
|
+
githubHosts: config.githubHosts,
|
|
50
|
+
});
|
|
51
|
+
const result = await processor.process(config.secrets, repoInfo, {
|
|
52
|
+
dryRun,
|
|
53
|
+
token,
|
|
54
|
+
noDelete,
|
|
55
|
+
});
|
|
56
|
+
if (result.skipped) {
|
|
57
|
+
logger.skip(i + 1, repoName, result.message);
|
|
58
|
+
}
|
|
59
|
+
else if (result.success) {
|
|
60
|
+
logger.success(i + 1, repoName, `Secrets: ${result.message}`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
logger.error(i + 1, repoName, `Secrets: ${result.message}`);
|
|
64
|
+
hasErrors = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
logger.error(i + 1, repoName, `Secrets: ${toErrorMessage(error)}`);
|
|
69
|
+
hasErrors = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (hasErrors) {
|
|
73
|
+
throw new Error("One or more repositories failed secrets sync.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProcessExecutor } from "../shared/command-executor.js";
|
|
2
|
+
import type { RulesetProcessorFactory, RepoSettingsProcessorFactory, LabelsProcessorFactory, CodeScanningProcessorFactory, VariablesProcessorFactory, SettingsProcessorFactories } from "./types.js";
|
|
3
|
+
export declare function createDefaultRulesetProcessorFactory(executor: ProcessExecutor): RulesetProcessorFactory;
|
|
4
|
+
export declare function createDefaultRepoSettingsProcessorFactory(executor: ProcessExecutor): RepoSettingsProcessorFactory;
|
|
5
|
+
export declare function createDefaultLabelsProcessorFactory(executor: ProcessExecutor): LabelsProcessorFactory;
|
|
6
|
+
export declare function createDefaultCodeScanningProcessorFactory(executor: ProcessExecutor): CodeScanningProcessorFactory;
|
|
7
|
+
export declare function createDefaultVariablesProcessorFactory(executor: ProcessExecutor): VariablesProcessorFactory;
|
|
8
|
+
export declare function createDefaultFactories(executor: ProcessExecutor, overrides?: Partial<SettingsProcessorFactories>): SettingsProcessorFactories;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, CodeScanningProcessor, VariablesProcessor, GitHubRulesetStrategy, GitHubRepoSettingsStrategy, GitHubLabelsStrategy, GitHubCodeScanningStrategy, GitHubVariablesStrategy, } from "../settings/index.js";
|
|
2
|
+
import { GitHubRepoMetadataProvider } from "../repo/index.js";
|
|
3
|
+
export function createDefaultRulesetProcessorFactory(executor) {
|
|
4
|
+
const cwd = process.cwd();
|
|
5
|
+
return () => new RulesetProcessor(new GitHubRulesetStrategy(executor, { cwd }));
|
|
6
|
+
}
|
|
7
|
+
export function createDefaultRepoSettingsProcessorFactory(executor) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
return () => new RepoSettingsProcessor(new GitHubRepoSettingsStrategy(executor, { cwd }), new GitHubRepoMetadataProvider(executor, { cwd }));
|
|
10
|
+
}
|
|
11
|
+
export function createDefaultLabelsProcessorFactory(executor) {
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
return () => new LabelsProcessor(new GitHubLabelsStrategy(executor, { cwd }));
|
|
14
|
+
}
|
|
15
|
+
export function createDefaultCodeScanningProcessorFactory(executor) {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
return () => new CodeScanningProcessor(new GitHubCodeScanningStrategy(executor, { cwd }), new GitHubRepoMetadataProvider(executor, { cwd }));
|
|
18
|
+
}
|
|
19
|
+
export function createDefaultVariablesProcessorFactory(executor) {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
return () => new VariablesProcessor(new GitHubVariablesStrategy(executor, { cwd }));
|
|
22
|
+
}
|
|
23
|
+
export function createDefaultFactories(executor, overrides) {
|
|
24
|
+
return {
|
|
25
|
+
rulesets: overrides?.rulesets ?? createDefaultRulesetProcessorFactory(executor),
|
|
26
|
+
labels: overrides?.labels ?? createDefaultLabelsProcessorFactory(executor),
|
|
27
|
+
repo: overrides?.repo ?? createDefaultRepoSettingsProcessorFactory(executor),
|
|
28
|
+
codeScanning: overrides?.codeScanning ??
|
|
29
|
+
createDefaultCodeScanningProcessorFactory(executor),
|
|
30
|
+
variables: overrides?.variables ?? createDefaultVariablesProcessorFactory(executor),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SettingsReport } from "../output/
|
|
2
|
-
import { type RepoSettingsPlanEntry, type RulesetPlanEntry, type LabelsPlanEntry, type CodeScanningPlanEntry } from "../settings/index.js";
|
|
1
|
+
import type { SettingsReport } from "../output/index.js";
|
|
2
|
+
import { type RepoSettingsPlanEntry, type RulesetPlanEntry, type LabelsPlanEntry, type CodeScanningPlanEntry, type VariablesPlanEntry } from "../settings/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Result from processing a repository's settings and rulesets.
|
|
5
5
|
* Used to collect results during settings command execution.
|
|
@@ -26,6 +26,11 @@ export interface ProcessorResults {
|
|
|
26
26
|
entries?: CodeScanningPlanEntry[];
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
|
+
variablesResult?: {
|
|
30
|
+
planOutput?: {
|
|
31
|
+
entries?: VariablesPlanEntry[];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
29
34
|
error?: string;
|
|
30
35
|
}
|
|
31
36
|
export declare function buildSettingsReport(results: ProcessorResults[]): SettingsReport;
|
|
@@ -5,6 +5,7 @@ export function buildSettingsReport(results) {
|
|
|
5
5
|
settings: { create: 0, update: 0 },
|
|
6
6
|
rulesets: { create: 0, update: 0, delete: 0 },
|
|
7
7
|
labels: { create: 0, update: 0, delete: 0 },
|
|
8
|
+
variables: { create: 0, update: 0, delete: 0 },
|
|
8
9
|
};
|
|
9
10
|
for (const result of results) {
|
|
10
11
|
const repoChanges = {
|
|
@@ -12,14 +13,12 @@ export function buildSettingsReport(results) {
|
|
|
12
13
|
settings: [],
|
|
13
14
|
rulesets: [],
|
|
14
15
|
labels: [],
|
|
16
|
+
variables: [],
|
|
15
17
|
};
|
|
16
|
-
// Convert settings processor output
|
|
17
18
|
if (result.settingsResult?.planOutput?.entries) {
|
|
18
19
|
for (const entry of result.settingsResult.planOutput.entries) {
|
|
19
|
-
|
|
20
|
-
if (entry.oldValue === undefined && entry.newValue === undefined) {
|
|
20
|
+
if (!isActiveAction(entry))
|
|
21
21
|
continue;
|
|
22
|
-
}
|
|
23
22
|
repoChanges.settings.push({
|
|
24
23
|
name: entry.property,
|
|
25
24
|
action: entry.action,
|
|
@@ -27,11 +26,24 @@ export function buildSettingsReport(results) {
|
|
|
27
26
|
newValue: entry.newValue,
|
|
28
27
|
});
|
|
29
28
|
}
|
|
29
|
+
}
|
|
30
|
+
if (result.codeScanningResult?.planOutput?.entries) {
|
|
31
|
+
for (const entry of result.codeScanningResult.planOutput.entries) {
|
|
32
|
+
if (!isActiveAction(entry))
|
|
33
|
+
continue;
|
|
34
|
+
repoChanges.settings.push({
|
|
35
|
+
name: `codeScanning.${entry.property}`,
|
|
36
|
+
action: entry.action,
|
|
37
|
+
oldValue: entry.oldValue,
|
|
38
|
+
newValue: entry.newValue ?? null,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (repoChanges.settings.length > 0) {
|
|
30
43
|
const counts = countActions(repoChanges.settings);
|
|
31
44
|
totals.settings.create += counts.create;
|
|
32
45
|
totals.settings.update += counts.update;
|
|
33
46
|
}
|
|
34
|
-
// Convert ruleset processor output
|
|
35
47
|
if (result.rulesetResult?.planOutput?.entries) {
|
|
36
48
|
for (const entry of result.rulesetResult.planOutput.entries) {
|
|
37
49
|
if (!isActiveAction(entry))
|
|
@@ -48,7 +60,6 @@ export function buildSettingsReport(results) {
|
|
|
48
60
|
totals.rulesets.update += counts.update;
|
|
49
61
|
totals.rulesets.delete += counts.delete;
|
|
50
62
|
}
|
|
51
|
-
// Convert labels processor output
|
|
52
63
|
if (result.labelsResult?.planOutput?.entries) {
|
|
53
64
|
for (const entry of result.labelsResult.planOutput.entries) {
|
|
54
65
|
if (!isActiveAction(entry))
|
|
@@ -66,24 +77,21 @@ export function buildSettingsReport(results) {
|
|
|
66
77
|
totals.labels.update += counts.update;
|
|
67
78
|
totals.labels.delete += counts.delete;
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
name: `codeScanning.${entry.property}`,
|
|
80
|
+
if (result.variablesResult?.planOutput?.entries) {
|
|
81
|
+
for (const entry of result.variablesResult.planOutput.entries) {
|
|
82
|
+
if (!isActiveAction(entry))
|
|
83
|
+
continue;
|
|
84
|
+
repoChanges.variables.push({
|
|
85
|
+
name: entry.name,
|
|
76
86
|
action: entry.action,
|
|
77
87
|
oldValue: entry.oldValue,
|
|
78
|
-
newValue: entry.newValue
|
|
88
|
+
newValue: entry.newValue,
|
|
79
89
|
});
|
|
80
|
-
if (entry.action === "create")
|
|
81
|
-
csCreates++;
|
|
82
|
-
if (entry.action === "update")
|
|
83
|
-
csUpdates++;
|
|
84
90
|
}
|
|
85
|
-
|
|
86
|
-
totals.
|
|
91
|
+
const counts = countActions(repoChanges.variables);
|
|
92
|
+
totals.variables.create += counts.create;
|
|
93
|
+
totals.variables.update += counts.update;
|
|
94
|
+
totals.variables.delete += counts.delete;
|
|
87
95
|
}
|
|
88
96
|
if (result.error) {
|
|
89
97
|
repoChanges.error = result.error;
|