From 5c06f383c612811416ae0620821a893d01368511 Mon Sep 17 00:00:00 2001 From: c-99-e <268417377+c-99-e@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:40:11 +0400 Subject: [PATCH 1/4] fix: load CWD .env explicitly, don't override real env vars - Split into loadEnvFile helper + loadConfigEnv orchestrator - Precedence: real env > CWD .env > ~/.config/shopq/.env - Never override existing env vars (fixes test failures) - Use node: protocol for imports (fixes lint) --- src/graphql.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/graphql.ts b/src/graphql.ts index 6a56c37..79e733e 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -54,18 +54,15 @@ export class ConfigError extends Error { } /** - * Load env vars from ~/.config/shopq/.env (XDG base dir convention), - * then let CWD .env and actual env vars override. - * This follows the pattern used by gh, vercel, railway, etc. + * Load env file without overriding existing env vars. + * Real env vars and earlier-loaded files always win. */ -function loadConfigEnv(): void { - const xdgConfig = - process.env.XDG_CONFIG_HOME || - `${process.env.HOME || require("os").homedir()}/.config`; - const configPath = `${xdgConfig}/shopq/.env`; - +function loadEnvFile(filePath: string): void { try { - const content = require("fs").readFileSync(configPath, "utf-8") as string; + const content = require("node:fs").readFileSync( + filePath, + "utf-8", + ) as string; for (const line of content.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; @@ -76,16 +73,31 @@ function loadConfigEnv(): void { .slice(eqIdx + 1) .trim() .replace(/^["']|["']$/g, ""); - // Don't override existing env vars (CWD .env or real env take precedence) if (!process.env[key]) { process.env[key] = value; } } } catch { - // Config file doesn't exist — that's fine + // File doesn't exist — that's fine } } +/** + * Load env vars with precedence: real env > CWD .env > ~/.config/shopq/.env + * This follows the pattern used by gh, vercel, railway, etc. + */ +function loadConfigEnv(): void { + const xdgConfig = + process.env.XDG_CONFIG_HOME || + `${process.env.HOME || require("node:os").homedir()}/.config`; + + // 1. Load CWD .env first (higher priority, fills gaps in real env) + loadEnvFile(`${process.cwd()}/.env`); + + // 2. Load ~/.config/shopq/.env as fallback defaults + loadEnvFile(`${xdgConfig}/shopq/.env`); +} + // Load on module init loadConfigEnv(); From f2a331b90878059b14f9b9166e074713699c3d65 Mon Sep 17 00:00:00 2001 From: c-99-e <268417377+c-99-e@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:41:59 +0400 Subject: [PATCH 2/4] fix: use 'key in process.env' to respect explicitly empty vars Tests set SHOPIFY_STORE='' to simulate missing credentials. Using 'in' check instead of falsy check preserves that intent. --- src/graphql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphql.ts b/src/graphql.ts index 79e733e..c8f771b 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -73,7 +73,7 @@ function loadEnvFile(filePath: string): void { .slice(eqIdx + 1) .trim() .replace(/^["']|["']$/g, ""); - if (!process.env[key]) { + if (!(key in process.env)) { process.env[key] = value; } } From bd0fe88110c1e4182912406802e6fc39b72432b6 Mon Sep 17 00:00:00 2001 From: c-99-e <268417377+c-99-e@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:57:31 +0400 Subject: [PATCH 3/4] fix: switch to --target=bun, remove custom .env parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root cause was building with --target=node and #!/usr/bin/env node, which meant Bun's native .env loading never kicked in at runtime. Fix: target bun directly. Bun auto-loads CWD .env — no custom code needed. For global use without a local .env, users export vars in their shell profile. --- bin/shopq.ts | 2 +- package.json | 2 +- src/graphql.ts | 50 +------------------------------------------------- 3 files changed, 3 insertions(+), 51 deletions(-) diff --git a/bin/shopq.ts b/bin/shopq.ts index 0fb3220..ec04157 100755 --- a/bin/shopq.ts +++ b/bin/shopq.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env bun import "../src/commands/config"; import "../src/commands/gql"; diff --git a/package.json b/package.json index 8970893..505dfa0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "skills" ], "scripts": { - "build": "bun build bin/shopq.ts --target=node --outfile dist/shopq.js && chmod +x dist/shopq.js", + "build": "bun build bin/shopq.ts --target=bun --outfile dist/shopq.js && chmod +x dist/shopq.js", "prepublishOnly": "bun run build", "test": "bun test", "check": "bunx --bun biome check --write .", diff --git a/src/graphql.ts b/src/graphql.ts index c8f771b..a05beb7 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -47,60 +47,12 @@ export class ConfigError extends Error { constructor(public missing: string[]) { super( `Missing required environment variables: ${missing.join(", ")}\n` + - `Set them in ~/.config/shopq/.env, a local .env file, or as environment variables.`, + `Set them in a .env file or as environment variables.`, ); this.name = "ConfigError"; } } -/** - * Load env file without overriding existing env vars. - * Real env vars and earlier-loaded files always win. - */ -function loadEnvFile(filePath: string): void { - try { - const content = require("node:fs").readFileSync( - filePath, - "utf-8", - ) as string; - for (const line of content.split("\n")) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) continue; - const eqIdx = trimmed.indexOf("="); - if (eqIdx === -1) continue; - const key = trimmed.slice(0, eqIdx).trim(); - const value = trimmed - .slice(eqIdx + 1) - .trim() - .replace(/^["']|["']$/g, ""); - if (!(key in process.env)) { - process.env[key] = value; - } - } - } catch { - // File doesn't exist — that's fine - } -} - -/** - * Load env vars with precedence: real env > CWD .env > ~/.config/shopq/.env - * This follows the pattern used by gh, vercel, railway, etc. - */ -function loadConfigEnv(): void { - const xdgConfig = - process.env.XDG_CONFIG_HOME || - `${process.env.HOME || require("node:os").homedir()}/.config`; - - // 1. Load CWD .env first (higher priority, fills gaps in real env) - loadEnvFile(`${process.cwd()}/.env`); - - // 2. Load ~/.config/shopq/.env as fallback defaults - loadEnvFile(`${xdgConfig}/shopq/.env`); -} - -// Load on module init -loadConfigEnv(); - export function resolveConfig(storeFlag?: string): { store: string; clientId: string; From 5acdcfb447cd52276465b46e0407522c0467f1d0 Mon Sep 17 00:00:00 2001 From: c-99-e <268417377+c-99-e@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:58:59 +0400 Subject: [PATCH 4/4] chore: bump version to 0.3.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 505dfa0..92fe59c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shopq", - "version": "0.3.8", + "version": "0.3.9", "description": "A zero-dependency Shopify Admin CLI built on Bun", "type": "module", "license": "MIT",