Skip to content

Programmatic API needs better support for testing and dry-run builds #234

@bcomnes

Description

@bcomnes

This issue was originally identified in voxpelli/voxpelli.github.com#32

Problem

The DomStack class exports a programmatic build() method, but it is difficult to use for isolated, repeatable tests because of three compounding issues:

  1. --copy paths must be absolute — The CLI resolves paths with resolve(cwd, p), but the DomStack constructor does not. Passing relative paths like 'images' silently fails or produces unexpected copy behavior.

  2. Build errors are opaque across the worker boundarybuild() throws a DomStackAggregateError but each inner error's cause is serialized to a plain object, losing the original stack trace. The errorData property containing the page path is attached but not printed by the default formatter.

  3. No built-in temp directory or dry-run support — Testing requires manually creating temp directories, building into them, asserting on the output, and cleaning up.

Current behavior

The constructor does not resolve copy paths

constructor (src, dest, opts = {}) {
  // ...
  const copyDirs = opts?.copy ?? []
  this.opts = {
    ...opts,
    ignore: [
      ...DEFAULT_IGNORES,
      basename(dest),
      ...copyDirs.map(dir => basename(dir)),  // Only uses basename, not resolve
    ],
  }
}

The CLI resolves paths before passing them to the constructor, but programmatic users must know to do this themselves.

Test suites depend on a pre-existing build

Projects end up with:

"test": "run-s check build test:build"

Tests cannot run in isolation — they require a full build first. This means tests are slow, stateful, and cannot run in parallel.

Proposed solution

1. Resolve copy paths in the constructor (bug fix)

constructor (src, dest, opts = {}) {
  const basedir = dirname(resolve(src))
  const copyDirs = (opts?.copy ?? []).map(dir => resolve(basedir, dir))
  // ...
}

2. Improve error reporting across the worker boundary

Include page path in error messages when available:

buildReport.errors = workerReport.errors.map(({ error, errorData = {} }) => {
  const pagePath = errorData.page?.path || errorData.template?.path || 'unknown';
  const richError = new Error(
    `${error.message} (page: "${pagePath}")`,
    { cause: error.cause }
  );
  richError.pageInfo = errorData.page;
  return richError;
});

3. Add a test helper module

Export a lightweight test helper from @domstack/static/test:

export async function testBuild(src, opts = {}) {
  const dest = await mkdtemp(join(tmpdir(), 'domstack-test-'));
  const ds = new DomStack(resolve(src), dest, {
    ...opts,
    copy: (opts.copy ?? []).map(p => resolve(p)),
  });
  const results = await ds.build();
  return {
    dest,
    results,
    readOutput: (path) => readFile(join(dest, path), 'utf8'),
    cleanup: () => rm(dest, { recursive: true, force: true }),
  };
}

Recommendation: Fix copy path resolution and improve error reporting as bug fixes, then ship the test helper as an enhancement.

Note

DomStack already has a test-cases/ directory with programmatic DomStack instantiation tests — so the pattern is proven, it just needs path resolution fixes and error quality improvements.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions