@aspruyt/xfg 5.3.1 → 5.5.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/{shared → cli}/branch-utils.js +1 -1
- package/dist/cli/settings-report-builder.d.ts +6 -1
- package/dist/cli/settings-report-builder.js +19 -0
- package/dist/cli/sync-command.js +38 -23
- package/dist/cli/sync-report-builder.d.ts +2 -1
- package/dist/cli/types.d.ts +5 -2
- package/dist/config/config-merger.d.ts +6 -0
- package/dist/config/config-merger.js +66 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +50 -6
- package/dist/config/merge.d.ts +4 -0
- package/dist/config/merge.js +47 -23
- package/dist/config/normalizer.js +31 -1
- package/dist/config/types.d.ts +12 -0
- package/dist/config/validator.js +104 -46
- package/dist/config/validators/ruleset-validator.js +46 -16
- package/dist/lifecycle/ado-migration-source.d.ts +1 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +1 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +2 -1
- package/dist/lifecycle/lifecycle-helpers.js +1 -1
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +1 -1
- package/dist/output/types.d.ts +2 -1
- package/dist/settings/code-scanning/diff.d.ts +19 -0
- package/dist/settings/code-scanning/diff.js +75 -0
- package/dist/settings/code-scanning/formatter.d.ts +17 -0
- package/dist/settings/code-scanning/formatter.js +37 -0
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +19 -0
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +20 -0
- package/dist/settings/code-scanning/index.d.ts +3 -0
- package/dist/settings/code-scanning/index.js +4 -0
- package/dist/settings/code-scanning/processor.d.ts +20 -0
- package/dist/settings/code-scanning/processor.js +81 -0
- package/dist/settings/code-scanning/types.d.ts +22 -0
- package/dist/settings/code-scanning/types.js +1 -0
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -0
- package/dist/settings/labels/github-labels-strategy.d.ts +2 -2
- package/dist/settings/repo-settings/diff.d.ts +2 -1
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +1 -1
- package/dist/settings/repo-settings/processor.d.ts +4 -2
- package/dist/settings/repo-settings/processor.js +14 -16
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -2
- package/dist/settings/rulesets/processor.d.ts +1 -1
- package/dist/shared/cleanup-utils.d.ts +8 -0
- package/dist/shared/cleanup-utils.js +15 -0
- package/dist/shared/gh-api-utils.d.ts +2 -2
- package/dist/shared/gh-api-utils.js +7 -4
- package/dist/shared/repo-detector.d.ts +1 -20
- package/dist/shared/repo-detector.js +3 -45
- package/dist/shared/repo-info-utils.d.ts +20 -0
- package/dist/shared/repo-info-utils.js +46 -0
- package/dist/shared/repo-metadata-provider.d.ts +27 -0
- package/dist/shared/repo-metadata-provider.js +20 -0
- package/dist/shared/retry-utils.d.ts +1 -1
- package/dist/shared/retry-utils.js +1 -1
- package/dist/shared/type-guards.d.ts +0 -8
- package/dist/shared/type-guards.js +0 -14
- package/dist/shared/xfg-template.js +2 -2
- package/dist/sync/auth-options-builder.d.ts +1 -1
- package/dist/sync/branch-manager.d.ts +3 -7
- package/dist/sync/commit-push-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.js +2 -2
- package/dist/sync/diff-utils.js +0 -7
- package/dist/sync/manifest.js +1 -8
- package/dist/sync/repository-processor.d.ts +1 -1
- package/dist/sync/repository-processor.js +1 -2
- package/dist/sync/repository-session.d.ts +2 -2
- package/dist/sync/repository-session.js +1 -1
- package/dist/sync/sync-workflow.d.ts +1 -1
- package/dist/sync/sync-workflow.js +1 -1
- package/dist/vcs/authenticated-git-ops.js +2 -2
- package/dist/vcs/azure-pr-strategy.d.ts +1 -1
- package/dist/vcs/azure-pr-strategy.js +2 -1
- package/dist/vcs/commit-strategy-selector.d.ts +2 -2
- package/dist/vcs/git-commit-strategy.d.ts +1 -1
- package/dist/vcs/git-ops.d.ts +1 -1
- package/dist/vcs/git-ops.js +1 -1
- package/dist/vcs/github-pr-strategy.js +3 -2
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +4 -3
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -1
- package/dist/vcs/index.d.ts +2 -0
- package/dist/vcs/index.js +3 -0
- package/dist/vcs/pr-creator.d.ts +2 -2
- package/dist/vcs/pr-creator.js +1 -1
- package/dist/vcs/pr-strategy-factory.d.ts +2 -2
- package/dist/vcs/pr-strategy.d.ts +4 -2
- package/dist/vcs/pr-strategy.js +7 -2
- package/package.json +2 -1
- /package/dist/{shared → cli}/branch-utils.d.ts +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { diffCodeScanning, hasCodeScanningChanges } from "./diff.js";
|
|
2
|
+
import { formatCodeScanningPlan, } from "./formatter.js";
|
|
3
|
+
import { withGitHubGuards, countActions, buildDryRunResult, buildApplyResult, } from "../base-processor.js";
|
|
4
|
+
export class CodeScanningProcessor {
|
|
5
|
+
strategy;
|
|
6
|
+
metadataProvider;
|
|
7
|
+
constructor(strategy, metadataProvider) {
|
|
8
|
+
this.strategy = strategy;
|
|
9
|
+
this.metadataProvider = metadataProvider;
|
|
10
|
+
}
|
|
11
|
+
async process(repoConfig, repoInfo, options) {
|
|
12
|
+
return withGitHubGuards(repoConfig, repoInfo, options, {
|
|
13
|
+
hasDesiredSettings: (rc) => {
|
|
14
|
+
const cs = rc.settings?.codeScanning;
|
|
15
|
+
return !!cs && typeof cs === "object";
|
|
16
|
+
},
|
|
17
|
+
emptySettingsMessage: "No code scanning settings configured",
|
|
18
|
+
applySettings: (githubRepo, rc, opts, token, repoName) => this.applySettings(githubRepo, rc, opts, token, repoName),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async applySettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
|
|
22
|
+
const { dryRun } = options;
|
|
23
|
+
const desiredSettings = repoConfig.settings
|
|
24
|
+
.codeScanning;
|
|
25
|
+
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
26
|
+
// Validate GHAS availability for private repos
|
|
27
|
+
const metadata = await this.metadataProvider.getMetadata(githubRepo, strategyOptions);
|
|
28
|
+
const validationError = this.validateGHAS(desiredSettings, metadata);
|
|
29
|
+
if (validationError) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
repoName,
|
|
33
|
+
message: `Failed: ${validationError}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Fetch current settings
|
|
37
|
+
const currentSettings = await this.strategy.getDefaultSetup(githubRepo, strategyOptions);
|
|
38
|
+
// Compute diff
|
|
39
|
+
const changes = diffCodeScanning(currentSettings, desiredSettings);
|
|
40
|
+
const changeCounts = countActions(changes);
|
|
41
|
+
if (!hasCodeScanningChanges(changes)) {
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
repoName,
|
|
45
|
+
message: "No changes needed",
|
|
46
|
+
changes: changeCounts,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Format plan output
|
|
50
|
+
const planOutput = formatCodeScanningPlan(changes);
|
|
51
|
+
if (dryRun) {
|
|
52
|
+
return buildDryRunResult(repoName, changeCounts, { planOutput });
|
|
53
|
+
}
|
|
54
|
+
// Build API payload from desired settings
|
|
55
|
+
const payload = {
|
|
56
|
+
state: desiredSettings.state,
|
|
57
|
+
};
|
|
58
|
+
if (desiredSettings.querySuite !== undefined) {
|
|
59
|
+
payload.query_suite = desiredSettings.querySuite;
|
|
60
|
+
}
|
|
61
|
+
if (desiredSettings.languages !== undefined) {
|
|
62
|
+
payload.languages = desiredSettings.languages;
|
|
63
|
+
}
|
|
64
|
+
await this.strategy.updateDefaultSetup(githubRepo, payload, strategyOptions);
|
|
65
|
+
const appliedCount = changes.filter((c) => c.action !== "unchanged").length;
|
|
66
|
+
return buildApplyResult(repoName, changeCounts, appliedCount, {
|
|
67
|
+
planOutput,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
validateGHAS(desired, metadata) {
|
|
71
|
+
if (desired.state !== "configured")
|
|
72
|
+
return undefined;
|
|
73
|
+
const isPublic = metadata.visibility === "public";
|
|
74
|
+
if (isPublic)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (!metadata.hasGHAS) {
|
|
77
|
+
return "Code scanning default setup requires GitHub Advanced Security (not available for this repository)";
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
2
|
+
import type { GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Current code scanning default setup state from GitHub API.
|
|
5
|
+
*/
|
|
6
|
+
export interface CurrentCodeScanningSettings {
|
|
7
|
+
state: "configured" | "not-configured";
|
|
8
|
+
query_suite?: "default" | "extended";
|
|
9
|
+
languages?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Strategy interface for code scanning default setup operations.
|
|
13
|
+
* Abstracts the GitHub API calls for testability.
|
|
14
|
+
*/
|
|
15
|
+
export interface ICodeScanningStrategy {
|
|
16
|
+
getDefaultSetup(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentCodeScanningSettings>;
|
|
17
|
+
updateDefaultSetup(repoInfo: RepoInfo, settings: {
|
|
18
|
+
state: string;
|
|
19
|
+
query_suite?: string;
|
|
20
|
+
languages?: string[];
|
|
21
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { type BaseProcessorResult, type ISettingsProcessor, countActions, isActi
|
|
|
2
2
|
export { type PropertyDiff, type RulesetPlanEntry, RulesetProcessor, type IRulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
|
|
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
|
+
export { type CodeScanningPlanEntry, CodeScanningProcessor, type ICodeScanningProcessor, GitHubCodeScanningStrategy, } from "./code-scanning/index.js";
|
package/dist/settings/index.js
CHANGED
|
@@ -6,3 +6,5 @@ export { RulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
|
|
|
6
6
|
export { RepoSettingsProcessor, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
|
|
7
7
|
// Labels
|
|
8
8
|
export { LabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
|
|
9
|
+
// Code scanning
|
|
10
|
+
export { CodeScanningProcessor, GitHubCodeScanningStrategy, } from "./code-scanning/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
-
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
1
|
+
import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
+
import { type RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { ILabelsStrategy, GitHubLabel } from "./types.js";
|
|
5
5
|
interface GitHubLabelsStrategyOptions {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
2
|
+
import type { SettingsAction } from "../base-processor.js";
|
|
2
3
|
import type { CurrentRepoSettings } from "./types.js";
|
|
3
|
-
export type RepoSettingsAction =
|
|
4
|
+
export type RepoSettingsAction = Exclude<SettingsAction, "delete">;
|
|
4
5
|
export interface RepoSettingsChange {
|
|
5
6
|
property: keyof GitHubRepoSettings;
|
|
6
7
|
action: RepoSettingsAction;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
1
|
+
import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
2
|
import { type RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RepoConfig } from "../../config/index.js";
|
|
2
2
|
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import type { IRepoSettingsStrategy } from "./types.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { IRepoMetadataProvider } from "../../shared/repo-metadata-provider.js";
|
|
5
|
+
import { type RepoSettingsPlanResult } from "./formatter.js";
|
|
5
6
|
import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
|
|
6
7
|
export type IRepoSettingsProcessor = ISettingsProcessor<RepoSettingsProcessorOptions, RepoSettingsProcessorResult>;
|
|
7
8
|
export type RepoSettingsProcessorOptions = BaseProcessorOptions;
|
|
@@ -12,7 +13,8 @@ export interface RepoSettingsProcessorResult extends BaseProcessorResult {
|
|
|
12
13
|
}
|
|
13
14
|
export declare class RepoSettingsProcessor implements IRepoSettingsProcessor {
|
|
14
15
|
private readonly strategy;
|
|
15
|
-
|
|
16
|
+
private readonly metadataProvider;
|
|
17
|
+
constructor(strategy: IRepoSettingsStrategy, metadataProvider: IRepoMetadataProvider);
|
|
16
18
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
|
|
17
19
|
private applySettings;
|
|
18
20
|
private applyChanges;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { diffRepoSettings, hasChanges } from "./diff.js";
|
|
2
|
-
import { formatRepoSettingsPlan } from "./formatter.js";
|
|
2
|
+
import { formatRepoSettingsPlan, } from "./formatter.js";
|
|
3
3
|
import { withGitHubGuards, buildDryRunResult, buildApplyResult, } from "../base-processor.js";
|
|
4
4
|
export class RepoSettingsProcessor {
|
|
5
5
|
strategy;
|
|
6
|
-
|
|
6
|
+
metadataProvider;
|
|
7
|
+
constructor(strategy, metadataProvider) {
|
|
7
8
|
this.strategy = strategy;
|
|
9
|
+
this.metadataProvider = metadataProvider;
|
|
8
10
|
}
|
|
9
11
|
async process(repoConfig, repoInfo, options) {
|
|
10
12
|
return withGitHubGuards(repoConfig, repoInfo, options, {
|
|
@@ -20,10 +22,13 @@ export class RepoSettingsProcessor {
|
|
|
20
22
|
const { dryRun } = options;
|
|
21
23
|
const desiredSettings = repoConfig.settings.repo;
|
|
22
24
|
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
23
|
-
// Fetch current settings
|
|
24
|
-
const currentSettings = await
|
|
25
|
+
// Fetch current settings and metadata in parallel
|
|
26
|
+
const [currentSettings, metadata] = await Promise.all([
|
|
27
|
+
this.strategy.getSettings(githubRepo, strategyOptions),
|
|
28
|
+
this.metadataProvider.getMetadata(githubRepo, strategyOptions),
|
|
29
|
+
]);
|
|
25
30
|
// Validate security settings compatibility
|
|
26
|
-
const securityErrors = this.validateSecuritySettings(desiredSettings,
|
|
31
|
+
const securityErrors = this.validateSecuritySettings(desiredSettings, metadata);
|
|
27
32
|
if (securityErrors.length > 0) {
|
|
28
33
|
return {
|
|
29
34
|
success: false,
|
|
@@ -93,23 +98,16 @@ export class RepoSettingsProcessor {
|
|
|
93
98
|
await this.strategy.setAutomatedSecurityFixes(repoInfo, automatedSecurityFixes, options);
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
|
-
validateSecuritySettings(desiredSettings,
|
|
101
|
+
validateSecuritySettings(desiredSettings, metadata) {
|
|
97
102
|
const errors = [];
|
|
98
|
-
|
|
99
|
-
// otherwise fall back to current visibility
|
|
100
|
-
const effectiveVisibility = desiredSettings.visibility ?? currentSettings.visibility;
|
|
103
|
+
const effectiveVisibility = desiredSettings.visibility ?? metadata.visibility;
|
|
101
104
|
const isPublic = effectiveVisibility === "public";
|
|
102
|
-
// privateVulnerabilityReporting is only available on public repos
|
|
103
105
|
if (desiredSettings.privateVulnerabilityReporting === true && !isPublic) {
|
|
104
106
|
errors.push("privateVulnerabilityReporting is only available for public repositories");
|
|
105
107
|
}
|
|
106
|
-
// secretScanning and secretScanningPushProtection:
|
|
107
|
-
// - Available on public repos (free)
|
|
108
|
-
// - Available on org private/internal repos with GHAS (security_and_analysis is populated)
|
|
109
|
-
// - NOT available on user private repos or org private/internal repos without GHAS
|
|
110
108
|
if (!isPublic) {
|
|
111
|
-
const isUserOwned =
|
|
112
|
-
const hasGHAS =
|
|
109
|
+
const isUserOwned = metadata.ownerType === "User";
|
|
110
|
+
const hasGHAS = metadata.hasGHAS;
|
|
113
111
|
if (desiredSettings.secretScanning === true &&
|
|
114
112
|
(isUserOwned || !hasGHAS)) {
|
|
115
113
|
errors.push("secretScanning requires GitHub Advanced Security (not available for this repository)");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
-
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
1
|
+
import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
+
import { type RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { Ruleset } from "../../config/index.js";
|
|
5
5
|
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetUpdateParams } from "./types.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RepoConfig } from "../../config/index.js";
|
|
2
2
|
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import type { IRulesetStrategy } from "./types.js";
|
|
4
|
-
import { RulesetPlanResult } from "./formatter.js";
|
|
4
|
+
import { type RulesetPlanResult } from "./formatter.js";
|
|
5
5
|
import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
|
|
6
6
|
export type IRulesetProcessor = ISettingsProcessor<RulesetProcessorOptions, RulesetProcessorResult>;
|
|
7
7
|
export interface RulesetProcessorOptions extends BaseProcessorOptions {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DebugLog } from "./logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run a cleanup action, swallowing errors with a debug log.
|
|
4
|
+
* Replaces the repetitive try-catch-debug-log cleanup pattern.
|
|
5
|
+
* If the function returns a Promise, the returned Promise resolves
|
|
6
|
+
* after the cleanup completes (or fails silently).
|
|
7
|
+
*/
|
|
8
|
+
export declare function safeCleanup(fn: () => void | Promise<void>, label: string, log: DebugLog): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { toErrorMessage } from "./type-guards.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run a cleanup action, swallowing errors with a debug log.
|
|
4
|
+
* Replaces the repetitive try-catch-debug-log cleanup pattern.
|
|
5
|
+
* If the function returns a Promise, the returned Promise resolves
|
|
6
|
+
* after the cleanup completes (or fails silently).
|
|
7
|
+
*/
|
|
8
|
+
export async function safeCleanup(fn, label, log) {
|
|
9
|
+
try {
|
|
10
|
+
await fn();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
log.debug(`Cleanup: ${label}: ${toErrorMessage(error)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ICommandExecutor } from "./command-executor.js";
|
|
2
2
|
import type { GitHubRepoInfo } from "./repo-detector.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { DebugWarnLog } from "./logger.js";
|
|
4
4
|
interface ITokenManager {
|
|
5
5
|
getTokenForRepo(repoInfo: GitHubRepoInfo): Promise<string | null>;
|
|
6
6
|
}
|
|
@@ -51,7 +51,7 @@ interface ResolveGitHubTokenOptions {
|
|
|
51
51
|
repoInfo: GitHubRepoInfo;
|
|
52
52
|
tokenManager: ITokenManager | null;
|
|
53
53
|
context: string;
|
|
54
|
-
log?:
|
|
54
|
+
log?: DebugWarnLog;
|
|
55
55
|
envToken?: string;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
@@ -139,10 +139,13 @@ export async function resolveGitHubToken(options) {
|
|
|
139
139
|
return { token: appToken ?? envToken, skipped: false };
|
|
140
140
|
}
|
|
141
141
|
catch (error) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
const errorMsg = `GitHub App token resolution failed for ${context}: ${toErrorMessage(error)}`;
|
|
143
|
+
if (envToken) {
|
|
144
|
+
log?.debug(`${errorMsg}; falling back to GH_TOKEN`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
log?.warn(`${errorMsg}; no fallback token available`);
|
|
148
|
+
}
|
|
146
149
|
return { token: envToken, skipped: false };
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -24,25 +24,6 @@ export interface GitLabRepoInfo extends BaseRepoInfo {
|
|
|
24
24
|
host: string;
|
|
25
25
|
}
|
|
26
26
|
export type RepoInfo = GitHubRepoInfo | AzureDevOpsRepoInfo | GitLabRepoInfo;
|
|
27
|
-
export
|
|
28
|
-
export declare function isAzureDevOpsRepo(info: RepoInfo): info is AzureDevOpsRepoInfo;
|
|
29
|
-
export declare function isGitLabRepo(info: RepoInfo): info is GitLabRepoInfo;
|
|
30
|
-
/**
|
|
31
|
-
* Assert that a RepoInfo is a GitHub repository, narrowing the type.
|
|
32
|
-
* Use in GitHub-specific strategies to avoid duplicating validation logic.
|
|
33
|
-
*/
|
|
34
|
-
export declare function assertGitHubRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is GitHubRepoInfo;
|
|
35
|
-
/**
|
|
36
|
-
* Assert that a RepoInfo is an Azure DevOps repository, narrowing the type.
|
|
37
|
-
* Use in Azure-specific strategies to avoid duplicating validation logic.
|
|
38
|
-
*/
|
|
39
|
-
export declare function assertAzureDevOpsRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is AzureDevOpsRepoInfo;
|
|
40
|
-
/**
|
|
41
|
-
* Assert that a RepoInfo is a GitLab repository, narrowing the type.
|
|
42
|
-
* Use in GitLab-specific strategies to avoid duplicating validation logic.
|
|
43
|
-
*/
|
|
44
|
-
export declare function assertGitLabRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is GitLabRepoInfo;
|
|
27
|
+
export { isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, assertGitHubRepo, assertAzureDevOpsRepo, assertGitLabRepo, getRepoDisplayName, } from "./repo-info-utils.js";
|
|
45
28
|
export declare function detectRepoType(gitUrl: string, context?: RepoDetectorContext): RepoType;
|
|
46
29
|
export declare function parseGitUrl(gitUrl: string, context?: RepoDetectorContext): RepoInfo;
|
|
47
|
-
export declare function getRepoDisplayName(repoInfo: RepoInfo): string;
|
|
48
|
-
export {};
|
|
@@ -1,40 +1,7 @@
|
|
|
1
1
|
import { ValidationError } from "./errors.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
export function isAzureDevOpsRepo(info) {
|
|
6
|
-
return info.type === "azure-devops";
|
|
7
|
-
}
|
|
8
|
-
export function isGitLabRepo(info) {
|
|
9
|
-
return info.type === "gitlab";
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Assert that a RepoInfo is a GitHub repository, narrowing the type.
|
|
13
|
-
* Use in GitHub-specific strategies to avoid duplicating validation logic.
|
|
14
|
-
*/
|
|
15
|
-
export function assertGitHubRepo(repoInfo, context) {
|
|
16
|
-
if (!isGitHubRepo(repoInfo)) {
|
|
17
|
-
throw new ValidationError(`${context} requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Assert that a RepoInfo is an Azure DevOps repository, narrowing the type.
|
|
22
|
-
* Use in Azure-specific strategies to avoid duplicating validation logic.
|
|
23
|
-
*/
|
|
24
|
-
export function assertAzureDevOpsRepo(repoInfo, context) {
|
|
25
|
-
if (!isAzureDevOpsRepo(repoInfo)) {
|
|
26
|
-
throw new ValidationError(`${context} requires Azure DevOps repositories. Got: ${repoInfo.type}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Assert that a RepoInfo is a GitLab repository, narrowing the type.
|
|
31
|
-
* Use in GitLab-specific strategies to avoid duplicating validation logic.
|
|
32
|
-
*/
|
|
33
|
-
export function assertGitLabRepo(repoInfo, context) {
|
|
34
|
-
if (!isGitLabRepo(repoInfo)) {
|
|
35
|
-
throw new ValidationError(`${context} requires GitLab repositories. Got: ${repoInfo.type}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
2
|
+
// Type guards, assertions, and display helpers live in repo-info-utils.ts.
|
|
3
|
+
// Re-exported here for backward compatibility.
|
|
4
|
+
export { isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, assertGitHubRepo, assertAzureDevOpsRepo, assertGitLabRepo, getRepoDisplayName, } from "./repo-info-utils.js";
|
|
38
5
|
function extractHostFromUrl(gitUrl) {
|
|
39
6
|
// SSH: git@hostname:path
|
|
40
7
|
const sshMatch = gitUrl.match(/^git@([^:]+):/);
|
|
@@ -217,12 +184,3 @@ function parseGitLabPath(gitUrl, host, fullPath) {
|
|
|
217
184
|
host,
|
|
218
185
|
};
|
|
219
186
|
}
|
|
220
|
-
export function getRepoDisplayName(repoInfo) {
|
|
221
|
-
if (repoInfo.type === "azure-devops") {
|
|
222
|
-
return `${repoInfo.organization}/${repoInfo.project}/${repoInfo.repo}`;
|
|
223
|
-
}
|
|
224
|
-
if (repoInfo.type === "gitlab") {
|
|
225
|
-
return `${repoInfo.namespace}/${repoInfo.repo}`;
|
|
226
|
-
}
|
|
227
|
-
return `${repoInfo.owner}/${repoInfo.repo}`;
|
|
228
|
-
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RepoInfo, GitHubRepoInfo, AzureDevOpsRepoInfo, GitLabRepoInfo } from "./repo-detector.js";
|
|
2
|
+
export declare function isGitHubRepo(info: RepoInfo): info is GitHubRepoInfo;
|
|
3
|
+
export declare function isAzureDevOpsRepo(info: RepoInfo): info is AzureDevOpsRepoInfo;
|
|
4
|
+
export declare function isGitLabRepo(info: RepoInfo): info is GitLabRepoInfo;
|
|
5
|
+
/**
|
|
6
|
+
* Assert that a RepoInfo is a GitHub repository, narrowing the type.
|
|
7
|
+
* Use in GitHub-specific strategies to avoid duplicating validation logic.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertGitHubRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is GitHubRepoInfo;
|
|
10
|
+
/**
|
|
11
|
+
* Assert that a RepoInfo is an Azure DevOps repository, narrowing the type.
|
|
12
|
+
* Use in Azure-specific strategies to avoid duplicating validation logic.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertAzureDevOpsRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is AzureDevOpsRepoInfo;
|
|
15
|
+
/**
|
|
16
|
+
* Assert that a RepoInfo is a GitLab repository, narrowing the type.
|
|
17
|
+
* Use in GitLab-specific strategies to avoid duplicating validation logic.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertGitLabRepo(repoInfo: RepoInfo, context: string): asserts repoInfo is GitLabRepoInfo;
|
|
20
|
+
export declare function getRepoDisplayName(repoInfo: RepoInfo): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.js";
|
|
2
|
+
export function isGitHubRepo(info) {
|
|
3
|
+
return info.type === "github";
|
|
4
|
+
}
|
|
5
|
+
export function isAzureDevOpsRepo(info) {
|
|
6
|
+
return info.type === "azure-devops";
|
|
7
|
+
}
|
|
8
|
+
export function isGitLabRepo(info) {
|
|
9
|
+
return info.type === "gitlab";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Assert that a RepoInfo is a GitHub repository, narrowing the type.
|
|
13
|
+
* Use in GitHub-specific strategies to avoid duplicating validation logic.
|
|
14
|
+
*/
|
|
15
|
+
export function assertGitHubRepo(repoInfo, context) {
|
|
16
|
+
if (!isGitHubRepo(repoInfo)) {
|
|
17
|
+
throw new ValidationError(`${context} requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Assert that a RepoInfo is an Azure DevOps repository, narrowing the type.
|
|
22
|
+
* Use in Azure-specific strategies to avoid duplicating validation logic.
|
|
23
|
+
*/
|
|
24
|
+
export function assertAzureDevOpsRepo(repoInfo, context) {
|
|
25
|
+
if (!isAzureDevOpsRepo(repoInfo)) {
|
|
26
|
+
throw new ValidationError(`${context} requires Azure DevOps repositories. Got: ${repoInfo.type}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Assert that a RepoInfo is a GitLab repository, narrowing the type.
|
|
31
|
+
* Use in GitLab-specific strategies to avoid duplicating validation logic.
|
|
32
|
+
*/
|
|
33
|
+
export function assertGitLabRepo(repoInfo, context) {
|
|
34
|
+
if (!isGitLabRepo(repoInfo)) {
|
|
35
|
+
throw new ValidationError(`${context} requires GitLab repositories. Got: ${repoInfo.type}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function getRepoDisplayName(repoInfo) {
|
|
39
|
+
if (repoInfo.type === "azure-devops") {
|
|
40
|
+
return `${repoInfo.organization}/${repoInfo.project}/${repoInfo.repo}`;
|
|
41
|
+
}
|
|
42
|
+
if (repoInfo.type === "gitlab") {
|
|
43
|
+
return `${repoInfo.namespace}/${repoInfo.repo}`;
|
|
44
|
+
}
|
|
45
|
+
return `${repoInfo.owner}/${repoInfo.repo}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ICommandExecutor } from "./command-executor.js";
|
|
2
|
+
import { type RepoInfo } from "./repo-detector.js";
|
|
3
|
+
import { type GhApiOptions } from "./gh-api-utils.js";
|
|
4
|
+
import type { RepoVisibility } from "../config/index.js";
|
|
5
|
+
export interface RepoMetadata {
|
|
6
|
+
visibility: RepoVisibility;
|
|
7
|
+
ownerType: "User" | "Organization";
|
|
8
|
+
hasGHAS: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Strategy interface for fetching repository metadata.
|
|
12
|
+
* Used to share repo metadata (visibility, owner type, GHAS)
|
|
13
|
+
* across settings processors without coupling them.
|
|
14
|
+
*/
|
|
15
|
+
export interface IRepoMetadataProvider {
|
|
16
|
+
getMetadata(repoInfo: RepoInfo, options?: GhApiOptions): Promise<RepoMetadata>;
|
|
17
|
+
}
|
|
18
|
+
interface GitHubRepoMetadataProviderOptions {
|
|
19
|
+
retries?: number;
|
|
20
|
+
cwd: string;
|
|
21
|
+
}
|
|
22
|
+
export declare class GitHubRepoMetadataProvider implements IRepoMetadataProvider {
|
|
23
|
+
private api;
|
|
24
|
+
constructor(executor: ICommandExecutor, options: GitHubRepoMetadataProviderOptions);
|
|
25
|
+
getMetadata(repoInfo: RepoInfo, options?: GhApiOptions): Promise<RepoMetadata>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { assertGitHubRepo } from "./repo-detector.js";
|
|
2
|
+
import { GhApiClient } from "./gh-api-utils.js";
|
|
3
|
+
import { parseApiJson } from "./json-utils.js";
|
|
4
|
+
export class GitHubRepoMetadataProvider {
|
|
5
|
+
api;
|
|
6
|
+
constructor(executor, options) {
|
|
7
|
+
this.api = new GhApiClient(executor, options.retries ?? 3, options.cwd);
|
|
8
|
+
}
|
|
9
|
+
async getMetadata(repoInfo, options) {
|
|
10
|
+
assertGitHubRepo(repoInfo, "Repo Metadata Provider");
|
|
11
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}`;
|
|
12
|
+
const result = await this.api.call("GET", endpoint, { options });
|
|
13
|
+
const parsed = parseApiJson(result, "repo metadata response");
|
|
14
|
+
return {
|
|
15
|
+
visibility: parsed.visibility ?? "public",
|
|
16
|
+
ownerType: parsed.owner?.type ?? "User",
|
|
17
|
+
hasGHAS: parsed.security_and_analysis != null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -44,7 +44,7 @@ export declare function isTransientError(error: unknown, patterns?: RegExp[]): b
|
|
|
44
44
|
* @param fn The async function to run with retry
|
|
45
45
|
* @param options Retry configuration options
|
|
46
46
|
* @returns The result of the function if successful
|
|
47
|
-
* @throws
|
|
47
|
+
* @throws The original error for permanent failures (pRetry unwraps AbortError before propagating), or the last transient error after all retries exhausted
|
|
48
48
|
*/
|
|
49
49
|
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
50
50
|
export {};
|
|
@@ -135,7 +135,7 @@ export function isTransientError(error, patterns = DEFAULT_TRANSIENT_ERROR_PATTE
|
|
|
135
135
|
* @param fn The async function to run with retry
|
|
136
136
|
* @param options Retry configuration options
|
|
137
137
|
* @returns The result of the function if successful
|
|
138
|
-
* @throws
|
|
138
|
+
* @throws The original error for permanent failures (pRetry unwraps AbortError before propagating), or the last transient error after all retries exhausted
|
|
139
139
|
*/
|
|
140
140
|
export async function withRetry(fn, options) {
|
|
141
141
|
const retries = options?.retries ?? 3;
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
import type { DebugLog } from "./logger.js";
|
|
2
1
|
export declare function isPlainObject(val: unknown): val is Record<string, unknown>;
|
|
3
2
|
export declare function toErrorMessage(error: unknown): string;
|
|
4
|
-
/**
|
|
5
|
-
* Run a cleanup action, swallowing errors with a debug log.
|
|
6
|
-
* Replaces the repetitive try-catch-debug-log cleanup pattern.
|
|
7
|
-
* If the function returns a Promise, the returned Promise resolves
|
|
8
|
-
* after the cleanup completes (or fails silently).
|
|
9
|
-
*/
|
|
10
|
-
export declare function safeCleanup(fn: () => void | Promise<void>, label: string, log: DebugLog): Promise<void>;
|
|
@@ -4,17 +4,3 @@ export function isPlainObject(val) {
|
|
|
4
4
|
export function toErrorMessage(error) {
|
|
5
5
|
return error instanceof Error ? error.message : String(error);
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Run a cleanup action, swallowing errors with a debug log.
|
|
9
|
-
* Replaces the repetitive try-catch-debug-log cleanup pattern.
|
|
10
|
-
* If the function returns a Promise, the returned Promise resolves
|
|
11
|
-
* after the cleanup completes (or fails silently).
|
|
12
|
-
*/
|
|
13
|
-
export async function safeCleanup(fn, label, log) {
|
|
14
|
-
try {
|
|
15
|
-
await fn();
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
log.debug(`Cleanup: ${label}: ${toErrorMessage(error)}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -28,7 +28,7 @@ const ESCAPED_XFG_VAR_REGEX = /\$\$\{xfg:([a-zA-Z0-9._]+)\}/g;
|
|
|
28
28
|
* Get the value of a built-in xfg variable.
|
|
29
29
|
* Returns undefined if the variable is not recognized.
|
|
30
30
|
*/
|
|
31
|
-
function
|
|
31
|
+
function getBuiltinVariable(varName, ctx) {
|
|
32
32
|
const { repoInfo, fileName } = ctx;
|
|
33
33
|
switch (varName) {
|
|
34
34
|
case "repo.name":
|
|
@@ -68,7 +68,7 @@ function buildXfgConfig(ctx, options) {
|
|
|
68
68
|
return ctx.vars[varName];
|
|
69
69
|
}
|
|
70
70
|
// Then check built-in vars
|
|
71
|
-
const builtinValue =
|
|
71
|
+
const builtinValue = getBuiltinVariable(varName, ctx);
|
|
72
72
|
if (builtinValue !== undefined) {
|
|
73
73
|
return builtinValue;
|
|
74
74
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RepoInfo } from "../shared/repo-detector.js";
|
|
1
|
+
import { type RepoInfo } from "../shared/repo-detector.js";
|
|
2
2
|
import type { GitHubAppTokenManager } from "../vcs/index.js";
|
|
3
3
|
import type { AuthResult, IAuthOptionsBuilder } from "./types.js";
|
|
4
4
|
import type { ILogger } from "../shared/logger.js";
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { IPRStrategy } from "../vcs/index.js";
|
|
2
2
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
3
3
|
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
4
|
+
import type { DebugInfoWarnLog } from "../shared/logger.js";
|
|
4
5
|
import type { IBranchManager, BranchSetupOptions } from "./types.js";
|
|
5
|
-
type
|
|
6
|
-
debug(msg: string): void;
|
|
7
|
-
info(msg: string): void;
|
|
8
|
-
warn(msg: string): void;
|
|
9
|
-
};
|
|
10
|
-
type PRStrategyFactory = (repoInfo: RepoInfo, executor: ICommandExecutor, log?: SyncLog) => IPRStrategy;
|
|
6
|
+
type PRStrategyFactory = (repoInfo: RepoInfo, executor: ICommandExecutor, log?: DebugInfoWarnLog) => IPRStrategy;
|
|
11
7
|
export declare class BranchManager implements IBranchManager {
|
|
12
8
|
private readonly log;
|
|
13
9
|
private readonly prStrategyFactory;
|
|
14
|
-
constructor(log:
|
|
10
|
+
constructor(log: DebugInfoWarnLog, prStrategyFactory?: PRStrategyFactory);
|
|
15
11
|
setupBranch(options: BranchSetupOptions): Promise<void>;
|
|
16
12
|
}
|
|
17
13
|
export {};
|
|
@@ -9,6 +9,6 @@ export declare class CommitPushManager implements ICommitPushManager {
|
|
|
9
9
|
private readonly commitStrategyFactory;
|
|
10
10
|
constructor(log: DebugInfoLog, commitStrategyFactory?: CommitStrategyFactory);
|
|
11
11
|
commitAndPush(options: CommitPushOptions): Promise<CommitPushResult>;
|
|
12
|
-
private
|
|
12
|
+
private rethrowIfUnexpectedCommitError;
|
|
13
13
|
}
|
|
14
14
|
export {};
|
|
@@ -43,10 +43,10 @@ export class CommitPushManager {
|
|
|
43
43
|
return { success: true };
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
|
-
return this.
|
|
46
|
+
return this.rethrowIfUnexpectedCommitError(error, isDirectMode, pushBranch, repoInfo);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
rethrowIfUnexpectedCommitError(error, isDirectMode, pushBranch, repoInfo) {
|
|
50
50
|
const repoName = getRepoDisplayName(repoInfo);
|
|
51
51
|
const message = toErrorMessage(error);
|
|
52
52
|
if (isDirectMode &&
|