catalyst-core 0.3.0-canary.1 → 0.3.0-canary.3
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/changelog.md +114 -80
- package/dist/errors/registry.js +17 -0
- package/dist/errors-index.json +14 -0
- package/dist/native/buildAndroid/build.js +158 -13
- package/dist/native/buildIos/build.js +118 -73
- package/dist/native/buildIos/config.js +11 -0
- package/dist/native/setupEmulatorIos.js +38 -7
- package/dist/scripts/loadEnvironmentVariables.js +20 -1
- package/dist/scripts/serve.js +0 -2
- package/dist/scripts/start.js +0 -2
- package/dist/server/renderer/render.js +1 -9
- package/dist/server/utils/validator.js +1 -1
- package/mcp_v2/knowledge-base.json +69 -15
- package/mcp_v2/lib/helpers.js +25 -13
- package/mcp_v2/mcp.js +1 -0
- package/package.json +1 -1
- package/test/scripts/preflight.test.ts +0 -3
- package/test/server/handler.assets.test.ts +0 -1
- package/test/server/handler.test.ts +1 -3
- package/test/server/render.test.ts +2 -21
- package/test/server/validator.test.ts +0 -3
package/changelog.md
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
# 1.0.0-beta.1
|
|
2
|
-
|
|
3
|
-
- Replaced the Webpack build and SSR pipeline with Vite and ESM.
|
|
4
|
-
- Added React 19 Suspense-aware routing, split components, and manifest-based asset loading.
|
|
5
|
-
- Added opt-in request, compression, flush, and bot-aware OpenTelemetry spans.
|
|
6
|
-
- Preserved native application builds, internal plugins, Sentry exports, and safe-area SSR hydration.
|
|
7
|
-
- Removed the legacy `devBuild` and `devServe` commands.
|
|
8
|
-
|
|
9
1
|
# Changelog
|
|
10
2
|
|
|
11
|
-
## [0.
|
|
3
|
+
## [1.0.0] - unreleased
|
|
12
4
|
|
|
13
5
|
This release freezes the public API. The root entry of `catalyst-core` now exports exactly eight names and nothing else: `RouterDataProvider`, `useCurrentRouteData`, `useRouterData`, `MetaTag`, `split`, `hydrationReady`, `Head`, and `Body`. Anything previously reachable through the root entry but not in that list is no longer public. Applications that import a removed name will fail at build time rather than silently resolving to `undefined`.
|
|
14
6
|
|
|
@@ -68,7 +60,7 @@ Where a hook had two names for one value, the shorter one is canonical and the o
|
|
|
68
60
|
|
|
69
61
|
Hooks fall into three categories, and the category decides whether a hook has `execute`. A hook that wraps a single native operation exposes it as `execute` plus a domain-specific alias — `takePhoto`, `pickFile`, `openFile`, `signIn`, `trigger`, `request`. A hook that wraps several distinct operations has no single action to name, so its named functions are the API and it has no `execute` at all: `useVideoStream`, `useDataProtection`, and `useNativeTransition` work this way. A read-only hook reports ambient device state and has no action functions at all: `useNetworkStatus`, `useDeviceInfo`, and `useSafeArea` return state plus the runtime-context keys. `useNotification` is the one exception. It carries `execute` and `schedule` as aliases of `scheduleLocal` for backward compatibility. Both are frozen, both are discouraged, and neither should be read as the pattern for multi-action hooks. `execute` never takes an operation name as a string argument; it takes the arguments of the operation itself.
|
|
70
62
|
|
|
71
|
-
Hook errors are `CatalystError` values from the framework-wide error registry introduced in this release, carrying a `RUNTIME-NATIVE-*` code, a message, a category, a suggested action, a documentation link, and the originating platform error as `cause`. `useVideoStream` in particular used to report seventeen failures as bare `{ message }` objects with no code at all; those now carry real codes, and a camera denial reports the same code whether it comes from the permission hook or the stream.
|
|
63
|
+
Hook errors are `CatalystError` values from the framework-wide error registry introduced in this release, carrying a `RUNTIME-NATIVE-*` code, a message, a category, a suggested action, a documentation link, and the originating platform error as `cause`. `useVideoStream` in particular used to report seventeen failures as bare `{ message }` objects with no code at all; those now carry real codes, and a camera denial reports the same code whether it comes from the permission hook or the stream. The error object's fields changed with it: `action` is now `suggestedAction`, `nativeError` is now `cause`, `recoverable` and `timestamp` are gone, and `code` values are registry codes such as `RUNTIME-NATIVE-001` instead of names such as `PERMISSION_DENIED`, so compare against `ERROR_CODES` from `catalyst-core/errors` rather than string literals.
|
|
72
64
|
|
|
73
65
|
One consequence for error logging: `CatalystError` extends `Error`, so `message` now lives on the prototype rather than as an own property. Reading `error.message` is unaffected, but `JSON.stringify(error)` no longer includes it. Log `error.message` explicitly, or use the `code` and `docUrl` fields.
|
|
74
66
|
|
|
@@ -86,13 +78,83 @@ The app contract validators in `server/utils/validator.js` used to log a bare me
|
|
|
86
78
|
|
|
87
79
|
The route and store checks moved out of the per-request path. `validateGetRoutes` and `validateConfigureStore` now run once when the renderer module loads — at server startup in production, and on the first SSR request in development, where the handler is loaded lazily — rather than once per request inside the handler's `try`/`catch`. The shape of `addMiddlewares` from `server/server.js` is checked during server setup.
|
|
88
80
|
|
|
89
|
-
|
|
81
|
+
Separately, `catalyst start`, `build` and `serve` now check `config/config.json` and `package.json` before starting and exit with a coded error when either is missing, a required config key is missing, or a required module alias is missing. An application that satisfies the contract sees no change. The full contract — the files Catalyst imports by fixed path and the required module aliases — is documented under File Conventions.
|
|
82
|
+
The webpack-era keys `WEBPACK_DEV_SERVER_HOSTNAME`, `WEBPACK_DEV_SERVER_PORT` and `ANALYZE_BUNDLE` are no longer required or read; remove them from `config/config.json`.
|
|
83
|
+
|
|
84
|
+
## [0.3.0-beta.5] - 2026-08-18
|
|
85
|
+
|
|
86
|
+
- Upgrade react-router-dom v6 to v7.18.2 (CVE fixes) (#399).
|
|
87
|
+
- Docs: clarify setMetaData must return array of JSX elements (#327).
|
|
88
|
+
|
|
89
|
+
## [0.3.0-beta.4] - 2026-08-10
|
|
90
|
+
|
|
91
|
+
- Detect CommonJS by parsing, not by matching source text (#397).
|
|
92
|
+
- Sub-path base + HMR config for reverse-proxy dev setups (#379).
|
|
93
|
+
|
|
94
|
+
## [0.3.0-beta.3] - 2026-08-04
|
|
95
|
+
|
|
96
|
+
- Resolve semgrep and dependency findings from security scan (#375).
|
|
97
|
+
|
|
98
|
+
## [0.3.0-beta.2] - 2026-08-03
|
|
99
|
+
|
|
100
|
+
- @catalyst/cloud-ai — multi-provider AI integration with useAI hook (#288).
|
|
101
|
+
- Eliminate SSR streaming race between onShellReady and onAllReady (#323).
|
|
102
|
+
- Update iOS release docs and MCP guidance (#324).
|
|
103
|
+
- Patch @tailwindcss/postcss base so Vite scans the whole app (#326).
|
|
104
|
+
|
|
105
|
+
## [0.3.0-beta.1] - 2026-07-15
|
|
106
|
+
|
|
107
|
+
- Replaced the Webpack build and SSR pipeline with Vite and ESM.
|
|
108
|
+
- Added React 19 Suspense-aware routing, split components, and manifest-based asset loading.
|
|
109
|
+
- Added opt-in request, compression, flush, and bot-aware OpenTelemetry spans.
|
|
110
|
+
- Preserved native application builds, internal plugins, Sentry exports, and safe-area SSR hydration.
|
|
111
|
+
- Removed the legacy `devBuild` and `devServe` commands.
|
|
112
|
+
|
|
113
|
+
## [0.2.0-beta.3] - 2026-07-14
|
|
114
|
+
|
|
115
|
+
- Catalyst Profiler Runtime (#274).
|
|
116
|
+
|
|
117
|
+
## [0.2.0-beta.2] - 2026-07-09
|
|
118
|
+
|
|
119
|
+
- Route Caching (#287).
|
|
120
|
+
- Android build fixes (#303).
|
|
121
|
+
|
|
122
|
+
## [0.2.0-beta.1] - 2026-07-08
|
|
123
|
+
|
|
124
|
+
- Catalyst react + rendering update (#293).
|
|
125
|
+
- Add GitHub feedback integration to Catalyst MCP v2 (#272).
|
|
126
|
+
- Sync MCP v2: extract seeding, delegate docs sync, add FTS + initial sync (#295).
|
|
127
|
+
|
|
128
|
+
## [0.1.0-beta.7] - 2026-06-29
|
|
129
|
+
|
|
130
|
+
- Syncing dweb branch with main (#283).
|
|
131
|
+
|
|
132
|
+
## [0.1.0-beta.6] - 2026-06-11
|
|
133
|
+
|
|
134
|
+
- Add examples folder with local testing app for catalyst-core (#246).
|
|
135
|
+
|
|
136
|
+
## [0.1.0-beta.5] - 2026-06-09
|
|
137
|
+
|
|
138
|
+
- Semgrep Plugin Fixes (#266).
|
|
139
|
+
|
|
140
|
+
## [0.1.0-beta.4] - 2026-06-03
|
|
141
|
+
|
|
142
|
+
- Merge video hook, web fallback, and plugin system into monorepo (#258).
|
|
143
|
+
|
|
144
|
+
## [0.1.0-beta.3] - 2026-06-02
|
|
145
|
+
|
|
146
|
+
- Security Fixes (semgrep) (#259).
|
|
147
|
+
- Catalyst Docs Mismatches Fix (#244).
|
|
90
148
|
|
|
91
149
|
## [0.1.0-beta.2] - 2026-05-06
|
|
92
150
|
|
|
93
151
|
- Moved Catalyst into a monorepo structure with `catalyst-core`, `create-catalyst-app`, the Catalyst docs app, and the Catalyst core test app managed from one repository.
|
|
94
152
|
- Replaced the internal-package pre-release flow with a local release sandbox that scaffolds a real app using current-branch CCA and a locally packed current-branch `catalyst-core`.
|
|
95
153
|
|
|
154
|
+
## [0.1.0-canary.9] - 2026-05-06
|
|
155
|
+
|
|
156
|
+
- Changelog-only update; same code as 0.1.0-beta.2.
|
|
157
|
+
|
|
96
158
|
## [0.1.0-canary.8] - 2026-04-15
|
|
97
159
|
|
|
98
160
|
- Introduced Catalyst’s internal plugin architecture, adding the `PluginBridge` web API, native plugin runtimes for Android and iOS, and manifest-driven build composition for modular native capabilities.
|
|
@@ -120,7 +182,7 @@ Module aliases and `config/config.json` keys are not checked at load time; a mis
|
|
|
120
182
|
- Improved universal app runtime behavior with safe-area inset support, edge-to-edge rendering, and notification permission override fixes.
|
|
121
183
|
- Expanded platform support with offline fallback handling, notification/access-control refinements, localhost HTTP allowances for local development, and file-picker/HTTPS server improvements.
|
|
122
184
|
|
|
123
|
-
## [0.1.0-canary.4] - 2026-02-
|
|
185
|
+
## [0.1.0-canary.4] - 2026-02-16
|
|
124
186
|
|
|
125
187
|
- Added Google Sign-In support for both Android and iOS in Catalyst, enabling a unified native authentication experience for apps built on the framework.
|
|
126
188
|
- Improved release/build reliability with related bridge and CI updates, making integration smoother and more consistent across platforms.
|
|
@@ -148,26 +210,26 @@ Module aliases and `config/config.json` keys are not checked at load time; a mis
|
|
|
148
210
|
- Notification refactor: streamlined config toggle for local vs push, centralized permission/asset handling, and simpler routing into the web app
|
|
149
211
|
- Access control: combined access control config for ios
|
|
150
212
|
|
|
151
|
-
## [0.0.3-canary.18] - 2025-11-
|
|
213
|
+
## [0.0.3-canary.18] - 2025-11-25
|
|
152
214
|
|
|
153
215
|
- Configuration Files
|
|
154
216
|
- Android Native WebView - MainActivity.kt
|
|
155
217
|
- Android Native WebView - CustomWebview.kt
|
|
156
218
|
|
|
157
|
-
## [0.0.3-canary.17] - 2025-11-
|
|
219
|
+
## [0.0.3-canary.17] - 2025-11-21
|
|
158
220
|
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
221
|
+
- Added complete notification system (local & push)
|
|
222
|
+
- New useNotification() React hook
|
|
223
|
+
- Firebase Cloud Messaging conditional build support
|
|
224
|
+
- Android notification handling in MainActivity
|
|
225
|
+
- 5 new notification commands + 5 callbacks in NativeBridge
|
|
226
|
+
- Auto-processing of notification icons & sounds
|
|
227
|
+
- Conditional dependencies based on notification config
|
|
228
|
+
- Updated .gitignore & .npmignore
|
|
229
|
+
- Enhanced build process with automatic permission injection
|
|
230
|
+
- Deep link integration with notifications
|
|
169
231
|
|
|
170
|
-
## [0.0.3-canary.16] - 2025-11-
|
|
232
|
+
## [0.0.3-canary.16] - 2025-11-20
|
|
171
233
|
|
|
172
234
|
- FeaturesGallery Support for File Picker: Enhanced file picker to open native gallery for selecting images and videos
|
|
173
235
|
- HTTPS Framework Server: Enabled HTTPS support for the framework server with self-signed SSL certificate
|
|
@@ -177,17 +239,17 @@ Module aliases and `config/config.json` keys are not checked at load time; a mis
|
|
|
177
239
|
|
|
178
240
|
- Adding Android release support and appInfo key
|
|
179
241
|
|
|
180
|
-
## [0.0.3-canary.14] - 2025-
|
|
242
|
+
## [0.0.3-canary.14] - 2025-10-29
|
|
181
243
|
|
|
182
244
|
- Android custom splashscreen
|
|
183
245
|
|
|
184
|
-
## [0.0.3-canary.13] - 2025-
|
|
246
|
+
## [0.0.3-canary.13] - 2025-10-28
|
|
185
247
|
|
|
186
248
|
- Multi file support in useFilePicker hook
|
|
187
249
|
- Android multiple app icon support
|
|
188
250
|
- Android whitelisting toggle support
|
|
189
251
|
|
|
190
|
-
## [0.0.3-canary.12] - 2025-
|
|
252
|
+
## [0.0.3-canary.12] - 2025-10-13
|
|
191
253
|
|
|
192
254
|
- Universal App Context: Added comprehensive context support for building universal mobile applications
|
|
193
255
|
- OpenTelemetry SDK Integration: Built-in support for observability and performance monitoring with Otel SDK
|
|
@@ -198,70 +260,42 @@ Module aliases and `config/config.json` keys are not checked at load time; a mis
|
|
|
198
260
|
- Enhanced Android native support: Add support for native keyboard and webview resizing in Android
|
|
199
261
|
- Device info API web support: Web support in device info API
|
|
200
262
|
|
|
201
|
-
## [0.0.3-canary.10] - 2025-09-
|
|
263
|
+
## [0.0.3-canary.10] - 2025-09-17
|
|
202
264
|
|
|
203
|
-
-
|
|
204
|
-
-
|
|
265
|
+
- Sentry import bug fix.
|
|
266
|
+
- Open telemetry SDK.
|
|
205
267
|
|
|
206
268
|
## [0.0.3-canary.9] - 2025-09-11
|
|
207
269
|
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
|
|
212
|
-
## [0.0.3-canary.8] - 2025-01-06
|
|
213
|
-
|
|
214
|
-
- **App icon configuration**
|
|
215
|
-
- **Splash screen integration**
|
|
216
|
-
- **Url Whitelisting**
|
|
270
|
+
- App name configuration.
|
|
271
|
+
- Device Info API.
|
|
272
|
+
- Access control configuration.
|
|
217
273
|
|
|
218
|
-
## [0.0.3-canary.
|
|
274
|
+
## [0.0.3-canary.8] - 2025-08-08
|
|
219
275
|
|
|
220
|
-
|
|
276
|
+
- App icon configuration.
|
|
277
|
+
- Splash screen integration.
|
|
278
|
+
- Url Whitelisting.
|
|
221
279
|
|
|
222
|
-
-
|
|
223
|
-
- **Automated IP Resolution**: Intelligent network detection eliminates manual IP configuration
|
|
224
|
-
- **Universal Build System**: Streamlined one-command builds for both Android and iOS platforms
|
|
225
|
-
- **Enhanced CLI Interface**: Improved command-line tools for better developer experience
|
|
226
|
-
|
|
227
|
-
### 🐛 Bug Fixes
|
|
280
|
+
## [0.0.3-canary.7] - 2025-08-06
|
|
228
281
|
|
|
282
|
+
- Native API Integration: Enhanced native module integration for better performance
|
|
283
|
+
- Automated IP Resolution: Intelligent network detection eliminates manual IP configuration
|
|
284
|
+
- Universal Build System: Streamlined one-command builds for both Android and iOS platforms
|
|
285
|
+
- Enhanced CLI Interface: Improved command-line tools for better developer experience
|
|
229
286
|
- Fixed IP address detection issues on development environments
|
|
230
287
|
- Resolved build configuration conflicts between platforms
|
|
231
288
|
- Improved error handling and recovery mechanisms
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- ✅ **Android Build**: Verified APK generation, installation, and runtime behavior
|
|
243
|
-
- ✅ **iOS Build**: Tested build process and device compatibility
|
|
244
|
-
- ✅ **IP Detection**: Validated automatic network resolution across platforms
|
|
245
|
-
- ✅ **Native Modules**: Confirmed API integration and performance benchmarks
|
|
246
|
-
- ✅ **CLI Operations**: Tested enhanced command-line workflows and error handling
|
|
247
|
-
|
|
248
|
-
### 🔄 Breaking Changes
|
|
249
|
-
|
|
250
|
-
**None** - All changes maintain backward compatibility while enhancing existing functionality
|
|
251
|
-
|
|
252
|
-
### 📝 Technical Details
|
|
253
|
-
|
|
289
|
+
- Performance: Optimized native module loading and execution
|
|
290
|
+
- Developer Experience: Enhanced CLI with better error messages and debugging info
|
|
291
|
+
- Configuration: Dynamic config updates without requiring restarts
|
|
292
|
+
- Build Process: Faster and more reliable build pipeline
|
|
293
|
+
- Android Build: Verified APK generation, installation, and runtime behavior
|
|
294
|
+
- iOS Build: Tested build process and device compatibility
|
|
295
|
+
- IP Detection: Validated automatic network resolution across platforms
|
|
296
|
+
- Native Modules: Confirmed API integration and performance benchmarks
|
|
297
|
+
- CLI Operations: Tested enhanced command-line workflows and error handling
|
|
254
298
|
- Improved native API bridge for better cross-platform communication
|
|
255
299
|
- Enhanced error reporting with detailed stack traces and suggestions
|
|
256
300
|
- Optimized build configuration for faster development cycles
|
|
257
301
|
- Added comprehensive logging for debugging and monitoring
|
|
258
|
-
|
|
259
|
-
### 🔗 Migration Notes
|
|
260
|
-
|
|
261
|
-
- No migration steps required
|
|
262
|
-
- Existing projects will automatically benefit from improvements
|
|
263
|
-
- Optional: Update CLI usage to leverage new enhanced features
|
|
264
|
-
|
|
265
|
-
## Version
|
|
266
|
-
|
|
267
|
-
- Target version: 0.0.3-canary.7
|
package/dist/errors/registry.js
CHANGED
|
@@ -28,7 +28,9 @@ export const ERROR_CODES = {
|
|
|
28
28
|
|
|
29
29
|
BUNDLE_UPSTREAM_ERROR: "BUNDLE-000",
|
|
30
30
|
IOS_UPSTREAM_ERROR: "IOS-000",
|
|
31
|
+
IOS_SIMULATOR_APP_NOT_FOUND: "IOS-001",
|
|
31
32
|
ANDROID_UPSTREAM_ERROR: "ANDROID-000",
|
|
33
|
+
ANDROID_EMULATOR_BOOT_TIMEOUT: "ANDROID-001",
|
|
32
34
|
|
|
33
35
|
RUNTIME_NATIVE_PERMISSION_DENIED: "RUNTIME-NATIVE-001",
|
|
34
36
|
RUNTIME_NATIVE_PERMISSION_REQUIRED: "RUNTIME-NATIVE-002",
|
|
@@ -231,12 +233,27 @@ export const ERROR_DEFINITIONS = {
|
|
|
231
233
|
defaultDetails: "This wraps an error from Xcode/CocoaPods. See the printed upstream output for the cause.",
|
|
232
234
|
suggestedAction: "Read the upstream Xcode/CocoaPods error printed above and fix the underlying issue.",
|
|
233
235
|
},
|
|
236
|
+
[ERROR_CODES.IOS_SIMULATOR_APP_NOT_FOUND]: {
|
|
237
|
+
category: "IOS",
|
|
238
|
+
defaultMessage: "Could not find an installed iOS Simulator app",
|
|
239
|
+
defaultDetails:
|
|
240
|
+
"Neither DeviceHub.app (Xcode 27+, bundle id com.apple.dt.Devices) nor the legacy Simulator.app (bundle id com.apple.iphonesimulator) is installed under Xcode's Contents/Applications. The simulator itself may still boot headlessly via simctl, but catalyst-core cannot open or focus its window.",
|
|
241
|
+
suggestedAction: "Confirm a full Xcode.app (not just Command Line Tools) is installed and selected via xcode-select -p, then reopen it once from Spotlight so macOS registers its app bundle.",
|
|
242
|
+
},
|
|
234
243
|
[ERROR_CODES.ANDROID_UPSTREAM_ERROR]: {
|
|
235
244
|
category: "ANDROID",
|
|
236
245
|
defaultMessage: "Android build failed in an upstream toolchain step",
|
|
237
246
|
defaultDetails: "This wraps an error from Gradle. See the printed upstream output for the cause.",
|
|
238
247
|
suggestedAction: "Read the upstream Gradle error printed above and fix the underlying issue.",
|
|
239
248
|
},
|
|
249
|
+
[ERROR_CODES.ANDROID_EMULATOR_BOOT_TIMEOUT]: {
|
|
250
|
+
category: "ANDROID",
|
|
251
|
+
defaultMessage: "Timed out waiting for the Android emulator to boot",
|
|
252
|
+
defaultDetails:
|
|
253
|
+
"buildApp:android launched the configured AVD but it did not appear in `adb devices` or did not finish booting (sys.boot_completed=1 and init.svc.bootanim=stopped) within the timeout.",
|
|
254
|
+
suggestedAction:
|
|
255
|
+
"Try booting the AVD manually first (emulator -avd <name>) to see the actual startup error, check available disk space/RAM, or increase the emulator's cold-boot allowance if the device is unusually slow.",
|
|
256
|
+
},
|
|
240
257
|
|
|
241
258
|
[ERROR_CODES.RUNTIME_NATIVE_PERMISSION_DENIED]: {
|
|
242
259
|
category: "RUNTIME-NATIVE",
|
package/dist/errors-index.json
CHANGED
|
@@ -244,6 +244,13 @@
|
|
|
244
244
|
"suggestedAction": "Read the upstream Xcode/CocoaPods error printed above and fix the underlying issue.",
|
|
245
245
|
"docUrl": "https://github.com/tata1mg/catalyst-core/blob/main/errors/IOS/IOS-000.md"
|
|
246
246
|
},
|
|
247
|
+
"IOS-001": {
|
|
248
|
+
"category": "IOS",
|
|
249
|
+
"message": "Could not find an installed iOS Simulator app",
|
|
250
|
+
"details": "Neither DeviceHub.app (Xcode 27+, bundle id com.apple.dt.Devices) nor the legacy Simulator.app (bundle id com.apple.iphonesimulator) is installed under Xcode's Contents/Applications. The simulator itself may still boot headlessly via simctl, but catalyst-core cannot open or focus its window.",
|
|
251
|
+
"suggestedAction": "Confirm a full Xcode.app (not just Command Line Tools) is installed and selected via xcode-select -p, then reopen it once from Spotlight so macOS registers its app bundle.",
|
|
252
|
+
"docUrl": "https://github.com/tata1mg/catalyst-core/blob/main/errors/IOS/IOS-001.md"
|
|
253
|
+
},
|
|
247
254
|
"ANDROID-000": {
|
|
248
255
|
"category": "ANDROID",
|
|
249
256
|
"message": "Android build failed in an upstream toolchain step",
|
|
@@ -251,6 +258,13 @@
|
|
|
251
258
|
"suggestedAction": "Read the upstream Gradle error printed above and fix the underlying issue.",
|
|
252
259
|
"docUrl": "https://github.com/tata1mg/catalyst-core/blob/main/errors/ANDROID/ANDROID-000.md"
|
|
253
260
|
},
|
|
261
|
+
"ANDROID-001": {
|
|
262
|
+
"category": "ANDROID",
|
|
263
|
+
"message": "Timed out waiting for the Android emulator to boot",
|
|
264
|
+
"details": "buildApp:android launched the configured AVD but it did not appear in `adb devices` or did not finish booting (sys.boot_completed=1 and init.svc.bootanim=stopped) within the timeout.",
|
|
265
|
+
"suggestedAction": "Try booting the AVD manually first (emulator -avd <name>) to see the actual startup error, check available disk space/RAM, or increase the emulator's cold-boot allowance if the device is unusually slow.",
|
|
266
|
+
"docUrl": "https://github.com/tata1mg/catalyst-core/blob/main/errors/ANDROID/ANDROID-001.md"
|
|
267
|
+
},
|
|
254
268
|
"RUNTIME-NATIVE-001": {
|
|
255
269
|
"category": "RUNTIME-NATIVE",
|
|
256
270
|
"message": "Permission denied",
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const {
|
|
6
|
+
spawn,
|
|
7
|
+
execFileSync
|
|
8
|
+
} = require("child_process");
|
|
5
9
|
const {
|
|
6
10
|
buildAndroidAAB
|
|
7
11
|
} = require("../renameAndroidProject.js");
|
|
@@ -101,36 +105,176 @@ function createBuildPhase(ctx) {
|
|
|
101
105
|
return false;
|
|
102
106
|
}
|
|
103
107
|
}
|
|
108
|
+
|
|
109
|
+
// Returns the serial of an online emulator (adb state "device"), or null if
|
|
110
|
+
// none is online. `devices.includes("emulator")` alone would also match a
|
|
111
|
+
// serial like "emulator-5554" whose state is "offline" or still enumerating —
|
|
112
|
+
// i.e. a device that's connected but not actually ready — so this checks the
|
|
113
|
+
// state column explicitly rather than doing a substring match.
|
|
104
114
|
async function checkEmulator(ADB_PATH) {
|
|
105
115
|
try {
|
|
106
116
|
// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
|
|
107
117
|
const devices = ctx.runCommand(`${ADB_PATH} devices`);
|
|
108
|
-
|
|
118
|
+
const onlineEmulator = devices.split("\n").map(line => line.trim().split(/\s+/)).find(([id, state]) => id?.startsWith("emulator-") && state === "device");
|
|
119
|
+
return onlineEmulator ? onlineEmulator[0] : null;
|
|
109
120
|
} catch (error) {
|
|
110
121
|
progress.log("Error checking emulator status: " + error.message, "error");
|
|
111
|
-
return
|
|
122
|
+
return null;
|
|
112
123
|
}
|
|
113
124
|
}
|
|
125
|
+
|
|
126
|
+
// The Android emulator binary is a long-running server process — it does not
|
|
127
|
+
// exit after boot, it keeps running until the emulator window is closed. Using
|
|
128
|
+
// runInteractiveCommand (which only resolves on the child's `close` event, see
|
|
129
|
+
// utils.js) would block forever waiting for an exit that never happens before
|
|
130
|
+
// the app finishes booting. Spawn it detached instead (same approach as
|
|
131
|
+
// androidSetup.js's startEmulator) and let handleEmulatorSetup poll for
|
|
132
|
+
// readiness separately.
|
|
133
|
+
/**
|
|
134
|
+
* Launch the emulator detached and return once it has actually spawned
|
|
135
|
+
* (not once it exits — see the note above on why runInteractiveCommand
|
|
136
|
+
* can't be used here).
|
|
137
|
+
* @returns {Promise<number>} the spawned process's pid, so a caller whose
|
|
138
|
+
* boot-readiness wait times out can kill it instead of leaving a
|
|
139
|
+
* CPU/RAM-heavy orphaned emulator running past a failed build.
|
|
140
|
+
*/
|
|
114
141
|
async function startEmulator(EMULATOR_PATH, androidConfig) {
|
|
115
142
|
progress.log(`Starting emulator: ${androidConfig.emulatorName}...`, "info");
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - spawn with an argv array (no shell), so androidConfig.emulatorName can't be interpreted as shell syntax even though it comes from a locally-editable config file.
|
|
145
|
+
const child = spawn(EMULATOR_PATH, ["-avd", androidConfig.emulatorName, "-read-only"], {
|
|
146
|
+
detached: true,
|
|
147
|
+
stdio: "ignore"
|
|
148
|
+
});
|
|
149
|
+
child.once("error", error => {
|
|
150
|
+
progress.log("Error starting emulator: " + error.message, "error");
|
|
151
|
+
reject(error);
|
|
152
|
+
});
|
|
153
|
+
// "error" only fires for spawn failures (e.g. bad EMULATOR_PATH); once the
|
|
154
|
+
// process has actually spawned we can detach and let the caller poll adb
|
|
155
|
+
// for boot readiness rather than waiting on this child at all.
|
|
156
|
+
child.once("spawn", () => {
|
|
157
|
+
const {
|
|
158
|
+
pid
|
|
159
|
+
} = child;
|
|
160
|
+
child.unref();
|
|
161
|
+
progress.log("Emulator process started, waiting for it to boot...", "info");
|
|
162
|
+
resolve(pid);
|
|
163
|
+
});
|
|
122
164
|
});
|
|
123
165
|
}
|
|
166
|
+
|
|
167
|
+
// Best-effort cleanup for an emulator process this build spawned but that
|
|
168
|
+
// never became ready — kills the whole detached process group (negative pid)
|
|
169
|
+
// so we don't leave a CPU/RAM-heavy orphaned emulator running after a failed
|
|
170
|
+
// build. Never throws: a failure here shouldn't mask the original timeout error.
|
|
171
|
+
function killOrphanedEmulator(pid, emulatorName) {
|
|
172
|
+
try {
|
|
173
|
+
process.kill(-pid, "SIGTERM");
|
|
174
|
+
progress.log(`Stopped orphaned emulator process (pid ${pid}) after boot failure`, "info");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
progress.log(`Could not stop orphaned emulator "${emulatorName}" (pid ${pid}): ${error.message}. ` + "You may need to quit it manually.", "warning");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function listEmulatorSerials(ADB_PATH) {
|
|
180
|
+
try {
|
|
181
|
+
// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
|
|
182
|
+
const devices = ctx.runCommand(`${ADB_PATH} devices`);
|
|
183
|
+
return devices.split("\n").map(line => line.trim().split(/\s+/)[0]).filter(id => id && id.startsWith("emulator-"));
|
|
184
|
+
} catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Waits for a NEW emulator-XXXX serial to appear in `adb devices` (one not in
|
|
190
|
+
// knownSerials, captured before startEmulator was called), so boot polling can
|
|
191
|
+
// be scoped to the emulator this build just launched rather than whichever
|
|
192
|
+
// device `adb shell` (with no -s) happens to pick when multiple are connected.
|
|
193
|
+
async function waitForNewEmulatorSerial(ADB_PATH, knownSerials, {
|
|
194
|
+
timeoutMs = 30000,
|
|
195
|
+
pollIntervalMs = 1000
|
|
196
|
+
} = {}) {
|
|
197
|
+
const deadline = Date.now() + timeoutMs;
|
|
198
|
+
while (Date.now() < deadline) {
|
|
199
|
+
const current = listEmulatorSerials(ADB_PATH);
|
|
200
|
+
const newSerial = current.find(id => !knownSerials.includes(id));
|
|
201
|
+
if (newSerial) return newSerial;
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Poll a specific emulator (by adb serial, e.g. "emulator-5554") until it
|
|
209
|
+
* reports a completed boot and a stopped boot animation, or the timeout
|
|
210
|
+
* elapses.
|
|
211
|
+
* @param {string} ADB_PATH Path to the adb executable.
|
|
212
|
+
* @param {string} serial The emulator's adb serial to target (adb -s).
|
|
213
|
+
* @param {Object} [options]
|
|
214
|
+
* @param {number} [options.timeoutMs=120000] Overall deadline in milliseconds.
|
|
215
|
+
* @param {number} [options.pollIntervalMs=2000] Delay between readiness checks.
|
|
216
|
+
* @returns {Promise<boolean>} true once both readiness signals are observed;
|
|
217
|
+
* false if the deadline elapses first (a transient/hung adb call counts
|
|
218
|
+
* against the same deadline via the per-call execFileSync timeout below).
|
|
219
|
+
*/
|
|
220
|
+
async function waitForEmulatorBoot(ADB_PATH, serial, {
|
|
221
|
+
timeoutMs = 120000,
|
|
222
|
+
pollIntervalMs = 2000
|
|
223
|
+
} = {}) {
|
|
224
|
+
const deadline = Date.now() + timeoutMs;
|
|
225
|
+
const getprop = prop => {
|
|
226
|
+
const remaining = Math.max(deadline - Date.now(), 1000);
|
|
227
|
+
return execFileSync(ADB_PATH, ["-s", serial, "shell", "getprop", prop], {
|
|
228
|
+
encoding: "utf8",
|
|
229
|
+
timeout: remaining
|
|
230
|
+
}).trim();
|
|
231
|
+
};
|
|
232
|
+
while (Date.now() < deadline) {
|
|
233
|
+
try {
|
|
234
|
+
const bootCompleted = getprop("sys.boot_completed");
|
|
235
|
+
const bootAnimDone = getprop("init.svc.bootanim");
|
|
236
|
+
if (bootCompleted === "1" && bootAnimDone === "stopped") return true;
|
|
237
|
+
} catch {
|
|
238
|
+
// adb not ready yet (device still enumerating), or this call hit its
|
|
239
|
+
// own per-call timeout — either way, keep polling against the outer
|
|
240
|
+
// deadline rather than treating it as fatal.
|
|
241
|
+
}
|
|
242
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Message mirrors errors/registry.js ERROR_DEFINITIONS[ANDROID-001] (see
|
|
248
|
+
// errors/ANDROID/ANDROID-001.md) — this CJS subtree can't import the ESM
|
|
249
|
+
// error registry (see buildErrorFormat.js), so the code is prefixed onto the
|
|
250
|
+
// thrown message directly rather than imported.
|
|
251
|
+
function emulatorBootTimeoutError(detail) {
|
|
252
|
+
return new Error(`[ANDROID-001] Timed out waiting for the Android emulator to boot: ${detail}`);
|
|
253
|
+
}
|
|
124
254
|
async function handleEmulatorSetup(ADB_PATH, EMULATOR_PATH, androidConfig) {
|
|
125
255
|
progress.log("Setting up emulator...", "info");
|
|
126
|
-
|
|
127
|
-
|
|
256
|
+
let serial = await checkEmulator(ADB_PATH);
|
|
257
|
+
let pid = null;
|
|
258
|
+
if (!serial) {
|
|
128
259
|
progress.log("No emulator running, attempting to start one...", "info");
|
|
129
|
-
|
|
130
|
-
|
|
260
|
+
const knownSerials = listEmulatorSerials(ADB_PATH);
|
|
261
|
+
pid = await startEmulator(EMULATOR_PATH, androidConfig);
|
|
262
|
+
serial = await waitForNewEmulatorSerial(ADB_PATH, knownSerials);
|
|
263
|
+
if (!serial) {
|
|
264
|
+
killOrphanedEmulator(pid, androidConfig.emulatorName);
|
|
265
|
+
throw emulatorBootTimeoutError(`emulator "${androidConfig.emulatorName}" never appeared in adb devices`);
|
|
266
|
+
}
|
|
131
267
|
} else {
|
|
132
|
-
progress.log(
|
|
268
|
+
progress.log(`Emulator ${serial} already online, confirming it's fully booted...`, "info");
|
|
269
|
+
}
|
|
270
|
+
// Wait for boot readiness whether the emulator was already online (it may
|
|
271
|
+
// still be finishing its boot animation) or was just launched above.
|
|
272
|
+
const booted = await waitForEmulatorBoot(ADB_PATH, serial);
|
|
273
|
+
if (!booted) {
|
|
274
|
+
if (pid) killOrphanedEmulator(pid, androidConfig.emulatorName);
|
|
275
|
+
throw emulatorBootTimeoutError(`emulator "${androidConfig.emulatorName}" (${serial}) never finished booting`);
|
|
133
276
|
}
|
|
277
|
+
progress.log("Emulator booted successfully", "success");
|
|
134
278
|
return {
|
|
135
279
|
type: "emulator",
|
|
136
280
|
name: androidConfig.emulatorName
|
|
@@ -286,6 +430,7 @@ function createBuildPhase(ctx) {
|
|
|
286
430
|
checkEmulator,
|
|
287
431
|
handleEmulatorSetup,
|
|
288
432
|
startEmulator,
|
|
433
|
+
waitForEmulatorBoot,
|
|
289
434
|
buildApp,
|
|
290
435
|
launchApp,
|
|
291
436
|
createAABConfig,
|