@symbiote-native/cli 0.0.1 → 0.1.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 CHANGED
@@ -1,92 +1,75 @@
1
1
  # @symbiote-native/cli
2
2
 
3
- **Status: `new` scaffolds a real app; `add` extends an existing one with all of `new`'s optional
4
- layers** (5 hand-named + one per `EXPO_PACKAGE_LAYERS` entry, 26 total as of 2026-09-18). `new`
5
- copies `templates/native` + `templates/js/<framework>` + any selected optional
6
- layers onto a fresh directory and merges `package.json` — closing the gap the root README's
7
- ["Try It In Your Own App"](../../README.md#try-it-in-your-own-app) section currently documents as
8
- a manual process and the `DX` row of the [Milestones table](../../README.md#milestones).
3
+ Scaffolds a new [SymbioteNative](../../README.md) app (`new`) or extends an already-scaffolded
4
+ one with more of `new`'s optional layers (`add`) — closing the gap the root README's
5
+ ["Try It In Your Own App"](../../README.md#try-it-in-your-own-app) section otherwise documents
6
+ as a manual process.
9
7
 
10
- `add` is a DIFFERENT command from what an earlier draft of this package planned (see "Design
11
- decisions" below, 2026-09-17): it does not bootstrap symbiote into a plain RN project. It only
12
- extends an app that already has a `@symbiote-native/<adapter>` dependency with more of `new`'s
13
- optional layers (`--navigation`, `--expo-modules`, `--testing`, `--splash-screen`, `--slider`, or
14
- one `--<id>` flag per Expo-backed package — `--battery`, `--sensors`, … — see `src/expo-package-
15
- layers.ts`), never touching App/user source — native files get idempotent text-splices, not
16
- overlays (see below).
8
+ ## Install
17
9
 
18
- ## Usage
10
+ ```bash
11
+ npx @symbiote-native/cli new my-app --framework react
12
+ ```
13
+
14
+ No install step: npx resolves the package's single `bin` entry automatically, the same
15
+ resolution `npx @angular/cli new` relies on. Published scoped, so there's no separate short
16
+ command to remember — `@symbiote-native/cli` is the one name for both identity and invocation.
17
+
18
+ ## Use it
19
+
20
+ ### New app
19
21
 
20
22
  ```bash
21
23
  npx @symbiote-native/cli new my-app --framework react
22
24
  npx @symbiote-native/cli new my-app --framework vue --vue-flavor sfc --navigation --expo-modules --pm pnpm
23
25
  npx @symbiote-native/cli new my-app --framework solid
24
26
  npx @symbiote-native/cli new my-app --framework svelte
25
-
26
- # inside an existing @symbiote-native/* app:
27
- npx @symbiote-native/cli add --navigation --testing --splash-screen
28
- npx @symbiote-native/cli add # interactive: offers only layers not already present
29
27
  ```
30
28
 
31
- `npx @symbiote-native/cli <args>` works with no install step: npx fetches the package and, since
32
- it ships exactly one `bin` entry, runs that bin with `<args>` — the same resolution
33
- `npx @angular/cli new` relies on. No `-p`/`--package` flag needed. There is no separate short
34
- command to remember: `@symbiote-native/cli` is the one name for the package and every
35
- invocation, whether or not the package happens to be installed locally.
29
+ `--framework` is one of `react | vue | angular | solid | svelte`; `--vue-flavor tsx|sfc` only
30
+ applies to Vue (default `sfc`). `--pm` defaults to whichever package manager launched the
31
+ process (npm/pnpm/yarn, detected from `npm_config_user_agent`). Any flag left out is asked for
32
+ interactively.
36
33
 
37
- Any flag left out is asked for interactively via `@clack/prompts`. `--pm` defaults to
38
- whichever package manager launched the process (npm/pnpm/yarn, however it was invoked),
39
- detected from `npm_config_user_agent` — see `src/detect-package-manager.ts`. `--framework` is
40
- one of `react | vue | angular | solid | svelte`; `--vue-flavor tsx|sfc` only applies to Vue
41
- (defaults to `sfc`).
34
+ `--javascript` opts out of TypeScript (default on). Angular is the one exception — its AOT
35
+ pipeline compiles templates as a TypeScript-compiler extension, so `--javascript` is ignored for
36
+ it:
42
37
 
43
- `--javascript` opts out of TypeScript (default on): `render-template.ts` drops every
44
- `tsconfig*.json`/`*.d.ts` file and the framework's `package.json.fragment.typescript.json`
45
- sibling fragment (`typescript`, `@react-native/typescript-config`, `@types/react`, `vue-tsc`).
46
- Angular is the one exception — its AOT pipeline (`ngtsc`) compiles templates as a TypeScript-
47
- compiler extension, so `--javascript` is ignored for it (`resolveTypescript` forces `true`):
38
+ | framework | JS | TS |
39
+ | ------------------ | --- | --- |
40
+ | react | ✅ | ✅ |
41
+ | vue (sfc \| tsx) | ✅ | ✅ |
42
+ | solid | ✅ | ✅ |
43
+ | svelte | ✅ | ✅ |
44
+ | angular | ❌ | ✅ |
48
45
 
49
- | framework | JS | TS |
50
- | ---------------- | --- | --- |
51
- | react | ✅ | ✅ |
52
- | vue (sfc \| tsx) | ✅ | ✅ |
53
- | solid | ✅ | ✅ |
54
- | svelte | ✅ | ✅ |
55
- | angular | ❌ | ✅ |
46
+ `--styling` picks the App stub's styling convention: `css` (default), `css-modules`, `scss`,
47
+ `less`, `stylus`, or `stylesheet` (`StyleSheet.create`).
56
48
 
57
- `add` detects the framework from a `@symbiote-native/<adapter>` dependency in the current
58
- directory's `package.json` (a DIFFERENT check from `new`'s plain `@angular/core`/`vue`/`svelte`/
59
- `solid-js`/`react` match — see `src/detect-framework.ts`'s `detectSymbioteFrameworkFromDependencies`).
60
- No dependency found → refuses immediately with no interactive fallback, since there is no existing
61
- symbiote app to extend in any terminal. No layer flag given, interactively → offers a multiselect
62
- of only the layers not already present (`src/detect-added-layers.ts`). `--force` skips the confirm
63
- before overwriting an already-generated `--testing` layer's files; without it, a non-interactive
64
- run leaves them untouched rather than clobbering or hanging.
49
+ ### Extend an existing app
65
50
 
66
- `--navigation` and `--expo-modules` are additive layers, not competing base profiles: every
67
- SymbioteNative app is bare React Native (never managed Expo/EAS). `--expo-modules` layers in
68
- `@symbiote-native/expo-modules-link` autolinking alone (`expo` + `expo-modules-link` — no specific
69
- Expo-backed package, since 2026-09-18); `--navigation` adds
70
- `@symbiote-native/navigation` alone. Every individual Expo-backed package `examples/expo-react`
71
- carries (`application`, `battery`, `sensors`, …) is its own flag now — `--battery`, `--sensors`,
72
- etc. — and picking any one of them implies `--expo-modules`'s wiring without asking for it twice.
73
- See `templates/layers/README.md`.
51
+ ```bash
52
+ npx @symbiote-native/cli add --navigation --testing --splash-screen
53
+ npx @symbiote-native/cli add # interactive: offers only layers not already present
54
+ ```
74
55
 
75
- ## Package shape
56
+ `add` requires a `@symbiote-native/<adapter>` dependency in the current directory's
57
+ `package.json` — it extends an app `new` already scaffolded (or an equivalent hand-built one),
58
+ the same way `vue add <plugin>` extends a `vue create` project. It never touches App/user
59
+ source: native files get idempotent text-splices, JS wiring (e.g. the navigator import) is
60
+ printed in the outro instead. `--force` skips the confirm before overwriting an
61
+ already-generated `--testing` layer's files.
76
62
 
77
- Mirrors [`create-vue`](https://www.npmjs.com/package/create-vue)'s own toolchain choice: zero
78
- runtime dependencies, bundled to a single dependency-free `bundle.js` at publish time (see
79
- `build.config.ts`). Published **scoped** as `@symbiote-native/cli` (not `create-symbiote`).
80
- `npm create symbiote`'s unscoped-`create-*` shorthand doesn't apply to a scoped package, so the
81
- invocation is always `npx @symbiote-native/cli new ...` (npx resolves the package's one `bin`
82
- entry automatically, same as `npx @angular/cli new` — see `build.config.ts`'s comment on why the
83
- `bin` key itself isn't `@symbiote-native/cli` verbatim). Deliberately **not** documented as a
84
- separate short command: one name (the package's) covers both identity and invocation, nothing
85
- else to remember. Argv parsing is hand-rolled (`src/cli.ts`) rather than a `commander`/`yargs`
86
- dependency, for the same fast-`npx`-cold-start reason create-vue avoids one. It still supports the
87
- conventions those libraries give you for free — `--framework=react` alongside `--framework react`
88
- (git/npm's own `--flag=value` form), "did you mean" suggestions for a typo'd command/flag, and
89
- `--flag=value` on a boolean flag (`--force=false`) rejected loudly rather than silently ignored.
63
+ ### Layers
64
+
65
+ `--navigation` and `--expo-modules` are additive layers, not competing base profiles — every
66
+ SymbioteNative app is bare React Native. `--expo-modules` layers in autolinking alone (`expo` +
67
+ `expo-modules-link`); every Expo-backed package (`@symbiote-native/battery`, `.../sensors`, …)
68
+ is its own flag (`--battery`, `--sensors`, …) and implies `--expo-modules` without asking twice.
69
+ See [`templates/layers/README.md`](templates/layers/README.md) for the full layer list and what
70
+ each one touches.
71
+
72
+ ## Shape
90
73
 
91
74
  ```
92
75
  src/
@@ -98,14 +81,15 @@ src/
98
81
  template-dir.ts # (framework, vueFlavor) → templates/js/<dir> name
99
82
  get-command.ts # (pm, 'install'|'dev') → printable shell command
100
83
  generate.ts # scaffoldApp() — the actual copy/merge orchestration
101
- add-layers.ts # addLayersToApp() — extends an EXISTING app with optional layers
102
- detect-added-layers.ts # cwd package.json deps -> which optional layers are already present
84
+ add-layers.ts # addLayersToApp() — extends an EXISTING app with optional layers
85
+ detect-added-layers.ts # cwd package.json deps -> which optional layers are already present
103
86
  errors.ts # CliUsageError, NotSymbioteAppError
87
+ expo-package-layers.ts # EXPO_PACKAGE_LAYERS — one entry per Expo-backed package
104
88
  utils/
105
- render-template.ts # ported from create-vue: recursive copy, package.json merge, .gitignore append
106
- deep-merge.ts # ported from create-vue
107
- sort-dependencies.ts # ported from create-vue
108
- json.ts # IJsonValue/IJsonObject + isJsonObject guard
89
+ render-template.ts # recursive copy, package.json merge, .gitignore append
90
+ deep-merge.ts
91
+ sort-dependencies.ts
92
+ json.ts
109
93
  commands/
110
94
  new.ts # resolves options, calls scaffoldApp(), prints install/dev commands
111
95
  add.ts # eligibility guard + resolveAddLayers, calls addLayersToApp()
@@ -113,380 +97,28 @@ templates/ # see templates/README.md
113
97
  build.config.ts # rolldown config for the bundle.js publish artifact
114
98
  ```
115
99
 
116
- ## What's real vs. stubbed
117
-
118
- Real: argv parsing, all interactive prompts, package-manager/framework autodetection, the full
119
- `templates/` content (`native/`, `js/{react,vue-tsx,vue-sfc,angular,solid,svelte}`,
120
- `layers/{navigation,expo-modules,testing,splash-screen,slider,<one per EXPO_PACKAGE_LAYERS entry>}`),
121
- `new`'s copy/merge mechanism end-to-end, and `add`'s full extend-an-existing-app path — all 26
122
- layers (`--navigation`/`--expo-modules`/`--testing`/`--splash-screen`/`--slider` + one `--<id>`
123
- flag per Expo-backed package, `src/add-layers.ts`), including `--splash-screen`'s 4 idempotent
124
- native-file text-splices (`MainActivity.kt`/`AppDelegate.swift`/`styles.xml`/`AndroidManifest.xml`)
125
- — verified against real scaffolded-app fixtures.
126
-
127
- Not wired in yet either: the root `pnpm run build`/`prepublish-build` pipeline doesn't invoke
128
- this package's `rolldown` build (every other publishable package goes through `tsc --build` +
129
- `fix-esm-extensions`, which this package deliberately does not — see `build.config.ts`).
100
+ Mirrors [`create-vue`](https://www.npmjs.com/package/create-vue)'s toolchain choice: zero
101
+ runtime dependencies, bundled to a single dependency-free `bundle.js` at publish time.
102
+ `render-template.ts` / `deep-merge.ts` / `sort-dependencies.ts` are ported from it directly.
103
+ Argv parsing is hand-rolled rather than a `commander`/`yargs` dependency, for the same
104
+ fast-`npx`-cold-start reason create-vue avoids one — it still supports `--flag=value` alongside
105
+ `--flag value`, "did you mean" suggestions for a typo'd command/flag, and rejects
106
+ `--flag=value` on a boolean flag loudly rather than silently ignoring it.
130
107
 
131
- - **`--styling css|css-modules|scss|less|stylus|stylesheet` (2026-09-17).** The App stub's own
132
- `style={{...}}` was flagged as a real gap: it never demonstrated the project's actual styling
133
- story (CSS classes work in RN — see the root README's "Styling" section) and tripped
134
- `react-native/no-inline-styles` on every fresh react/vue-tsx/solid scaffold. Fixed by making
135
- `css` (plain class + external stylesheet) the default across all six frameworks, with five more
136
- selectable code shapes:
137
- - vue-sfc/svelte's `css` default uses an inline `<style scoped>`/`<style>` block — the idiomatic
138
- web convention for those two — while react/vue-tsx/solid/angular (no template block to put one
139
- in) use an external `App.css` + `className`/`class="container"`.
140
- - `scss`/`less`/`stylus` produce byte-identical output to `css` for the trivial flat rule this
141
- stub carries, so they are NOT separate template content — `applyStyling`
142
- (`src/utils/apply-styling.ts`) just renames `App.css` → `App.<ext>` and rewrites the one import
143
- string. **vue-sfc/svelte take a different path (2026-09-17, no file swap at all):** both
144
- compilers already dispatch a `<style lang="scss|less|stylus">` block through the same
145
- preprocessor pipeline a standalone file gets
146
- (`adapters/vue/metro-vue-transformer.cjs`'s `SFC_STYLE_LANG_TO_PREPROCESSOR`,
147
- `adapters/svelte/src/preprocessor/scoped-styles.ts`'s `STYLE_LANG_TO_PREPROCESSOR`), confirmed
148
- live by `core/css-parser/src/svelte-less-style-block.test.ts` — so a preprocessor choice for
149
- these two is just inserting `lang="<option>"` into the existing inline `<style>` tag in place.
150
- An earlier version of this feature instead swapped in a whole second `external-css` override
151
- (external file + class) before renaming, because it assumed inline preprocessor syntax wasn't
152
- supported — checking first (an `Explore` sub-agent read both compiler sources) deleted 4
153
- template folders and the swap-then-rename two-step, and is MORE idiomatic besides (an SFC/
154
- Svelte author reaches for `lang="scss"` on the block they already have, not a second file).
155
- - `css-modules` and `stylesheet` (StyleSheet.create) are genuinely different code and get their
156
- own `styling/js/<framework>/<option>/` (+ the navigation layer's own
157
- `styling/navigation/<framework>/<option>/`) override, applied AFTER the navigation
158
- layer so it overwrites the final nav-shaped files, not the pre-nav base ones.
159
- - Angular's `StyleSheet.create({...})` call needs no `as const` on its argument (unlike the bare
160
- `readonly rootStyle = {...}` shape this project used before) — a value passed through a typed
161
- function PARAMETER gets that parameter's type as its contextual type, so `alignItems: 'center'`
162
- checks against the union directly instead of widening to `string`. Verified with a real `ngc`
163
- build, not assumed.
164
- - A `renderTemplate` layer picks its destination filename from the SOURCE file's own basename,
165
- not from the `dest` path passed to it (see the `templates/layers/README.md` note on the
166
- testing layer for the same gotcha) — every per-framework override folder here is named exactly
167
- `<option>/`, never a differently-named file merged into a differently-named dest.
168
- - **These overrides originally lived at `templates/js/<framework>/styling/` and
169
- `templates/layers/navigation/app/<framework>/styling/` — a real bug (2026-09-17), found
170
- while auditing the feature for unnecessary code, not by any test.** `renderTemplate` recursively
171
- copies a source directory verbatim; nesting the override folders *inside* the tree the base
172
- `js/<framework>` and navigation-layer renders already copy meant every single scaffolded app —
173
- for every styling choice, `css` included — shipped a leaked `styling/` folder at its root
174
- containing all six frameworks' unused override variants. None of the matrix/install/lint/`ngc`
175
- verification below caught it, because none of those checks assert on the generated file *tree*,
176
- only on whether known files build and lint clean — an orphaned, never-imported file passes all
177
- of them silently. Moved to a sibling `templates/styling/{js,navigation}/<framework>/<option>/`
178
- tree that the base copies never touch; re-verified across the full 72-combination matrix with
179
- an explicit "no `styling/` dir at the scaffolded root" assertion.
180
- - **`App.module.css` was 12 byte-identical copies of the same 5-line rule** — one per
181
- framework × {base, navigation}. Deleted the template files; `applyStyling` now writes the one
182
- shared constant directly next to wherever the App component landed, found by walking the
183
- scaffolded tree for an `App.{tsx,jsx,ts,vue,svelte}` file. CSS content carries no per-framework
184
- idiom the way component code does, so unlike the JS/TS-pair duplication noted elsewhere in this
185
- file, this one was pure copy-paste with nothing to keep independent. (The `external-css`
186
- override this bullet used to also cite for vue-sfc/svelte no longer exists at all — see the
187
- `lang` attribute note above.)
188
- - Verified: the full 6-framework × 6-styling × {base, navigation} = 72-combination matrix
189
- scaffolds without error and leaves no leaked `styling/` directory in any combination; a
190
- representative sample (react × css/css-modules/stylesheet/scss, vue-sfc × scss/css-modules
191
- with navigation, svelte × stylus with navigation, solid × css-modules with navigation, angular
192
- × stylesheet/css-modules with navigation) passes a REAL `npm install` + `npm run lint`, and the
193
- two angular and one vue-sfc-scss-with-navigation combinations additionally pass a real
194
- type-check (`ngc`/`vue-tsc`).
108
+ ## Status
195
109
 
196
- ## Design decisions (2026-09-15)
110
+ Real and verified against scaffolded-app fixtures: argv parsing, every interactive prompt,
111
+ package-manager/framework autodetection, the full `templates/` tree (6 frameworks × all 26
112
+ optional layers), and `add`'s idempotent native-file text-splices.
197
113
 
198
- - **templates/ vs examples/\*: independent fork, not a sync target.** `examples/*` is the dev
199
- canary; `templates/` starts as a copy but diverges and gets patched directly as needed. No
200
- CI drift check — deliberate, not an oversight. (One apparent drift turned out to be correct
201
- as-is: `templates/js/react/babel.config.js` lacks the `allowDeclareFields` override that
202
- `examples/react/babel.config.js` has, but that override exists solely for
203
- `ContextProviderDemo.tsx`'s `declare context` field — a demo file the template deliberately
204
- doesn't ship. No demo, no need for the override. Worth the reminder that a config diff against
205
- `examples/*` isn't automatically a bug — check what the diff is actually *for* first.)
206
- - **Layering, not one verbatim copy per framework.** A "layer" is a named patch that can touch
207
- `native/{ios,android}`, `js/<framework>`, and `package.json.fragment.json` together — not
208
- just the JS side. `renderTemplate` (ported from create-vue) copies `base` first, then each
209
- selected layer on top; `package.json`/`.gitignore` merge instead of overwrite.
210
- - **v1 layers: `base` + `expo-modules` + `navigation` + `testing` + `splash-screen` + `slider`.**
211
- All JS/package.json-only except `splash-screen` (native changes, see below).
212
- - **`--slider` (2026-09-17, real gap): the one package outside the `expo-modules` bundle with zero
213
- scaffolder wiring, despite being a real dependency of every example** (`examples/react`,
214
- `vue-sfc`, `vue-tsx`, `angular`, `svelte`, `solid`). Unlike `--splash-screen` it needs no
215
- `native/` overlay at all: `@symbiote-native/slider` autolinks itself via its own
216
- `react-native.config.cjs` + `symbiote-slider.podspec` (see `packages/slider/README.md`), so the
217
- "necessary native side" the scaffolder owns is just the dependency declaration —
218
- `layers/slider/package.json.fragment.json`. `npm install` + the developer's own `pod install`
219
- do the rest.
220
- - **`@symbiote-native/android` (2026-09-17, real bug, unconditional).** Every framework's real
221
- example pins this Android host-shim (re-provides RN host signals — keyboard events, … — that
222
- SymbioteNative's Fabric surface bypasses; `packages/android/package.json`'s own description) as
223
- a plain dependency, no feature flag involved. None of the six `js/<framework>/
224
- package.json.fragment.json` base fragments declared it — every scaffolded app was missing a
225
- base runtime dependency its Android build needs. Fixed directly in all six base fragments, not
226
- behind an option: unlike `--slider`/`--splash-screen` this isn't optional in any example.
227
- - **`--expo-modules` was also missing its Info.plist half (2026-09-17, real bug).** The layer
228
- wired the package.json fragment + postinstall linker (see above) but never touched
229
- `ios/Canary/Info.plist` — three of its bundled modules need an iOS usage-description string or
230
- the OS rejects the call outright (`NSFaceIDUsageDescription` for local-auth,
231
- `NSMotionUsageDescription` for sensors, `NSUserTrackingUsageDescription` for
232
- tracking-transparency), App Store review included. Fixed by overlaying
233
- `layers/expo-modules/ios/Canary/Info.plist` — copied from `examples/expo-react`'s real one, the
234
- only checked-in expo-* example with a native iOS project.
235
- - **`--expo-modules`'s Android half had the same gap (2026-09-17, real bug).** 4 missing
236
- `<uses-permission>` (local-auth's biometric prompt, brightness's `setSystemBrightnessAsync`,
237
- cellular's carrier lookups all fail on Android without them) plus 2 `<application>` backup-
238
- control attributes expo-secure-store's own Android config-plugin writes unconditionally
239
- (verified via `/vendor`-ing `expo-secure-store` and reading `plugin/src/withSecureStore.ts` —
240
- also confirmed the `@xml/secure_store_*` resources it points at ship inside expo-secure-store's
241
- OWN Android library module and resolve through Gradle's cross-module resource merge, so the
242
- reference example's manifest reference isn't broken despite no local `res/xml/` copy). Fixed as
243
- a text-splice POST-process (`utils/apply-expo-modules-manifest.ts`), not a third
244
- `AndroidManifest.xml` overlay: `--expo-modules` renders before `--splash-screen`, and
245
- `renderTemplate` overwrites rather than merges — a competing overlay would silently lose
246
- whichever layer wrote last (BootTheme vs. the permissions). The splice runs after both layers,
247
- so it works regardless of which manifest variant ended up on disk.
248
- - **`@symbiote-native/engine` was missing from react's fragment specifically (2026-09-17, real
249
- bug).** It's a `peerDependency` of every adapter identically (npm never auto-installs a peer —
250
- the app must declare it directly, same convention as `react-native` itself per CLAUDE.md's
251
- `<react_native_is_an_explicit_top_level_peer>`); `vue-sfc`/`vue-tsx`/`angular`/`solid`/`svelte`
252
- already had it, only `js/react/package.json.fragment.json` didn't. Found by diffing a full
253
- scaffold's dependency set against `examples/react`'s real `package.json` — the same audit that
254
- found `--slider` and `@symbiote-native/android`.
255
- - **`--splash-screen` (2026-09-17) is the first layer with native changes, and a real bug it
256
- fixed: every scaffolded app's `native/` template already wired `react-native-bootsplash`
257
- unconditionally (`MainActivity.kt`/`AppDelegate.swift` call its init API, `styles.xml` extends
258
- its `Theme.BootSplash`) but never added `@symbiote-native/splash-screen` as a dependency — so
259
- every fresh scaffold failed to compile natively on both platforms. Fixed by making it optional
260
- instead of mandatory: the base template ships WITHOUT the plugin wired (compiles clean with
261
- nothing installed), and `--splash-screen` overlays the 4 files that actually need the
262
- dependency to compile. Everything else that references bootsplash-looking assets
263
- (`colors.xml`, the drawables, `BootSplash.storyboard`, the `.xcassets`, `Info.plist`'s
264
- `UILaunchStoryboardName`, the `.xcodeproj`) stays in the base template unconditionally, because
265
- none of it actually depends on the plugin being installed — see `templates/layers/README.md`
266
- for the exact file-by-file split and why.
267
- - **`--splash-screen` never wired the JS side, and the native side alone means the app never
268
- starts (2026-09-17, real bug, found by cross-checking every real example's App entry).** All 6
269
- real examples call `hide()` once, at mount, from the framework's own lifecycle hook
270
- (`examples/react`'s `useEffect`, `.../vue-sfc`'s `onMounted`, `.../angular`'s `ngOnInit`,
271
- `.../solid`'s `onMount`, `.../svelte`'s bare top-level call — svelte needs no wrapper, its
272
- script body already runs once at component init) — react-native-bootsplash's native splash
273
- screen never hides itself. Without this, a scaffolded `--splash-screen` app compiles and installs
274
- fine but the user never sees anything past the launch screen: the app is frozen there forever.
275
- Fixed as a text-splice post-process (`utils/apply-splash-screen-hide.ts`), same shape as the
276
- `AndroidManifest.xml` splice above and for the same reason: `--navigation`'s own `app/<framework>`
277
- overlay and `--styling`'s `css-modules`/`stylesheet` overlay BOTH already overwrite the whole App
278
- file, so this has to run last, against whatever's actually on disk, not as a `js/` layer overlay.
279
- Verified against all 6 frameworks × with/without `--navigation` × `css-modules`/`stylesheet`
280
- styling — the splice never depends on the CSS import line (it differs per styling option), only
281
- on anchors proven stable across every variant (`export default function App() {`, `setup() {`,
282
- `</script>`), and a merge-or-insert helper for each framework's own lifecycle import so it never
283
- emits a duplicate `import { useState } from 'react'`-shaped line.
284
- - **Investigated (not fixed) — `RNScreensFragmentFactory` wiring, found via `/vendor`-ing
285
- `react-native-bootsplash@7.3.2` itself.** Its own README documents THREE Android `MainActivity`
286
- variants gated on the installed `react-native-screens` version; ours matches the "without
287
- react-native-screens" one, but `react-native-screens` is pinned to `4.26.0` (`pnpm-workspace.
288
- yaml`) and IS present the moment `--navigation` is selected (`@symbiote-native/navigation` is
289
- built on its `RNSScreen`/`RNSScreenStack` primitives) — past the documented `>= v4.16.0`
290
- threshold, where upstream says `MainActivity.onCreate` must also set
291
- `supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()` before calling
292
- `RNBootSplash.init(...)`, or Android can crash reconstructing a screens `Fragment` after
293
- process death. Web-search (2026-09-17) confirmed the version threshold is current, not stale
294
- advice. **Not wired into the scaffolder**, because it isn't wired into any of the 11 real
295
- examples that combine `--navigation` + bootsplash either (`grep -rl RNScreensFragmentFactory
296
- examples --include='*.kt'` — zero hits) — adding it here would make a fresh scaffold diverge
297
- from every real reference app, the opposite of this whole audit's method. If this is a real gap,
298
- it belongs in the examples first; the scaffolder mirrors them, it doesn't correct them.
299
- - **`native/` was missing its own `package.json.fragment.json` entirely (2026-09-17, real bug).**
300
- Every scaffolded app inherited zero baseline RN devDependencies — no
301
- `@react-native-community/cli` (so `npm run ios`/`android` couldn't even resolve the CLI),
302
- no `@symbiote-native/css-parser`, no `sass`/`less`/`stylus`, no `eslint`/`prettier`, no
303
- `engines`. Fixed with `native/package.json.fragment.json` (JS+TS universal) +
304
- `native/package.json.fragment.typescript.json` (the `typescript`/`@types/*`/
305
- `@react-native/typescript-config` floor, via the same `.typescript.` infix every other
306
- TS/JS split in this project uses). Verified against `examples/*`'s real `package.json`s —
307
- a scaffolded app's merged devDependencies now match byte-for-byte (module names/versions).
308
- - **`--testing` adds Detox e2e, not unit tests.** `vitest`/`@symbiote-native/test-utils` unit-test
309
- setup is only genuinely real in `examples/vue-tsx` (one real test, `vue-fragment.test.ts`) —
310
- `examples/react` has the `vitest` devDependency and `"test"` script but no actual unit test file
311
- yet, and `examples/vue-sfc`, `examples/svelte`, `examples/angular` have neither. Inconsistent
312
- and, in react's case, incomplete upstream, so none of it is cloned into the scaffolder yet
313
- (would need per-framework unit-test authoring, not just config copying). Both examples were also
314
- missing a local `vitest.config.ts`: with no `exclude`, a standalone `npm test` picks up
315
- `e2e/*.test.ts` under vitest's own default include glob and crashes on Detox's `device`/
316
- `element`/`by` (only wired up by Detox's own on-device runner) — fixed directly in both
317
- examples, independent of the scaffolder question.
318
- `detox` + a generic smoke test IS consistent across every non-Solid example and is dependency +
319
- config only, so it shipped first. Tracked as a follow-up, not silently dropped.
320
- - **SUPERSEDED — the entry above (`--splash-screen` "is deferred, not in v1") described a plan
321
- that was abandoned before it shipped, and shipping made the whole concern moot.** The original
322
- worry was that `.pbxproj`/`Info.plist`/`AndroidManifest.xml`/`colors.xml`/`styles.xml` would all
323
- need a real with/without-BootSplash variant generated via `@expo/config-plugins` + `xcode`
324
- AST surgery. It turned out none of that needs two variants: `.pbxproj`, `Info.plist`,
325
- `BootSplash.storyboard`, `colors.xml` and the drawables reference only OUR OWN asset catalog,
326
- never the plugin, so they stay in the base template unconditionally and are harmless when the
327
- option is off. Only 4 files actually reference the plugin's API/resources
328
- (`MainActivity.kt`, `AppDelegate.swift`, `styles.xml`'s `BootTheme`, `AndroidManifest.xml`'s
329
- activity theme attribute) — those are exactly what `--splash-screen` overlays. No `.pbxproj`
330
- mutation, no `@expo/config-plugins` dependency, ever needed. Verified end-to-end (2026-09-17):
331
- every native file the layer produces is byte-identical to `examples/react`'s real one, and
332
- cross-framework coverage confirms the overlay is genuinely framework-agnostic.
333
- - **`eslint.config.js` was missing from every template entirely (2026-09-17) — this IS the
334
- "File ignored because of a matching ignore pattern" bug flagged earlier and left unresolved.**
335
- Every scaffold shipped `"lint": "eslint ."` + `eslint` as a devDependency but no config file at
336
- all, so ESLint fell back to its flat-config default (only recognized JS-like extensions) and
337
- reported every source file ignored. Fixed with `native/eslint.config.js` (the trivial
338
- `@react-native/eslint-config/flat` default react/vue-tsx/angular/solid all use verbatim) plus
339
- framework-specific overrides in `js/vue-sfc` and `js/svelte` (they need `eslint-plugin-vue` /
340
- `eslint-plugin-svelte` + `@typescript-eslint/parser` wired in for `.vue`/`.svelte` files) — same
341
- native-default-plus-per-framework-override shape as the earlier `.typescript.` infix split, just
342
- at the layer level. Verified with a REAL `npm install` + `npm run lint`, both frameworks, zero
343
- errors, zero "ignored" warnings.
344
- - **Investigated unit-test (vitest) scaffolding and found nothing real to clone.**
345
- `examples/react`/`examples/vue-tsx` carry `vitest` + `@symbiote-native/test-utils` as
346
- dependencies and a `"test": "vitest run"` script, but zero `*.test.ts` files outside `e2e/` and
347
- no `vitest.config.*` — the unit-test setup is vestigial in the reference examples themselves,
348
- and `@symbiote-native/test-utils` isn't even a package that exists in this monorepo
349
- (`packages/test-utils` doesn't exist). Nothing to scaffold from; not cloning dead config.
350
- - **Confirmed (not adopted) — Angular CLI's `ng new` auto-runs `git init` AND `npm install` by
351
- default** (`--skip-git`/`--skip-install` opt out), the opposite of create-vue's suggest-only
352
- convention this project already follows. Deliberately NOT adopted — stays consistent with the
353
- project's own "nothing happens without the user typing it" stance for git/install, which was
354
- chosen before this comparison and this only confirms it was the right call.
355
- - **Three more real gaps found by a systematic `comm` diff of every example's root files against
356
- the merged template output (2026-09-17), same method that found the eslint bug above.**
357
- `js/angular` was missing `.prettierrc.js` entirely (the same trivial default react/vue-tsx/
358
- vue-sfc/solid already ship) AND its own `"lint": "eslint ."` script — `npm run lint` errored
359
- "Missing script" on every angular scaffold. `js/svelte` was missing `svelte.config.js` (its own
360
- header calls `fragments: 'tree'` "mandatory, not a preference" — read by svelte-check, the
361
- editor's language server, and Metro) and the `prettier-plugin-svelte` devDependency its own
362
- `.prettierrc.js` already referenced (`plugins: ['prettier-plugin-svelte']` with the package never
363
- installed). All three fixed and verified with real `npm install` + `npm run lint`/`prettier
364
- --check`. Files intentionally NOT cloned in this same sweep: `App.css`/`fabric-call-counter.ts`/
365
- `navigation-lines.ts`/`navigation-linking.ts`/`routes.ts`/`tunnel-demo.ts`/`*.test.ts` (canary
366
- benchmark/demo-specific, not starter concerns) and `.cdp-console-probe.mjs` (a manual Chrome
367
- DevTools Protocol debugging script, not app config).
368
- - **Deep recursive audit (2026-09-17, not just root files) against `examples/{vue-tsx,angular}`
369
- found one more real cleanup and confirmed no further real gaps.** `js/{react,vue-sfc,vue-tsx}`
370
- each carried a stale legacy `.eslintrc.js` (`extends: '@react-native'` — a package that isn't
371
- even a declared dependency anymore) left over from before the project's flat-config migration;
372
- deleted as dead/misleading now that `eslint.config.js` is real. Confirmed working: ESLint 8.57
373
- picks flat config over a stray `.eslintrc.js` automatically, so this was never a functional bug,
374
- just confusing cruft. Everything else the recursive diff surfaced (`assets/bootsplash/*`,
375
- `components/*`, `screens/*` beyond Menu/Details, `*.test.ts`) is canary-demo content, already
376
- out of scope by the same reasoning as the root-file audit above.
377
- - **The App stub's inline `style={{ ... }}` (flagged above as trip-hazard for
378
- `react-native/no-inline-styles`) is fixed, not just noted — see `--styling` below**, which
379
- replaced every inline style object with the framework's real styling convention.
380
- - **Git-init suggestion, borrowed from create-vue (2026-09-17).** create-vue never runs `git init`
381
- itself — it only prints `git init && git add -A && git commit -m "initial commit"` in the outro,
382
- and only when the target directory had no `.git` before scaffolding
383
- (`dotGitDirectoryState.hasDotGitDirectory`, set by its `canSkipEmptying`). Ported the same two
384
- pieces: `isEmptyDir` now treats a directory holding only `.git` as empty (so scaffolding into an
385
- already-`git init`'d empty folder no longer triggers the "empty it?" prompt and can't `rm -rf`
386
- the user's git history), and `runNew`'s outro prints the same suggested command when
387
- `<root>/.git` doesn't exist after scaffolding — never runs it automatically, matching this
388
- project's own "nothing happens without the user typing it" convention for git operations.
389
- - **Solid and Svelte added as `js/` templates**, alongside the original four
390
- (react/vue-tsx/vue-sfc/angular) — both are full milestone-complete adapters and had no
391
- reason to be missing.
392
- - **`package.json` merge ports create-vue's `deepMerge` + `sortDependencies` verbatim** (see
393
- `src/utils/`) rather than a from-scratch merge — proven code, and layering (base + navigation
394
- + expo-modules stacking) needs the same array-dedupe/object-merge semantics.
395
- - **Post-scaffold install is printed, not spawned** — matches create-vue's `outro` behavior.
396
- No child_process install call; the CLI prints `cd`/install/dev commands and stops.
397
- - **Priority: `new` before `add`.** `new` has no existing `package.json` to merge into;
398
- `add`'s retrofit-into-existing-project path comes after the copy/merge mechanism is proven.
399
- - **`add`'s purpose changed from "bootstrap symbiote into any RN app" to "extend an existing
400
- @symbiote-native/* app" (2026-09-17).** The earlier plan (see the now-superseded `TODO(symbiote)`
401
- this replaced in `src/commands/add.ts`'s git history) was to install `@symbiote-native/<framework>`
402
- + `react-native` into an arbitrary existing RN project. Dropped: a plain RN app has no reason to
403
- expect SymbioteNative's renderer swap, and `new`'s own native templates are hand-authored — there
404
- is no safe, generic way to retrofit them onto a real project's already-customized native files
405
- without a real risk of breaking it. The new, narrower job: an app `new` already scaffolded (or an
406
- equivalent hand-built one) picks up more of `new`'s OWN optional layers later, the same way
407
- `vue add <plugin>` extends a `vue create` project rather than converting a random app to Vue.
408
- - **Eligibility guard, not framework detection.** `add` requires a `@symbiote-native/<adapter>`
409
- dependency (`detectSymbioteFrameworkFromDependencies`, `src/detect-framework.ts`) — a
410
- different, narrower check than `new`'s plain `react`/`vue`/etc. match, which any RN app would
411
- pass. Missing → `NotSymbioteAppError`, no interactive fallback (unlike every other resolver in
412
- `prompts.ts`): there is no existing symbiote app to extend in ANY terminal, so nothing is
413
- gained by asking.
414
- - **Never touches user/App source.** `--navigation` in `new` overwrites the App entry with a
415
- Stack demo; in `add` it only merges the `@symbiote-native/navigation` dependency and prints the
416
- import snippet in the outro — wiring the `<Stack>` into a real, already-written App is the
417
- developer's own call, `add` doesn't know what else lives in that file.
418
- - **Native touches are idempotent text-splices, never whole-file overlays.** `--expo-modules` is
419
- the one v1 layer with native files: `apply-expo-modules-manifest.ts` was made idempotent (a
420
- second pass used to duplicate the permissions block — it matched its own previously-inserted
421
- anchor line) and reused as-is; a new `apply-expo-modules-plist.ts` does the iOS half, since
422
- `new`'s whole-file `Info.plist` overlay would silently discard whatever a real app's Info.plist
423
- already had. Both check "is this key already there" before writing, so a repeat `add
424
- --expo-modules` (or one running against an app that got the same permissions another way)
425
- is a no-op, not a duplicate.
426
- - **`--splash-screen` landed as a follow-up pass the same day, once `--expo-modules` proved the
427
- idempotent-splice shape.** Same treatment, 4 native files instead of 2:
428
- `apply-splash-screen-manifest.ts` swaps only `<activity android:name=".MainActivity">`'s theme
429
- to `BootTheme` (never the `<application>` theme), `apply-splash-screen-styles.ts` inserts the
430
- `BootTheme` style block, and `apply-splash-screen-main-activity.ts`/
431
- `apply-splash-screen-app-delegate.ts` insert the plugin's init call into an EXISTING
432
- `onCreate`/`customize()` override when the real app already has one, or add a whole new one
433
- when it doesn't — Kotlin/Swift don't allow declaring the same override twice, so which shape
434
- the real file is in has to be detected, not assumed. `findIosAppDir`/the Kotlin package walk
435
- locate the real (never `Canary`-named) app dir and MainActivity package path, factored into
436
- `find-ios-app-dir.ts` once `apply-expo-modules-plist.ts` needed the same iOS lookup. Same as
437
- `--navigation`, the JS-side `hide()` call is never spliced into App source — `add`'s outro
438
- prints the exact per-framework snippet instead
439
- (`SPLASH_SCREEN_HIDE_SNIPPET`, `src/commands/add.ts`).
440
- - **`--testing` asks before overwriting.** `detox.config.js`/`e2e/*` are new files, not user
441
- App source, but a re-run of `add --testing` could clobber customizations made since the first
442
- run — `resolveAddOverwrite` (`prompts.ts`) confirms interactively, defaults to SKIP (not
443
- overwrite) when there's no terminal to ask in, and `--force` bypasses the ask either way.
444
- - **No-flags UX mirrors `new`'s `resolveFeatures` multiselect**, but offers only layers
445
- `detect-added-layers.ts` doesn't already find a dependency marker for — re-offering an
446
- installed layer would either no-op confusingly or, for `--testing`, trigger the overwrite
447
- confirm for no reason.
448
- - **Removed the checked-in `templates/native/ios/Podfile.lock` (2026-09-18, real device bug).** A
449
- lockfile is dependency-installer OUTPUT, never scaffolder input — shipping one anyway caused a
450
- real `dyld: Library not loaded: @rpath/ReactNativeDependencies.framework` crash at app launch,
451
- no red-box, no build error. Mechanism: `applyAppIdentity` text-renames `Canary` -> the real app
452
- name INSIDE `Podfile` itself (extension-less files are in its rewrite set); RN's own
453
- `react_native_pods.rb` gates BOTH `React-Core-prebuilt` and the separate `ReactNativeDependencies`
454
- pod behind independent Maven-artifact-availability checks, and `pod install` trusts an existing
455
- `Podfile.lock` only while its `PODFILE CHECKSUM` (a SHA1 of `Podfile`'s own text) still matches —
456
- the rename breaks that match, forcing a fresh resolve. The shipped lock had frozen an
457
- INCONSISTENT split from whatever environment originally produced it (Core resolved prebuilt,
458
- Dependencies resolved from-source) — React's prebuilt binary hard-links
459
- `@rpath/ReactNativeDependencies.framework` unconditionally, so the moment a real fresh install
460
- reused half of that frozen split, the framework it needed was never fetched. Fix verified
461
- on-device: with the lock removed, a genuinely fresh `pod install` resolves BOTH sides
462
- consistently (this device: both from-source, logged as `[ReactNativeCore] Building from source:
463
- true` / `[ReactNativeDependencies] Building from source: true`) — no `React-Core-prebuilt` in
464
- the mix at all, so no split to be inconsistent. No Podfile env-var workaround needed; the lock
465
- was the whole bug.
466
- - **Every Expo-backed package split into its own selectable layer (2026-09-18, real gap).**
467
- `--expo-modules` used to bundle all 21 `@symbiote-native/<expo-wrapper>` packages
468
- unconditionally (`application`, `battery`, `brightness`, …) — picking it, or picking any ONE of
469
- our packages from `packages/*`, always installed every other one too, with no way to scaffold
470
- e.g. a battery-only app. Fixed by giving each package its OWN dependency-only layer (same shape
471
- as `--slider`), registered ONCE in `src/expo-package-layers.ts` (`EXPO_PACKAGE_LAYERS`) rather
472
- than 21 hand-written near-duplicates — that one table drives cli.ts's `--<id>` flag, prompts.ts's
473
- multiselect option, and `detect-added-layers.ts`'s marker generically. `expo-modules` itself
474
- shrank to just the core autolinking bits (`expo` + `expo-modules-link`): it doesn't make sense to
475
- ask a developer to opt into autolinking a second time once they've picked a real Expo-backed
476
- package, so it rides along automatically — `resolveFeatures` (`new`) folds `hasExpoModules` to
477
- `true`, and `resolveAddLayers` (`add`) appends the `expo-modules` layer, in both cases without a
478
- second explicit tick. (A same-day follow-up also added the raw npm `expo-linking` package
479
- unconditionally, on the unverified assumption that Expo's wrapper modules need it for autolinking
480
- — reverted once checking the real installed `expo-battery`/`expo-web-browser`/`expo-sharing`/
481
- `expo-store-review`/`expo-local-authentication`/`expo`/`expo-modules-autolinking` `package.json`s
482
- showed none of them depend on it.)
114
+ Not wired in yet: the root `pnpm run build`/`prepublish-build` pipeline doesn't invoke this
115
+ package's `rolldown` build — every other publishable package goes through `tsc --build` +
116
+ `fix-esm-extensions`, which this one deliberately doesn't (see `build.config.ts`).
483
117
 
484
118
  ## References
485
119
 
486
- - Root [`README.md`](../../README.md) — the `DX` milestone row and the "Try It In Your Own App"
487
- section this package replaces.
488
- - `symbiote-release-publishing` skill — how every other package's publish pipeline works;
489
- this package's `rolldown`-bundled `bin` is a deliberate divergence from it, not yet
490
- reconciled with the shared CI gate.
491
- - `symbiote-new-package-skeleton` skill — the tiered scope-triage convention this package's
492
- own design conversation borrowed from, adapted for a CLI tool rather than a runtime library.
120
+ - Root [`README.md`](../../README.md) — the "Try It In Your Own App" section this package
121
+ replaces.
122
+ - [`templates/layers/README.md`](templates/layers/README.md) — the full layer list.
123
+ - `symbiote-create-cli` skill — design history: why `add` extends rather than bootstraps, the
124
+ layering model, and the real bugs a systematic `examples/*` diff found while building this.
package/bundle.js CHANGED
@@ -2774,7 +2774,7 @@ async function runNew(parsed) {
2774
2774
  }
2775
2775
  //#endregion
2776
2776
  //#region package.json
2777
- var version = "0.0.1";
2777
+ var version = "0.1.0";
2778
2778
  //#endregion
2779
2779
  //#region src/index.ts
2780
2780
  function printHelp() {
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "@symbiote-native/cli",
3
- "version": "0.0.1",
4
- "description": "Scaffold a new SymbioteNative app (React/Vue/Angular/Solid/Svelte, bare React Native, optional Expo-modules autolinking), or wire SymbioteNative into an existing bare RN app — the `@symbiote-native/cli new` companion to the manual steps in the root README's \"Try It In Your Own App\".",
3
+ "version": "0.1.0",
4
+ "description": "Create a native iOS/Android app in Vue, Svelte, Solid, Angular or React, on React Native's own Fabric renderer. Scaffolds a new app, or wires SymbioteNative into an existing bare React Native one.",
5
+ "keywords": [
6
+ "react-native",
7
+ "symbiote-native",
8
+ "cli",
9
+ "scaffold",
10
+ "create-app",
11
+ "starter",
12
+ "vue",
13
+ "svelte",
14
+ "solid",
15
+ "angular",
16
+ "react",
17
+ "ios",
18
+ "android",
19
+ "cross-platform"
20
+ ],
5
21
  "license": "MIT",
6
22
  "repository": {
7
23
  "type": "git",
@@ -17,11 +33,11 @@
17
33
  "engines": {
18
34
  "node": ">=22.11.0"
19
35
  },
20
- "main": "src/index.ts",
21
- "module": "src/index.ts",
36
+ "main": "./bundle.js",
37
+ "module": "./bundle.js",
22
38
  "types": "src/index.ts",
23
39
  "exports": {
24
- ".": "./src/index.ts"
40
+ ".": "./bundle.js"
25
41
  },
26
42
  "bin": {
27
43
  "symbiote-native-cli": "bundle.js"
package/src/index.ts DELETED
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env node
2
- import pc from 'picocolors';
3
- import { parseArgv } from './cli.js';
4
- import { runAdd } from './commands/add.js';
5
- import { runNew } from './commands/new.js';
6
- import { CliUsageError, NotSymbioteAppError } from './errors.js';
7
- import { EXPO_PACKAGE_LAYERS } from './expo-package-layers.js';
8
- // A static JSON import, not fs/createRequire: rolldown resolves and inlines it (relative to
9
- // this SOURCE file) into bundle.js at build time, so the published artifact needs no
10
- // package.json lookup at runtime at all — unlike a runtime `require('../package.json')`, which
11
- // breaks the moment the bundle no longer sits one directory level away from it (see git history).
12
- import pkg from '../package.json' with { type: 'json' };
13
-
14
- function printHelp(): void {
15
- const expoPackageFlags = EXPO_PACKAGE_LAYERS.map(
16
- layer => `--${layer.id}`,
17
- ).join(', ');
18
- console.log(`@symbiote-native/cli — scaffold a SymbioteNative app, or add it to an existing one.
19
-
20
- Usage:
21
- npx @symbiote-native/cli new <app-name> [--framework react|vue|angular|solid|svelte] [--vue-flavor tsx|sfc]
22
- [--javascript] [--force] [--bundle-id com.example.app]
23
- [--styling css|css-modules|scss|less|stylus|stylesheet]
24
- [--navigation] [--expo-modules] [--testing] [--splash-screen] [--slider] [--pm npm|pnpm|yarn]
25
- [--<expo-package> ...] (one per Expo-backed package, implies --expo-modules)
26
- npx @symbiote-native/cli add [--navigation] [--expo-modules] [--testing] [--splash-screen] [--slider]
27
- [--<expo-package> ...] [--force] [--pm npm|pnpm|yarn]
28
- (run inside an existing @symbiote-native/* app)
29
-
30
- npx @symbiote-native/cli --version print the CLI version
31
- npx @symbiote-native/cli --help print this message
32
-
33
- <expo-package> flags: ${expoPackageFlags}
34
-
35
- Flags left out of either command are asked for interactively.`);
36
- }
37
-
38
- async function main(): Promise<void> {
39
- const parsed = parseArgv(process.argv.slice(2));
40
-
41
- switch (parsed.kind) {
42
- case 'new':
43
- await runNew(parsed);
44
- return;
45
- case 'add':
46
- await runAdd(parsed);
47
- return;
48
- case 'help':
49
- printHelp();
50
- return;
51
- case 'version':
52
- console.log(pkg.version);
53
- return;
54
- }
55
- }
56
-
57
- main().catch((error: unknown) => {
58
- if (error instanceof CliUsageError) {
59
- console.error(pc.red(`@symbiote-native/cli: ${error.message}`));
60
- process.exitCode = 1;
61
- return;
62
- }
63
- if (error instanceof NotSymbioteAppError) {
64
- console.error(pc.red(`@symbiote-native/cli: ${error.message}`));
65
- process.exitCode = 1;
66
- return;
67
- }
68
- console.error(pc.red('@symbiote-native/cli: unexpected error'), error);
69
- process.exitCode = 1;
70
- });