From ce0778d5354e5cfec05c33927807fa2a735f124e Mon Sep 17 00:00:00 2001 From: vivek Date: Wed, 20 May 2026 10:20:21 +0530 Subject: [PATCH 1/6] feat: sort snapshot tags alphabetically in listSnapshots(). Closes #6 --- src/storage/snapshotStore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/storage/snapshotStore.js b/src/storage/snapshotStore.js index 0af020d..1a45178 100644 --- a/src/storage/snapshotStore.js +++ b/src/storage/snapshotStore.js @@ -26,5 +26,6 @@ export function listSnapshots() { return fs .readdirSync(SNAP_DIR) .filter((f) => f.endsWith(".json")) - .map((f) => f.replace(".json", "")); + .map((f) => f.replace(".json", "")) + .sort(); } From b5d38b7febf08fe0d767bf07d268d8ff5197226c Mon Sep 17 00:00:00 2001 From: vivek Date: Wed, 20 May 2026 10:25:49 +0530 Subject: [PATCH 2/6] fix: print helpful guidance when init declines overwrite. Closes #4 --- src/commands/init.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/commands/init.js b/src/commands/init.js index c6d5cb9..a2b22ad 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -28,6 +28,21 @@ export async function runInit() { initial: false, }); if (!overwrite) { + console.log(chalk.yellow("✖ Keeping existing apidrift.config.json")); + console.log(""); + console.log(" Your current config was not modified. Next steps:"); + console.log( + ` 1. Make sure you have a ${chalk.cyan(".env")} file with your tokens:` + ); + console.log(` ${chalk.gray("STAGING_TOKEN=your_token_here")}`); + console.log(` ${chalk.gray("PROD_TOKEN=your_token_here")}`); + console.log( + ` 2. Add ${chalk.cyan(".env")} to your ${chalk.cyan(".gitignore")} to keep tokens out of git` + ); + console.log( + ` 3. Run: ${chalk.cyan("apidrift snapshot --tag v1.0 --env staging")}` + ); + console.log(""); return; } } From 3f067a8ad684422e83cc6fd275d4df78817bf1d9 Mon Sep 17 00:00:00 2001 From: vivek Date: Wed, 20 May 2026 10:43:15 +0530 Subject: [PATCH 3/6] feat: allow configuring HTTP timeout via APIDRIFT_TIMEOUT_MS env var. Closes #7 --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++ src/core/fetcher.js | 15 ++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd80d26..1735484 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,59 @@ Options: | Schema engine | custom (zero dependencies) | | Diff engine | custom recursive differ | +## Environment Variables + +| Variable | Default | Description | +| -------- | ------- | ----------- | +| `STAGING_TOKEN` | — | Auth token interpolated into `${STAGING_TOKEN}` in your config | +| `PROD_TOKEN` | — | Auth token interpolated into `${PROD_TOKEN}` in your config | +| `APIDRIFT_TIMEOUT_MS` | `10000` | HTTP request timeout in milliseconds. Increase for slow APIs. | + +**Bash / macOS / Linux:** +```bash +export APIDRIFT_TIMEOUT_MS=30000 +apidrift snapshot --tag v1.0 --env staging +``` + +**PowerShell (Windows):** +```powershell +$env:APIDRIFT_TIMEOUT_MS = "30000" +apidrift snapshot --tag v1.0 --env staging +``` + +--- + +## Troubleshooting + +### Requests time out on slow APIs + +If you see `ECONNABORTED` or `ETIMEDOUT` errors, your API is responding slower than the default 10-second timeout. Set `APIDRIFT_TIMEOUT_MS` to a larger value: + +```bash +APIDRIFT_TIMEOUT_MS=60000 apidrift snapshot --tag v1.0 --env staging +``` + +### `Authorization` header shows `Bearer ` (empty token) + +This means your `${STAGING_TOKEN}` or `${PROD_TOKEN}` env var is not set. Verify: + +```bash +# Bash / macOS / Linux +echo $STAGING_TOKEN + +# PowerShell (Windows) +echo $env:STAGING_TOKEN +``` + +If empty, create a `.env` file in your project directory: + +```bash +STAGING_TOKEN=your_actual_token_here +PROD_TOKEN=your_actual_token_here +``` + +Make sure `.env` is listed in your `.gitignore` so tokens are never committed. + --- ## License diff --git a/src/core/fetcher.js b/src/core/fetcher.js index 4997523..205ae0c 100644 --- a/src/core/fetcher.js +++ b/src/core/fetcher.js @@ -7,6 +7,19 @@ const RETRY_DEFAULTS = { maxDelay: 8000, }; +/** + * HTTP request timeout in milliseconds. + * Override via the APIDRIFT_TIMEOUT_MS environment variable. + * Defaults to 10000 (10 seconds) if unset or if the value is not a positive integer. + */ +const DEFAULT_TIMEOUT_MS = 10000; +const TIMEOUT_MS = (() => { + const raw = process.env.APIDRIFT_TIMEOUT_MS; + if (!raw) return DEFAULT_TIMEOUT_MS; + const parsed = Number(raw); + return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS; +})(); + let didWarnEmptyAuth = false; function isRetryable(err) { @@ -29,7 +42,7 @@ async function fetchWithRetry( config = RETRY_DEFAULTS, ) { try { - return await axios({ ...options, url, timeout: 10000 }); + return await axios({ ...options, url, timeout: TIMEOUT_MS }); } catch (err) { if (attempt < config.retries && isRetryable(err)) { const delay = Math.min( From 9472fc14ed792be76cf40c32de7b51e9f1ee80f0 Mon Sep 17 00:00:00 2001 From: vivek Date: Fri, 22 May 2026 10:27:43 +0530 Subject: [PATCH 4/6] fix: sanitize listSnapshots tag extraction regex --- src/storage/snapshotStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/snapshotStore.js b/src/storage/snapshotStore.js index 1a45178..b04c95f 100644 --- a/src/storage/snapshotStore.js +++ b/src/storage/snapshotStore.js @@ -26,6 +26,6 @@ export function listSnapshots() { return fs .readdirSync(SNAP_DIR) .filter((f) => f.endsWith(".json")) - .map((f) => f.replace(".json", "")) + .map((f) => f.replace(/\.json$/, "")) .sort(); } From c155c045f9e687b22aeb074b8ba7df1810dbedfe Mon Sep 17 00:00:00 2001 From: vivek Date: Fri, 22 May 2026 10:28:16 +0530 Subject: [PATCH 5/6] fix: update declined overwrite warning prefix and environment variables guide --- src/commands/init.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/init.js b/src/commands/init.js index a2b22ad..0cafce5 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -28,11 +28,11 @@ export async function runInit() { initial: false, }); if (!overwrite) { - console.log(chalk.yellow("✖ Keeping existing apidrift.config.json")); + console.log(chalk.yellow("ℹ Keeping existing apidrift.config.json")); console.log(""); console.log(" Your current config was not modified. Next steps:"); console.log( - ` 1. Make sure you have a ${chalk.cyan(".env")} file with your tokens:` + ` 1. Make sure you have a ${chalk.cyan(".env")} file or environment variables set for your tokens:` ); console.log(` ${chalk.gray("STAGING_TOKEN=your_token_here")}`); console.log(` ${chalk.gray("PROD_TOKEN=your_token_here")}`); From 269c9b631c4f208f58278ae141a29abc1dfdfaa1 Mon Sep 17 00:00:00 2001 From: vivek Date: Fri, 22 May 2026 10:28:49 +0530 Subject: [PATCH 6/6] fix: enforce integer validation for timeout and capitalize Git in init guidance --- src/commands/init.js | 2 +- src/core/fetcher.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/init.js b/src/commands/init.js index 0cafce5..32645a0 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -37,7 +37,7 @@ export async function runInit() { console.log(` ${chalk.gray("STAGING_TOKEN=your_token_here")}`); console.log(` ${chalk.gray("PROD_TOKEN=your_token_here")}`); console.log( - ` 2. Add ${chalk.cyan(".env")} to your ${chalk.cyan(".gitignore")} to keep tokens out of git` + ` 2. Add ${chalk.cyan(".env")} to your ${chalk.cyan(".gitignore")} to keep tokens out of Git` ); console.log( ` 3. Run: ${chalk.cyan("apidrift snapshot --tag v1.0 --env staging")}` diff --git a/src/core/fetcher.js b/src/core/fetcher.js index 205ae0c..9804e00 100644 --- a/src/core/fetcher.js +++ b/src/core/fetcher.js @@ -17,7 +17,7 @@ const TIMEOUT_MS = (() => { const raw = process.env.APIDRIFT_TIMEOUT_MS; if (!raw) return DEFAULT_TIMEOUT_MS; const parsed = Number(raw); - return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS; + return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS; })(); let didWarnEmptyAuth = false;