Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
11 changes: 8 additions & 3 deletions .claude/rules/imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 2 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -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'],
},
],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"",
Expand Down
29 changes: 29 additions & 0 deletions scripts/verify-lib.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading