From 75fbe7f1d1a85f1d2b64fee11da3bd712baeb61b Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:50:59 -0400 Subject: [PATCH] fix(@angular/build): ensure parent directory exists in SQLite cache store Previously, initializing DatabaseSync directly with a path in a nonexistent directory failed with an unable to open database file error. Unlike other storage backends, node:sqlite does not recursively create parent directory structures. Parent directories for the cache database are now created recursively before opening the database file, preventing initialization errors when the cache directory does not yet exist. --- .../src/tools/esbuild/sqlite-cache-store.ts | 16 +++++++++++- .../tools/esbuild/sqlite-cache-store_spec.ts | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts b/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts index a46251ce4d31..9de037800844 100644 --- a/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts +++ b/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts @@ -6,6 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ +import { mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; import { DatabaseSync, StatementSync } from 'node:sqlite'; import { deserialize, serialize } from 'node:v8'; import { Cache, PersistentCacheStore } from './cache'; @@ -35,7 +37,19 @@ export class SqliteCacheStore implements PersistentCacheStore { #ensureDb(): DatabaseSync { if (!this.#db) { - this.#db = new DatabaseSync(this.cachePath); + if (this.cachePath === ':memory:') { + this.#db = new DatabaseSync(this.cachePath); + } else { + // Optimistically attempt to open the database file first to avoid directory creation + // syscalls on warm builds where the parent directory already exists. + try { + this.#db = new DatabaseSync(this.cachePath); + } catch { + mkdirSync(dirname(this.cachePath), { recursive: true }); + this.#db = new DatabaseSync(this.cachePath); + } + } + // Optimize SQLite for cache usage this.#db.exec('PRAGMA auto_vacuum = FULL;'); this.#db.exec('PRAGMA journal_mode = WAL;'); diff --git a/packages/angular/build/src/tools/esbuild/sqlite-cache-store_spec.ts b/packages/angular/build/src/tools/esbuild/sqlite-cache-store_spec.ts index c76394cb7ee3..15eec7cb1298 100644 --- a/packages/angular/build/src/tools/esbuild/sqlite-cache-store_spec.ts +++ b/packages/angular/build/src/tools/esbuild/sqlite-cache-store_spec.ts @@ -220,6 +220,31 @@ describe('SqliteCacheStore', () => { } }); + it('should create parent directories if they do not exist', async () => { + const nestedDir = join(tempDir, 'nested', 'deeply', 'cache'); + const nestedCachePath = join(nestedDir, 'nested-cache.db'); + const nestedStore = new SqliteCacheStore(nestedCachePath); + + try { + await nestedStore.set('nested-key', 'nested-value'); + const result = await nestedStore.get('nested-key'); + expect(result).toBe('nested-value'); + } finally { + nestedStore.close(); + } + }); + + it('should support in-memory databases', async () => { + const memoryStore = new SqliteCacheStore(':memory:'); + try { + await memoryStore.set('mem-key', 'mem-value'); + const result = await memoryStore.get('mem-key'); + expect(result).toBe('mem-value'); + } finally { + memoryStore.close(); + } + }); + describe('NG_BUILD_CACHE_STORE env variable option', () => { it('should force SQLite when NG_BUILD_CACHE_STORE=sqlite', () => { const code = `