Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"CHANGELOG.md"
],
"engines": {
"node": ">=18.13.0"
"node": ">=22.0.0"
},
"packageManager": "pnpm@11.0.8",
"scripts": {
Expand Down
24 changes: 24 additions & 0 deletions src/env.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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 });
}
});
});
11 changes: 7 additions & 4 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();

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 {
Expand All @@ -46,5 +49,5 @@ export function loadSecrets(options: LoadSecretsOptions): Secrets {

// Test-only escape hatch.
export function _resetDotenvLoaded(): void {
dotenvLoaded = false;
loadedDotenvPaths.clear();
}
Loading