@aspruyt/xfg 3.2.0 → 3.3.2

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.
@@ -1,4 +1,5 @@
1
1
  import { execSync } from "node:child_process";
2
+ import { sanitizeCredentials } from "./sanitize-utils.js";
2
3
  /**
3
4
  * Default implementation that uses Node.js child_process.execSync.
4
5
  * Note: Commands are escaped using escapeShellArg before being passed here.
@@ -18,9 +19,17 @@ export class ShellCommandExecutor {
18
19
  if (execError.stderr && typeof execError.stderr !== "string") {
19
20
  execError.stderr = execError.stderr.toString();
20
21
  }
21
- // Include stderr in error message for better debugging
22
+ // Sanitize credentials from stderr before including in error
23
+ if (execError.stderr) {
24
+ execError.stderr = sanitizeCredentials(execError.stderr);
25
+ }
26
+ // Include sanitized stderr in error message for better debugging
22
27
  if (execError.stderr && execError.message) {
23
- execError.message = `${execError.message}\n${execError.stderr}`;
28
+ execError.message =
29
+ sanitizeCredentials(execError.message) + "\n" + execError.stderr;
30
+ }
31
+ else if (execError.message) {
32
+ execError.message = sanitizeCredentials(execError.message);
24
33
  }
25
34
  throw error;
26
35
  }
package/dist/index.js CHANGED
@@ -208,19 +208,15 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
208
208
  return;
209
209
  }
210
210
  console.log(`Found ${reposWithRulesets.length} repositories with rulesets\n`);
211
+ logger.setTotal(reposWithRulesets.length);
211
212
  const processor = processorFactory();
212
213
  const repoProcessor = repoProcessorFactory();
213
214
  const results = [];
214
215
  let successCount = 0;
215
216
  let failCount = 0;
216
217
  let skipCount = 0;
217
- for (let i = 0; i < config.repos.length; i++) {
218
- const repoConfig = config.repos[i];
219
- // Skip repos without rulesets
220
- if (!repoConfig.settings?.rulesets ||
221
- Object.keys(repoConfig.settings.rulesets).length === 0) {
222
- continue;
223
- }
218
+ for (let i = 0; i < reposWithRulesets.length; i++) {
219
+ const repoConfig = reposWithRulesets[i];
224
220
  let repoInfo;
225
221
  try {
226
222
  repoInfo = parseGitUrl(repoConfig.git, {
@@ -1,5 +1,6 @@
1
1
  import pRetry, { AbortError } from "p-retry";
2
2
  import { logger } from "./logger.js";
3
+ import { sanitizeCredentials } from "./sanitize-utils.js";
3
4
  /**
4
5
  * Default patterns indicating permanent errors that should NOT be retried.
5
6
  * These typically indicate configuration issues, auth failures, or invalid resources.
@@ -121,7 +122,7 @@ export async function withRetry(fn, options) {
121
122
  onFailedAttempt: (context) => {
122
123
  // Only log if this isn't the last attempt
123
124
  if (context.retriesLeft > 0) {
124
- const msg = context.error.message || "Unknown error";
125
+ const msg = sanitizeCredentials(context.error.message) || "Unknown error";
125
126
  logger.info(`Attempt ${context.attemptNumber}/${retries + 1} failed: ${msg}. Retrying...`);
126
127
  options?.onRetry?.(context.error, context.attemptNumber);
127
128
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Sanitizes credentials from error messages and logs.
3
+ * Replaces sensitive tokens/passwords with '***' to prevent leakage.
4
+ *
5
+ * @param message The message that may contain credentials
6
+ * @returns The sanitized message with credentials replaced by '***'
7
+ */
8
+ export declare function sanitizeCredentials(message: string | undefined | null): string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sanitizes credentials from error messages and logs.
3
+ * Replaces sensitive tokens/passwords with '***' to prevent leakage.
4
+ *
5
+ * @param message The message that may contain credentials
6
+ * @returns The sanitized message with credentials replaced by '***'
7
+ */
8
+ export function sanitizeCredentials(message) {
9
+ if (!message) {
10
+ return "";
11
+ }
12
+ let result = message;
13
+ // Handle URL credentials (most common case)
14
+ // Replace password portion in https://user:password@host patterns
15
+ result = result.replace(/(https:\/\/[^:]+:)([^@]+)(@)/g, "$1***$3");
16
+ // Handle Authorization headers
17
+ result = result.replace(/(Authorization:\s*Bearer\s+)(\S+)/gi, "$1***");
18
+ result = result.replace(/(Authorization:\s*Basic\s+)(\S+)/gi, "$1***");
19
+ return result;
20
+ }
@@ -5,6 +5,7 @@ import { isAzureDevOpsRepo } from "../repo-detector.js";
5
5
  import { BasePRStrategy, } from "./pr-strategy.js";
