delacour 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +225 -0
  2. package/dist/index.js +28407 -0
  3. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # delacour
2
+
3
+ React Native components for Expo apps. The CLI copies the source into your
4
+ repository — you own `button.tsx` and can edit it, rather than waiting for a
5
+ release.
6
+
7
+ ```bash
8
+ bunx delacour@alpha init
9
+ bunx delacour@alpha add button
10
+ ```
11
+
12
+ ## Why not a component package
13
+
14
+ Installing `@some/ui-kit` means every change to a button is someone else's
15
+ release. This works the way shadcn/ui does instead: `add` writes real files into
16
+ your project, rewrites the imports to your own aliases, and gets out of the way.
17
+
18
+ Unlike shadcn, the target is Expo, which changes real things:
19
+
20
+ - Native modules install through **`expo install`**, so the SDK resolves a
21
+ version it can build. `bun add react-native-reanimated` fetches the newest
22
+ release, which on any older SDK does not compile.
23
+ - `init` wraps **Metro** with Uniwind's config, outermost, so `className`
24
+ compiles.
25
+ - `init` writes **`@source`** globs pointing at where the components actually
26
+ landed — by real path, because Tailwind's scanner does not follow the
27
+ `node_modules` symlink a monorepo creates, and a missed glob strips every
28
+ class from a release build without erroring.
29
+ - **`delacour doctor`** checks all of it, plus New Architecture, path aliases,
30
+ `GestureHandlerRootView`, whether anything actually imports the CSS entry, and duplicate copies
31
+ of a native module.
32
+
33
+ ## Commands
34
+
35
+ | | |
36
+ |---|---|
37
+ | `init [components...]` | Write `native-components.json`, wire up Metro and the CSS entry, add the theme |
38
+ | `add <components...>` | Copy components and everything they need, and say what to install |
39
+ | `list` / `search <q>` | Browse the registry |
40
+ | `view <name>` | One item: its files, what it pulls in, what it installs |
41
+ | `diff [name]` | What has changed upstream since you copied it |
42
+ | `doctor` | Check this app is wired up correctly |
43
+ | `info` | The resolved config and what was detected |
44
+
45
+ Every registry-reading command takes `--registry <url>` (a URL,
46
+ `github:owner/repo`, or a local path), `--ref <git-ref>`, `--offline`, and
47
+ `--cwd <path>`.
48
+
49
+ ## What a component needs installed
50
+
51
+ `add` copies files. It ends by naming the external packages the components need — every time,
52
+ whether or not any are missing:
53
+
54
+ ```txt
55
+ ● Needs 14 external packages, 2 not here yet:
56
+ │ bunx expo install @gorhom/bottom-sheet react-native-keyboard-controller
57
+ │ already installed — clsx, react-native-reanimated, react-native-svg, tailwind-variants, …
58
+ ```
59
+
60
+ Then it asks. `--install` answers yes without asking, `--no-install` answers no, and a run with
61
+ nobody to ask — a script, CI, an agent — installs nothing and prints the commands instead:
62
+
63
+ ```bash
64
+ bunx delacour@alpha add bottom-sheet --install
65
+ ```
66
+
67
+ The missing packages are shown as commands, not names, because the command is the part that is not
68
+ obvious: a native module goes through `expo install` so the SDK picks a version it can build, a
69
+ plain one through whatever package manager the project is on, and both from the **app** — a native
70
+ module resolved from two places registers twice and breaks at runtime. `delacour view <name>`
71
+ prints the same lines before you copy anything.
72
+
73
+ ## Where components go
74
+
75
+ In a workspace `init` asks whether the components belong to one app or to a package several apps
76
+ share. Choose the package and it writes a real one — `package.json` with an `exports` map, the
77
+ app's dependency on it, and Metro's resolver wiring:
78
+
79
+ ```tsx
80
+ import { Button } from "@acme/ui/button";
81
+ ```
82
+
83
+ ```bash
84
+ bunx delacour@alpha init --package-name @acme/ui --package-path packages/ui # no prompts
85
+ ```
86
+
87
+
88
+
89
+ `native-components.json` lives wherever the components live, and the nearest one wins
90
+ walking up from `--cwd`. In a monorepo that means:
91
+
92
+ ```bash
93
+ cd packages/ui && bunx delacour@alpha init # shared package, several apps use it
94
+ cd apps/mobile && bunx delacour@alpha init # straight into the app
95
+ ```
96
+
97
+ Either way the CLI finds the Expo app and wires *its* Metro config and CSS
98
+ entry, because that is where Metro runs.
99
+
100
+ ```jsonc
101
+ {
102
+ "$schema": "https://raw.githubusercontent.com/delacournz/delacour-ui/main/registry/config.schema.json",
103
+ "framework": "expo",
104
+ "paths": {
105
+ "ui": "src/components/ui",
106
+ "lib": "src/lib",
107
+ "hooks": "src/hooks",
108
+ "styles": "src/styles",
109
+ "icons": "src/lib/icons"
110
+ },
111
+ "aliases": { "ui": "@/components/ui", "lib": "@/lib" },
112
+ "app": { "root": "../../apps/mobile", "css": "src/styles/global.css" }
113
+ }
114
+ ```
115
+
116
+ `paths` is where files land; `aliases` is how they import each other. They are
117
+ separate because a project may have no path aliases at all — in which case the
118
+ CLI writes relative imports, which Metro resolves whether or not
119
+ `experiments.tsconfigPaths` is on. Aliases are **read** from `tsconfig.json` and
120
+ never written to it.
121
+
122
+ ## After `init`
123
+
124
+ Three things need a human, because each is a decision inside a file you own:
125
+
126
+ ```tsx
127
+ // app/_layout.tsx
128
+ import "@/styles/global.css";
129
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
130
+
131
+ export default function RootLayout() {
132
+ return <GestureHandlerRootView style={{ flex: 1 }}>{/* … */}</GestureHandlerRootView>;
133
+ }
134
+ ```
135
+
136
+ ```ts
137
+ // app.config.ts — only if you use path aliases
138
+ experiments: { tsconfigPaths: true }
139
+ ```
140
+
141
+ And after any `add` that installed a native module, rebuild the dev client —
142
+ a JS reload alone will red-box. `add` says so at the point it becomes true.
143
+
144
+ `delacour doctor` checks every one of these.
145
+
146
+ ## Working on this package
147
+
148
+ ```bash
149
+ bun run registry:build # rebuild registry/ from packages/native-ui
150
+ bun test # unit + end-to-end against the local registry
151
+ bun run verify:expo # scaffold a real Expo app, add everything, typecheck it
152
+ bun run build # bundle dist/index.js
153
+ bun run build:check # assert the bundle stayed small and executable
154
+ ```
155
+
156
+ ### `verify:expo`
157
+
158
+ `bun test` copies files into a fixture and asserts on the result. That catches a broken path but
159
+ not a broken *component* — an import naming a package nobody installed, a peer the registry never
160
+ classified, a type that only resolves inside this monorepo.
161
+
162
+ `verify:expo` scaffolds a real Expo app in a temp directory, installs Uniwind and Tailwind the way
163
+ a consumer would, runs `init` and adds **every** item, then runs `tsc --noEmit` against the
164
+ result. A clean typecheck is the proof that all of it resolves.
165
+
166
+ Run it from the repository root (`bun run verify:expo`) or from this package. `--help` lists
167
+ everything.
168
+
169
+ The app is built at **`packages/cli/.verify/app`** — gitignored, and below `packages/cli`, which the
170
+ workspace globs do not reach, so Bun never treats it as a workspace member.
171
+
172
+ | | |
173
+ | --- | --- |
174
+ | *(default)* | Removes the directory afterwards, `node_modules` included — around half a gigabyte. |
175
+ | `--keep` | Leaves everything. The next run reuses the install: **~5s instead of ~2 min**. |
176
+ | `--fresh` | Discards whatever a previous `--keep` left, before starting. |
177
+
178
+ `--simulator` implies `--keep`, since the point of it is an app you can open.
179
+
180
+ ```bash
181
+ bun run verify:expo # scaffold, install, add everything, typecheck
182
+ bun run verify:expo --bundle # ...and compile it with Metro
183
+ bun run verify:expo --simulator # ...and build a dev client and launch it
184
+ bun run verify:expo --no-install # structure only, offline
185
+ bun run verify:expo --only button # one component and its closure
186
+ bun run verify:expo --keep # leave the app behind to poke at
187
+ bun run verify:expo --verbose # stream every subprocess
188
+ ```
189
+
190
+ Four levels, each catching what the one below cannot:
191
+
192
+ | Level | Proves | Cost |
193
+ | --- | --- | --- |
194
+ | `--no-install` | The right files land, with the right imports | seconds, offline |
195
+ | *(default)* | Every import resolves and every type lines up (`tsc`) | ~2 min |
196
+ | `--bundle` | Metro resolves every module and **Uniwind compiles every class** | +~1 min |
197
+ | `--simulator` | The components actually render on a device | +~10 min, needs Xcode |
198
+
199
+ `--bundle` is the one worth understanding. `tsc` says nothing about `className`, because Uniwind
200
+ compiles it in a *Metro transform* — a class the scanner never saw is dropped silently rather than
201
+ reported. Only a real bundle exercises that path.
202
+
203
+ #### One warning is expected
204
+
205
+ `doctor` reports **"Native modules — two copies of react-native, …"** against the scaffolded app.
206
+ That is an artefact of the app living inside this monorepo, whose root `node_modules` holds the
207
+ same packages. It is a warning rather than a failure, and the run passes.
208
+
209
+ It does not weaken the check that matters. "Every package imported is actually installed" reads
210
+ **only the app's own `node_modules`**, so a package the registry failed to declare is still caught
211
+ even though `tsc` would have resolved it from the repository root — verified by deleting one and
212
+ watching the check fire.
213
+
214
+ `--all` adds components and what they pull in, so the run also names the standalone utilities
215
+ explicitly — a verification pass has to cover the whole registry, not most of it.
216
+
217
+ The registry is **derived** from `packages/native-ui/src` — one item per
218
+ component folder, dependencies read off the imports. There is no `registry.json`
219
+ to maintain, and the builder throws rather than guessing: an unclassified npm
220
+ import or a component folder without an `index.ts` fails the build.
221
+
222
+ It writes three things: `registry.json` (the index), `r/<name>.json` (one item,
223
+ naming its files) and `files/<namespace>/<target>` (the files themselves). An
224
+ item references its files rather than inlining them, so a component's diff is
225
+ the TypeScript that changed rather than a JSON string nobody can read.