@aspruyt/xfg 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +0 -6
- package/dist/cli/program.js +3 -2
- package/dist/cli/settings-report-builder.js +4 -4
- package/dist/cli/sync-command.js +72 -36
- package/dist/cli/sync-report-builder.d.ts +2 -6
- package/dist/cli/types.d.ts +2 -14
- package/dist/cli/types.js +1 -9
- package/dist/config/file-reference-resolver.js +13 -23
- package/dist/config/formatter.d.ts +0 -6
- package/dist/config/formatter.js +0 -9
- package/dist/config/index.d.ts +1 -2
- package/dist/config/index.js +0 -2
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +3 -3
- package/dist/config/normalizer.d.ts +1 -1
- package/dist/config/normalizer.js +44 -57
- package/dist/config/validator.d.ts +1 -1
- package/dist/config/validator.js +120 -121
- package/dist/config/validators/file-validator.d.ts +2 -4
- package/dist/config/validators/file-validator.js +3 -7
- package/dist/config/validators/repo-settings-validator.js +1 -1
- package/dist/config/validators/ruleset-validator.js +28 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +0 -1
- package/dist/lifecycle/ado-migration-source.d.ts +2 -1
- package/dist/lifecycle/ado-migration-source.js +7 -5
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -3
- package/dist/lifecycle/github-lifecycle-provider.js +29 -19
- package/dist/lifecycle/lifecycle-formatter.js +2 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +5 -1
- package/dist/lifecycle/lifecycle-helpers.js +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +4 -4
- package/dist/lifecycle/repo-lifecycle-factory.js +12 -9
- package/dist/lifecycle/repo-lifecycle-manager.d.ts +4 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +11 -7
- package/dist/lifecycle/types.d.ts +0 -15
- package/dist/output/github-summary.d.ts +6 -5
- package/dist/output/github-summary.js +36 -52
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.d.ts +2 -12
- package/dist/output/lifecycle-report.js +18 -35
- package/dist/output/settings-report.d.ts +4 -4
- package/dist/output/settings-report.js +6 -6
- package/dist/output/sync-report.d.ts +4 -6
- package/dist/output/sync-report.js +2 -2
- package/dist/output/unified-summary.d.ts +1 -0
- package/dist/output/unified-summary.js +8 -8
- package/dist/settings/base-processor.d.ts +1 -1
- package/dist/settings/base-processor.js +1 -1
- package/dist/settings/index.d.ts +3 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/diff.js +3 -2
- package/dist/settings/labels/formatter.js +3 -3
- package/dist/settings/labels/github-labels-strategy.d.ts +2 -23
- package/dist/settings/labels/github-labels-strategy.js +8 -28
- package/dist/settings/labels/index.d.ts +1 -0
- package/dist/settings/labels/index.js +2 -0
- package/dist/settings/labels/processor.d.ts +2 -2
- package/dist/settings/labels/processor.js +3 -4
- package/dist/settings/labels/types.d.ts +0 -3
- package/dist/settings/repo-settings/diff.d.ts +1 -1
- package/dist/settings/repo-settings/diff.js +2 -2
- package/dist/settings/repo-settings/formatter.d.ts +1 -1
- package/dist/settings/repo-settings/formatter.js +4 -4
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -7
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +9 -17
- package/dist/settings/repo-settings/index.d.ts +1 -0
- package/dist/settings/repo-settings/index.js +2 -0
- package/dist/settings/repo-settings/processor.d.ts +2 -2
- package/dist/settings/repo-settings/processor.js +5 -6
- package/dist/settings/repo-settings/types.d.ts +9 -13
- package/dist/settings/repo-settings/types.js +1 -14
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -1
- package/dist/settings/rulesets/diff-algorithm.js +6 -8
- package/dist/settings/rulesets/formatter.js +15 -51
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -20
- package/dist/settings/rulesets/github-ruleset-strategy.js +6 -30
- package/dist/settings/rulesets/index.d.ts +2 -1
- package/dist/settings/rulesets/index.js +3 -1
- package/dist/settings/rulesets/processor.d.ts +2 -2
- package/dist/settings/rulesets/processor.js +3 -4
- package/dist/{vcs → shared}/branch-utils.js +5 -4
- package/dist/shared/command-executor.d.ts +2 -1
- package/dist/shared/command-executor.js +9 -5
- package/dist/shared/env.d.ts +6 -6
- package/dist/shared/env.js +10 -17
- package/dist/shared/errors.d.ts +26 -0
- package/dist/shared/errors.js +34 -0
- package/dist/shared/gh-api-utils.d.ts +21 -14
- package/dist/shared/gh-api-utils.js +33 -22
- package/dist/shared/index.d.ts +9 -2
- package/dist/shared/index.js +16 -2
- package/dist/shared/logger.d.ts +24 -1
- package/dist/shared/logger.js +8 -3
- package/dist/shared/repo-detector.js +9 -11
- package/dist/shared/retry-utils.d.ts +5 -7
- package/dist/shared/retry-utils.js +3 -10
- package/dist/shared/shell-utils.d.ts +0 -3
- package/dist/shared/shell-utils.js +2 -4
- package/dist/shared/type-guards.d.ts +2 -9
- package/dist/shared/type-guards.js +0 -6
- package/dist/shared/xfg-template.d.ts +2 -2
- package/dist/shared/xfg-template.js +2 -1
- package/dist/sync/auth-options-builder.d.ts +3 -2
- package/dist/sync/auth-options-builder.js +14 -10
- package/dist/sync/branch-manager.d.ts +12 -7
- package/dist/sync/branch-manager.js +4 -7
- package/dist/sync/commit-message.d.ts +1 -1
- package/dist/sync/commit-push-manager.d.ts +8 -2
- package/dist/sync/commit-push-manager.js +6 -5
- package/dist/sync/file-sync-orchestrator.js +17 -21
- package/dist/sync/file-writer.js +3 -5
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/manifest-manager.d.ts +1 -0
- package/dist/sync/manifest.d.ts +4 -7
- package/dist/sync/manifest.js +42 -45
- package/dist/sync/repository-processor.d.ts +5 -2
- package/dist/sync/repository-processor.js +11 -17
- package/dist/sync/repository-session.js +2 -1
- package/dist/sync/sync-workflow.d.ts +2 -2
- package/dist/sync/sync-workflow.js +16 -23
- package/dist/sync/types.d.ts +20 -25
- package/dist/vcs/authenticated-git-ops.d.ts +3 -4
- package/dist/vcs/authenticated-git-ops.js +5 -1
- package/dist/vcs/azure-pr-strategy.d.ts +6 -1
- package/dist/vcs/azure-pr-strategy.js +38 -31
- package/dist/vcs/commit-strategy-selector.d.ts +10 -19
- package/dist/vcs/commit-strategy-selector.js +8 -24
- package/dist/vcs/git-commit-strategy.d.ts +1 -1
- package/dist/vcs/git-commit-strategy.js +1 -3
- package/dist/vcs/git-ops.d.ts +4 -8
- package/dist/vcs/git-ops.js +18 -22
- package/dist/vcs/github-app-token-manager.js +9 -8
- package/dist/vcs/github-pr-strategy.js +18 -11
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +14 -7
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -7
- package/dist/vcs/graphql-commit-strategy.js +24 -32
- package/dist/vcs/index.d.ts +2 -1
- package/dist/vcs/pr-creator.d.ts +6 -9
- package/dist/vcs/pr-strategy-factory.d.ts +1 -1
- package/dist/vcs/pr-strategy-factory.js +2 -1
- package/dist/vcs/pr-strategy.d.ts +1 -1
- package/dist/vcs/pr-strategy.js +2 -3
- package/dist/vcs/types.d.ts +6 -10
- package/package.json +2 -2
- package/dist/config/errors.d.ts +0 -9
- package/dist/config/errors.js +0 -11
- /package/dist/{vcs → shared}/branch-utils.d.ts +0 -0
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { sanitizeCredentials } from "./sanitize-utils.js";
|
|
3
3
|
export class ShellCommandExecutor {
|
|
4
|
+
baseEnv;
|
|
5
|
+
constructor(baseEnv) {
|
|
6
|
+
this.baseEnv = baseEnv;
|
|
7
|
+
}
|
|
4
8
|
async exec(command, cwd, options) {
|
|
5
9
|
try {
|
|
6
10
|
return execSync(command, {
|
|
7
11
|
cwd,
|
|
8
12
|
encoding: "utf-8",
|
|
9
13
|
stdio: ["pipe", "pipe", "pipe"],
|
|
10
|
-
env: options?.env
|
|
14
|
+
env: options?.env
|
|
15
|
+
? { ...this.baseEnv, ...options.env }
|
|
16
|
+
: this.baseEnv,
|
|
11
17
|
}).trim();
|
|
12
18
|
}
|
|
13
19
|
catch (error) {
|
|
14
|
-
//
|
|
20
|
+
// Normalise and sanitise the exec error so downstream retry logic
|
|
21
|
+
// sees a string stderr with no raw credentials.
|
|
15
22
|
const execError = error;
|
|
16
23
|
if (execError.stderr && typeof execError.stderr !== "string") {
|
|
17
24
|
execError.stderr = execError.stderr.toString();
|
|
18
25
|
}
|
|
19
|
-
// Sanitize credentials from stderr before including in error
|
|
20
26
|
if (execError.stderr) {
|
|
21
27
|
execError.stderr = sanitizeCredentials(execError.stderr);
|
|
22
28
|
}
|
|
23
|
-
// Include sanitized stderr in error message for better debugging
|
|
24
29
|
if (execError.stderr && execError.message) {
|
|
25
30
|
execError.message =
|
|
26
31
|
sanitizeCredentials(execError.message) + "\n" + execError.stderr;
|
|
@@ -32,7 +37,6 @@ export class ShellCommandExecutor {
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
|
-
export const defaultExecutor = new ShellCommandExecutor();
|
|
36
40
|
/** Extract stderr string from an exec error (child_process errors attach stderr). */
|
|
37
41
|
export function getStderr(error) {
|
|
38
42
|
if (error != null && typeof error === "object" && "stderr" in error) {
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface EnvInterpolationOptions {
|
|
|
9
9
|
* and has no default value. If false, leaves the placeholder as-is.
|
|
10
10
|
*/
|
|
11
11
|
strict: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Environment variables to resolve from.
|
|
14
|
+
*/
|
|
15
|
+
env: Record<string, string | undefined>;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* Interpolate environment variables in a JSON object.
|
|
@@ -18,14 +22,10 @@ export interface EnvInterpolationOptions {
|
|
|
18
22
|
* - ${VAR:-default} - Replace with env value, or use default if missing
|
|
19
23
|
* - ${VAR:?message} - Replace with env value, or throw error with message if missing
|
|
20
24
|
* - $${VAR} - Escape: outputs literal ${VAR} without interpolation
|
|
21
|
-
*
|
|
22
|
-
* @param json - The JSON object to process
|
|
23
|
-
* @param options - Interpolation options (default: strict mode)
|
|
24
|
-
* @returns A new object with interpolated values
|
|
25
25
|
*/
|
|
26
|
-
export declare function interpolateEnvVars(json: Record<string, unknown>, options
|
|
26
|
+
export declare function interpolateEnvVars(json: Record<string, unknown>, options: EnvInterpolationOptions): Record<string, unknown>;
|
|
27
27
|
/**
|
|
28
28
|
* Interpolate environment variables in content of any supported type.
|
|
29
29
|
* Handles objects, strings, and string arrays.
|
|
30
30
|
*/
|
|
31
|
-
export declare function interpolateContent(content: Record<string, unknown> | string | string[], options
|
|
31
|
+
export declare function interpolateContent(content: Record<string, unknown> | string | string[], options: EnvInterpolationOptions): Record<string, unknown> | string | string[];
|
package/dist/shared/env.js
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
* Use $${VAR} to escape and output literal ${VAR}.
|
|
5
5
|
*/
|
|
6
6
|
import { interpolateString, interpolateValue, } from "./interpolation-engine.js";
|
|
7
|
-
|
|
8
|
-
strict: true,
|
|
9
|
-
};
|
|
7
|
+
import { ValidationError } from "./errors.js";
|
|
10
8
|
/**
|
|
11
9
|
* Regex to match environment variable placeholders.
|
|
12
10
|
* Captures:
|
|
@@ -29,26 +27,25 @@ const ENV_VAR_REGEX = /\$\{([A-Za-z_][A-Za-z0-9_.]*)(?::([?-])([^}]*))?\}/g;
|
|
|
29
27
|
*/
|
|
30
28
|
const ESCAPED_VAR_REGEX = /\$\$\{((?!xfg:)[^}]+)\}/g;
|
|
31
29
|
function buildEnvConfig(options) {
|
|
30
|
+
const envSource = options.env;
|
|
32
31
|
function resolveEnvVar(match, varName, modifier, defaultOrMsg) {
|
|
33
|
-
|
|
34
|
-
//
|
|
32
|
+
// Resolution follows bash parameter expansion semantics:
|
|
33
|
+
// ${VAR} → value or error, ${VAR:-fallback} → value or fallback,
|
|
34
|
+
// ${VAR:?msg} → value or throw with msg.
|
|
35
|
+
const envValue = envSource[varName];
|
|
35
36
|
if (envValue !== undefined) {
|
|
36
37
|
return envValue;
|
|
37
38
|
}
|
|
38
|
-
// Has default value (:-default)
|
|
39
39
|
if (modifier === "-") {
|
|
40
40
|
return defaultOrMsg ?? "";
|
|
41
41
|
}
|
|
42
|
-
// Required with message (:?message)
|
|
43
42
|
if (modifier === "?") {
|
|
44
43
|
const message = defaultOrMsg || `is required`;
|
|
45
|
-
throw new
|
|
44
|
+
throw new ValidationError(`${varName}: ${message}`);
|
|
46
45
|
}
|
|
47
|
-
// No modifier - check strictness
|
|
48
46
|
if (options.strict) {
|
|
49
|
-
throw new
|
|
47
|
+
throw new ValidationError(`Missing required environment variable: ${varName}`);
|
|
50
48
|
}
|
|
51
|
-
// Non-strict mode - leave placeholder as-is
|
|
52
49
|
return match;
|
|
53
50
|
}
|
|
54
51
|
return {
|
|
@@ -66,19 +63,15 @@ function buildEnvConfig(options) {
|
|
|
66
63
|
* - ${VAR:-default} - Replace with env value, or use default if missing
|
|
67
64
|
* - ${VAR:?message} - Replace with env value, or throw error with message if missing
|
|
68
65
|
* - $${VAR} - Escape: outputs literal ${VAR} without interpolation
|
|
69
|
-
*
|
|
70
|
-
* @param json - The JSON object to process
|
|
71
|
-
* @param options - Interpolation options (default: strict mode)
|
|
72
|
-
* @returns A new object with interpolated values
|
|
73
66
|
*/
|
|
74
|
-
export function interpolateEnvVars(json, options
|
|
67
|
+
export function interpolateEnvVars(json, options) {
|
|
75
68
|
return interpolateValue(json, buildEnvConfig(options));
|
|
76
69
|
}
|
|
77
70
|
/**
|
|
78
71
|
* Interpolate environment variables in content of any supported type.
|
|
79
72
|
* Handles objects, strings, and string arrays.
|
|
80
73
|
*/
|
|
81
|
-
export function interpolateContent(content, options
|
|
74
|
+
export function interpolateContent(content, options) {
|
|
82
75
|
const config = buildEnvConfig(options);
|
|
83
76
|
if (typeof content === "string") {
|
|
84
77
|
return interpolateString(content, config);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when config validation fails.
|
|
3
|
+
* Distinguishable from I/O errors by type, so callers and retry logic
|
|
4
|
+
* can treat validation failures as permanent without message-parsing.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ValidationError extends Error {
|
|
7
|
+
readonly name = "ValidationError";
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a GitHub GraphQL API call fails.
|
|
12
|
+
* Standardizes all GraphQL error messages under one type so catch blocks
|
|
13
|
+
* and retry logic can identify them without message-parsing.
|
|
14
|
+
*/
|
|
15
|
+
export declare class GraphQLApiError extends Error {
|
|
16
|
+
readonly name = "GraphQLApiError";
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class SyncError extends Error {
|
|
20
|
+
readonly name = "SyncError";
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class LifecycleError extends Error {
|
|
24
|
+
readonly name = "LifecycleError";
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when config validation fails.
|
|
3
|
+
* Distinguishable from I/O errors by type, so callers and retry logic
|
|
4
|
+
* can treat validation failures as permanent without message-parsing.
|
|
5
|
+
*/
|
|
6
|
+
export class ValidationError extends Error {
|
|
7
|
+
name = "ValidationError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when a GitHub GraphQL API call fails.
|
|
14
|
+
* Standardizes all GraphQL error messages under one type so catch blocks
|
|
15
|
+
* and retry logic can identify them without message-parsing.
|
|
16
|
+
*/
|
|
17
|
+
export class GraphQLApiError extends Error {
|
|
18
|
+
name = "GraphQLApiError";
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class SyncError extends Error {
|
|
24
|
+
name = "SyncError";
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class LifecycleError extends Error {
|
|
30
|
+
name = "LifecycleError";
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { ICommandExecutor } from "
|
|
2
|
-
import type { GitHubRepoInfo } from "
|
|
3
|
-
import type {
|
|
1
|
+
import type { ICommandExecutor } from "./command-executor.js";
|
|
2
|
+
import type { GitHubRepoInfo } from "./repo-detector.js";
|
|
3
|
+
import type { DebugLog } from "./logger.js";
|
|
4
|
+
interface ITokenManager {
|
|
5
|
+
getTokenForRepo(repoInfo: GitHubRepoInfo): Promise<string | null>;
|
|
6
|
+
}
|
|
4
7
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
5
8
|
export interface GhApiOptions {
|
|
6
9
|
token?: string;
|
|
7
10
|
host?: string;
|
|
8
11
|
}
|
|
12
|
+
interface GhApiCallParams {
|
|
13
|
+
payload?: unknown;
|
|
14
|
+
options?: GhApiOptions;
|
|
15
|
+
paginate?: boolean;
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* Get the hostname flag for gh commands.
|
|
11
19
|
* Returns "--hostname HOST" for GHE, empty string for github.com.
|
|
@@ -19,28 +27,27 @@ export declare function buildTokenEnv(token?: string): Record<string, string> |
|
|
|
19
27
|
export declare class GhApiClient {
|
|
20
28
|
private readonly executor;
|
|
21
29
|
private readonly retries;
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
private readonly cwd;
|
|
31
|
+
constructor(executor: ICommandExecutor, retries: number, cwd: string);
|
|
32
|
+
call(method: HttpMethod, endpoint: string, params?: GhApiCallParams): Promise<string>;
|
|
24
33
|
}
|
|
25
34
|
/**
|
|
26
|
-
* Resolve a GitHub token for a repo: GitHub App token →
|
|
35
|
+
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
27
36
|
* Returns { token, skipped } where skipped=true means no App installation found
|
|
28
|
-
*
|
|
37
|
+
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
29
38
|
*/
|
|
30
|
-
export declare function resolveGitHubToken(repoInfo: GitHubRepoInfo, tokenManager:
|
|
31
|
-
debug(msg: string): void;
|
|
32
|
-
}, envToken?: string): Promise<{
|
|
39
|
+
export declare function resolveGitHubToken(repoInfo: GitHubRepoInfo, tokenManager: ITokenManager | null, context: string, log?: DebugLog, envToken?: string): Promise<{
|
|
33
40
|
token: string | undefined;
|
|
34
41
|
skipped: boolean;
|
|
35
42
|
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isHttp404Error(error: unknown): boolean;
|
|
36
47
|
/**
|
|
37
48
|
* Parse a JSON API response with a contextual error message.
|
|
38
49
|
* Wraps JSON.parse so callers get "Failed to parse <context>: ..." instead of
|
|
39
50
|
* a bare "Unexpected token" SyntaxError.
|
|
40
51
|
*/
|
|
41
|
-
/**
|
|
42
|
-
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
43
|
-
*/
|
|
44
|
-
export declare function isHttp404Error(error: unknown): boolean;
|
|
45
52
|
export declare function parseApiJson<T>(response: string, context: string): T;
|
|
46
53
|
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { escapeShellArg } from "
|
|
2
|
-
import { withRetry } from "
|
|
3
|
-
import { toErrorMessage } from "
|
|
1
|
+
import { escapeShellArg } from "./shell-utils.js";
|
|
2
|
+
import { withRetry } from "./retry-utils.js";
|
|
3
|
+
import { toErrorMessage } from "./type-guards.js";
|
|
4
|
+
import { SyncError } from "./errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* Get the hostname flag for gh commands.
|
|
6
7
|
* Returns "--hostname HOST" for GHE, empty string for github.com.
|
|
7
8
|
*/
|
|
8
9
|
export function getHostnameFlag(repoInfo) {
|
|
9
|
-
if (repoInfo.host
|
|
10
|
+
if (repoInfo.host !== "github.com") {
|
|
10
11
|
return `--hostname ${escapeShellArg(repoInfo.host)}`;
|
|
11
12
|
}
|
|
12
13
|
return "";
|
|
@@ -22,7 +23,7 @@ export function buildTokenEnv(token) {
|
|
|
22
23
|
* rather than shell-prefix string interpolation.
|
|
23
24
|
*/
|
|
24
25
|
async function ghApiCall(method, endpoint, opts) {
|
|
25
|
-
const { executor, retries, apiOpts, payload, paginate } = opts;
|
|
26
|
+
const { executor, retries, cwd, apiOpts, payload, paginate } = opts;
|
|
26
27
|
const args = ["gh", "api"];
|
|
27
28
|
if (method !== "GET") {
|
|
28
29
|
args.push("-X", method);
|
|
@@ -40,9 +41,13 @@ async function ghApiCall(method, endpoint, opts) {
|
|
|
40
41
|
(method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
41
42
|
const payloadJson = JSON.stringify(payload);
|
|
42
43
|
const command = `echo ${escapeShellArg(payloadJson)} | ${baseCommand} --input -`;
|
|
43
|
-
return await withRetry(() => executor.exec(command,
|
|
44
|
+
return await withRetry(() => executor.exec(command, cwd, { env }), {
|
|
45
|
+
retries,
|
|
46
|
+
});
|
|
44
47
|
}
|
|
45
|
-
return await withRetry(() => executor.exec(baseCommand,
|
|
48
|
+
return await withRetry(() => executor.exec(baseCommand, cwd, { env }), {
|
|
49
|
+
retries,
|
|
50
|
+
});
|
|
46
51
|
}
|
|
47
52
|
/**
|
|
48
53
|
* Encapsulates executor + retries for GitHub API calls.
|
|
@@ -51,24 +56,27 @@ async function ghApiCall(method, endpoint, opts) {
|
|
|
51
56
|
export class GhApiClient {
|
|
52
57
|
executor;
|
|
53
58
|
retries;
|
|
54
|
-
|
|
59
|
+
cwd;
|
|
60
|
+
constructor(executor, retries, cwd) {
|
|
55
61
|
this.executor = executor;
|
|
56
62
|
this.retries = retries;
|
|
63
|
+
this.cwd = cwd;
|
|
57
64
|
}
|
|
58
|
-
async call(method, endpoint,
|
|
65
|
+
async call(method, endpoint, params) {
|
|
59
66
|
return ghApiCall(method, endpoint, {
|
|
60
67
|
executor: this.executor,
|
|
61
68
|
retries: this.retries,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
cwd: this.cwd,
|
|
70
|
+
apiOpts: params?.options,
|
|
71
|
+
payload: params?.payload,
|
|
72
|
+
paginate: params?.paginate,
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
/**
|
|
69
|
-
* Resolve a GitHub token for a repo: GitHub App token →
|
|
77
|
+
* Resolve a GitHub token for a repo: GitHub App token → envToken fallback.
|
|
70
78
|
* Returns { token, skipped } where skipped=true means no App installation found
|
|
71
|
-
*
|
|
79
|
+
* for this owner (token will be undefined). Both sync and settings paths use this.
|
|
72
80
|
*/
|
|
73
81
|
export async function resolveGitHubToken(repoInfo, tokenManager, context, log, envToken) {
|
|
74
82
|
try {
|
|
@@ -81,27 +89,30 @@ export async function resolveGitHubToken(repoInfo, tokenManager, context, log, e
|
|
|
81
89
|
return { token: appToken ?? envToken, skipped: false };
|
|
82
90
|
}
|
|
83
91
|
catch (error) {
|
|
84
|
-
|
|
92
|
+
const fallbackDesc = envToken
|
|
93
|
+
? "falling back to GH_TOKEN"
|
|
94
|
+
: "no fallback token available";
|
|
95
|
+
log?.debug(`GitHub App token resolution failed for ${context}: ${toErrorMessage(error)}; ${fallbackDesc}`);
|
|
85
96
|
return { token: envToken, skipped: false };
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Parse a JSON API response with a contextual error message.
|
|
90
|
-
* Wraps JSON.parse so callers get "Failed to parse <context>: ..." instead of
|
|
91
|
-
* a bare "Unexpected token" SyntaxError.
|
|
92
|
-
*/
|
|
93
99
|
/**
|
|
94
100
|
* Check if an error message indicates an HTTP 404 response from the GitHub API.
|
|
95
101
|
*/
|
|
96
102
|
export function isHttp404Error(error) {
|
|
97
103
|
return toErrorMessage(error).includes("HTTP 404");
|
|
98
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Parse a JSON API response with a contextual error message.
|
|
107
|
+
* Wraps JSON.parse so callers get "Failed to parse <context>: ..." instead of
|
|
108
|
+
* a bare "Unexpected token" SyntaxError.
|
|
109
|
+
*/
|
|
99
110
|
export function parseApiJson(response, context) {
|
|
100
111
|
try {
|
|
101
112
|
return JSON.parse(response);
|
|
102
113
|
}
|
|
103
|
-
catch {
|
|
114
|
+
catch (error) {
|
|
104
115
|
const preview = response.slice(0, 200);
|
|
105
|
-
throw new
|
|
116
|
+
throw new SyncError(`Failed to parse ${context}: ${toErrorMessage(error)} — ${preview}`);
|
|
106
117
|
}
|
|
107
118
|
}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
export { Logger,
|
|
1
|
+
export { Logger, NO_OP_DEBUG_LOG, type ILogger, type DebugLog, type DebugWarnLog, type DebugInfoLog, type DebugInfoWarnLog, } from "./logger.js";
|
|
2
2
|
export { withRetry, isPermanentError, isTransientError, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "./retry-utils.js";
|
|
3
|
-
export { ShellCommandExecutor,
|
|
3
|
+
export { ShellCommandExecutor, type ICommandExecutor, } from "./command-executor.js";
|
|
4
4
|
export { escapeShellArg, escapeRegExp } from "./shell-utils.js";
|
|
5
5
|
export { sanitizeCredentials } from "./sanitize-utils.js";
|
|
6
6
|
export { interpolateEnvVars, interpolateContent, type EnvInterpolationOptions, } from "./env.js";
|
|
7
7
|
export { generateWorkspaceName } from "./workspace-utils.js";
|
|
8
8
|
export { detectRepoType, parseGitUrl, getRepoDisplayName, isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, type RepoInfo, type GitHubRepoInfo, type AzureDevOpsRepoInfo, type GitLabRepoInfo, } from "./repo-detector.js";
|
|
9
|
+
export { ValidationError, GraphQLApiError, SyncError, LifecycleError, } from "./errors.js";
|
|
10
|
+
export { formatStatusBadge, type FileStatus } from "./file-status.js";
|
|
11
|
+
export { GhApiClient, getHostnameFlag, buildTokenEnv, resolveGitHubToken, isHttp404Error, parseApiJson, type GhApiOptions, } from "./gh-api-utils.js";
|
|
12
|
+
export { interpolateString, interpolateValue, type InterpolationConfig, } from "./interpolation-engine.js";
|
|
13
|
+
export { camelToSnake } from "./string-utils.js";
|
|
14
|
+
export { isPlainObject, toErrorMessage, safeCleanup } from "./type-guards.js";
|
|
15
|
+
export { interpolateXfgContent, type XfgTemplateContext, } from "./xfg-template.js";
|
package/dist/shared/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Logging
|
|
2
|
-
export { Logger,
|
|
2
|
+
export { Logger, NO_OP_DEBUG_LOG, } from "./logger.js";
|
|
3
3
|
// Retry utilities
|
|
4
4
|
export { withRetry, isPermanentError, isTransientError, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "./retry-utils.js";
|
|
5
5
|
// Command execution
|
|
6
|
-
export { ShellCommandExecutor,
|
|
6
|
+
export { ShellCommandExecutor, } from "./command-executor.js";
|
|
7
7
|
// Shell utilities
|
|
8
8
|
export { escapeShellArg, escapeRegExp } from "./shell-utils.js";
|
|
9
9
|
// Sanitization
|
|
@@ -14,3 +14,17 @@ export { interpolateEnvVars, interpolateContent, } from "./env.js";
|
|
|
14
14
|
export { generateWorkspaceName } from "./workspace-utils.js";
|
|
15
15
|
// Repository detection
|
|
16
16
|
export { detectRepoType, parseGitUrl, getRepoDisplayName, isGitHubRepo, isAzureDevOpsRepo, isGitLabRepo, } from "./repo-detector.js";
|
|
17
|
+
// Errors
|
|
18
|
+
export { ValidationError, GraphQLApiError, SyncError, LifecycleError, } from "./errors.js";
|
|
19
|
+
// File status
|
|
20
|
+
export { formatStatusBadge } from "./file-status.js";
|
|
21
|
+
// GitHub API utilities
|
|
22
|
+
export { GhApiClient, getHostnameFlag, buildTokenEnv, resolveGitHubToken, isHttp404Error, parseApiJson, } from "./gh-api-utils.js";
|
|
23
|
+
// Interpolation engine
|
|
24
|
+
export { interpolateString, interpolateValue, } from "./interpolation-engine.js";
|
|
25
|
+
// String utilities
|
|
26
|
+
export { camelToSnake } from "./string-utils.js";
|
|
27
|
+
// Type guards
|
|
28
|
+
export { isPlainObject, toErrorMessage, safeCleanup } from "./type-guards.js";
|
|
29
|
+
// XFG templating
|
|
30
|
+
export { interpolateXfgContent, } from "./xfg-template.js";
|
package/dist/shared/logger.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { FileStatus } from "./file-status.js";
|
|
2
|
+
/** Minimal log interface: debug only. */
|
|
3
|
+
export type DebugLog = {
|
|
4
|
+
debug(msg: string): void;
|
|
5
|
+
};
|
|
6
|
+
/** Log interface: debug + warn. */
|
|
7
|
+
export type DebugWarnLog = {
|
|
8
|
+
debug(msg: string): void;
|
|
9
|
+
warn(msg: string): void;
|
|
10
|
+
};
|
|
11
|
+
/** Log interface: debug + info. */
|
|
12
|
+
export type DebugInfoLog = {
|
|
13
|
+
debug(msg: string): void;
|
|
14
|
+
info(msg: string): void;
|
|
15
|
+
};
|
|
16
|
+
/** Log interface: debug + info + warn. */
|
|
17
|
+
export type DebugInfoWarnLog = {
|
|
18
|
+
debug(msg: string): void;
|
|
19
|
+
info(msg: string): void;
|
|
20
|
+
warn(msg: string): void;
|
|
21
|
+
};
|
|
2
22
|
export interface ILogger {
|
|
3
23
|
log(message: string): void;
|
|
4
24
|
info(message: string): void;
|
|
@@ -13,7 +33,9 @@ export interface ILogger {
|
|
|
13
33
|
error(current: number, repoName: string, error: string): void;
|
|
14
34
|
}
|
|
15
35
|
export declare class Logger implements ILogger {
|
|
36
|
+
private readonly debugEnabled;
|
|
16
37
|
private stats;
|
|
38
|
+
constructor(debugEnabled?: boolean);
|
|
17
39
|
log(message: string): void;
|
|
18
40
|
setTotal(total: number): void;
|
|
19
41
|
progress(current: number, repoName: string, message: string): void;
|
|
@@ -33,4 +55,5 @@ export declare class Logger implements ILogger {
|
|
|
33
55
|
*/
|
|
34
56
|
diffSummary(newCount: number, modifiedCount: number, unchangedCount: number, deletedCount?: number): void;
|
|
35
57
|
}
|
|
36
|
-
|
|
58
|
+
/** No-op debug logger for use as a fallback when logging is optional. */
|
|
59
|
+
export declare const NO_OP_DEBUG_LOG: DebugLog;
|
package/dist/shared/logger.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { formatStatusBadge } from "./file-status.js";
|
|
3
3
|
export class Logger {
|
|
4
|
+
debugEnabled;
|
|
4
5
|
stats = {
|
|
5
6
|
total: 0,
|
|
6
7
|
succeeded: 0,
|
|
7
8
|
failed: 0,
|
|
8
9
|
skipped: 0,
|
|
9
10
|
};
|
|
11
|
+
constructor(debugEnabled) {
|
|
12
|
+
this.debugEnabled = debugEnabled ?? false;
|
|
13
|
+
}
|
|
10
14
|
log(message) {
|
|
11
15
|
console.log(message);
|
|
12
16
|
}
|
|
@@ -24,7 +28,7 @@ export class Logger {
|
|
|
24
28
|
console.log(chalk.yellow(` ⚠ ${message}`));
|
|
25
29
|
}
|
|
26
30
|
debug(message) {
|
|
27
|
-
if (
|
|
31
|
+
if (this.debugEnabled) {
|
|
28
32
|
console.log(chalk.dim(` [debug] ${message}`));
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -65,7 +69,7 @@ export class Logger {
|
|
|
65
69
|
parts.push(chalk.green(`${newCount} new`));
|
|
66
70
|
if (modifiedCount > 0)
|
|
67
71
|
parts.push(chalk.yellow(`${modifiedCount} modified`));
|
|
68
|
-
if (deletedCount
|
|
72
|
+
if ((deletedCount ?? 0) > 0)
|
|
69
73
|
parts.push(chalk.red(`${deletedCount} deleted`));
|
|
70
74
|
if (unchangedCount > 0)
|
|
71
75
|
parts.push(chalk.gray(`${unchangedCount} unchanged`));
|
|
@@ -74,4 +78,5 @@ export class Logger {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
/** No-op debug logger for use as a fallback when logging is optional. */
|
|
82
|
+
export const NO_OP_DEBUG_LOG = { debug() { } };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.js";
|
|
1
2
|
// Type guards
|
|
2
3
|
export function isGitHubRepo(info) {
|
|
3
4
|
return info.type === "github";
|
|
@@ -14,7 +15,7 @@ export function isGitLabRepo(info) {
|
|
|
14
15
|
*/
|
|
15
16
|
export function assertGitHubRepo(repoInfo, context) {
|
|
16
17
|
if (!isGitHubRepo(repoInfo)) {
|
|
17
|
-
throw new
|
|
18
|
+
throw new ValidationError(`${context} requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -23,7 +24,7 @@ export function assertGitHubRepo(repoInfo, context) {
|
|
|
23
24
|
*/
|
|
24
25
|
export function assertAzureDevOpsRepo(repoInfo, context) {
|
|
25
26
|
if (!isAzureDevOpsRepo(repoInfo)) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new ValidationError(`${context} requires Azure DevOps repositories. Got: ${repoInfo.type}`);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
@@ -32,12 +33,9 @@ export function assertAzureDevOpsRepo(repoInfo, context) {
|
|
|
32
33
|
*/
|
|
33
34
|
export function assertGitLabRepo(repoInfo, context) {
|
|
34
35
|
if (!isGitLabRepo(repoInfo)) {
|
|
35
|
-
throw new
|
|
36
|
+
throw new ValidationError(`${context} requires GitLab repositories. Got: ${repoInfo.type}`);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Extract hostname from a git URL.
|
|
40
|
-
*/
|
|
41
39
|
function extractHostFromUrl(gitUrl) {
|
|
42
40
|
// SSH: git@hostname:path
|
|
43
41
|
const sshMatch = gitUrl.match(/^git@([^:]+):/);
|
|
@@ -116,7 +114,7 @@ export function detectRepoType(gitUrl, context) {
|
|
|
116
114
|
return "gitlab";
|
|
117
115
|
}
|
|
118
116
|
// Throw for unrecognized URL formats
|
|
119
|
-
throw new
|
|
117
|
+
throw new ValidationError(`Unrecognized git URL format: ${gitUrl}. Supported formats: GitHub (git@github.com: or https://github.com/), Azure DevOps (git@ssh.dev.azure.com: or https://dev.azure.com/), and GitLab (git@gitlab.com: or https://gitlab.com/)`);
|
|
120
118
|
}
|
|
121
119
|
export function parseGitUrl(gitUrl, context) {
|
|
122
120
|
const type = detectRepoType(gitUrl, context);
|
|
@@ -155,7 +153,7 @@ function parseGitHubUrl(gitUrl, host) {
|
|
|
155
153
|
host,
|
|
156
154
|
};
|
|
157
155
|
}
|
|
158
|
-
throw new
|
|
156
|
+
throw new ValidationError(`Unable to parse GitHub URL: ${gitUrl}`);
|
|
159
157
|
}
|
|
160
158
|
function parseAzureDevOpsUrl(gitUrl) {
|
|
161
159
|
// Handle SSH format: git@ssh.dev.azure.com:v3/organization/project/repo
|
|
@@ -184,7 +182,7 @@ function parseAzureDevOpsUrl(gitUrl) {
|
|
|
184
182
|
project: httpsMatch[2],
|
|
185
183
|
};
|
|
186
184
|
}
|
|
187
|
-
throw new
|
|
185
|
+
throw new ValidationError(`Unable to parse Azure DevOps URL: ${gitUrl}`);
|
|
188
186
|
}
|
|
189
187
|
function parseGitLabUrl(gitUrl) {
|
|
190
188
|
// Handle SSH format: git@gitlab.com:owner/repo.git or git@gitlab.com:org/group/repo.git
|
|
@@ -203,13 +201,13 @@ function parseGitLabUrl(gitUrl) {
|
|
|
203
201
|
const fullPath = httpsMatch[2];
|
|
204
202
|
return parseGitLabPath(gitUrl, host, fullPath);
|
|
205
203
|
}
|
|
206
|
-
throw new
|
|
204
|
+
throw new ValidationError(`Unable to parse GitLab URL: ${gitUrl}`);
|
|
207
205
|
}
|
|
208
206
|
function parseGitLabPath(gitUrl, host, fullPath) {
|
|
209
207
|
// Split path into segments: org/group/subgroup/repo -> [org, group, subgroup, repo]
|
|
210
208
|
const segments = fullPath.split("/");
|
|
211
209
|
if (segments.length < 2) {
|
|
212
|
-
throw new
|
|
210
|
+
throw new ValidationError(`Unable to parse GitLab URL: ${gitUrl}`);
|
|
213
211
|
}
|
|
214
212
|
// Last segment is repo, everything else is namespace
|
|
215
213
|
const repo = segments[segments.length - 1];
|
|
@@ -17,21 +17,19 @@ interface RetryOptions {
|
|
|
17
17
|
permanentErrorPatterns?: RegExp[];
|
|
18
18
|
/** Custom transient error patterns (defaults to DEFAULT_TRANSIENT_ERROR_PATTERNS) */
|
|
19
19
|
transientErrorPatterns?: RegExp[];
|
|
20
|
+
/** Logger for retry messages (defaults to no logging) */
|
|
21
|
+
log?: {
|
|
22
|
+
info(msg: string): void;
|
|
23
|
+
};
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Classifies an error as permanent (should not retry) or transient (should retry).
|
|
23
|
-
* @param error The error to classify
|
|
24
|
-
* @param patterns Custom patterns to use (defaults to DEFAULT_PERMANENT_ERROR_PATTERNS)
|
|
25
|
-
* @returns true if the error is permanent, false if it might be transient
|
|
26
27
|
*/
|
|
27
28
|
export declare function isPermanentError(error: unknown, patterns?: RegExp[]): boolean;
|
|
28
29
|
/**
|
|
29
30
|
* Checks if an error matches known transient patterns.
|
|
30
|
-
* @param error The error to check
|
|
31
|
-
* @param patterns Custom patterns to use (defaults to DEFAULT_TRANSIENT_ERROR_PATTERNS)
|
|
32
|
-
* @returns true if the error appears to be transient
|
|
33
31
|
*/
|
|
34
|
-
export declare function isTransientError(error:
|
|
32
|
+
export declare function isTransientError(error: unknown, patterns?: RegExp[]): boolean;
|
|
35
33
|
/**
|
|
36
34
|
* Wraps an async operation with retry logic using exponential backoff.
|
|
37
35
|
* Automatically classifies errors and aborts retries for permanent failures.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pRetry, { AbortError } from "p-retry";
|
|
2
|
-
import { logger } from "./logger.js";
|
|
3
2
|
import { sanitizeCredentials } from "./sanitize-utils.js";
|
|
4
|
-
import { ValidationError } from "
|
|
3
|
+
import { ValidationError } from "./errors.js";
|
|
5
4
|
/**
|
|
6
5
|
* Core permanent error patterns shared across all strategies (API, GraphQL, CLI).
|
|
7
6
|
* Auth failures, permission issues, and resource-not-found errors.
|
|
@@ -68,9 +67,6 @@ const DEFAULT_TRANSIENT_ERROR_PATTERNS = [
|
|
|
68
67
|
];
|
|
69
68
|
/**
|
|
70
69
|
* Classifies an error as permanent (should not retry) or transient (should retry).
|
|
71
|
-
* @param error The error to classify
|
|
72
|
-
* @param patterns Custom patterns to use (defaults to DEFAULT_PERMANENT_ERROR_PATTERNS)
|
|
73
|
-
* @returns true if the error is permanent, false if it might be transient
|
|
74
70
|
*/
|
|
75
71
|
export function isPermanentError(error, patterns = DEFAULT_PERMANENT_ERROR_PATTERNS) {
|
|
76
72
|
// Validation errors are always permanent — no point retrying bad input
|
|
@@ -90,12 +86,9 @@ export function isPermanentError(error, patterns = DEFAULT_PERMANENT_ERROR_PATTE
|
|
|
90
86
|
}
|
|
91
87
|
/**
|
|
92
88
|
* Checks if an error matches known transient patterns.
|
|
93
|
-
* @param error The error to check
|
|
94
|
-
* @param patterns Custom patterns to use (defaults to DEFAULT_TRANSIENT_ERROR_PATTERNS)
|
|
95
|
-
* @returns true if the error appears to be transient
|
|
96
89
|
*/
|
|
97
90
|
export function isTransientError(error, patterns = DEFAULT_TRANSIENT_ERROR_PATTERNS) {
|
|
98
|
-
const message = error.message;
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
99
92
|
const stderr = error.stderr?.toString() ?? "";
|
|
100
93
|
const combined = `${message} ${stderr}`;
|
|
101
94
|
for (const pattern of patterns) {
|
|
@@ -135,7 +128,7 @@ export async function withRetry(fn, options) {
|
|
|
135
128
|
// Only log if this isn't the last attempt
|
|
136
129
|
if (context.retriesLeft > 0) {
|
|
137
130
|
const msg = sanitizeCredentials(context.error.message) || "Unknown error";
|
|
138
|
-
|
|
131
|
+
options?.log?.info(`Attempt ${context.attemptNumber}/${retries + 1} failed: ${msg}. Retrying...`);
|
|
139
132
|
options?.onRetry?.(context.error, context.attemptNumber);
|
|
140
133
|
}
|
|
141
134
|
},
|