Fix the TypeScript build: make build fails with TS6059 on main #3

Closed
opened 2026-08-09 03:43:28 +02:00 by clawbot · 3 comments
Collaborator

Problem

make build is broken on main right now:

error TS6059: File '/…/quak/bin/quak.ts' is not under 'rootDir' '/…/quak/src'.
'rootDir' is expected to contain all source files.

tsconfig.json:8 sets "rootDir": "./src" while tsconfig.json:20 sets
"include": ["src/**/*", "bin/**/*"]. The two contradict each other.

Nothing catches this: make check runs test, lint and fmt-check only (script/check), and
the Dockerfile runs make check but never make build. So a repo whose package.json
declares "main": "./dist/index.js" and "bin": {"quak": "./dist/bin/quak.js"} cannot
produce either artifact, and CI is blind to it.

The README's Getting Started block also tells users to run yarn quak login, yarn quak collections, etc. There is no quak script in package.json, so every one of those
commands fails.

Definition of done

  1. make build succeeds from a clean checkout and emits JavaScript plus declaration files
    under dist/.
  2. package.json main, types and bin point at paths that the build actually produces,
    verified by inspecting dist/ after a build.
  3. The emitted CLI entrypoint retains the #!/usr/bin/env node shebang from bin/quak.ts:1
    and is executable.
  4. yarn quak <command> works after yarn build, matching what the README's Getting Started
    section already promises — add the missing quak script to package.json.
  5. The Dockerfile runs make build in addition to make check, so this class of error can
    never reach main again. (Do not change what make check itself runs — the repo policy
    fixes its composition as test + lint + fmt-check.)
  6. make clean still removes everything the build produces.
  7. make check green.
  8. TODO.md updated in the same commit.

Set "rootDir": "." and update package.json main/types/bin to the resulting
dist/src/… and dist/bin/… paths. This is the smallest change and keeps the source layout
the README documents.

The alternative — moving the CLI body into src/cli.ts and leaving a thin shim in bin/
keeps dist/index.js as the package main but churns more files and contradicts the layout
diagram in the README's Design section. Pick the first unless you find a concrete reason not
to, and record the reasoning in the PR body.

Out of scope

engines, exports, and prepublishOnly — tracked separately.

## Problem `make build` is **broken on `main` right now**: ``` error TS6059: File '/…/quak/bin/quak.ts' is not under 'rootDir' '/…/quak/src'. 'rootDir' is expected to contain all source files. ``` `tsconfig.json:8` sets `"rootDir": "./src"` while `tsconfig.json:20` sets `"include": ["src/**/*", "bin/**/*"]`. The two contradict each other. Nothing catches this: `make check` runs test, lint and fmt-check only (`script/check`), and the Dockerfile runs `make check` but never `make build`. So a repo whose `package.json` declares `"main": "./dist/index.js"` and `"bin": {"quak": "./dist/bin/quak.js"}` cannot produce either artifact, and CI is blind to it. The README's Getting Started block also tells users to run `yarn quak login`, `yarn quak collections`, etc. There is no `quak` script in `package.json`, so every one of those commands fails. ## Definition of done 1. `make build` succeeds from a clean checkout and emits JavaScript plus declaration files under `dist/`. 2. `package.json` `main`, `types` and `bin` point at paths that the build actually produces, verified by inspecting `dist/` after a build. 3. The emitted CLI entrypoint retains the `#!/usr/bin/env node` shebang from `bin/quak.ts:1` and is executable. 4. `yarn quak <command>` works after `yarn build`, matching what the README's Getting Started section already promises — add the missing `quak` script to `package.json`. 5. The Dockerfile runs `make build` in addition to `make check`, so this class of error can never reach `main` again. (Do not change what `make check` itself runs — the repo policy fixes its composition as test + lint + fmt-check.) 6. `make clean` still removes everything the build produces. 7. `make check` green. 8. `TODO.md` updated in the same commit. ## Recommended approach Set `"rootDir": "."` and update `package.json` `main`/`types`/`bin` to the resulting `dist/src/…` and `dist/bin/…` paths. This is the smallest change and keeps the source layout the README documents. The alternative — moving the CLI body into `src/cli.ts` and leaving a thin shim in `bin/` — keeps `dist/index.js` as the package main but churns more files and contradicts the layout diagram in the README's Design section. Pick the first unless you find a concrete reason not to, and record the reasoning in the PR body. ## Out of scope `engines`, `exports`, and `prepublishOnly` — tracked separately.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:43:28 +02:00
clawbot self-assigned this 2026-08-09 03:43:28 +02:00
Author
Collaborator

