From 998d8b05cb652b3d06cd3ce87d641f7672c76b8c Mon Sep 17 00:00:00 2001 From: eumaninho54 Date: Thu, 27 Aug 2026 19:23:42 -0300 Subject: [PATCH 1/3] fix(build): emit relative imports instead of absolute paths --- babel.config.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/babel.config.js b/babel.config.js index 988fdae..0e38a92 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,11 +1,9 @@ -const path = require('path'); - module.exports = { plugins: [ [ 'module-resolver', { - alias: { '@': path.resolve(__dirname, 'src') }, + alias: { '@': './src' }, extensions: ['.ts', '.tsx', '.js', '.jsx'], }, ], From 22bdfc4cf5b45a3c6ee2b5836967f07d9a4ef6d2 Mon Sep 17 00:00:00 2001 From: eumaninho54 Date: Thu, 27 Aug 2026 19:26:59 -0300 Subject: [PATCH 2/3] ci(build): guard lib/ against unportable imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release guard only grepped for '@/', so it passed on a build whose alias had already been resolved — into the build machine's absolute path. 0.1.0 shipped 22 files importing from /Users/... and nothing caught it. verify-lib.sh checks the real invariant, that no specifier in lib/ is absolute, and now runs on every PR through build-library rather than only at release time. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 9 ++------- package.json | 1 + scripts/verify-lib.sh | 29 +++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100755 scripts/verify-lib.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0076c0..a5ea2a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,9 @@ jobs: - name: Build package run: yarn prepare + - name: Check lib/ is portable + run: yarn verify:lib + build-android: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 730ed00..b691e9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,13 +73,8 @@ jobs: - name: Build package run: yarn prepare - - name: Check the '@/' alias did not reach lib/ - run: | - if grep -rq '@/' lib/; then - echo "::error::The '@/' alias leaked into lib/, consumers cannot resolve it." - grep -rn '@/' lib/ - exit 1 - fi + - name: Check lib/ is portable + run: yarn verify:lib # The very first version goes up by hand, before npm can trust this workflow. - name: Check whether the version is already on npm diff --git a/package.json b/package.json index 198b50b..1db793e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "clean": "del-cli lib", "prepare": "husky || true; bob build && tsc-alias -p tsconfig.build.json --outDir lib/typescript", "typecheck": "tsc", + "verify:lib": "bash scripts/verify-lib.sh", "release": "release-it --only-version", "test": "jest", "lint": "eslint \"**/*.{js,jsx,cjs,mjs,ts,tsx}\"", diff --git a/scripts/verify-lib.sh b/scripts/verify-lib.sh new file mode 100755 index 0000000..fd0fba7 --- /dev/null +++ b/scripts/verify-lib.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Consumers resolve lib/ from their own node_modules, so every specifier in it must be portable. +set -uo pipefail + +if [ ! -d lib ]; then + echo "error: lib/ does not exist, run 'yarn prepare' first" + exit 1 +fi + +status=0 + +if grep -rq '@/' lib/; then + echo "error: the '@/' alias reached lib/, consumers cannot resolve it" + grep -rn '@/' lib/ + status=1 +fi + +# An absolute specifier means module-resolver baked in a path from the build machine. +if grep -rqE "from [\"']/|require\([\"']/" lib/; then + echo "error: lib/ imports from an absolute path" + grep -rnE "from [\"']/|require\([\"']/" lib/ + status=1 +fi + +if [ "$status" -eq 0 ]; then + echo "lib/ is portable" +fi + +exit "$status" From b8ae380ff1a9c36bd414921dfd9b4ce669c9a1a5 Mon Sep 17 00:00:00 2001 From: eumaninho54 Date: Thu, 27 Aug 2026 19:27:00 -0300 Subject: [PATCH 3/3] docs(rules): correct the alias absolute-path claim imports.md said both aliases are declared with absolute paths on purpose, which is what put the build machine's path into 0.1.0. The example carries no module-resolver, so nothing depended on it. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/CLAUDE.md | 2 +- .claude/rules/imports.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 779844d..b2216f2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -33,5 +33,5 @@ yarn example start # the demo app - **`yarn lint` is zero errors and zero warnings.** Not "mostly clean". - **Every view model has a test.** No exceptions worth arguing about. - **The published `lib/` must not contain `@/`.** `yarn prepare` resolves it; if you change - the build, verify with `grep -r '@/' lib/`. + the build, verify with `yarn verify:lib` — it also catches absolute paths. - **Never commit to `main`.** Branch first. diff --git a/.claude/rules/imports.md b/.claude/rules/imports.md index a6edf74..be63832 100644 --- a/.claude/rules/imports.md +++ b/.claude/rules/imports.md @@ -24,8 +24,13 @@ import { CICERONE } from '@/constants'; | `~/` | `example/src/` | the example app | **They must stay distinct.** The example's babel config also transforms library files, so a -shared prefix makes `@/context` in the library resolve to `example/src/context`. Both aliases -are declared with absolute paths for the same reason. +shared prefix makes `@/context` in the library resolve to `example/src/context`. + +**`@` maps to `./src`, never to an absolute path.** `babel-plugin-module-resolver` emits the +alias target verbatim, so `path.resolve(__dirname, 'src')` ships the build machine's own +path — that is how `0.1.0` reached npm with `import ... from "/Users/.../src/constants"` in +22 files. The example is safe either way: it has no `module-resolver`, Metro resolves both +aliases from `example/tsconfig.json`. Three places have to agree, and all three are already wired: @@ -51,7 +56,7 @@ default `false` it ignores `babel.config.js` and ships `import '@/constants'` to After touching anything in the build, verify: ```sh -yarn prepare && grep -r '@/' lib/ && echo "LEAK" || echo "clean" +yarn prepare && yarn verify:lib ``` ## Order