diff --git a/CHANGELOG.md b/CHANGELOG.md index a8c439e..ed4e255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.2] — 2026-06-04 + +### Changed + +- Credentials loader now falls back to `.env` when `.env.local` is absent. + `.env.local` still takes precedence when both files exist. + ## [0.1.1] — 2026-05-25 ### Fixed @@ -31,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `dryRun` mode and a `purgeAfterUpload` toggle. - Credentials read from environment variables or a `.env.local` file. -[Unreleased]: https://github.com/lostium/angular-deploy-bunny/compare/v0.1.1...HEAD +[Unreleased]: https://github.com/lostium/angular-deploy-bunny/compare/v0.1.2...HEAD +[0.1.2]: https://github.com/lostium/angular-deploy-bunny/compare/v0.1.1...v0.1.2 [0.1.1]: https://github.com/lostium/angular-deploy-bunny/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/lostium/angular-deploy-bunny/releases/tag/v0.1.0 diff --git a/package.json b/package.json index 468bd7e..22a9e18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-deploy-bunny", - "version": "0.1.1", + "version": "0.1.2", "description": "Angular Architect builder (ng deploy) that syncs your build output to a Bunny.net CDN Storage Zone with SHA256-based incremental diffing, then purges the Pull Zone.", "keywords": [ "angular", diff --git a/src/env.spec.ts b/src/env.spec.ts index 564cca5..a1485bb 100644 --- a/src/env.spec.ts +++ b/src/env.spec.ts @@ -51,6 +51,33 @@ describe('loadSecrets', () => { ); }); + it('falls back to .env when .env.local does not exist', () => { + const dir = mkdtempSync(join(tmpdir(), 'bunny-env-fallback-')); + writeFileSync(join(dir, '.env'), 'BUNNY_STORAGE_PASSWORD=fromEnv\n'); + try { + expect(loadSecrets({ requireAccountApiKey: false, workspaceRoot: dir })).toEqual({ + storagePassword: 'fromEnv', + accountApiKey: null, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it('.env.local takes precedence over .env when both exist', () => { + const dir = mkdtempSync(join(tmpdir(), 'bunny-env-precedence-')); + writeFileSync(join(dir, '.env'), 'BUNNY_STORAGE_PASSWORD=fromEnv\n'); + writeFileSync(join(dir, '.env.local'), 'BUNNY_STORAGE_PASSWORD=fromLocal\n'); + try { + expect(loadSecrets({ requireAccountApiKey: false, workspaceRoot: dir })).toEqual({ + storagePassword: 'fromLocal', + accountApiKey: null, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + 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-')); diff --git a/src/env.ts b/src/env.ts index 891c2bf..89b2921 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,18 +13,23 @@ export interface LoadSecretsOptions { workspaceRoot?: string; } -// 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(); +// Keyed by the resolved workspace root so each distinct 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 loadedWorkspaceRoots = new Set(); function ensureDotenv(workspaceRoot: string): void { - const path = resolve(workspaceRoot, '.env.local'); - if (loadedDotenvPaths.has(path)) return; - if (existsSync(path)) { - loadDotenv({ path }); + if (loadedWorkspaceRoots.has(workspaceRoot)) return; + const envLocal = resolve(workspaceRoot, '.env.local'); + if (existsSync(envLocal)) { + loadDotenv({ path: envLocal }); + } else { + const envFile = resolve(workspaceRoot, '.env'); + if (existsSync(envFile)) { + loadDotenv({ path: envFile }); + } } - loadedDotenvPaths.add(path); + loadedWorkspaceRoots.add(workspaceRoot); } export function loadSecrets(options: LoadSecretsOptions): Secrets { @@ -33,14 +38,14 @@ export function loadSecrets(options: LoadSecretsOptions): Secrets { const storagePassword = process.env['BUNNY_STORAGE_PASSWORD']; if (!storagePassword) { throw new Error( - 'Missing BUNNY_STORAGE_PASSWORD. Set it in your shell or in .env.local at the repo root.', + 'Missing BUNNY_STORAGE_PASSWORD. Set it in your shell, in .env.local, or in .env at the repo root.', ); } const accountApiKey = process.env['BUNNY_ACCOUNT_API_KEY'] ?? null; if (options.requireAccountApiKey && !accountApiKey) { throw new Error( - 'Missing BUNNY_ACCOUNT_API_KEY (required when purgeAfterUpload is true). Set it in your shell or in .env.local at the repo root.', + 'Missing BUNNY_ACCOUNT_API_KEY (required when purgeAfterUpload is true). Set it in your shell, in .env.local, or in .env at the repo root.', ); } @@ -49,5 +54,5 @@ export function loadSecrets(options: LoadSecretsOptions): Secrets { // Test-only escape hatch. export function _resetDotenvLoaded(): void { - loadedDotenvPaths.clear(); + loadedWorkspaceRoots.clear(); }