Implementation requirements

Written against main at 348f23b, after #1 and #2 landed. make check is green there (18 test
files, 210 tests, ~10s); make build is not.

The contradiction

tsconfig.json sets "rootDir": "./src" and "include": ["src/**/*", "bin/**/*"]. Those cannot
both hold — bin/quak.ts is outside rootDir, hence TS6059. Reproduce it first via make build
so you have the exact message in front of you before changing anything.

Nothing catches this today: script/check runs test, lint and fmt-check only, and the Dockerfile
runs make check but never make build. That is why a repo declaring "main": "./dist/index.js"
and "bin": {"quak": "./dist/bin/quak.js"} has been unable to produce either artifact.

Set "rootDir": ".". Emitted layout becomes dist/src/… and dist/bin/…, so update
package.json:

  • main./dist/src/index.js
  • types./dist/src/index.d.ts
  • bin.quak./dist/bin/quak.js

The alternative — moving the CLI body into src/ with a thin shim in bin/ — keeps
dist/index.js as the package main but churns more files and contradicts the layout diagram in
the README's Design section. If you take it instead, justify it in the PR body with the specific
obstacle you hit.

Either way, resolveJsonModule is already on, and #5 (single-sourcing the version from
package.json) will need to import it from the built output — so whatever layout you pick, make
sure a relative import of package.json would resolve from both src/ under vitest and from
dist/ after a build. You do not have to implement #5 here, but do not pick a layout that makes
it impossible.

Verify the artifacts exist rather than assuming tsc emitted them

After make build, confirm on disk:

  • the file each of main, types and bin.quak points at actually exists;
  • the emitted CLI retains the #!/usr/bin/env node shebang from bin/quak.ts:1;
  • .d.ts and .js.map files are present for the library entrypoint.

State the observed paths in the PR body. This repo has failed four reviews on claims that were
not checked; "the build succeeds" is not the same statement as "the declared artifacts exist".

The quak script

The README's Getting Started block tells users to run yarn quak login, yarn quak collections
and so on. There is no quak script in package.json, so every one of those commands fails
today. Add it so the documented commands work after yarn build. If the invocation you add
differs from what the README shows, update the README to match — the two must agree at the end.

Dockerfile

Add make build to the Dockerfile so this class of error cannot reach main again. Do not
change what make check runs — REPO_POLICIES.md fixes its composition as test + lint +
fmt-check, and #4 is separately reworking the Dockerfile into a multi-stage build. Keep your change
minimal and additive so it does not collide with #4.

Note that #4 also documents a script/cibuild cache defect: a bare docker build . can serve
RUN make check from cache and exit 0 without running anything. That is #4's to fix, not yours —
but it means you must not treat a fast, green docker build as evidence your build step ran. If
you verify the Docker path at all, do it with docker build --no-cache . scoped to this image.
Never run docker builder prune or docker system prune — the BuildKit cache is shared across
every repo on this host and an agent elsewhere already destroyed ~41 GB of it doing exactly that.

make clean

It must still remove everything the build now produces. If the output layout changes, update the
target.

