[E00-S01-T03] Configure ESM package boundaries #370

Merged
kpcto merged 1 commits from feature/156 into main 2026-08-28 08:21:10 +00:00
Member

What changed

Configures ESM package boundaries for [E00-S01-T03] Configure ESM package boundaries (#156):

  • apps/server/package.json, packages/core/package.json, extensions/example/package.json — each workspace package now declares its ESM boundary in its manifest:
    • "type": "module" — package source and emit are ESM
    • "main": "./dist/index.js" + "types": "./dist/index.d.ts" — declared entry points matching the rootDir src / outDir dist tsconfig emit (T02)
    • "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } } — an export map that is the package boundary: only the package root is reachable, deep subpath imports (@personal-blog/core/src/...) are blocked, and the types condition is declared first for TS consumers
  • Root package.json — adds "type": "module" so the whole workspace (apps, packages, extensions) is uniformly ESM, per Engineering-Standards "ESM-first"
  • No source changes — the src/index.ts placeholders stay as empty ESM modules (export {}); real exports land with later stories

Explicitly out of scope per the brief: tsconfig.base.json (E00-S01-T02, merged), dependency-boundary rule/test (E00-S01-T04), root build/test scripts (E00-S01-T05). No tsconfig, no lockfile, no CI changes in this PR.

Criterion → test table

Acceptance criterion Evidence / test
each package declares ESM boundaries in its manifest apps/server, packages/core, extensions/example each commit "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts" and "exports" with . → { types, import }; verified by parsing each manifest (3/3)
package boundaries are configured consistently across the workspace all three packages use the identical boundary shape (type/main/types/exports); root package.json also declares "type": "module" so apps, packages and extensions are uniformly ESM; verified with pnpm -r list --depth -1 → all 4 workspace projects (root + 3 packages) install/listed with pnpm 11.23.0
compile every workspace package and confirm boundaries resolve tsc -p <pkg>/tsconfig.json (TypeScript 6.0.3, NodeNext) succeeds for all 3 packages and emits ESM dist/index.js (export {};, no CJS exports./require) + dist/index.d.ts + sourcemap; a consumer project that depends on the workspace packages by name (@personal-blog/server, @personal-blog/core, @personal-blog/example-extension via file: links) imports all three through the exports map as real ESM modules ([object Module]), and a deep import @personal-blog/core/src/index.js is rejected with ERR_PACKAGE_PATH_NOT_EXPORTED

Test plan executed

  • tsc -p apps/server/tsconfig.json, -p packages/core/tsconfig.json, -p extensions/example/tsconfig.json (TypeScript 6.0.3) → all compile, all emit dist/index.js + dist/index.d.ts + dist/index.js.map ✓
  • emitted index.js is ESM (top-level export {};, no CJS artifacts) ✓
  • Node (v22) import('@personal-blog/<pkg>') by name resolves through the exports map for all 3 packages → [object Module] ✓
  • deep subpath import blocked by the exports map: ERR_PACKAGE_PATH_NOT_EXPORTED ✓
  • pnpm install --frozen-lockfile (pnpm 11.23.0, same as CI) → "Already up to date", exit 0, lockfile unchanged (git status clean apart from the 4 manifests) ✓
  • pnpm -r list --depth -1 → all 4 workspace projects listed ✓

Risks / notes

  • The exports map means deep imports into package internals now fail fast at the module boundary; the explicit dependency-direction rule/test is E00-S01-T04 (out of scope here).
  • Placeholder sources remain export {} — they compile as valid ESM under "type": "module"; meaningful exports arrive with the packages' real stories.
  • Root "type": "module" is a zero-behaviour change today (no root-level JS files) but keeps the workspace uniformly ESM; pnpm install/CI unaffected (verified above).

Closes #156

## What changed Configures ESM package boundaries for [E00-S01-T03] Configure ESM package boundaries (#156): - **`apps/server/package.json`, `packages/core/package.json`, `extensions/example/package.json`** — each workspace package now declares its ESM boundary in its manifest: - `"type": "module"` — package source and emit are ESM - `"main": "./dist/index.js"` + `"types": "./dist/index.d.ts"` — declared entry points matching the `rootDir src` / `outDir dist` tsconfig emit (T02) - `"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } }` — an export map that is the package boundary: only the package root is reachable, deep subpath imports (`@personal-blog/core/src/...`) are blocked, and the `types` condition is declared first for TS consumers - **Root `package.json`** — adds `"type": "module"` so the whole workspace (apps, packages, extensions) is uniformly ESM, per Engineering-Standards "ESM-first" - **No source changes** — the `src/index.ts` placeholders stay as empty ESM modules (`export {}`); real exports land with later stories Explicitly out of scope per the brief: `tsconfig.base.json` (E00-S01-T02, merged), dependency-boundary rule/test (E00-S01-T04), root build/test scripts (E00-S01-T05). No tsconfig, no lockfile, no CI changes in this PR. ## Criterion → test table | Acceptance criterion | Evidence / test | | --- | --- | | each package declares ESM boundaries in its manifest | `apps/server`, `packages/core`, `extensions/example` each commit `"type": "module"`, `"main": "./dist/index.js"`, `"types": "./dist/index.d.ts"` and `"exports"` with `.` → `{ types, import }`; verified by parsing each manifest (3/3) | | package boundaries are configured consistently across the workspace | all three packages use the identical boundary shape (`type`/`main`/`types`/`exports`); root `package.json` also declares `"type": "module"` so apps, packages and extensions are uniformly ESM; verified with `pnpm -r list --depth -1` → all 4 workspace projects (root + 3 packages) install/listed with pnpm 11.23.0 | | compile every workspace package and confirm boundaries resolve | `tsc -p <pkg>/tsconfig.json` (TypeScript 6.0.3, NodeNext) succeeds for all 3 packages and emits ESM `dist/index.js` (`export {};`, no CJS `exports.`/`require`) + `dist/index.d.ts` + sourcemap; a consumer project that depends on the workspace packages by name (`@personal-blog/server`, `@personal-blog/core`, `@personal-blog/example-extension` via `file:` links) imports all three through the `exports` map as real ESM modules (`[object Module]`), and a deep import `@personal-blog/core/src/index.js` is rejected with `ERR_PACKAGE_PATH_NOT_EXPORTED` | ## Test plan executed - `tsc -p apps/server/tsconfig.json`, `-p packages/core/tsconfig.json`, `-p extensions/example/tsconfig.json` (TypeScript 6.0.3) → all compile, all emit `dist/index.js` + `dist/index.d.ts` + `dist/index.js.map` ✓ - emitted `index.js` is ESM (top-level `export {};`, no CJS artifacts) ✓ - Node (v22) `import('@personal-blog/<pkg>')` by name resolves through the `exports` map for all 3 packages → `[object Module]` ✓ - deep subpath import blocked by the exports map: `ERR_PACKAGE_PATH_NOT_EXPORTED` ✓ - `pnpm install --frozen-lockfile` (pnpm 11.23.0, same as CI) → "Already up to date", exit 0, lockfile unchanged (`git status` clean apart from the 4 manifests) ✓ - `pnpm -r list --depth -1` → all 4 workspace projects listed ✓ ## Risks / notes - The exports map means deep imports into package internals now fail fast at the module boundary; the explicit dependency-direction rule/test is E00-S01-T04 (out of scope here). - Placeholder sources remain `export {}` — they compile as valid ESM under `"type": "module"`; meaningful exports arrive with the packages' real stories. - Root `"type": "module"` is a zero-behaviour change today (no root-level JS files) but keeps the workspace uniformly ESM; `pnpm install`/CI unaffected (verified above). Closes #156
bot-implementer added 1 commit 2026-08-28 08:13:13 +00:00
feat: configure ESM package boundaries across the workspace (E00-S01-T03)
CI / Frozen lockfile install (pull_request) Successful in 39s
b1fef8d592
Adds ESM boundary declarations to every workspace package manifest
(apps/server, packages/core, extensions/example): "type": "module",
"main"/"types" entry points and an "exports" map (types + import
conditions) so each package exposes only its public root; adds
"type": "module" to the workspace root package.json so the workspace is
uniformly ESM. Placeholder src/index.ts files stay as empty ESM modules.

Verified: each package compiles under tsconfig.base.json (NodeNext) with
TypeScript 6.0.3 and emits ESM dist/index.js + dist/index.d.ts; Node
imports each package by name through its exports map and rejects deep
subpath imports (ERR_PACKAGE_PATH_NOT_EXPORTED); pnpm 11.23.0
install --frozen-lockfile passes with the lockfile unchanged.

Closes #156
kpcto merged commit 4ef0ec9ae4 into main 2026-08-28 08:21:10 +00:00
kpcto deleted branch feature/156 2026-08-28 08:21:11 +00:00
Sign in to join this conversation.