From 4dd014ea3be92018b637c75f5e345d42f377f260 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 11:44:17 -0700 Subject: [PATCH 1/5] Resolve copy paths relative to src in DomStack constructor Relative copy paths like 'images' now resolve against the parent directory of src rather than process.cwd(), matching what the CLI does. Resolved paths are stored in this.opts.copy so all downstream consumers (buildCopy, watch watchers) use the same absolute paths. Closes #234 --- index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 3a062db..169e44c 100644 --- a/index.js +++ b/index.js @@ -160,10 +160,12 @@ export class DomStack { this.#src = src this.#dest = dest - const copyDirs = opts?.copy ?? [] + const basedir = dirname(resolve(src)) + const copyDirs = (opts?.copy ?? []).map(dir => resolve(basedir, dir)) this.opts = { ...opts, + copy: copyDirs, ignore: [ ...DEFAULT_IGNORES, basename(dest), @@ -172,12 +174,11 @@ export class DomStack { ], } - if (copyDirs && copyDirs.length > 0) { + if (copyDirs.length > 0) { const absDest = resolve(this.#dest) for (const copyDir of copyDirs) { // Copy dirs can be in the src dir (nested builds), but not in the dest dir. - const absCopyDir = resolve(copyDir) - const relToDest = relative(absDest, absCopyDir) + const relToDest = relative(absDest, copyDir) if (relToDest === '' || !relToDest.startsWith('..')) { throw new Error(`copyDir ${copyDir} is within the dest directory`) } From c0ca6d6c56551c950728f61c82f49907002e744a Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 11:59:23 -0700 Subject: [PATCH 2/5] Use process.cwd() as base for relative copy paths, matching the CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dirname(resolve(src)) is incorrect when src is '.' (the common default), as it resolves to the parent of cwd rather than cwd itself. Using plain resolve(dir) — which is equivalent to resolve(cwd, dir) — matches exactly what the CLI does before passing paths to the constructor. --- index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.js b/index.js index 169e44c..3bd4293 100644 --- a/index.js +++ b/index.js @@ -160,8 +160,7 @@ export class DomStack { this.#src = src this.#dest = dest - const basedir = dirname(resolve(src)) - const copyDirs = (opts?.copy ?? []).map(dir => resolve(basedir, dir)) + const copyDirs = (opts?.copy ?? []).map(dir => resolve(dir)) this.opts = { ...opts, From 8eb2c0a891f7ff84fa796fe2fd37829cbf08e85c Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 14:46:13 -0700 Subject: [PATCH 3/5] Add unit tests for DomStack constructor copy path resolution Verifies that relative copy paths are resolved to absolute paths (matching process.cwd()), that already-absolute paths are preserved, and that mixed relative+absolute arrays are both resolved correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../constructor-copy-paths/index.test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test-cases/constructor-copy-paths/index.test.js diff --git a/test-cases/constructor-copy-paths/index.test.js b/test-cases/constructor-copy-paths/index.test.js new file mode 100644 index 0000000..843fb24 --- /dev/null +++ b/test-cases/constructor-copy-paths/index.test.js @@ -0,0 +1,34 @@ +import { test } from 'node:test' +import assert from 'node:assert' +import { isAbsolute } from 'node:path' +import { DomStack } from '../../index.js' + +test.describe('DomStack constructor - copy path resolution', () => { + test('resolves a relative copy path to an absolute path', () => { + const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { + copy: ['some-relative-copy-dir'], + }) + + assert.strictEqual(ds.opts.copy.length, 1, 'one copy entry') + assert.ok(isAbsolute(ds.opts.copy[0]), `copy path should be absolute, got: "${ds.opts.copy[0]}"`) + }) + + test('leaves an already-absolute copy path unchanged', () => { + const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { + copy: ['/absolute/copy/dir'], + }) + + assert.strictEqual(ds.opts.copy[0], '/absolute/copy/dir', 'absolute path is preserved') + }) + + test('resolves multiple mixed copy paths', () => { + const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { + copy: ['relative-dir', '/absolute/dir'], + }) + + assert.strictEqual(ds.opts.copy.length, 2, 'two copy entries') + for (const p of ds.opts.copy) { + assert.ok(isAbsolute(p), `each copy path should be absolute, got: "${p}"`) + } + }) +}) From d89abf5fd96652072f164020b24acce8b67c397e Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 19:49:48 -0700 Subject: [PATCH 4/5] Use cross-platform temp paths and path.resolve() in copy path tests Co-Authored-By: Claude Sonnet 4.6 --- .../constructor-copy-paths/index.test.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test-cases/constructor-copy-paths/index.test.js b/test-cases/constructor-copy-paths/index.test.js index 843fb24..d729b32 100644 --- a/test-cases/constructor-copy-paths/index.test.js +++ b/test-cases/constructor-copy-paths/index.test.js @@ -1,11 +1,15 @@ import { test } from 'node:test' import assert from 'node:assert' -import { isAbsolute } from 'node:path' +import { isAbsolute, resolve, join } from 'node:path' +import { tmpdir } from 'node:os' import { DomStack } from '../../index.js' +const tmpSrc = join(tmpdir(), 'domstack-test-src') +const tmpDest = join(tmpdir(), 'domstack-test-dest') + test.describe('DomStack constructor - copy path resolution', () => { test('resolves a relative copy path to an absolute path', () => { - const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { + const ds = new DomStack(tmpSrc, tmpDest, { copy: ['some-relative-copy-dir'], }) @@ -13,17 +17,19 @@ test.describe('DomStack constructor - copy path resolution', () => { assert.ok(isAbsolute(ds.opts.copy[0]), `copy path should be absolute, got: "${ds.opts.copy[0]}"`) }) - test('leaves an already-absolute copy path unchanged', () => { - const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { - copy: ['/absolute/copy/dir'], + test('leaves an already-absolute copy path normalized', () => { + const absPath = join(tmpdir(), 'absolute', 'copy', 'dir') + const ds = new DomStack(tmpSrc, tmpDest, { + copy: [absPath], }) - assert.strictEqual(ds.opts.copy[0], '/absolute/copy/dir', 'absolute path is preserved') + assert.strictEqual(ds.opts.copy[0], resolve(absPath), 'absolute path is preserved and normalized') }) test('resolves multiple mixed copy paths', () => { - const ds = new DomStack('/tmp/test-src', '/tmp/test-dest', { - copy: ['relative-dir', '/absolute/dir'], + const absPath = join(tmpdir(), 'absolute', 'dir') + const ds = new DomStack(tmpSrc, tmpDest, { + copy: ['relative-dir', absPath], }) assert.strictEqual(ds.opts.copy.length, 2, 'two copy entries') From fce6532a52a3619cb939ad6c7c13127845988699 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 20:00:25 -0700 Subject: [PATCH 5/5] fix: cast array index access to satisfy noUncheckedIndexedAccess in tsc Co-Authored-By: Claude Sonnet 4.6 --- test-cases/constructor-copy-paths/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-cases/constructor-copy-paths/index.test.js b/test-cases/constructor-copy-paths/index.test.js index d729b32..b43af32 100644 --- a/test-cases/constructor-copy-paths/index.test.js +++ b/test-cases/constructor-copy-paths/index.test.js @@ -14,7 +14,8 @@ test.describe('DomStack constructor - copy path resolution', () => { }) assert.strictEqual(ds.opts.copy.length, 1, 'one copy entry') - assert.ok(isAbsolute(ds.opts.copy[0]), `copy path should be absolute, got: "${ds.opts.copy[0]}"`) + const copyPath = /** @type {string} */ (ds.opts.copy[0]) + assert.ok(isAbsolute(copyPath), `copy path should be absolute, got: "${copyPath}"`) }) test('leaves an already-absolute copy path normalized', () => {