Process

  • Branch off main. TDD applies as far as it sensibly can here: this is a build-configuration
    fix, so if you cannot write a meaningful failing test first, say so explicitly in the PR body
    rather than staging a token one. A check that the declared package.json entrypoints exist
    after a build is the closest thing to a real regression test and is worth adding.
  • Verify only via make targets / script/ entrypoints. Never invoke tsc, vitest, eslint,
    prettier or yarn scripts directly.
  • make check and make build must both be green at the end. Report the measured make test
    time — baseline ~10s at 210 tests, 20s budget, 30s hard cap.
  • Before measuring anything, confirm no git worktrees exist under .claude/worktrees/ in the
    tree you are measuring.
    Per #25, vitest globs them and silently runs the suite N+1 times; a
    polluted run reported 1050 tests where the real number is 210.
  • Run make fmt. TODO.md and any README change go in the same commit as the implementation.
  • Commit title ends with (closes #3).
  • Never mention Claude or Anthropic anywhere. No attribution or co-author trailers.
  • Do not touch anything belonging to #4, #5, #6, #13, #24 or #25.
## Implementation requirements Written against `main` at `348f23b`, after #1 and #2 landed. `make check` is green there (18 test files, 210 tests, ~10s); `make build` is not. ### The contradiction `tsconfig.json` sets `"rootDir": "./src"` and `"include": ["src/**/*", "bin/**/*"]`. Those cannot both hold — `bin/quak.ts` is outside `rootDir`, hence TS6059. Reproduce it first via `make build` so you have the exact message in front of you before changing anything. Nothing catches this today: `script/check` runs test, lint and fmt-check only, and the Dockerfile runs `make check` but never `make build`. That is why a repo declaring `"main": "./dist/index.js"` and `"bin": {"quak": "./dist/bin/quak.js"}` has been unable to produce either artifact. ### Take the recommended approach unless you find a real obstacle Set `"rootDir": "."`. Emitted layout becomes `dist/src/…` and `dist/bin/…`, so update `package.json`: - `main` → `./dist/src/index.js` - `types` → `./dist/src/index.d.ts` - `bin.quak` → `./dist/bin/quak.js` The alternative — moving the CLI body into `src/` with a thin shim in `bin/` — keeps `dist/index.js` as the package main but churns more files and contradicts the layout diagram in the README's Design section. If you take it instead, justify it in the PR body with the specific obstacle you hit. Either way, `resolveJsonModule` is already on, and #5 (single-sourcing the version from `package.json`) will need to import it from the built output — so whatever layout you pick, make sure a relative import of `package.json` would resolve from both `src/` under vitest and from `dist/` after a build. You do not have to implement #5 here, but do not pick a layout that makes it impossible. ### Verify the artifacts exist rather than assuming tsc emitted them After `make build`, confirm on disk: - the file each of `main`, `types` and `bin.quak` points at actually exists; - the emitted CLI retains the `#!/usr/bin/env node` shebang from `bin/quak.ts:1`; - `.d.ts` and `.js.map` files are present for the library entrypoint. State the observed paths in the PR body. This repo has failed four reviews on claims that were not checked; "the build succeeds" is not the same statement as "the declared artifacts exist". ### The `quak` script The README's Getting Started block tells users to run `yarn quak login`, `yarn quak collections` and so on. There is no `quak` script in `package.json`, so every one of those commands fails today. Add it so the documented commands work after `yarn build`. If the invocation you add differs from what the README shows, update the README to match — the two must agree at the end. ### Dockerfile Add `make build` to the Dockerfile so this class of error cannot reach `main` again. Do **not** change what `make check` runs — `REPO_POLICIES.md` fixes its composition as test + lint + fmt-check, and #4 is separately reworking the Dockerfile into a multi-stage build. Keep your change minimal and additive so it does not collide with #4. Note that #4 also documents a `script/cibuild` cache defect: a bare `docker build .` can serve `RUN make check` from cache and exit 0 without running anything. That is #4's to fix, not yours — but it means you must not treat a fast, green `docker build` as evidence your build step ran. If you verify the Docker path at all, do it with `docker build --no-cache .` scoped to this image. **Never run `docker builder prune` or `docker system prune`** — the BuildKit cache is shared across every repo on this host and an agent elsewhere already destroyed ~41 GB of it doing exactly that. ### `make clean` It must still remove everything the build now produces. If the output layout changes, update the target. ### Process - Branch off `main`. TDD applies as far as it sensibly can here: this is a build-configuration fix, so if you cannot write a meaningful failing test first, say so explicitly in the PR body rather than staging a token one. A check that the declared `package.json` entrypoints exist after a build is the closest thing to a real regression test and is worth adding. - Verify only via `make` targets / `script/` entrypoints. Never invoke `tsc`, `vitest`, `eslint`, `prettier` or `yarn` scripts directly. - `make check` and `make build` must both be green at the end. Report the measured `make test` time — baseline ~10s at 210 tests, 20s budget, 30s hard cap. - **Before measuring anything, confirm no git worktrees exist under `.claude/worktrees/` in the tree you are measuring.** Per #25, vitest globs them and silently runs the suite N+1 times; a polluted run reported 1050 tests where the real number is 210. - Run `make fmt`. `TODO.md` and any README change go in the same commit as the implementation. - Commit title ends with ` (closes #3)`. - Never mention Claude or Anthropic anywhere. No attribution or co-author trailers. - Do not touch anything belonging to #4, #5, #6, #13, #24 or #25.
Author
Collaborator

Implementation plan

Reproduced first, on 348f23b, before touching anything. make bootstrap then make build:

error TS6059: File '/…/bin/quak.ts' is not under 'rootDir' '/…/src'.
'rootDir' is expected to contain all source files.
  The file is in the program because:
    Matched by include pattern 'bin/**/*' in '/…/tsconfig.json'

Baseline make test in this tree (no worktrees nested under it, checked with git worktree list): 18 files, 210 tests, 7.9s wall.

Plan

  1. Red commit first. test/build/entrypoints.test.ts, asserting the contract that is broken today, without needing a build:

    • every include pattern in tsconfig.json roots under compilerOptions.rootDir — this is exactly the TS6059 precondition, and it fails at 348f23b;
    • main, types and bin.quak in package.json equal the paths tsc will emit for src/index.ts and bin/quak.ts given outDir/rootDir;
    • the quak script invokes the same path as bin.quak;
    • bin/quak.ts still starts with the #!/usr/bin/env node shebang.
  2. Take the recommended approach: "rootDir": ".". Emitted layout becomes dist/src/… + dist/bin/…; package.json gets main./dist/src/index.js, types./dist/src/index.d.ts, bin.quak./dist/bin/quak.js. This also keeps #5 possible: with rootDir at the repo root, package.json sits inside the root dir, so a relative import of it resolves under both vitest and dist/. I will only deviate to the shim layout if I hit a concrete obstacle, and will say what it was.

  3. script/build (POSIX sh, Makefile build becomes a thin shim per the Scripts to Rule Them All section of REPO_POLICIES.md): run tsc, then verify on disk that each declared entrypoint exists and that the emitted CLI kept its shebang, then chmod +x it. Putting the artifact check in the build rather than in make check keeps make check composition untouched (test + lint + fmt-check) and keeps it independent of a prior build, while still failing loudly in CI. package.json's build script points at script/build so the README's yarn build gets the same guarantees.

  4. quak script in package.json so the README Getting Started commands (yarn quak login, ...) work after yarn build. README stays as-is if the invocation matches; otherwise I update the README.

  5. Dockerfile: add RUN make build after the existing RUN make check — additive only, no change to what make check runs, minimal so it does not collide with #4. I will not run docker builder prune or docker system prune.

  6. make clean already does rm -rf dist, which still covers the new layout; will confirm rather than assume.

  7. make fmt, then make check and make build both green. TODO.md and any README change in the same commit as the implementation; commit title ends with (closes #3).

Verification I will report

The actual ls-observed emitted paths for main, types, bin.quak, the presence of .d.ts/.js.map for the library entrypoint, the observed shebang line and mode bits of the emitted CLI, and the make test count/timing. Nothing goes in the PR body that I did not run in this session.

Out of scope, untouched: engines, exports, prepublishOnly, and anything belonging to #4, #5, #6, #13, #24, #25.

## Implementation plan Reproduced first, on `348f23b`, before touching anything. `make bootstrap` then `make build`: ``` error TS6059: File '/…/bin/quak.ts' is not under 'rootDir' '/…/src'. 'rootDir' is expected to contain all source files. The file is in the program because: Matched by include pattern 'bin/**/*' in '/…/tsconfig.json' ``` Baseline `make test` in this tree (no worktrees nested under it, checked with `git worktree list`): 18 files, 210 tests, 7.9s wall. ### Plan 1. **Red commit first.** `test/build/entrypoints.test.ts`, asserting the contract that is broken today, without needing a build: - every `include` pattern in `tsconfig.json` roots under `compilerOptions.rootDir` — this is exactly the TS6059 precondition, and it fails at `348f23b`; - `main`, `types` and `bin.quak` in `package.json` equal the paths `tsc` will emit for `src/index.ts` and `bin/quak.ts` given `outDir`/`rootDir`; - the `quak` script invokes the same path as `bin.quak`; - `bin/quak.ts` still starts with the `#!/usr/bin/env node` shebang. 2. **Take the recommended approach:** `"rootDir": "."`. Emitted layout becomes `dist/src/…` + `dist/bin/…`; `package.json` gets `main` → `./dist/src/index.js`, `types` → `./dist/src/index.d.ts`, `bin.quak` → `./dist/bin/quak.js`. This also keeps #5 possible: with `rootDir` at the repo root, `package.json` sits inside the root dir, so a relative import of it resolves under both vitest and `dist/`. I will only deviate to the shim layout if I hit a concrete obstacle, and will say what it was. 3. **`script/build`** (POSIX sh, Makefile `build` becomes a thin shim per the Scripts to Rule Them All section of `REPO_POLICIES.md`): run `tsc`, then verify on disk that each declared entrypoint exists and that the emitted CLI kept its shebang, then `chmod +x` it. Putting the artifact check in the build rather than in `make check` keeps `make check` composition untouched (test + lint + fmt-check) and keeps it independent of a prior build, while still failing loudly in CI. `package.json`'s `build` script points at `script/build` so the README's `yarn build` gets the same guarantees. 4. **`quak` script** in `package.json` so the README Getting Started commands (`yarn quak login`, ...) work after `yarn build`. README stays as-is if the invocation matches; otherwise I update the README. 5. **Dockerfile:** add `RUN make build` after the existing `RUN make check` — additive only, no change to what `make check` runs, minimal so it does not collide with #4. I will not run `docker builder prune` or `docker system prune`. 6. `make clean` already does `rm -rf dist`, which still covers the new layout; will confirm rather than assume. 7. `make fmt`, then `make check` and `make build` both green. `TODO.md` and any README change in the same commit as the implementation; commit title ends with ` (closes #3)`. ### Verification I will report The actual `ls`-observed emitted paths for `main`, `types`, `bin.quak`, the presence of `.d.ts`/`.js.map` for the library entrypoint, the observed shebang line and mode bits of the emitted CLI, and the `make test` count/timing. Nothing goes in the PR body that I did not run in this session. Out of scope, untouched: `engines`, `exports`, `prepublishOnly`, and anything belonging to #4, #5, #6, #13, #24, #25.
Author
Collaborator

Implemented in PR #26: #26 (branch fix-ts-build-rootdir, off main at 348f23b).

Took the recommended approach — rootDir: "." — with no obstacle found, so main and types moved to ./dist/src/index.js and ./dist/src/index.d.ts and bin.quak stayed at ./dist/bin/quak.js. All three exist on disk after make build, and dist/bin/quak.js is mode -rwxr-xr-x with #!/usr/bin/env node on line 1; ./dist/bin/quak.js --help runs. make check is 19 files / 216 tests / 8.79s and make build exits 0; docker build --no-cache . ran both steps green.

Two things worth flagging against the definition of done:

  • Clearing TS6059 let the compiler type-check for the first time and it found two genuine errors in src/crypto/stream.ts that no build had ever reached. Both are fixed in the PR; make build could not go green without them.
  • Point 4 is only partly verified. The quak script exists and the built CLI runs, but the task I am working under forbids invoking yarn scripts, so I did not run yarn quak login itself. The PR body says so explicitly.

Follow-up found while working, filed separately rather than fixed here: @types/libsodium-wrappers-sumo is a deprecated stub package that ships no type definitions.

Implemented in PR #26: https://git.eeqj.de/sneak/quak/pulls/26 (branch `fix-ts-build-rootdir`, off `main` at `348f23b`). Took the recommended approach — `rootDir: "."` — with no obstacle found, so `main` and `types` moved to `./dist/src/index.js` and `./dist/src/index.d.ts` and `bin.quak` stayed at `./dist/bin/quak.js`. All three exist on disk after `make build`, and `dist/bin/quak.js` is mode `-rwxr-xr-x` with `#!/usr/bin/env node` on line 1; `./dist/bin/quak.js --help` runs. `make check` is 19 files / 216 tests / 8.79s and `make build` exits 0; `docker build --no-cache .` ran both steps green. Two things worth flagging against the definition of done: - Clearing TS6059 let the compiler type-check for the first time and it found two genuine errors in `src/crypto/stream.ts` that no build had ever reached. Both are fixed in the PR; `make build` could not go green without them. - Point 4 is only partly verified. The `quak` script exists and the built CLI runs, but the task I am working under forbids invoking yarn scripts, so I did not run `yarn quak login` itself. The PR body says so explicitly. Follow-up found while working, filed separately rather than fixed here: `@types/libsodium-wrappers-sumo` is a deprecated stub package that ships no type definitions.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/quak#3