@aspruyt/xfg 6.0.3 → 6.2.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 +2 -3
- 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/settings-factories.d.ts +7 -0
- package/dist/cli/settings-factories.js +27 -0
- package/dist/cli/settings-report-builder.d.ts +1 -1
- package/dist/cli/settings-report-builder.js +12 -23
- package/dist/cli/settings-runner.d.ts +2 -0
- package/dist/cli/settings-runner.js +87 -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 +5 -7
- 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 +18 -1
- package/dist/config/index.d.ts +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 +53 -38
- package/dist/config/validator.d.ts +1 -4
- package/dist/config/validator.js +13 -599
- 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/sync-report.d.ts +25 -3
- package/dist/output/sync-report.js +11 -11
- 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/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/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/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 +5 -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/output/types.js +0 -1
- 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/{vcs → shared}/sanitize-utils.d.ts +0 -0
- /package/dist/{vcs → shared}/sanitize-utils.js +0 -0
|
@@ -78,14 +78,16 @@ function diffObjectArrays(currentArr, desiredArr, parentPath) {
|
|
|
78
78
|
const currentByType = new Map();
|
|
79
79
|
for (let i = 0; i < currentArr.length; i++) {
|
|
80
80
|
const item = currentArr[i];
|
|
81
|
-
const type = item.type;
|
|
81
|
+
const type = typeof item.type === "string" ? item.type : String(item.type ?? "");
|
|
82
82
|
if (type)
|
|
83
83
|
currentByType.set(type, { item, index: i });
|
|
84
84
|
}
|
|
85
85
|
const matchedTypes = new Set();
|
|
86
86
|
for (let i = 0; i < desiredArr.length; i++) {
|
|
87
87
|
const desiredItem = desiredArr[i];
|
|
88
|
-
const type = desiredItem.type
|
|
88
|
+
const type = typeof desiredItem.type === "string"
|
|
89
|
+
? desiredItem.type
|
|
90
|
+
: String(desiredItem.type ?? "");
|
|
89
91
|
const label = `[${i}] (${type})`;
|
|
90
92
|
const currentEntry = currentByType.get(type);
|
|
91
93
|
if (currentEntry) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { RULESET_COMPARABLE_FIELDS } from "../../config/index.js";
|
|
2
|
+
import { findMatchKey } from "../../config/merge.js";
|
|
2
3
|
import { isPlainObject } from "../../shared/type-guards.js";
|
|
3
4
|
import { camelToSnake } from "../../shared/string-utils.js";
|
|
4
5
|
import { countActions } from "../base-processor.js";
|
|
6
|
+
import { deepEqual } from "./diff-algorithm.js";
|
|
5
7
|
/**
|
|
6
8
|
* Normalizes a value recursively, converting keys to a consistent format (snake_case).
|
|
7
9
|
* This allows comparing GitHub API responses (snake_case) with config (camelCase).
|
|
@@ -54,37 +56,6 @@ function normalizeConfigRuleset(ruleset) {
|
|
|
54
56
|
};
|
|
55
57
|
return normalizeRuleset(withDefaults);
|
|
56
58
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Performs deep equality comparison of two normalized values.
|
|
59
|
-
*/
|
|
60
|
-
function deepEqual(a, b) {
|
|
61
|
-
if (a === b) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
if (a === null || b === null || a === undefined || b === undefined) {
|
|
65
|
-
return a === b;
|
|
66
|
-
}
|
|
67
|
-
if (typeof a !== typeof b) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
71
|
-
if (a.length !== b.length) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
return a.every((val, i) => deepEqual(val, b[i]));
|
|
75
|
-
}
|
|
76
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
77
|
-
const objA = a;
|
|
78
|
-
const objB = b;
|
|
79
|
-
const keysA = Object.keys(objA);
|
|
80
|
-
const keysB = Object.keys(objB);
|
|
81
|
-
if (keysA.length !== keysB.length) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
return keysA.every((key) => deepEqual(objA[key], objB[key]));
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
59
|
/**
|
|
89
60
|
* Projects `current` onto the shape of `desired`.
|
|
90
61
|
* Only keeps keys/structure present in `desired`, filtering out API noise.
|
|
@@ -129,26 +100,6 @@ function projectObjects(current, desired) {
|
|
|
129
100
|
}
|
|
130
101
|
return result;
|
|
131
102
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Candidate keys for matching array items by identity rather than index.
|
|
134
|
-
* Order matters — first key found across all items wins.
|
|
135
|
-
*/
|
|
136
|
-
const MATCH_KEY_CANDIDATES = ["type", "actor_id"];
|
|
137
|
-
/**
|
|
138
|
-
* Finds a key that uniquely identifies items in both arrays.
|
|
139
|
-
* Returns the first candidate key present in every item of both arrays, or undefined.
|
|
140
|
-
*/
|
|
141
|
-
function findMatchKey(current, desired) {
|
|
142
|
-
const allItems = [...current, ...desired];
|
|
143
|
-
if (allItems.length === 0)
|
|
144
|
-
return undefined;
|
|
145
|
-
for (const candidate of MATCH_KEY_CANDIDATES) {
|
|
146
|
-
const everyItemHasKey = allItems.every((item) => isPlainObject(item) && candidate in item);
|
|
147
|
-
if (everyItemHasKey)
|
|
148
|
-
return candidate;
|
|
149
|
-
}
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
103
|
function projectArrays(current, desired) {
|
|
153
104
|
// Primitive arrays — return current as-is
|
|
154
105
|
if (desired.length === 0 || !isPlainObject(desired[0])) {
|
|
@@ -114,6 +114,10 @@ function getActionStyle(action) {
|
|
|
114
114
|
return { symbol: "-", color: chalk.red };
|
|
115
115
|
case "change":
|
|
116
116
|
return { symbol: "~", color: chalk.yellow };
|
|
117
|
+
default: {
|
|
118
|
+
const _ = action;
|
|
119
|
+
throw new Error(`Unknown DiffAction: ${String(_)}`);
|
|
120
|
+
}
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
function hasComplexValue(value) {
|
|
@@ -2,7 +2,7 @@ import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
|
2
2
|
import { type RepoInfo } from "../../repo/index.js";
|
|
3
3
|
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
4
|
import type { Ruleset } from "../../config/index.js";
|
|
5
|
-
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetUpdateParams } from "./types.js";
|
|
5
|
+
import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule, RulesetCreateParams, RulesetUpdateParams } from "./types.js";
|
|
6
6
|
/**
|
|
7
7
|
* Converts camelCase config ruleset to snake_case GitHub API format.
|
|
8
8
|
*/
|
|
@@ -24,8 +24,8 @@ export declare class GitHubRulesetStrategy implements IRulesetStrategy {
|
|
|
24
24
|
constructor(executor: ICommandExecutor, options: GitHubRulesetStrategyOptions);
|
|
25
25
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
26
26
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
27
|
-
create(repoInfo: RepoInfo,
|
|
28
|
-
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<
|
|
27
|
+
create(repoInfo: RepoInfo, params: RulesetCreateParams, options?: GhApiOptions): Promise<void>;
|
|
28
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
29
29
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
30
30
|
}
|
|
31
31
|
export {};
|
|
@@ -111,22 +111,20 @@ export class GitHubRulesetStrategy {
|
|
|
111
111
|
const result = await this.api.call("GET", endpoint, { options });
|
|
112
112
|
return parseApiJson(result, "ruleset response");
|
|
113
113
|
}
|
|
114
|
-
async create(repoInfo,
|
|
114
|
+
async create(repoInfo, params, options) {
|
|
115
115
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
116
116
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`;
|
|
117
|
-
const payload = configToGitHub(name, ruleset);
|
|
118
|
-
|
|
119
|
-
return parseApiJson(result, "ruleset response");
|
|
117
|
+
const payload = configToGitHub(params.name, params.ruleset);
|
|
118
|
+
await this.api.call("POST", endpoint, { payload, options });
|
|
120
119
|
}
|
|
121
120
|
async update(repoInfo, params, options) {
|
|
122
121
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
123
122
|
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${params.rulesetId}`;
|
|
124
123
|
const payload = configToGitHub(params.name, params.ruleset);
|
|
125
|
-
|
|
124
|
+
await this.api.call("PUT", endpoint, {
|
|
126
125
|
payload,
|
|
127
126
|
options,
|
|
128
127
|
});
|
|
129
|
-
return parseApiJson(result, "ruleset response");
|
|
130
128
|
}
|
|
131
129
|
async delete(repoInfo, rulesetId, options) {
|
|
132
130
|
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { type PropertyDiff } from "./diff-algorithm.js";
|
|
2
2
|
export { type RulesetPlanEntry } from "./formatter.js";
|
|
3
3
|
export { RulesetProcessor, type IRulesetProcessor } from "./processor.js";
|
|
4
4
|
export { GitHubRulesetStrategy } from "./github-ruleset-strategy.js";
|
|
@@ -50,7 +50,7 @@ export class RulesetProcessor {
|
|
|
50
50
|
switch (change.action) {
|
|
51
51
|
case "create":
|
|
52
52
|
if (change.desired) {
|
|
53
|
-
await this.strategy.create(githubRepo, change.name, change.desired, strategyOptions);
|
|
53
|
+
await this.strategy.create(githubRepo, { name: change.name, ruleset: change.desired }, strategyOptions);
|
|
54
54
|
appliedCount++;
|
|
55
55
|
}
|
|
56
56
|
break;
|
|
@@ -30,6 +30,10 @@ export interface GitHubRule {
|
|
|
30
30
|
type: string;
|
|
31
31
|
parameters?: Record<string, unknown>;
|
|
32
32
|
}
|
|
33
|
+
export interface RulesetCreateParams {
|
|
34
|
+
name: string;
|
|
35
|
+
ruleset: Ruleset;
|
|
36
|
+
}
|
|
33
37
|
export interface RulesetUpdateParams {
|
|
34
38
|
rulesetId: number;
|
|
35
39
|
name: string;
|
|
@@ -38,7 +42,7 @@ export interface RulesetUpdateParams {
|
|
|
38
42
|
export interface IRulesetStrategy {
|
|
39
43
|
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
|
|
40
44
|
get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
|
|
41
|
-
create(repoInfo: RepoInfo,
|
|
42
|
-
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<
|
|
45
|
+
create(repoInfo: RepoInfo, params: RulesetCreateParams, options?: GhApiOptions): Promise<void>;
|
|
46
|
+
update(repoInfo: RepoInfo, params: RulesetUpdateParams, options?: GhApiOptions): Promise<void>;
|
|
43
47
|
delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
|
|
44
48
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export interface ExecOptions {
|
|
2
|
-
/** Additional environment variables to set for the command */
|
|
3
2
|
env?: Record<string, string>;
|
|
3
|
+
input?: string;
|
|
4
4
|
}
|
|
5
5
|
export interface ICommandExecutor {
|
|
6
|
-
exec(
|
|
6
|
+
exec(executable: string, args: string[], cwd: string, options?: ExecOptions): Promise<string>;
|
|
7
7
|
}
|
|
8
|
-
export declare class
|
|
8
|
+
export declare class ProcessExecutor implements ICommandExecutor {
|
|
9
9
|
private readonly baseEnv;
|
|
10
10
|
constructor(baseEnv: Record<string, string | undefined>);
|
|
11
|
-
exec(
|
|
11
|
+
exec(executable: string, args: string[], cwd: string, options?: ExecOptions): Promise<string>;
|
|
12
12
|
}
|
|
13
13
|
/** Extract stderr string from an exec error (child_process errors attach stderr). */
|
|
14
14
|
export declare function getStderr(error: unknown): string;
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { sanitizeCredentials } from "
|
|
3
|
-
export class
|
|
2
|
+
import { sanitizeCredentials } from "./sanitize-utils.js";
|
|
3
|
+
export class ProcessExecutor {
|
|
4
4
|
baseEnv;
|
|
5
5
|
constructor(baseEnv) {
|
|
6
6
|
this.baseEnv = baseEnv;
|
|
7
7
|
}
|
|
8
|
-
async exec(
|
|
8
|
+
async exec(executable, args, cwd, options) {
|
|
9
9
|
try {
|
|
10
|
-
return execFileSync(
|
|
10
|
+
return execFileSync(executable, args, {
|
|
11
11
|
cwd,
|
|
12
12
|
encoding: "utf-8",
|
|
13
13
|
stdio: ["pipe", "pipe", "pipe"],
|
|
14
|
+
input: options?.input,
|
|
14
15
|
env: options?.env
|
|
15
16
|
? { ...this.baseEnv, ...options.env }
|
|
16
17
|
: this.baseEnv,
|
|
17
18
|
}).trim();
|
|
18
19
|
}
|
|
19
20
|
catch (error) {
|
|
20
|
-
// Normalise and sanitise the exec error so downstream retry logic
|
|
21
|
-
// sees a string stderr with no raw credentials.
|
|
22
21
|
const execError = error;
|
|
23
22
|
if (execError.stderr && typeof execError.stderr !== "string") {
|
|
24
23
|
execError.stderr = execError.stderr.toString();
|
|
@@ -41,7 +40,10 @@ export class ShellCommandExecutor {
|
|
|
41
40
|
export function getStderr(error) {
|
|
42
41
|
if (error != null && typeof error === "object" && "stderr" in error) {
|
|
43
42
|
const { stderr } = error;
|
|
44
|
-
|
|
43
|
+
if (typeof stderr === "string")
|
|
44
|
+
return stderr;
|
|
45
|
+
if (Buffer.isBuffer(stderr))
|
|
46
|
+
return stderr.toString();
|
|
45
47
|
}
|
|
46
48
|
return "";
|
|
47
49
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatDiffLine(line: string): string;
|
package/dist/shared/errors.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class ValidationError extends Error {
|
|
7
7
|
readonly name = "ValidationError";
|
|
8
|
-
constructor(message: string);
|
|
8
|
+
constructor(message: string, options?: ErrorOptions);
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Thrown when a GitHub GraphQL API call fails.
|
|
@@ -14,13 +14,16 @@ export declare class ValidationError extends Error {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class GraphQLApiError extends Error {
|
|
16
16
|
readonly name = "GraphQLApiError";
|
|
17
|
-
constructor(message: string);
|
|
17
|
+
constructor(message: string, options?: ErrorOptions);
|
|
18
18
|
}
|
|
19
19
|
export declare class SyncError extends Error {
|
|
20
20
|
readonly name = "SyncError";
|
|
21
|
-
constructor(message: string);
|
|
21
|
+
constructor(message: string, options?: ErrorOptions);
|
|
22
|
+
}
|
|
23
|
+
export interface RateLimitedError {
|
|
24
|
+
retryAfter?: number;
|
|
22
25
|
}
|
|
23
26
|
export declare class LifecycleError extends Error {
|
|
24
27
|
readonly name = "LifecycleError";
|
|
25
|
-
constructor(message: string);
|
|
28
|
+
constructor(message: string, options?: ErrorOptions);
|
|
26
29
|
}
|
package/dist/shared/errors.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export class ValidationError extends Error {
|
|
7
7
|
name = "ValidationError";
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
@@ -16,19 +16,19 @@ export class ValidationError extends Error {
|
|
|
16
16
|
*/
|
|
17
17
|
export class GraphQLApiError extends Error {
|
|
18
18
|
name = "GraphQLApiError";
|
|
19
|
-
constructor(message) {
|
|
20
|
-
super(message);
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super(message, options);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export class SyncError extends Error {
|
|
24
24
|
name = "SyncError";
|
|
25
|
-
constructor(message) {
|
|
26
|
-
super(message);
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export class LifecycleError extends Error {
|
|
30
30
|
name = "LifecycleError";
|
|
31
|
-
constructor(message) {
|
|
32
|
-
super(message);
|
|
31
|
+
constructor(message, options) {
|
|
32
|
+
super(message, options);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { ICommandExecutor } from "./command-executor.js";
|
|
2
|
-
import type { DebugWarnLog } from "./logger.js";
|
|
3
2
|
export interface GitHubApiTarget {
|
|
4
3
|
host: string;
|
|
5
4
|
owner: string;
|
|
6
5
|
}
|
|
7
|
-
interface ITokenManager {
|
|
8
|
-
getTokenForRepo(repoInfo: GitHubApiTarget): Promise<string | null>;
|
|
9
|
-
}
|
|
10
6
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
7
|
export interface GhApiOptions {
|
|
12
8
|
token?: string;
|
|
@@ -20,10 +16,10 @@ interface GhApiCallParams {
|
|
|
20
16
|
_retryDelay?: (ms: number) => Promise<void>;
|
|
21
17
|
}
|
|
22
18
|
/**
|
|
23
|
-
*
|
|
24
|
-
* Returns "--hostname HOST
|
|
19
|
+
* Build the hostname args for gh commands.
|
|
20
|
+
* Returns ["--hostname", HOST] for GHE, empty array for github.com.
|
|
25
21
|
*/
|
|
26
|
-
export declare function
|
|
22
|
+
export declare function buildHostnameArgs(repoInfo: Pick<GitHubApiTarget, "host">): string[];
|
|
27
23
|
export declare function buildTokenEnv(token?: string): Record<string, string> | undefined;
|
|
28
24
|
/**
|
|
29
25
|
* Strips HTTP response headers from `gh api --include` output.
|
|
@@ -31,13 +27,6 @@ export declare function buildTokenEnv(token?: string): Record<string, string> |
|
|
|
31
27
|
* If no blank line is found, returns the full string (no headers present).
|
|
32
28
|
*/
|
|
33
29
|
export declare function parseResponseBody(raw: string): string;
|
|
34
|
-
/**
|
|
35
|
-
* Parses Retry-After header from an exec error's stdout and attaches it
|
|
36
|
-
* as error.retryAfter (number of seconds). Only extracts the numeric value
|
|
37
|
-
* to avoid leaking tokens from other headers.
|
|
38
|
-
*
|
|
39
|
-
* No-op if stdout is absent or does not contain a numeric Retry-After header.
|
|
40
|
-
*/
|
|
41
30
|
export declare function attachRetryAfter(error: unknown): void;
|
|
42
31
|
/**
|
|
43
32
|
* Extracts GitHub API validation error details from `gh api --include` stdout
|
|
@@ -59,24 +48,4 @@ export declare class GhApiClient {
|
|
|
59
48
|
constructor(executor: ICommandExecutor, retries: number, cwd: string);
|
|
60
49
|
call(method: HttpMethod, endpoint: string, params?: GhApiCallParams): Promise<string>;
|
|
61
50
|
}
|
|
62
|
-
interface ResolveGitHubTokenOptions {
|
|
63
|
-
repoInfo: GitHubApiTarget;
|
|
64
|
-
tokenManager: ITokenManager | null;
|
|
65
|
-
context: string;
|
|
66
|
-
log?: DebugWarnLog;
|
|
67
|
-
envToken?: string;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
71
|
-
* Returns { token, skipped } where skipped=true means no App installation found
|
|
72
|
-
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
73
|
-
*/
|
|
74
|
-
export declare function resolveGitHubToken(options: ResolveGitHubTokenOptions): Promise<{
|
|
75
|
-
token: string | undefined;
|
|
76
|
-
skipped: boolean;
|
|
77
|
-
}>;
|
|
78
|
-
/**
|
|
79
|
-
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
80
|
-
*/
|
|
81
|
-
export declare function isHttp404Error(error: unknown): boolean;
|
|
82
51
|
export {};
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { escapeShellArg } from "./shell-utils.js";
|
|
2
1
|
import { withRetry } from "./retry-utils.js";
|
|
3
|
-
import { toErrorMessage } from "./type-guards.js";
|
|
4
2
|
/**
|
|
5
|
-
*
|
|
6
|
-
* Returns "--hostname HOST
|
|
3
|
+
* Build the hostname args for gh commands.
|
|
4
|
+
* Returns ["--hostname", HOST] for GHE, empty array for github.com.
|
|
7
5
|
*/
|
|
8
|
-
export function
|
|
6
|
+
export function buildHostnameArgs(repoInfo) {
|
|
9
7
|
if (repoInfo.host !== "github.com") {
|
|
10
|
-
return
|
|
8
|
+
return ["--hostname", repoInfo.host];
|
|
11
9
|
}
|
|
12
|
-
return
|
|
10
|
+
return [];
|
|
13
11
|
}
|
|
14
12
|
export function buildTokenEnv(token) {
|
|
15
13
|
return token ? { GH_TOKEN: token } : undefined;
|
|
@@ -38,10 +36,17 @@ export function parseResponseBody(raw) {
|
|
|
38
36
|
*
|
|
39
37
|
* No-op if stdout is absent or does not contain a numeric Retry-After header.
|
|
40
38
|
*/
|
|
39
|
+
function hasStdout(error) {
|
|
40
|
+
return (typeof error === "object" &&
|
|
41
|
+
error !== null &&
|
|
42
|
+
"stdout" in error &&
|
|
43
|
+
(typeof error.stdout === "string" ||
|
|
44
|
+
Buffer.isBuffer(error.stdout)));
|
|
45
|
+
}
|
|
41
46
|
export function attachRetryAfter(error) {
|
|
42
|
-
|
|
43
|
-
if (!stdout)
|
|
47
|
+
if (!hasStdout(error))
|
|
44
48
|
return;
|
|
49
|
+
const stdout = error.stdout;
|
|
45
50
|
const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
|
|
46
51
|
const match = stdoutStr.match(/^retry-after:\s*(\d+)\s*$/im);
|
|
47
52
|
if (match) {
|
|
@@ -57,9 +62,9 @@ export function attachRetryAfter(error) {
|
|
|
57
62
|
* No-op if stdout is absent or does not contain parseable error JSON.
|
|
58
63
|
*/
|
|
59
64
|
export function attachValidationDetails(error) {
|
|
60
|
-
|
|
61
|
-
if (!stdout)
|
|
65
|
+
if (!hasStdout(error))
|
|
62
66
|
return;
|
|
67
|
+
const stdout = error.stdout;
|
|
63
68
|
const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
|
|
64
69
|
const body = parseResponseBody(stdoutStr);
|
|
65
70
|
try {
|
|
@@ -93,7 +98,7 @@ export function attachValidationDetails(error) {
|
|
|
93
98
|
*/
|
|
94
99
|
async function ghApiCall(method, endpoint, opts) {
|
|
95
100
|
const { executor, retries, cwd, apiOpts, payload, paginate } = opts;
|
|
96
|
-
const args = ["
|
|
101
|
+
const args = ["api"];
|
|
97
102
|
if (method !== "GET") {
|
|
98
103
|
args.push("-X", method);
|
|
99
104
|
}
|
|
@@ -104,14 +109,13 @@ async function ghApiCall(method, endpoint, opts) {
|
|
|
104
109
|
args.push("--include");
|
|
105
110
|
}
|
|
106
111
|
if (apiOpts?.host && apiOpts.host !== "github.com") {
|
|
107
|
-
args.push("--hostname",
|
|
112
|
+
args.push("--hostname", apiOpts.host);
|
|
108
113
|
}
|
|
109
|
-
args.push(
|
|
110
|
-
const baseCommand = args.join(" ");
|
|
114
|
+
args.push(endpoint);
|
|
111
115
|
const env = buildTokenEnv(apiOpts?.token);
|
|
112
|
-
const execAndParse = async (
|
|
116
|
+
const execAndParse = async (execArgs, execOptions) => {
|
|
113
117
|
try {
|
|
114
|
-
const raw = await executor.exec(
|
|
118
|
+
const raw = await executor.exec("gh", execArgs, cwd, execOptions);
|
|
115
119
|
return paginate ? raw : parseResponseBody(raw);
|
|
116
120
|
}
|
|
117
121
|
catch (error) {
|
|
@@ -129,10 +133,9 @@ async function ghApiCall(method, endpoint, opts) {
|
|
|
129
133
|
if (payload &&
|
|
130
134
|
(method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
131
135
|
const payloadJson = JSON.stringify(payload);
|
|
132
|
-
|
|
133
|
-
return withRetry(() => execAndParse(command), retryOpts);
|
|
136
|
+
return withRetry(() => execAndParse([...args, "--input", "-"], { env, input: payloadJson }), retryOpts);
|
|
134
137
|
}
|
|
135
|
-
return withRetry(() => execAndParse(
|
|
138
|
+
return withRetry(() => execAndParse(args, { env }), retryOpts);
|
|
136
139
|
}
|
|
137
140
|
/**
|
|
138
141
|
* Encapsulates executor + retries for GitHub API calls.
|
|
@@ -159,36 +162,3 @@ export class GhApiClient {
|
|
|
159
162
|
});
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
164
|
-
* Returns { token, skipped } where skipped=true means no App installation found
|
|
165
|
-
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
166
|
-
*/
|
|
167
|
-
export async function resolveGitHubToken(options) {
|
|
168
|
-
const { repoInfo, tokenManager, context, log, envToken } = options;
|
|
169
|
-
try {
|
|
170
|
-
const appToken = await tokenManager?.getTokenForRepo(repoInfo);
|
|
171
|
-
if (appToken === null) {
|
|
172
|
-
// null = no installation found for this owner
|
|
173
|
-
return { token: undefined, skipped: true };
|
|
174
|
-
}
|
|
175
|
-
// string = app token; undefined = no manager configured
|
|
176
|
-
return { token: appToken ?? envToken, skipped: false };
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
const errorMsg = `GitHub App token resolution failed for ${context}: ${toErrorMessage(error)}`;
|
|
180
|
-
if (envToken) {
|
|
181
|
-
log?.debug(`${errorMsg}; falling back to GH_TOKEN`);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
log?.warn(`${errorMsg}; no fallback token available`);
|
|
185
|
-
}
|
|
186
|
-
return { token: envToken, skipped: false };
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
191
|
-
*/
|
|
192
|
-
export function isHttp404Error(error) {
|
|
193
|
-
return toErrorMessage(error).includes("HTTP 404");
|
|
194
|
-
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DebugWarnLog } from "./logger.js";
|
|
2
|
+
import type { GitHubApiTarget } from "./gh-api-utils.js";
|
|
3
|
+
interface ITokenManager {
|
|
4
|
+
getTokenForRepo(repoInfo: GitHubApiTarget): Promise<string | null>;
|
|
5
|
+
}
|
|
6
|
+
export interface ResolveGitHubTokenOptions {
|
|
7
|
+
repoInfo: GitHubApiTarget;
|
|
8
|
+
tokenManager: ITokenManager | null;
|
|
9
|
+
context: string;
|
|
10
|
+
log?: DebugWarnLog;
|
|
11
|
+
envToken?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
15
|
+
* Returns { token, skipped } where skipped=true means no App installation found
|
|
16
|
+
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveGitHubToken(options: ResolveGitHubTokenOptions): Promise<{
|
|
19
|
+
token: string | undefined;
|
|
20
|
+
skipped: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isHttp404Error(error: unknown): boolean;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { toErrorMessage } from "./type-guards.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
4
|
+
* Returns { token, skipped } where skipped=true means no App installation found
|
|
5
|
+
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
6
|
+
*/
|
|
7
|
+
export async function resolveGitHubToken(options) {
|
|
8
|
+
const { repoInfo, tokenManager, context, log, envToken } = options;
|
|
9
|
+
try {
|
|
10
|
+
const appToken = await tokenManager?.getTokenForRepo(repoInfo);
|
|
11
|
+
if (appToken === null) {
|
|
12
|
+
return { token: undefined, skipped: true };
|
|
13
|
+
}
|
|
14
|
+
return { token: appToken ?? envToken, skipped: false };
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const errorMsg = `GitHub App token resolution failed for ${context}: ${toErrorMessage(error)}`;
|
|
18
|
+
if (envToken) {
|
|
19
|
+
log?.debug(`${errorMsg}; falling back to GH_TOKEN`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
log?.warn(`${errorMsg}; no fallback token available`);
|
|
23
|
+
}
|
|
24
|
+
return { token: envToken, skipped: false };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
29
|
+
*/
|
|
30
|
+
export function isHttp404Error(error) {
|
|
31
|
+
return toErrorMessage(error).includes("HTTP 404");
|
|
32
|
+
}
|
|
@@ -11,6 +11,6 @@ export function parseApiJson(response, context) {
|
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
13
|
const preview = response.slice(0, 200);
|
|
14
|
-
throw new SyncError(`Failed to parse ${context}: ${toErrorMessage(error)} — ${preview}
|
|
14
|
+
throw new SyncError(`Failed to parse ${context}: ${toErrorMessage(error)} — ${preview}`, { cause: error });
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function escapeRegExp(str: string): string;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Auth failures, permission issues, and resource-not-found errors.
|
|
4
4
|
*/
|
|
5
5
|
export declare const CORE_PERMANENT_ERROR_PATTERNS: RegExp[];
|
|
6
|
+
export declare const BRANCH_PROTECTION_ERROR_PATTERNS: RegExp[];
|
|
6
7
|
/**
|
|
7
8
|
* Default patterns indicating permanent errors that should NOT be retried.
|
|
8
9
|
* Extends CORE_PERMANENT_ERROR_PATTERNS with git-CLI-specific patterns.
|