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.
- package/README.md +225 -0
- package/dist/index.js +28407 -0
- 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.
|