create-cmp-cli 0.2.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/LICENSE +21 -0
- package/README.md +232 -0
- package/bin/create-cmp.mjs +91 -0
- package/options.schema.json +110 -0
- package/package.json +52 -0
- package/src/bootstrap/checks.mjs +283 -0
- package/src/bootstrap/exec.mjs +90 -0
- package/src/commands/clean.mjs +195 -0
- package/src/commands/create.mjs +223 -0
- package/src/commands/doctor.mjs +256 -0
- package/src/commands/upgrade.mjs +196 -0
- package/src/commands/verify.mjs +94 -0
- package/src/doctor.mjs +109 -0
- package/src/lib/args.mjs +38 -0
- package/src/lib/clean.mjs +72 -0
- package/src/lib/fsutil.mjs +90 -0
- package/src/lib/log.mjs +40 -0
- package/src/lib/project-doctor.mjs +329 -0
- package/src/lib/registry.mjs +102 -0
- package/src/lib/rename.mjs +116 -0
- package/src/lib/schema.mjs +123 -0
- package/src/lib/toggle.mjs +130 -0
- package/src/lib/tokens.mjs +96 -0
- package/src/lib/toml.mjs +137 -0
- package/src/lib/upgrade.mjs +159 -0
- package/src/lib/verify.mjs +97 -0
- package/src/scaffold.mjs +347 -0
- package/src/verify.mjs +4 -0
- package/src/versions/registry.json +42 -0
- package/template/.github/workflows/verify.yml +70 -0
- package/template/build.gradle.kts +17 -0
- package/template/composeApp/build.gradle.kts +257 -0
- package/template/composeApp/google-services.json +29 -0
- package/template/composeApp/proguard-rules.pro +34 -0
- package/template/composeApp/src/androidDebug/AndroidManifest.xml +13 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/ComposeRootRegistry.kt +40 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/InspectorCatalog.kt +80 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/InspectorHttpServer.kt +322 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/InspectorInit.kt +16 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/LiveSemanticsJson.kt +102 -0
- package/template/composeApp/src/androidDebug/kotlin/com/example/app/inspector/RemoteControlPage.kt +106 -0
- package/template/composeApp/src/androidDebug/res/xml/debug_network_security_config.xml +9 -0
- package/template/composeApp/src/androidMain/AndroidManifest.xml +28 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/AppApplication.kt +72 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/MainActivity.kt +24 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/core/connectivity/NetworkMonitor.kt +42 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/data/local/DatabaseBuilder.android.kt +13 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/di/AndroidModule.kt +9 -0
- package/template/composeApp/src/androidMain/kotlin/com/example/app/presentation/components/TestTagAutomation.android.kt +11 -0
- package/template/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml +9 -0
- package/template/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- package/template/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- package/template/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/template/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/template/composeApp/src/androidMain/res/values/colors.xml +6 -0
- package/template/composeApp/src/androidMain/res/values/themes.xml +9 -0
- package/template/composeApp/src/androidMain/res/values-v31/themes.xml +8 -0
- package/template/composeApp/src/androidRelease/kotlin/com/example/app/inspector/InspectorInit.kt +13 -0
- package/template/composeApp/src/commonMain/composeResources/font/DMSans_Bold.ttf +0 -0
- package/template/composeApp/src/commonMain/composeResources/font/DMSans_Medium.ttf +0 -0
- package/template/composeApp/src/commonMain/composeResources/font/DMSans_Regular.ttf +0 -0
- package/template/composeApp/src/commonMain/composeResources/font/DMSans_SemiBold.ttf +0 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/App.kt +7 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/core/connectivity/NetworkMonitor.kt +7 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/data/local/AppDatabase.kt +28 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/data/local/DatabaseBuilder.kt +14 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/data/local/ItemDao.kt +18 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/data/remote/FirebaseConfig.kt +5 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/data/remote/ItemRepositoryImpl.kt +22 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/di/AppModule.kt +23 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/domain/model/Item.kt +8 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/domain/repository/ItemRepository.kt +8 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/domain/usecase/GetItemsUseCase.kt +12 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/App.kt +13 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/components/BaseScreen.kt +69 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/components/TestTagAutomation.kt +18 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/DetailScreen.kt +39 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/HomeScreen.kt +87 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/home/HomeViewModel.kt +35 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/AppNavHost.kt +43 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/AppShell.kt +144 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/AppTab.kt +25 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/navigation/Screen.kt +15 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/profile/ProfileScreen.kt +43 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/theme/DesignToken.kt +25 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/theme/Shape.kt +12 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/theme/Theme.kt +64 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/theme/Tokens.kt +19 -0
- package/template/composeApp/src/commonMain/kotlin/com/example/app/presentation/theme/Typography.kt +36 -0
- package/template/composeApp/src/desktopMain/kotlin/com/example/app/core/connectivity/NetworkMonitor.desktop.kt +9 -0
- package/template/composeApp/src/desktopMain/kotlin/com/example/app/data/local/DatabaseBuilder.desktop.kt +13 -0
- package/template/composeApp/src/desktopMain/kotlin/com/example/app/di/DesktopModule.kt +32 -0
- package/template/composeApp/src/desktopMain/kotlin/com/example/app/main.kt +29 -0
- package/template/composeApp/src/desktopMain/kotlin/com/example/app/presentation/components/TestTagAutomation.desktop.kt +6 -0
- package/template/composeApp/src/iosMain/kotlin/com/example/app/KoinHelper.kt +50 -0
- package/template/composeApp/src/iosMain/kotlin/com/example/app/MainViewController.kt +10 -0
- package/template/composeApp/src/iosMain/kotlin/com/example/app/core/connectivity/NetworkMonitor.kt +32 -0
- package/template/composeApp/src/iosMain/kotlin/com/example/app/data/local/DatabaseBuilder.ios.kt +10 -0
- package/template/composeApp/src/iosMain/kotlin/com/example/app/presentation/components/TestTagAutomation.ios.kt +11 -0
- package/template/docs/dev-client.md +52 -0
- package/template/gitignore +21 -0
- package/template/gradle/libs.versions.toml +100 -0
- package/template/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/template/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/template/gradle.properties +9 -0
- package/template/gradlew +152 -0
- package/template/gradlew.bat +94 -0
- package/template/iosApp/Podfile +27 -0
- package/template/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png +0 -0
- package/template/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/template/iosApp/iosApp/Assets.xcassets/Contents.json +6 -0
- package/template/iosApp/iosApp/ContentView.swift +17 -0
- package/template/iosApp/iosApp/GoogleService-Info.plist +34 -0
- package/template/iosApp/iosApp/Info.plist +43 -0
- package/template/iosApp/iosApp/PrivacyInfo.xcprivacy +42 -0
- package/template/iosApp/iosApp/iOSApp.swift +33 -0
- package/template/iosApp/project.yml +56 -0
- package/template/local.properties.example +2 -0
- package/template/manifest.json +125 -0
- package/template/qa/appium/README.md +23 -0
- package/template/qa/appium/lib/appium-client.mjs +225 -0
- package/template/qa/appium/package.json +8 -0
- package/template/qa/appium/run-android-smoke.mjs +39 -0
- package/template/settings.gradle.kts +34 -0
- package/template/tests/appium/cmp/conftest.py +96 -0
- package/template/tests/appium/cmp/test_smoke.py +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Karel van der Merwe and create-cmp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# create-cmp
|
|
4
|
+
|
|
5
|
+
**Scaffold a green-building Kotlin/Compose Multiplatform app (Android + iOS) in minutes — not hours.**
|
|
6
|
+
|
|
7
|
+
Toolchain auto-bootstrapped · Navigation & insets pre-solved · Clean Architecture wired · Appium harness ready.
|
|
8
|
+
|
|
9
|
+
[](https://github.com/kvdm-co-pilot/create-cmp/actions/workflows/ci.yml)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](./CONTRIBUTING.md)
|
|
12
|
+
[](https://kotlinlang.org/docs/multiplatform.html)
|
|
13
|
+
[](#use-it-from-claude-code)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Starting a Compose Multiplatform project is slow and flaky — not because the knowledge is hard, but
|
|
20
|
+
because it isn't *reproducible*. Kotlin ↔ KSP ↔ CMP ↔ Room ↔ AGP must all agree or the build dies;
|
|
21
|
+
the iOS shell (XcodeGen + CocoaPods + Firebase pods + AppDelegate + framework embedding) is a
|
|
22
|
+
multi-step minefield; edge-to-edge insets get re-debugged on every project; and the toolchain itself
|
|
23
|
+
is a cliff most scaffolders leave to you. That friction is why greenfield mobile apps quietly default
|
|
24
|
+
to React Native.
|
|
25
|
+
|
|
26
|
+
**`create-cmp` removes the friction.** It *stamps* a frozen, CI-verified golden template (it does not
|
|
27
|
+
freehand-generate your project), bootstraps the toolchain, and **proves a green build** before it
|
|
28
|
+
reports success.
|
|
29
|
+
|
|
30
|
+
## Contents
|
|
31
|
+
|
|
32
|
+
- [Quick start](#quick-start)
|
|
33
|
+
- [Commands](#commands)
|
|
34
|
+
- [What you get](#what-you-get-the-pre-solved-moat)
|
|
35
|
+
- [Options](#options)
|
|
36
|
+
- [Use it from Claude Code](#use-it-from-claude-code)
|
|
37
|
+
- [Why CMP, not React Native](#why-cmp-not-react-native)
|
|
38
|
+
- [How it works](#how-it-works)
|
|
39
|
+
- [Requirements](#requirements)
|
|
40
|
+
- [Roadmap](#roadmap)
|
|
41
|
+
- [Contributing](#contributing)
|
|
42
|
+
|
|
43
|
+
> **Full usage in one place:** [`docs/USAGE.md`](./docs/USAGE.md) is the single entry point — setup,
|
|
44
|
+
> every CLI command, all 8 skills, the 14-tool `cmp-inspector` MCP, and the workflows that tie them
|
|
45
|
+
> together. Read it and you can drive create-cmp end to end (agents included).
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx create-cmp-cli@latest
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
…or non-interactively:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx create-cmp-cli@latest --name Acme --package com.acme.app --yes --verify
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The scaffolder interviews you (or takes flags), runs the toolchain doctor, stamps the template, and
|
|
60
|
+
builds the app to prove it's green.
|
|
61
|
+
|
|
62
|
+
> **On the package name:** it publishes as `create-cmp-cli` — the bare name `create-cmp` is held by
|
|
63
|
+
> an unrelated placeholder, and `create-cmp-app` is a real, unrelated CMP generator, so we didn't
|
|
64
|
+
> reuse either. The installed *command* is still `create-cmp`. You can also run straight from GitHub
|
|
65
|
+
> with no install: `npx github:kvdm-co-pilot/create-cmp`, or use the
|
|
66
|
+
> [Claude Code plugin](#use-it-from-claude-code).
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
`create-cmp` is useful across the whole life of a project, not just day one — and every command
|
|
71
|
+
except `create` works on **any** KMP project, not only ones it scaffolded:
|
|
72
|
+
|
|
73
|
+
| Command | What it does |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `create-cmp [dir]` / `create-cmp create` | Scaffold a new app from the frozen golden template (the default command). |
|
|
76
|
+
| `create-cmp doctor [--fix]` | Toolchain preflight (JDK/SDK/Xcode/Appium, consent-gated installs) **plus** project diagnosis when run inside a Gradle project: kotlin↔ksp lockstep, drift vs proven-green sets, the KSP2/iOS catch-22, sdk.dir, `~/.konan` bloat, disk space. `--fix` applies the safe heals. |
|
|
77
|
+
| `create-cmp upgrade [--dry-run]` | Migrate `gradle/libs.versions.toml` to the next proven-green version set — diff table first, surgical in-place edits with `.bak-upgrade` backups, kotlin↔ksp lockstep guardrail, `--verify` to prove the result. |
|
|
78
|
+
| `create-cmp clean` | Cache & build-output hygiene: stale `~/.konan` toolchains + project `build/`/`.gradle/` dirs (sizes shown, consent-gated); `~/.gradle/caches` is size-reported only. |
|
|
79
|
+
| `create-cmp verify [--target-dir .]` | Run the green-build gate (Android, and iOS on macOS) against an existing project. |
|
|
80
|
+
|
|
81
|
+
> **North-star (a goal, measured honestly — not a benchmark):** *time-to-green* — a running app on
|
|
82
|
+
> the Android emulator **and** the iOS simulator, smoke-passing, with zero manual steps modulo the
|
|
83
|
+
> one Xcode App Store install. Target: **under 5 minutes on a clean machine.** Every scaffold ends
|
|
84
|
+
> with a `--verify` build gate, so success is *proven*, not assumed.
|
|
85
|
+
|
|
86
|
+
## What you get (the pre-solved moat)
|
|
87
|
+
|
|
88
|
+
Everything below is **stamped from a template that builds green on Android + iOS** — not regenerated
|
|
89
|
+
per run, so it can't silently drift:
|
|
90
|
+
|
|
91
|
+
- **A pinned, version-locked dependency set** — Kotlin, KSP, Compose Multiplatform, Room, AGP, Koin,
|
|
92
|
+
Ktor, GitLive Firebase KMP, Navigation Compose, Lifecycle — chosen to actually agree with each
|
|
93
|
+
other, including the iOS Room/KSP2 catch-22 (`ksp.useKSP2=true`). Frozen and CI-gated.
|
|
94
|
+
- **The iOS shell, in the order that builds** — `project.yml` (XcodeGen), `Podfile` (Firebase static
|
|
95
|
+
linkage), `Info.plist`, `iOSApp.swift` (AppDelegate + `FirebaseApp.configure()` before Koin),
|
|
96
|
+
`ContentView.swift` (ComposeUIViewController bridge).
|
|
97
|
+
- **The Android shell** — `AndroidManifest`, `MainActivity` with `enableEdgeToEdge()`, `Application`
|
|
98
|
+
starting Koin + Room, adaptive-icon structure.
|
|
99
|
+
- **Navigation + insets, solved once** — a generic `BaseScreen` Scaffold owning window insets /
|
|
100
|
+
status- and nav-bar padding, plus a data-driven `AppShell` + bottom nav fed by your `tabs` list
|
|
101
|
+
(not role-hardcoded), with type-safe routes.
|
|
102
|
+
- **Clean Architecture, wired** — `core / data{local,remote} / domain{model,repository,usecase} /
|
|
103
|
+
presentation{components,theme,navigation,<feature>} / di` with Koin modules registered and **one
|
|
104
|
+
example feature wired end-to-end** as the copy-paste pattern.
|
|
105
|
+
- **Theme & tokens** — `<Prefix>Theme`, `<Prefix>Tokens`, `<Prefix>Colors`, DM Sans.
|
|
106
|
+
- **An Appium harness** — an Appium client + smoke runner and a sample smoke asserting the bottom nav
|
|
107
|
+
renders, so "done" can be proven on a device.
|
|
108
|
+
- **A toolchain doctor** — diagnoses *and* heals JDK, Android SDK + AVD, Xcode/CLT, CocoaPods,
|
|
109
|
+
XcodeGen, Appium + drivers, Node. Idempotent, OS-aware, consent-gated.
|
|
110
|
+
- **AI-inspectable by default** — every generated app ships a debug-only live inspector
|
|
111
|
+
(`127.0.0.1:9500`, loopback, structurally absent from release). An agent runs `connect_live` and
|
|
112
|
+
reads the running UI as JSON — hierarchy, geometry, resolved design tokens, real navigation
|
|
113
|
+
state. Never screenshots. Toggle with `--no-inspector`; `create-cmp doctor` verifies it stays
|
|
114
|
+
debug-only and that every declared theme token is served on `/inspect/design-system`.
|
|
115
|
+
- **Live device view** — with the debug app running, open `http://127.0.0.1:9500/inspect/remote`
|
|
116
|
+
(after `adb forward tcp:9500 tcp:9500`) to watch the real device and click the image to tap it,
|
|
117
|
+
while the agent drives `navigate_and_inspect` on the semantics tree and proves every change with
|
|
118
|
+
`prove_change`. Pixels for the human, structure for the AI.
|
|
119
|
+
- **Desktop dev-client** — a phone-sized JVM window running the shared UI with Compose Hot Reload
|
|
120
|
+
attached (`./gradlew :composeApp:hotRunDesktop --auto`); Firebase never initializes on desktop
|
|
121
|
+
(offline DI fakes). Toggle with `--no-dev-client`.
|
|
122
|
+
|
|
123
|
+
`google-services.json` / `GoogleService-Info.plist` are intentionally **not** real — you get
|
|
124
|
+
placeholders and clear "drop your Firebase config here" instructions.
|
|
125
|
+
|
|
126
|
+
## Options
|
|
127
|
+
|
|
128
|
+
| Option | Choices | Default |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Platforms | Android (always) + iOS | iOS on |
|
|
131
|
+
| App name / package / iOS bundle id | — | required / derived |
|
|
132
|
+
| Firebase region | any Firebase region | `us-central1` |
|
|
133
|
+
| Firebase (GitLive KMP) | on / off | on |
|
|
134
|
+
| Auth | `email` / `phone` / `both` / `none` | `both` |
|
|
135
|
+
| Firebase services | Firestore · Storage · Functions · FCM | all on |
|
|
136
|
+
| Room local cache | on / off | on |
|
|
137
|
+
| Appium harness | on / off | on |
|
|
138
|
+
| Bottom-nav tabs | label + icon, any count | Home, Profile |
|
|
139
|
+
|
|
140
|
+
_(Web/PWA is intentionally out of scope — Android + iOS only.)_
|
|
141
|
+
|
|
142
|
+
## Use it from Claude Code
|
|
143
|
+
|
|
144
|
+
`create-cmp` also ships as a [Claude Code](https://docs.claude.com/en/docs/claude-code) plugin — one
|
|
145
|
+
shared engine, two front doors. Install from the bundled marketplace:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
/plugin marketplace add kvdm-co-pilot/create-cmp
|
|
149
|
+
/plugin install create-cmp
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
It bundles eight skills (plus the `cmp-inspector` MCP server), each with a deterministic engine
|
|
153
|
+
behind it:
|
|
154
|
+
|
|
155
|
+
- **cmp-new** — conversational interview, then shells out to the same engine to scaffold, and
|
|
156
|
+
generates your tab screens from the example-feature pattern.
|
|
157
|
+
- **cmp-doctor** — toolchain bootstrap + project diagnosis on any KMP project (consent-gated).
|
|
158
|
+
- **cmp-upgrade** — migrate to the next proven-green version set (diff → apply → verify).
|
|
159
|
+
- **cmp-inspect** — see and drive a rendered Compose UI as structured JSON: hierarchy, geometry,
|
|
160
|
+
resolved design tokens, drift diffs, golden-tree snapshots, a11y audit, live navigation
|
|
161
|
+
(`navigate_and_inspect`), wireframe rendering, and `prove_change` — the verified dev loop.
|
|
162
|
+
- **cmp-dev-client** — run the shared UI in a phone-sized desktop window with Compose Hot Reload
|
|
163
|
+
(`./gradlew :composeApp:hotRunDesktop --auto`) — the Expo-style dev loop for KMP.
|
|
164
|
+
- **cmp-firebase-connect** — post-scaffold onboarding: create/reuse a Firebase project, register
|
|
165
|
+
the app, and replace the placeholder config via the Firebase CLI (consent-gated), proven by a
|
|
166
|
+
green build.
|
|
167
|
+
- **cmp-test** — generate the Appium regression suite by *observing* the app: read the running
|
|
168
|
+
UI's semantics tree and emit tests in the shipped harness style.
|
|
169
|
+
- **cmp-qa-prep** — brings up the emulator + Appium session + smoke.
|
|
170
|
+
|
|
171
|
+
## Why CMP, not React Native
|
|
172
|
+
|
|
173
|
+
This isn't a knock on React Native — it's about defaults. The only place CMP loses to RN on a new
|
|
174
|
+
app is **time-to-first-green-build**, and that's a tooling problem, not a merits problem. With
|
|
175
|
+
`create-cmp`, CMP's `npx`-and-go is competitive:
|
|
176
|
+
|
|
177
|
+
- **One language, real native UI.** Kotlin shared logic *and* Compose UI across Android and iOS — no
|
|
178
|
+
JS bridge, no separate native-module dance for the common case.
|
|
179
|
+
- **Native performance and platform access** without the RN bridge tax.
|
|
180
|
+
- **Reproducible by construction.** A frozen, version-locked, CI-gated template means the build that
|
|
181
|
+
was green yesterday is green today — the exact property ad-hoc CMP setups lack.
|
|
182
|
+
- **Proven, not assumed.** Every scaffold ends on a real build (and an Appium smoke), with a
|
|
183
|
+
GREEN/FAIL verdict.
|
|
184
|
+
|
|
185
|
+
If `create-next-app` made React the default for the web by removing setup friction, the goal here is
|
|
186
|
+
the same for multiplatform mobile.
|
|
187
|
+
|
|
188
|
+
## How it works
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
Front doors: npx create-cmp-cli + Claude Code plugin (cmp-new / cmp-doctor / cmp-qa-prep)
|
|
192
|
+
│ one shared engine, two front doors
|
|
193
|
+
Engine (Node, deterministic): copy → token-replace → rename packages → toggle features → VERIFY
|
|
194
|
+
Golden template (frozen, CI'd): pinned versions · iOS shell · nav+insets · Clean Arch · DI · Appium
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The engine never puts an LLM in the hot path: it copies the template, replaces placeholders in file
|
|
198
|
+
contents *and* paths, atomically renames package directories, toggles features (Firebase / auth type
|
|
199
|
+
/ Room / Appium), and runs the verify build. Determinism is the moat. See
|
|
200
|
+
[`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full design.
|
|
201
|
+
|
|
202
|
+
## Requirements
|
|
203
|
+
|
|
204
|
+
- **Node.js ≥ 18** to run the scaffolder.
|
|
205
|
+
- **macOS** for iOS output (Xcode, CocoaPods, XcodeGen). Android output works on macOS or Linux.
|
|
206
|
+
- Everything else — JDK 17, Android SDK + emulator, CocoaPods, XcodeGen, Appium + drivers — the
|
|
207
|
+
built-in `doctor` detects and (with your consent) installs. Xcode itself must be installed from the
|
|
208
|
+
App Store; the doctor surfaces that as the one manual step.
|
|
209
|
+
|
|
210
|
+
## Roadmap
|
|
211
|
+
|
|
212
|
+
The full plan — six pillars from scaffold to store release, and the sequencing — lives in
|
|
213
|
+
[`docs/ROADMAP.md`](./docs/ROADMAP.md). Near-term:
|
|
214
|
+
|
|
215
|
+
- [ ] Publish to npm as `create-cmp-cli` (`npx create-cmp-cli`) — release cut, publish pending.
|
|
216
|
+
- [ ] Record the asciinema demo (`npx create-cmp-cli` → green Android + iOS).
|
|
217
|
+
- [ ] Full Android + iOS build matrix in CI (currently CI runs the engine unit tests).
|
|
218
|
+
- [ ] More example features and nav shapes.
|
|
219
|
+
- **AI-native Compose inspector** — read a running app's hierarchy, geometry, and *resolved design
|
|
220
|
+
tokens* as structured JSON (no screenshots). **Phase 0 (headless host-JVM render → inspect) is
|
|
221
|
+
built and verified**; live-emulator inspection is next. See
|
|
222
|
+
[`docs/INSPECTOR-PLAN.md`](./docs/INSPECTOR-PLAN.md).
|
|
223
|
+
|
|
224
|
+
## Contributing
|
|
225
|
+
|
|
226
|
+
Issues and PRs welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) and our
|
|
227
|
+
[Code of Conduct](./CODE_OF_CONDUCT.md). The golden template is CI-gated: an upstream version bump
|
|
228
|
+
must fail CI, not your generated project.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
[MIT](./LICENSE) © Karel van der Merwe and create-cmp contributors.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// create-cmp — scaffold and maintain production-ready Kotlin/Compose
|
|
3
|
+
// Multiplatform apps.
|
|
4
|
+
//
|
|
5
|
+
// Thin subcommand dispatcher; each command lives in src/commands/<name>.mjs
|
|
6
|
+
// and the deterministic engine in src/. Two front doors (this CLI + the
|
|
7
|
+
// Claude Code plugin) share that one engine.
|
|
8
|
+
//
|
|
9
|
+
// npx create-cmp [target-dir] [flags] # scaffold (default command)
|
|
10
|
+
// npx create-cmp create [target-dir] [flags]
|
|
11
|
+
// npx create-cmp doctor [flags] # toolchain + project diagnosis
|
|
12
|
+
// npx create-cmp upgrade [flags] # migrate to a proven-green version set
|
|
13
|
+
// npx create-cmp clean [flags] # konan/Gradle cache & build-output hygiene
|
|
14
|
+
// npx create-cmp verify [flags] # green-build gate on an existing project
|
|
15
|
+
|
|
16
|
+
import { parseArgs } from "../src/lib/args.mjs";
|
|
17
|
+
|
|
18
|
+
const COMMANDS = new Set(["create", "doctor", "upgrade", "clean", "verify", "help"]);
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
const { _: positionals, flags } = parseArgs(argv);
|
|
23
|
+
|
|
24
|
+
// Backward compatible dispatch: the first positional is a subcommand only if
|
|
25
|
+
// it names one; otherwise it is the scaffold target dir (bare `create-cmp`
|
|
26
|
+
// and all pre-router invocations keep working exactly as before).
|
|
27
|
+
const command = COMMANDS.has(positionals[0]) ? positionals[0] : "create";
|
|
28
|
+
const rest = COMMANDS.has(positionals[0]) ? positionals.slice(1) : positionals;
|
|
29
|
+
|
|
30
|
+
if (flags.help || flags.h || command === "help") {
|
|
31
|
+
printHelp();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (command) {
|
|
36
|
+
case "doctor": {
|
|
37
|
+
const { runDoctor } = await import("../src/commands/doctor.mjs");
|
|
38
|
+
await runDoctor(flags, rest[0]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
case "upgrade": {
|
|
42
|
+
const { runUpgrade } = await import("../src/commands/upgrade.mjs");
|
|
43
|
+
await runUpgrade(flags, rest[0]);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
case "clean": {
|
|
47
|
+
const { runClean } = await import("../src/commands/clean.mjs");
|
|
48
|
+
await runClean(flags, rest[0]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
case "verify": {
|
|
52
|
+
const { runVerifyCommand } = await import("../src/commands/verify.mjs");
|
|
53
|
+
await runVerifyCommand(flags, rest[0]);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
case "create":
|
|
57
|
+
default: {
|
|
58
|
+
const { runCreate } = await import("../src/commands/create.mjs");
|
|
59
|
+
await runCreate(flags, rest[0]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printHelp() {
|
|
65
|
+
process.stdout.write(
|
|
66
|
+
`create-cmp — scaffold & maintain Kotlin/Compose Multiplatform apps (Android + iOS)\n\n` +
|
|
67
|
+
`Usage:\n` +
|
|
68
|
+
` npx create-cmp [target-dir] [flags] scaffold a new app (default command)\n` +
|
|
69
|
+
` npx create-cmp create [target-dir] same, explicit\n` +
|
|
70
|
+
` npx create-cmp doctor toolchain doctor + project diagnosis (any KMP project)\n` +
|
|
71
|
+
` npx create-cmp upgrade migrate to the next proven-green version set\n` +
|
|
72
|
+
` npx create-cmp clean ~/.konan + Gradle build-output hygiene (consent-gated)\n` +
|
|
73
|
+
` npx create-cmp verify run the green-build gate on an existing project\n\n` +
|
|
74
|
+
`create (scaffold) flags:\n` +
|
|
75
|
+
` --name --package --bundle-id --region --theme-prefix\n` +
|
|
76
|
+
` --ios/--no-ios --firebase/--no-firebase --auth <email|phone|both|none>\n` +
|
|
77
|
+
` --room/--no-room --appium/--no-appium --inspector/--no-inspector\n` +
|
|
78
|
+
` --dev-client/--no-dev-client (desktop JVM window + Compose Hot Reload)\n` +
|
|
79
|
+
` --tabs Home:home,Profile:person\n` +
|
|
80
|
+
` --target-dir --verify/--no-verify --yes --force --dry-run-verify\n\n` +
|
|
81
|
+
`doctor flags: --yes --dry-run --no-ios --no-install --target-dir <dir> --fix\n` +
|
|
82
|
+
`upgrade flags: --target-dir <dir> --set <id> --dry-run --yes --verify\n` +
|
|
83
|
+
`clean flags: --target-dir <dir> --dry-run --yes\n` +
|
|
84
|
+
`verify flags: --target-dir <dir> --no-ios --dry-run\n`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch((err) => {
|
|
89
|
+
process.stderr.write(`Fatal: ${err && err.stack ? err.stack : err}\n`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/create-cmp/create-cmp/options.schema.json",
|
|
4
|
+
"title": "create-cmp engine config",
|
|
5
|
+
"description": "The config object built by the CLI/plugin front door and consumed by the scaffold engine.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"appName",
|
|
10
|
+
"package",
|
|
11
|
+
"iosBundleId",
|
|
12
|
+
"region",
|
|
13
|
+
"themePrefix",
|
|
14
|
+
"platforms",
|
|
15
|
+
"firebase",
|
|
16
|
+
"room",
|
|
17
|
+
"appium",
|
|
18
|
+
"inspector",
|
|
19
|
+
"devClient",
|
|
20
|
+
"tabs",
|
|
21
|
+
"targetDir"
|
|
22
|
+
],
|
|
23
|
+
"properties": {
|
|
24
|
+
"appName": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Human display name.",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"pattern": "^[^\\n\\r]+$"
|
|
29
|
+
},
|
|
30
|
+
"package": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Reverse-DNS package id, e.g. com.acme.app.",
|
|
33
|
+
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
|
|
34
|
+
},
|
|
35
|
+
"iosBundleId": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "iOS bundle id, e.g. com.acme.app.",
|
|
38
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9-]*(\\.[A-Za-z0-9][A-Za-z0-9-]*)+$"
|
|
39
|
+
},
|
|
40
|
+
"region": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Firebase region, e.g. us-central1.",
|
|
43
|
+
"minLength": 1,
|
|
44
|
+
"pattern": "^[a-z]+(-[a-z]+[0-9]+)?$|^[a-z-]+[0-9]+$"
|
|
45
|
+
},
|
|
46
|
+
"themePrefix": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "PascalCase theme symbol prefix, e.g. Acme.",
|
|
49
|
+
"pattern": "^[A-Z][A-Za-z0-9]*$"
|
|
50
|
+
},
|
|
51
|
+
"platforms": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": false,
|
|
54
|
+
"required": ["android", "ios"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"android": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"const": true,
|
|
59
|
+
"description": "Android is always enabled."
|
|
60
|
+
},
|
|
61
|
+
"ios": { "type": "boolean" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"firebase": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"required": ["enabled"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"enabled": { "type": "boolean" },
|
|
70
|
+
"auth": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"enum": ["email", "phone", "both", "none"]
|
|
73
|
+
},
|
|
74
|
+
"firestore": { "type": "boolean" },
|
|
75
|
+
"storage": { "type": "boolean" },
|
|
76
|
+
"functions": { "type": "boolean" },
|
|
77
|
+
"fcm": { "type": "boolean" }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"room": { "type": "boolean" },
|
|
81
|
+
"appium": { "type": "boolean" },
|
|
82
|
+
"inspector": {
|
|
83
|
+
"type": "boolean",
|
|
84
|
+
"description": "Live on-device inspector: debug-only loopback HTTP server (127.0.0.1:9500) serving the Compose semantics tree + design-token catalog to the cmp-inspector MCP. Structurally absent from release builds."
|
|
85
|
+
},
|
|
86
|
+
"devClient": {
|
|
87
|
+
"type": "boolean",
|
|
88
|
+
"description": "Desktop dev-client: a jvm(\"desktop\") target running the shared commonMain UI in a phone-sized JVM window with Compose Hot Reload attached (./gradlew :composeApp:hotRunDesktop --auto). Firebase is never initialized on desktop; platform seams get JVM fakes/impls in desktopMain."
|
|
89
|
+
},
|
|
90
|
+
"tabs": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"minItems": 1,
|
|
93
|
+
"maxItems": 5,
|
|
94
|
+
"items": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"additionalProperties": false,
|
|
97
|
+
"required": ["label", "icon"],
|
|
98
|
+
"properties": {
|
|
99
|
+
"label": { "type": "string", "minLength": 1 },
|
|
100
|
+
"icon": { "type": "string", "minLength": 1 }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"targetDir": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"description": "Destination directory for the stamped project.",
|
|
107
|
+
"minLength": 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-cmp-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Scaffold a production-ready Kotlin/Compose Multiplatform app (Android + iOS) that builds green in minutes — toolchain auto-bootstrapped, navigation/insets pre-solved, Clean Architecture wired, Appium harness ready. Installs the `create-cmp` command.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-cmp": "bin/create-cmp.mjs",
|
|
8
|
+
"create-cmp-cli": "bin/create-cmp.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"src",
|
|
16
|
+
"template",
|
|
17
|
+
"options.schema.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test",
|
|
21
|
+
"prepublishOnly": "node --test",
|
|
22
|
+
"doctor": "node bin/create-cmp.mjs doctor"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"create-cmp",
|
|
26
|
+
"create-cmp-cli",
|
|
27
|
+
"compose-multiplatform",
|
|
28
|
+
"kotlin-multiplatform",
|
|
29
|
+
"kmp",
|
|
30
|
+
"cmp",
|
|
31
|
+
"scaffold",
|
|
32
|
+
"android",
|
|
33
|
+
"ios"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"author": "Karel van der Merwe",
|
|
37
|
+
"homepage": "https://github.com/kvdm-co-pilot/create-cmp#readme",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/kvdm-co-pilot/create-cmp.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/kvdm-co-pilot/create-cmp/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"prompts": "^2.4.2",
|
|
50
|
+
"picocolors": "^1.1.1"
|
|
51
|
+
}
|
|
52
|
+
}
|