From 86901e529c9a03a45a32eaf208d7c18ce3e42033 Mon Sep 17 00:00:00 2001 From: agarciar Date: Mon, 25 May 2026 12:04:40 +0200 Subject: [PATCH 1/2] fix(env): load .env.local per workspace root, not just the first A module-level boolean latched after the first loadSecrets call and ignored workspaceRoot thereafter, so a second deploy in the same process (multi-project workspace / watch mode) reused the first root's env and never read its own .env.local. Track loaded files in a Set keyed by the resolved .env.local path so each distinct root loads once. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/env.spec.ts | 24 ++++++++++++++++++++++++ src/env.ts | 11 +++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/env.spec.ts b/src/env.spec.ts index 27035ed..564cca5 100644 --- a/src/env.spec.ts +++ b/src/env.spec.ts @@ -1,3 +1,6 @@ +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { loadSecrets, _resetDotenvLoaded } from './env.js'; @@ -47,4 +50,25 @@ describe('loadSecrets', () => { /BUNNY_ACCOUNT_API_KEY/, ); }); + + it('loads .env.local for each distinct workspaceRoot, not only the first', () => { + const dirA = mkdtempSync(join(tmpdir(), 'bunny-env-a-')); // no .env.local + const dirB = mkdtempSync(join(tmpdir(), 'bunny-env-b-')); + writeFileSync(join(dirB, '.env.local'), 'BUNNY_STORAGE_PASSWORD=fromB\n'); + try { + // First call against a root with no .env.local must not latch globally + // and skip loading later roots. + expect(() => loadSecrets({ requireAccountApiKey: false, workspaceRoot: dirA })).toThrow( + /BUNNY_STORAGE_PASSWORD/, + ); + // A different root that DOES have .env.local must still be loaded. + expect(loadSecrets({ requireAccountApiKey: false, workspaceRoot: dirB })).toEqual({ + storagePassword: 'fromB', + accountApiKey: null, + }); + } finally { + rmSync(dirA, { recursive: true, force: true }); + rmSync(dirB, { recursive: true, force: true }); + } + }); }); diff --git a/src/env.ts b/src/env.ts index 77580df..891c2bf 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,15 +13,18 @@ export interface LoadSecretsOptions { workspaceRoot?: string; } -let dotenvLoaded = false; +// Keyed by the resolved .env.local path so each distinct workspace root loads +// its own file once. A single boolean would latch on the first root and skip +// every later one (breaks multi-project workspaces deploying in one process). +const loadedDotenvPaths = new Set(); function ensureDotenv(workspaceRoot: string): void { - if (dotenvLoaded) return; const path = resolve(workspaceRoot, '.env.local'); + if (loadedDotenvPaths.has(path)) return; if (existsSync(path)) { loadDotenv({ path }); } - dotenvLoaded = true; + loadedDotenvPaths.add(path); } export function loadSecrets(options: LoadSecretsOptions): Secrets { @@ -46,5 +49,5 @@ export function loadSecrets(options: LoadSecretsOptions): Secrets { // Test-only escape hatch. export function _resetDotenvLoaded(): void { - dotenvLoaded = false; + loadedDotenvPaths.clear(); } From 6fcb8869e09ecb5e1f361e3115759cab757d0b25 Mon Sep 17 00:00:00 2001 From: agarciar Date: Mon, 25 May 2026 12:04:40 +0200 Subject: [PATCH 2/2] chore: require Node >=22 to match CI and avoid experimental fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit engines.node was >=18.13.0 but CI only tests Node 22/24, so the declared floor was unverified, and global fetch (used by the Pull Zone purge) emits an ExperimentalWarning before Node 21. Bump the floor to >=22.0.0 — it matches the CI matrix (declared support is now verified) and fetch is stable there. Update README and CONTRIBUTING to match. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 ++-- CONTRIBUTING.md | 2 +- README.md | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c3bd68..27406d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - # Min is 22.13 because the pinned pnpm (packageManager) requires it. - # The published builder itself runs on Node >=18.13 (see package.json engines). + # The published builder requires Node >=22 (package.json engines), which + # also satisfies the pinned pnpm's own >=22.13 requirement. node: [22, 24] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41e35ce..81230bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ focused package, so the workflow is lightweight. ## Prerequisites - **Node.js ≥ 22.13** — required by the pinned pnpm version. (The *published* - builder runs on Node ≥ 18.13; the higher floor only applies to development.) + builder requires Node ≥ 22; development just needs the slightly higher pnpm floor.) - **pnpm** — managed via Corepack, which ships with Node: ```sh diff --git a/README.md b/README.md index c3e855c..dd81945 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ pnpm add -D angular-deploy-bunny Requires **Angular 17+** using the esbuild-based **application builder** — the default since v17, which emits the browser bundle into a `browser/` folder — and -**Node 18.13+**. The test suite runs in CI against Angular 17 through 21. +**Node 22+**. The test suite runs in CI against Angular 17 through 21. ## Quick start diff --git a/package.json b/package.json index 246ff9b..ef6e598 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "CHANGELOG.md" ], "engines": { - "node": ">=18.13.0" + "node": ">=22.0.0" }, "packageManager": "pnpm@11.0.8", "scripts": {