6
6
  import { logger } from "../logger.js";
7
7
  import { withRetry, isPermanentError } from "../retry-utils.js";
8
+ import { sanitizeCredentials } from "../sanitize-utils.js";
8
9
  export class AzurePRStrategy extends BasePRStrategy {
9
10
  constructor(executor) {
10
11
  super(executor);
@@ -35,7 +36,7 @@ export class AzurePRStrategy extends BasePRStrategy {
35
36
  }
36
37
  const stderr = error.stderr ?? "";
37
38
  if (stderr && !stderr.includes("does not exist")) {
38
- logger.info(`Debug: Azure PR check failed - ${stderr.trim()}`);
39
+ logger.info(`Debug: Azure PR check failed - ${sanitizeCredentials(stderr).trim()}`);
39
40
  }
40
41
  }
41
42
  return null;
@@ -5,6 +5,7 @@ import { isGitHubRepo } from "../repo-detector.js";
5
5
  import { BasePRStrategy, } from "./pr-strategy.js";
6
6
  import { logger } from "../logger.js";
7
7
  import { withRetry, isPermanentError } from "../retry-utils.js";
8
+ import { sanitizeCredentials } from "../sanitize-utils.js";
8
9
  /**
9
10
  * Get the repo flag value for gh CLI commands.
10
11
  * Returns HOST/OWNER/REPO for GHE, OWNER/REPO for github.com.
@@ -66,7 +67,7 @@ export class GitHubPRStrategy extends BasePRStrategy {
66
67
  // Log unexpected errors for debugging (expected: empty result means no PR)
67
68
  const stderr = error.stderr ?? "";
68
69
  if (stderr && !stderr.includes("no pull requests match")) {
69
- logger.info(`Debug: GitHub PR check failed - ${stderr.trim()}`);
70
+ logger.info(`Debug: GitHub PR check failed - ${sanitizeCredentials(stderr).trim()}`);
70
71
  }
71
72
  }
72
73
  return null;
@@ -5,6 +5,7 @@ import { isGitLabRepo } from "../repo-detector.js";
5
5
  import { BasePRStrategy, } from "./pr-strategy.js";
6
6
  import { logger } from "../logger.js";
7
7
  import { withRetry, isPermanentError } from "../retry-utils.js";
8
+ import { sanitizeCredentials } from "../sanitize-utils.js";
8
9
  export class GitLabPRStrategy extends BasePRStrategy {
9
10
  constructor(executor) {
10
11
  super(executor);
@@ -89,7 +90,7 @@ export class GitLabPRStrategy extends BasePRStrategy {
89
90
  // Log unexpected errors for debugging
90
91
  const stderr = error.stderr ?? "";
91
92
  if (stderr && !stderr.includes("no merge requests")) {
92
- logger.info(`Debug: GitLab MR check failed - ${stderr.trim()}`);
93
+ logger.info(`Debug: GitLab MR check failed - ${sanitizeCredentials(stderr).trim()}`);
93
94
  }
94
95
  }
95
96
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.2.0",
3
+ "version": "3.3.2",
4
4
  "description": "CLI tool for repository-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",