catalyst-core 1.1.0 → 1.2.0-canary.2
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/bin/catalyst.js +54 -6
- package/changelog.md +27 -1
- package/dist/errors/registry.js +306 -18
- package/dist/errors-index.json +273 -70
- package/dist/native/androidProject/app/build.gradle.kts +53 -79
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +3 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +32 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +5 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +25 -0
- package/dist/native/androidSetup.js +76 -170
- package/dist/native/androidToolchain.js +642 -0
- package/dist/native/appIcon.js +100 -0
- package/dist/native/buildAndroid/assets.js +29 -5
- package/dist/native/buildAndroid/build.js +110 -223
- package/dist/native/buildAndroid/config.js +57 -65
- package/dist/native/buildAndroid/devices.js +176 -0
- package/dist/native/buildAndroid/index.js +271 -46
- package/dist/native/buildAndroid/output.js +43 -0
- package/dist/native/buildAndroid/signing.js +456 -0
- package/dist/native/buildAppAndroid.js +27 -7
- package/dist/native/buildAppIos.js +139 -20
- package/dist/native/buildIos/assets.js +76 -4
- package/dist/native/buildIos/build.js +31 -181
- package/dist/native/buildIos/config.js +126 -11
- package/dist/native/buildIos/index.js +354 -34
- package/dist/native/buildIos/localhostCert.js +87 -0
- package/dist/native/buildIos/output.js +28 -0
- package/dist/native/buildIos/signing.js +596 -0
- package/dist/native/credentials.js +309 -0
- package/dist/native/exportCredentials.js +51 -0
- package/dist/native/iosToolchain.js +583 -0
- package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +1 -3
- package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +2 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/PrivacyInfo.xcprivacy +40 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +0 -2
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -4
- package/dist/native/localState.js +148 -0
- package/dist/native/nativeConfig.js +621 -0
- package/dist/native/releaseOutput.js +53 -0
- package/dist/native/renameAndroidProject.js +154 -369
- package/dist/native/setupEmulatorIos.js +58 -275
- package/dist/native/setupServer.js +520 -124
- package/dist/native/utils.js +418 -115
- package/dist/vite/resolveDevServerConfig.js +21 -0
- package/mcp_v2/knowledge-base.json +13 -11
- package/mcp_v2/test/config.test.ts +40 -0
- package/mcp_v2/tools/build.js +80 -104
- package/mcp_v2/tools/config.js +181 -174
- package/package.json +1 -1
- package/test/cli/nativeFlags.test.ts +97 -0
- package/test/native/androidDevices.test.ts +230 -0
- package/test/native/androidElfAlignment.test.ts +148 -0
- package/test/native/androidSigning.test.ts +646 -0
- package/test/native/androidToolchain.test.ts +766 -0
- package/test/native/appIcon.test.ts +124 -0
- package/test/native/credentials.test.ts +241 -0
- package/test/native/interaction.test.ts +179 -0
- package/test/native/iosConfigPhase.test.ts +611 -0
- package/test/native/iosSigning.test.ts +1060 -0
- package/test/native/iosToolchain.test.ts +821 -0
- package/test/native/localState.test.ts +80 -0
- package/test/native/nativeConfig.test.ts +602 -0
- package/test/native/setupServer.test.ts +529 -0
- package/test/native/utils.test.ts +102 -127
- package/test/vite/resolveDevServerConfig.test.js +25 -0
- package/dist/native/androidProject/keystore/Untitled +0 -0
- package/dist/native/androidProject/keystore/release-key.jks +0 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
{
|
|
155
155
|
"section": "build_system",
|
|
156
156
|
"title": "WEBVIEW_CONFIG Location and Schema",
|
|
157
|
-
"content": "Location: config/config.json. Schema: { port, android: {
|
|
157
|
+
"content": "Location: config/config.json. Schema (catalyst-core 1.3.0+): { appId, appName, url, allowPrivateHost, LOCAL_IP: 'localhost', port, android: { emulatorName, sdkPath, cachePattern, buildOptimisation }, ios: { developmentTeam, simulatorName, deviceUDID, exportMethod, cachePattern } }. appId is the Android application id and iOS bundle id and wins over android.packageName / ios.appBundleId; appName wins over the per-platform appName. url is the https origin a release build loads. Every android/ios key is optional; buildType 'release' is deprecated in favour of --release. cachePattern is comma-separated globs: *.css,*.js,*.png,*.svg,*.jpg",
|
|
158
158
|
"layer": "Config",
|
|
159
159
|
"source": "static",
|
|
160
160
|
"tags": ["config", "build"]
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
{
|
|
171
171
|
"section": "build_system",
|
|
172
172
|
"title": "iOS Build Pipeline",
|
|
173
|
-
"content": "npm run buildApp:ios generates ConfigConstants.swift and plugin metadata from WEBVIEW_CONFIG, syncs
|
|
173
|
+
"content": "npm run ios (catalyst buildApp:ios) checks full Xcode and offers the licence and first-launch steps, installs a simulator runtime, uses an existing iPhone simulator (ios.simulatorName, else the last pick, a booted one or the newest; 'Catalyst iPhone' is created after asking only when there is none), picks the target (simulator by default; a terminal run asks when an iPhone is connected; --device uses the iPhone; the pick is remembered in .catalyst/local.json), starts catalyst start when nothing answers and waits for it, generates ConfigConstants.swift and plugin metadata from WEBVIEW_CONFIG, syncs public/ios assets into the package-owned Xcode project, compiles with the Debug configuration, and installs and launches the .app. The simulator loads the dev server on localhost; an iPhone on the Mac's <LocalHostName>.local (else the LAN IP), with NSAllowsLocalNetworking in debug, and needs ios.developmentTeam plus an Apple account signed in to Xcode once (a free account works). config.json is never written. With --release (or the deprecated ios.buildType release) it archives and exports the signed store IPA instead. The generated project is located under the resolved catalyst-core package at dist/native/iosnativeWebView/iosnativeWebView.xcodeproj.",
|
|
174
174
|
"layer": "Build",
|
|
175
175
|
"source": "static",
|
|
176
176
|
"tags": ["ios", "build", "buildApp:ios", "debug", "xcode"],
|
|
@@ -181,17 +181,19 @@
|
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"section": "build_system",
|
|
184
|
-
"title": "Catalyst
|
|
185
|
-
"content": "
|
|
184
|
+
"title": "Catalyst Native Release Flow (1.3.0+)",
|
|
185
|
+
"content": "From catalyst-core 1.3.0 the store builds need no Xcode or Android Studio UI. (1) Deploy the web app over HTTPS and set WEBVIEW_CONFIG.url (or pass --url); localhost, private, .local and example hosts fail with NATIVE-001 unless allowPrivateHost is true. (2) Set WEBVIEW_CONFIG.appId to your own reverse-DNS id (com.example.* and template ids fail with NATIVE-003) and the version in package.json. The build number is the minutes since 2024-01-01 UTC, or CATALYST_BUILD_NUMBER (NATIVE-002 if invalid). (3) Android: npm run android:release. The first run in a terminal creates the upload key in credentials/android/ (never without a terminal, ANDROID-005), refuses if the app is already on Google Play, and writes build-output/android/<version>-<build>/<name>.aab plus upload_certificate.pem. (4) iOS: put signing files in credentials/ios/: AuthKey_<KEYID>.p8 (Admin-role App Store Connect API key) plus signing.properties with ascIssuerId for cloud signing (needs ios.developmentTeam), or one .p12 and .mobileprovision plus p12Password for local signing. npm run ios:release archives (Release, generic/platform=iOS), exports with ios.exportMethod (default app-store-connect) and writes build-output/ios/<version>-<build>/<name>.ipa and <name>-dSYMs.zip, with ITSAppUsesNonExemptEncryption=false. (5) Back up and CI: catalyst credentials:export android|ios prints the folder as one value for the CATALYST_ANDROID_CREDENTIALS / CATALYST_IOS_CREDENTIALS secret (48 KB limit). (6) Upload is manual: Play Console internal testing; Apple's Transporter app for TestFlight. A second release with the same version and build number (two in one minute) fails with NATIVE-011; an app id that is not a valid Android package name or iOS bundle id fails with NATIVE-010. A shipped WEBVIEW_CONFIG.ios.version object still sets the iOS version and build number (deprecated; CATALYST_BUILD_NUMBER wins). Errors: IOS-010 signing not set up, IOS-011 API key incomplete, IOS-012 p12/profile inputs, IOS-013 export method. There is no --build-number, no --no-sign and no WEBVIEW_CONFIG.release block.",
|
|
186
186
|
"layer": "Build",
|
|
187
187
|
"source": "static",
|
|
188
188
|
"tags": [
|
|
189
189
|
"ios",
|
|
190
|
+
"android",
|
|
190
191
|
"release",
|
|
191
192
|
"production",
|
|
192
|
-
"
|
|
193
|
-
"0.3.x",
|
|
193
|
+
"1.3.0",
|
|
194
194
|
"buildApp:ios",
|
|
195
|
+
"buildApp:android",
|
|
196
|
+
"credentials",
|
|
195
197
|
"xcode",
|
|
196
198
|
"archive",
|
|
197
199
|
"app-store",
|
|
@@ -205,7 +207,7 @@
|
|
|
205
207
|
{
|
|
206
208
|
"section": "build_system",
|
|
207
209
|
"title": "Generated iOS Project Signing and Persistence",
|
|
208
|
-
"content": "The generated Xcode project is package-owned state
|
|
210
|
+
"content": "The generated Xcode project is package-owned state, regenerated by every build and package install, so keep release settings in config/config.json and signing files in credentials/ios/. Shared.xcconfig is written per build: CODE_SIGN_STYLE Automatic for ios.developmentTeam (cloud signing with the App Store Connect API key from credentials/ios/, else the Apple account in Xcode), or Manual with the profile when credentials/ios/ holds a .p12 and .mobileprovision (imported into a temporary keychain and removed after the build) or ios.provisioningProfile is set. The project carries no team or profile of its own. The aps-environment entitlement is only written when notifications.enabled is true (development for automatic signing, which export turns into production; production for a manually signed release). The app bundles PrivacyInfo.xcprivacy and a localhost.p12 generated per build for the in-app HTTPS server.",
|
|
209
211
|
"layer": "Build",
|
|
210
212
|
"source": "static",
|
|
211
213
|
"tags": [
|
|
@@ -226,7 +228,7 @@
|
|
|
226
228
|
{
|
|
227
229
|
"section": "build_system",
|
|
228
230
|
"title": "Legacy 0.2.x Build NPM Scripts",
|
|
229
|
-
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild \u2014 dev web build. devServe \u2014 dev server. buildApp \u2014 builds native apps. buildApp:android \u2014 Android native app build. buildApp:ios \u2014 iOS
|
|
231
|
+
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild \u2014 dev web build. devServe \u2014 dev server. buildApp \u2014 builds native apps. buildApp:android \u2014 Android native app build. buildApp:ios \u2014 iOS Debug build and run (add -- --release --build-number <n> for the signed store IPA). buildApp:android:release \u2014 Android release build. setupEmulator \u2014 sets up both emulators. setupEmulator:android \u2014 sets up Android AVD. setupEmulator:ios \u2014 sets up iOS simulator. There is no separate buildApp:ios:release script: pass --release to buildApp:ios. NOTE: build:android / build:android:release / build:ios do NOT exist in catalyst \u2014 common mistake. Missing any script = that npm run command fails.",
|
|
230
232
|
"layer": "Build",
|
|
231
233
|
"source": "static",
|
|
232
234
|
"tags": ["build", "scripts", "package.json", "native"]
|
|
@@ -466,7 +468,7 @@
|
|
|
466
468
|
{
|
|
467
469
|
"section": "webview_config",
|
|
468
470
|
"title": "Real WEBVIEW_CONFIG Example",
|
|
469
|
-
"content": "Full WEBVIEW_CONFIG example (
|
|
471
|
+
"content": "Full WEBVIEW_CONFIG example (catalyst-core 1.3.0+): { appId: 'com.yourcompany.app', appName: 'MyApp', url: 'https://www.yourdomain.com', LOCAL_IP: 'localhost', port: '3005', appInfo: 'android-5Feb2026-v2.1.0', cachePattern: '*.css', accessControl: { enabled: false, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] }, android: { cachePattern: '*.css,*.js,*.png', security: { mode: 'custom', allowBackup: true } }, ios: { developmentTeam: 'ABCDE12345', cachePattern: '*.css,*.js', accessControl: { enabled: true, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] } }, splashScreen: { backgroundColor: '#ffffff', duration: 2000, imageWidth: 400, imageHeight: 200, cornerRadius: 20 }, notifications: { enabled: true, color: \"#007AFF\" } }. Note: splashScreen is INSIDE WEBVIEW_CONFIG, not at top-level config.json. Note canary.19 change: iOS has its OWN accessControl block separate from top-level accessControl. LOCAL_IP stays localhost: debug builds reach the dev server through adb reverse (Android) or the Mac's .local name (iPhone).",
|
|
470
472
|
"layer": "Config",
|
|
471
473
|
"source": "extracted:config/config.json",
|
|
472
474
|
"tags": ["config", "example"]
|
|
@@ -482,7 +484,7 @@
|
|
|
482
484
|
{
|
|
483
485
|
"section": "config_structure",
|
|
484
486
|
"title": "WEBVIEW_CONFIG Required Fields",
|
|
485
|
-
"content": "WEBVIEW_CONFIG field reference (
|
|
487
|
+
"content": "WEBVIEW_CONFIG field reference (catalyst-core 1.3.0+, from nativeConfig.js):\n\nTOP-LEVEL (both platforms):\n- appId (string, required for release): Android application id and iOS bundle id, e.g. com.company.app. Wins over android.packageName / ios.appBundleId / ios.bundleIdentifier. Release refuses com.example.* (NATIVE-003)\n- appName (string, optional): name under the icon; wins over android.appName / ios.appName\n- url (string, required for release): https origin the release app loads, no path. --url overrides. Debug builds ignore it\n- allowPrivateHost (boolean, optional): lets a release load a private host\n- LOCAL_IP (string, optional): keep 'localhost'. Debug builds work out the dev server address; apps without url still use LOCAL_IP/port/useHttps as the release host\n- port (string, optional): dev server port when NODE_SERVER_PORT is not set\n- appInfo (string, required): build identifier e.g. \"android-5Feb2026-v2.1.0\". Missing = iOS build breaks\n- cachePattern (string, optional): comma-separated globs e.g. \"*.css\"\n- accessControl.enabled / allowedUrls: URL allowlist; debug builds add the dev server origin\n\nANDROID (WEBVIEW_CONFIG.android, all optional):\n- sdkPath: else ANDROID_HOME, ANDROID_SDK_ROOT, default location; installed when missing\n- emulatorName: else the last used or catalyst-api36, created when missing\n- buildType: deprecated; 'release' builds a release with a warning\n- outputPath: release folder instead of build-output/android\n- buildOptimisation, cachePattern\n\nIOS (WEBVIEW_CONFIG.ios, all optional):\n- developmentTeam: Apple team ID, needed for an iPhone and for cloud or Xcode-account signing (IOS-010)\n- simulatorName: simulator to use when it exists; else the last pick, a booted iPhone simulator or the newest one. 'Catalyst iPhone' is created only when there is no iPhone simulator\n- deviceUDID: which iPhone --device uses without a terminal\n- provisioningProfile: installed profile name for manual signing\n- exportMethod: app-store-connect (default), release-testing, enterprise or debugging\n- buildType: deprecated; 'release' builds the store IPA with a warning\n\nVersion comes from package.json; signing files live in credentials/android/ and credentials/ios/, never in config.json.",
|
|
486
488
|
"layer": "Config",
|
|
487
489
|
"source": "static",
|
|
488
490
|
"tags": ["config", "WEBVIEW_CONFIG", "android", "ios", "required", "fields"]
|
|
@@ -1281,7 +1283,7 @@
|
|
|
1281
1283
|
{
|
|
1282
1284
|
"section": "build_system",
|
|
1283
1285
|
"title": "Catalyst 0.3.x CLI Commands",
|
|
1284
|
-
"content": "Current web commands are catalyst start, catalyst build, catalyst serve, and catalyst serve:inspect. devBuild and devServe are legacy 0.2.x commands and are not accepted by the 0.3.x CLI. Native commands
|
|
1286
|
+
"content": "Current web commands are catalyst start, catalyst build, catalyst serve, and catalyst serve:inspect. devBuild and devServe are legacy 0.2.x commands and are not accepted by the 0.3.x CLI. Native commands are buildApp:android and buildApp:ios (template scripts android, ios, android:release, ios:release; flags --release, --device, --url, --yes, --accept-licenses; nothing is prompted without a TTY or with CI set, NATIVE-005 names the missing flag), credentials:export <android|ios>, and the install-only setupEmulator:android and setupEmulator:ios. catalyst-core 1.0.2 OUTPUT: every command prints a short header, one line per step with timing, and one diagnostic block on failure. Flags: --verbose shows every step and, on failure, the full tool output (bundler, Gradle); --debug also prints stacks and the environment. Child processes inherit the mode through CATALYST_OUTPUT_MODE=verbose|debug. Failures carry registry codes from the error catalog (for example BUNDLE-000, ANDROID-000, IOS-000, CCA-001); use the explain_error tool for any code. catalyst serve (1.0.2+) exits when there is no build in BUILD_OUTPUT_PATH and refuses a stale build with PREFLIGHT-022 when PUBLIC_STATIC_ASSET_URL, PUBLIC_STATIC_ASSET_PATH or a CLIENT_ENV_VARIABLES value in config/config.json changed after catalyst build (the build records them in <BUILD_OUTPUT_PATH>/.catalyst-build.json; a build from an older version has no record, so serve warns and skips the check until you rebuild). Fix: run catalyst build again, then catalyst serve. catalyst serve passes the app's own stdout/stderr through unchanged, so JSON log shippers keep working. Scripts that parsed the old CLI text output must be updated.",
|
|
1285
1287
|
"layer": "Build",
|
|
1286
1288
|
"source": "static",
|
|
1287
1289
|
"tags": [
|
|
@@ -109,3 +109,43 @@ describe("check_config: splash screen", () => {
|
|
|
109
109
|
expect(findings(r).some((f) => f.field === "WEBVIEW_CONFIG.splashScreen.minDuration")).toBe(false)
|
|
110
110
|
})
|
|
111
111
|
})
|
|
112
|
+
|
|
113
|
+
describe("check_config: native port and cache patterns", () => {
|
|
114
|
+
const fieldsOf = (list: Array<{ field: string }>) => list.map((f) => f.field)
|
|
115
|
+
|
|
116
|
+
it("accepts WEBVIEW_CONFIG.port as the template's string or a number", () => {
|
|
117
|
+
for (const port of ["3005", 3005]) {
|
|
118
|
+
const r = handle_check_config({
|
|
119
|
+
project_path: makeSplashProject({ NODE_SERVER_PORT: 3005, WEBVIEW_CONFIG: { port } }),
|
|
120
|
+
})
|
|
121
|
+
expect(fieldsOf(r.issues)).not.toContain("WEBVIEW_CONFIG.port")
|
|
122
|
+
expect(fieldsOf(r.warnings)).not.toContain("WEBVIEW_CONFIG.port")
|
|
123
|
+
expect(fieldsOf(r.passed)).toContain("WEBVIEW_CONFIG.port")
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("still rejects a port that is not a port", () => {
|
|
128
|
+
for (const port of ["abc", 0, 70000, true]) {
|
|
129
|
+
const r = handle_check_config({ project_path: makeSplashProject({ WEBVIEW_CONFIG: { port } }) })
|
|
130
|
+
expect(fieldsOf(r.issues)).toContain("WEBVIEW_CONFIG.port")
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("reads ios.cachePattern as an array, and warns about a comma-separated string", () => {
|
|
135
|
+
const ok = handle_check_config({
|
|
136
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { ios: { cachePattern: ["*.css", "*.js"] } } }),
|
|
137
|
+
})
|
|
138
|
+
expect(fieldsOf(ok.passed)).toContain("WEBVIEW_CONFIG.ios.cachePattern")
|
|
139
|
+
expect(fieldsOf(ok.warnings)).not.toContain("WEBVIEW_CONFIG.ios.cachePattern")
|
|
140
|
+
|
|
141
|
+
const commas = handle_check_config({
|
|
142
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { ios: { cachePattern: "*.css,*.js" } } }),
|
|
143
|
+
})
|
|
144
|
+
expect(fieldsOf(commas.warnings)).toContain("WEBVIEW_CONFIG.ios.cachePattern")
|
|
145
|
+
|
|
146
|
+
const wrong = handle_check_config({
|
|
147
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { ios: { cachePattern: 5 } } }),
|
|
148
|
+
})
|
|
149
|
+
expect(fieldsOf(wrong.warnings)).toContain("WEBVIEW_CONFIG.ios.cachePattern")
|
|
150
|
+
})
|
|
151
|
+
})
|
package/mcp_v2/tools/build.js
CHANGED
|
@@ -29,7 +29,7 @@ const MASTER_FLOWS = {
|
|
|
29
29
|
step: 2,
|
|
30
30
|
cmd: null,
|
|
31
31
|
label: "Browser / WebView loads",
|
|
32
|
-
detail: "Web: browser hits localhost. Universal:
|
|
32
|
+
detail: "Web: browser hits localhost. Universal debug builds load the same dev server: Android over adb reverse on localhost, the iOS simulator on localhost, an iPhone on <mac>.local. Nothing is written to config.json.",
|
|
33
33
|
},
|
|
34
34
|
],
|
|
35
35
|
|
|
@@ -87,27 +87,27 @@ const MASTER_FLOWS = {
|
|
|
87
87
|
android_debug: [
|
|
88
88
|
{
|
|
89
89
|
step: 1,
|
|
90
|
-
cmd:
|
|
91
|
-
label: "
|
|
92
|
-
detail:
|
|
90
|
+
cmd: "npm run android",
|
|
91
|
+
label: "Android debug build",
|
|
92
|
+
detail: "catalyst buildApp:android. Checks JDK 17 to 23 and the Android SDK (sdkPath, ANDROID_HOME, ANDROID_SDK_ROOT, default location) and installs what is missing after asking (--yes / --accept-licenses without a terminal). Since 1.0.2 Gradle runs behind one progress line; on failure --verbose or --debug add the ANDROID-000 report.",
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
step: 2,
|
|
96
|
-
cmd:
|
|
97
|
-
label: "
|
|
98
|
-
detail: "
|
|
96
|
+
cmd: null,
|
|
97
|
+
label: "Target",
|
|
98
|
+
detail: "The emulator (android.emulatorName, else the last one used, else catalyst-api36, created when missing). With a phone connected, a terminal run asks; --device uses the phone (ANDROID_SERIAL picks one without a terminal). The pick is remembered in .catalyst/local.json.",
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
step: 3,
|
|
102
|
-
cmd:
|
|
103
|
-
label: "
|
|
104
|
-
detail: "
|
|
102
|
+
cmd: null,
|
|
103
|
+
label: "Dev server",
|
|
104
|
+
detail: "Starts `catalyst start` when nothing answers on NODE_SERVER_PORT and waits until it answers; a running server is reused. No separate npm start needed.",
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
step: 4,
|
|
108
108
|
cmd: null,
|
|
109
|
-
label: "
|
|
110
|
-
detail: "
|
|
109
|
+
label: "Install, reverse, launch",
|
|
110
|
+
detail: "Installs <appId>.debug, runs adb reverse for NODE_SERVER_PORT, the HMR port and every localhost:<port> in config.json, then launches. The app loads http://localhost:<port>; WEBVIEW_CONFIG.url is ignored in debug.",
|
|
111
111
|
},
|
|
112
112
|
],
|
|
113
113
|
|
|
@@ -116,52 +116,46 @@ const MASTER_FLOWS = {
|
|
|
116
116
|
step: 1,
|
|
117
117
|
cmd: null,
|
|
118
118
|
label: "Pre-check: config",
|
|
119
|
-
detail:
|
|
119
|
+
detail: "WEBVIEW_CONFIG.url (or --url) is the production origin; a localhost/LAN/.local/example host fails with NATIVE-001 unless allowPrivateHost is true. appId must not be com.example.* or a template id (NATIVE-003). Version from package.json; build number = minutes since 2024-01-01 UTC, or CATALYST_BUILD_NUMBER.",
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
step: 2,
|
|
123
123
|
cmd: null,
|
|
124
|
-
label: "Pre-check:
|
|
125
|
-
detail: "
|
|
124
|
+
label: "Pre-check: upload key",
|
|
125
|
+
detail: "credentials/android/upload.jks + keystore.properties. The first release in a terminal asks to create it (never without a terminal, even with --yes: ANDROID-005) and refuses if the app is already on Google Play. Back it up with `catalyst credentials:export android`; CI reads the same value from CATALYST_ANDROID_CREDENTIALS.",
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
step: 3,
|
|
129
|
-
cmd: "
|
|
130
|
-
label: "Build web assets",
|
|
131
|
-
detail: "Production web build. Assets are consumed by the Android release build.",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
step: 4,
|
|
135
|
-
cmd: "npm run buildApp:android",
|
|
129
|
+
cmd: "npm run android:release",
|
|
136
130
|
label: "Android release build",
|
|
137
|
-
detail: "
|
|
131
|
+
detail: "catalyst buildApp:android --release. Signs the AAB with the upload key (passwords via environment, never argv). targetSdk 36, 16 KB aligned native libraries. Run `catalyst build` first only with android.buildOptimisation.",
|
|
138
132
|
},
|
|
139
133
|
{
|
|
140
|
-
step:
|
|
134
|
+
step: 4,
|
|
141
135
|
cmd: null,
|
|
142
136
|
label: "AAB output",
|
|
143
|
-
detail: "
|
|
137
|
+
detail: "build-output/android/<version>-<build>/<name>.aab and upload_certificate.pem. Upload to Play Console (Testing > Internal testing) by hand; Catalyst does not publish.",
|
|
144
138
|
},
|
|
145
139
|
],
|
|
146
140
|
|
|
147
141
|
ios_debug: [
|
|
148
142
|
{
|
|
149
143
|
step: 1,
|
|
150
|
-
cmd:
|
|
151
|
-
label: "
|
|
152
|
-
detail:
|
|
144
|
+
cmd: "npm run ios",
|
|
145
|
+
label: "iOS debug build",
|
|
146
|
+
detail: 'catalyst buildApp:ios. Checks full Xcode, offers the Xcode licence and first-launch steps, installs a simulator runtime, and uses an existing iPhone simulator (ios.simulatorName, else the last pick, a booted one or the newest), creating "Catalyst iPhone" after asking only when there is none.',
|
|
153
147
|
},
|
|
154
148
|
{
|
|
155
149
|
step: 2,
|
|
156
|
-
cmd:
|
|
157
|
-
label: "
|
|
158
|
-
detail: "
|
|
150
|
+
cmd: null,
|
|
151
|
+
label: "Target",
|
|
152
|
+
detail: "Simulator by default; with an iPhone connected a terminal run asks; --device uses the iPhone (ios.deviceUDID picks one without a terminal). The pick is remembered in .catalyst/local.json. An iPhone needs ios.developmentTeam and an Apple account signed in to Xcode once (a free account works).",
|
|
159
153
|
},
|
|
160
154
|
{
|
|
161
155
|
step: 3,
|
|
162
156
|
cmd: null,
|
|
163
|
-
label: "
|
|
164
|
-
detail: "
|
|
157
|
+
label: "Dev server",
|
|
158
|
+
detail: "Starts `catalyst start` when nothing answers and waits. Simulator: localhost. iPhone: <LocalHostName>.local (else the LAN IP), with the server bound to the network; a server already running for localhost only fails with NATIVE-006. Debug builds add NSAllowsLocalNetworking and ignore WEBVIEW_CONFIG.url.",
|
|
165
159
|
},
|
|
166
160
|
{
|
|
167
161
|
step: 4,
|
|
@@ -175,44 +169,26 @@ const MASTER_FLOWS = {
|
|
|
175
169
|
{
|
|
176
170
|
step: 1,
|
|
177
171
|
cmd: null,
|
|
178
|
-
label: "
|
|
179
|
-
detail: "
|
|
172
|
+
label: "Pre-check: config",
|
|
173
|
+
detail: "WEBVIEW_CONFIG.url (or --url), appId (not com.example.*), ios.developmentTeam for cloud or Xcode-account signing (else IOS-010). Version from package.json, build number from the clock or CATALYST_BUILD_NUMBER.",
|
|
180
174
|
},
|
|
181
175
|
{
|
|
182
176
|
step: 2,
|
|
183
177
|
cmd: null,
|
|
184
|
-
label: "Pre-check:
|
|
185
|
-
detail: "
|
|
178
|
+
label: "Pre-check: signing",
|
|
179
|
+
detail: "credentials/ios/ (CI: CATALYST_IOS_CREDENTIALS from `catalyst credentials:export ios`). Cloud signing: AuthKey_<KEYID>.p8 (Admin-role App Store Connect API key) + signing.properties with ascIssuerId. Local signing: one .p12 + one .mobileprovision + p12Password in signing.properties (temporary keychain, removed after the build). Bad key inputs: IOS-011; bad p12/profile: IOS-012.",
|
|
186
180
|
},
|
|
187
181
|
{
|
|
188
182
|
step: 3,
|
|
189
|
-
cmd: "npm run
|
|
190
|
-
label: "
|
|
191
|
-
detail: "
|
|
183
|
+
cmd: "npm run ios:release",
|
|
184
|
+
label: "iOS release build",
|
|
185
|
+
detail: "catalyst buildApp:ios --release. No Xcode UI: xcodebuild archive (Release, generic/platform=iOS), then -exportArchive with ios.exportMethod (default app-store-connect). Writes build-output/ios/<version>-<build>/<name>.ipa and <name>-dSYMs.zip. ITSAppUsesNonExemptEncryption=false.",
|
|
192
186
|
},
|
|
193
187
|
{
|
|
194
188
|
step: 4,
|
|
195
189
|
cmd: null,
|
|
196
|
-
label: "
|
|
197
|
-
detail: "
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
step: 5,
|
|
201
|
-
cmd: null,
|
|
202
|
-
label: "Configure signing and archive",
|
|
203
|
-
detail: "Select the correct Apple Developer team, distribution signing, capabilities, entitlements, bundle ID, version, and build. Choose a generic iOS device destination, then Product → Archive.",
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
step: 6,
|
|
207
|
-
cmd: null,
|
|
208
|
-
label: "Validate and upload",
|
|
209
|
-
detail: "In Xcode Organizer, run Validate App, then Distribute App → TestFlight & App Store and upload the build.",
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
step: 7,
|
|
213
|
-
cmd: null,
|
|
214
|
-
label: "Complete the release",
|
|
215
|
-
detail: "Wait for App Store Connect processing, test through TestFlight, complete store metadata and privacy disclosures, select the processed build, and submit it for App Review.",
|
|
190
|
+
label: "Upload and release",
|
|
191
|
+
detail: "Upload the IPA with Transporter or `xcrun altool --upload-app` (Catalyst does not upload), test through TestFlight, complete App Store Connect metadata and privacy details, and submit for App Review.",
|
|
216
192
|
},
|
|
217
193
|
],
|
|
218
194
|
}
|
|
@@ -296,16 +272,17 @@ const MASTER_DIAGRAMS = {
|
|
|
296
272
|
"→ [web path] hook falls back to browser API (navigator.vibrate, <input type=file>, etc.)",
|
|
297
273
|
],
|
|
298
274
|
configurable_today: [
|
|
299
|
-
"
|
|
300
|
-
"
|
|
301
|
-
"
|
|
275
|
+
"appId: Android application id and iOS bundle id (release refuses com.example.*)",
|
|
276
|
+
"appName: name under the app icon on both platforms",
|
|
277
|
+
"url: production origin release builds load (--url overrides); debug builds use the dev server",
|
|
278
|
+
"allowPrivateHost: lets a release load a private host (internal apps)",
|
|
302
279
|
"accessControl.allowedUrls — whitelist for outbound requests from WebView",
|
|
303
280
|
"googleSignIn.enabled / clientId / iosClientId",
|
|
304
281
|
"splashScreen — duration, minDuration, backgroundColor, image size; optional animated public/splashscreen.html (1.0.2+)",
|
|
305
282
|
"notifications.enabled — push notification permission prompt",
|
|
306
283
|
"android.buildOptimisation — enables build caching",
|
|
307
|
-
"
|
|
308
|
-
"
|
|
284
|
+
"version: package.json version; build number from the clock or CATALYST_BUILD_NUMBER",
|
|
285
|
+
"signing: credentials/android/ and credentials/ios/ (CI: CATALYST_ANDROID_CREDENTIALS / CATALYST_IOS_CREDENTIALS)",
|
|
309
286
|
],
|
|
310
287
|
},
|
|
311
288
|
|
|
@@ -383,7 +360,7 @@ const MASTER_DIAGRAMS = {
|
|
|
383
360
|
"",
|
|
384
361
|
"[App build] In app project root:",
|
|
385
362
|
"→ catalyst build (Vite client + SSR output)",
|
|
386
|
-
"→ npm run buildApp:android / buildApp:ios",
|
|
363
|
+
"→ npm run android / npm run ios (catalyst buildApp:android / buildApp:ios)",
|
|
387
364
|
"→ native build script reads WEBVIEW_CONFIG from config/config.json",
|
|
388
365
|
"→ copies build/ assets into native project",
|
|
389
366
|
"→ compiles native project (Gradle / xcodebuild)",
|
|
@@ -490,7 +467,7 @@ const LEGACY_BUILD_PIPELINE = {
|
|
|
490
467
|
flow: [
|
|
491
468
|
"catalyst build (webpack)",
|
|
492
469
|
"→ catalyst serve for production-style validation",
|
|
493
|
-
"→ npm run
|
|
470
|
+
"→ npm run android or npm run ios",
|
|
494
471
|
],
|
|
495
472
|
}
|
|
496
473
|
|
|
@@ -510,7 +487,6 @@ function getProjectContext(root) {
|
|
|
510
487
|
hasIos: !!wv.ios,
|
|
511
488
|
androidBuildType: (wv.android?.buildType || "debug").toLowerCase(),
|
|
512
489
|
iosBuildType: (wv.ios?.buildType || "debug").toLowerCase(),
|
|
513
|
-
hasKeystore: !!wv.android?.keystoreConfig,
|
|
514
490
|
hasGoogleSignIn: !!wv.googleSignIn?.enabled,
|
|
515
491
|
hasNotifications: !!wv.notifications?.enabled,
|
|
516
492
|
hasEcosystem: fileExists("ecosystem.config.js"),
|
|
@@ -520,6 +496,27 @@ function getProjectContext(root) {
|
|
|
520
496
|
}
|
|
521
497
|
}
|
|
522
498
|
|
|
499
|
+
/** Warnings shared by both platforms for appId, url and LOCAL_IP (catalyst-core 1.3.0+). */
|
|
500
|
+
function appIdentityWarnings(ctx, isRelease, warnings) {
|
|
501
|
+
const wv = ctx.wv
|
|
502
|
+
const appId = wv.appId || wv.android?.packageName || wv.ios?.appBundleId || wv.ios?.bundleIdentifier
|
|
503
|
+
if (isRelease && (!appId || /^com\.example\./i.test(appId))) {
|
|
504
|
+
warnings.push(
|
|
505
|
+
"WEBVIEW_CONFIG.appId is missing or a com.example placeholder: the release fails with NATIVE-003. Set your own reverse-DNS id."
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
if (isRelease && !wv.url && !wv.LOCAL_IP) {
|
|
509
|
+
warnings.push(
|
|
510
|
+
"WEBVIEW_CONFIG.url not set: set the production origin (e.g. https://shop.acme.com) or pass --url."
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
if (wv.LOCAL_IP && /^\d{1,3}(\.\d{1,3}){3}$/.test(wv.LOCAL_IP) && wv.LOCAL_IP !== "127.0.0.1") {
|
|
514
|
+
warnings.push(
|
|
515
|
+
`WEBVIEW_CONFIG.LOCAL_IP is ${wv.LOCAL_IP}. Keep it "localhost"; debug builds work out the dev server address, and the first 1.3.0 build resets saved LAN addresses.`
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
523
520
|
function getKnownErrors(keywords) {
|
|
524
521
|
if (!keywords || !keywords.length) return []
|
|
525
522
|
// Score known_errors rows by keyword overlap
|
|
@@ -604,25 +601,13 @@ function handle_get_build_flow({ platform, mode, symptom } = {}) {
|
|
|
604
601
|
|
|
605
602
|
const isRelease = m === "release" || ctx.androidBuildType === "release"
|
|
606
603
|
flow_key = isRelease ? "android_release" : "android_debug"
|
|
607
|
-
|
|
608
|
-
if (
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
)
|
|
613
|
-
}
|
|
614
|
-
} else {
|
|
615
|
-
if (!ctx.wv.android?.sdkPath) {
|
|
616
|
-
warnings.push(
|
|
617
|
-
"WEBVIEW_CONFIG.android.sdkPath not set — ADB and emulator validation will fail."
|
|
618
|
-
)
|
|
619
|
-
}
|
|
620
|
-
if (!ctx.wv.android?.emulatorName) {
|
|
621
|
-
warnings.push("WEBVIEW_CONFIG.android.emulatorName not set — emulator launch step will fail.")
|
|
622
|
-
}
|
|
604
|
+
appIdentityWarnings(ctx, isRelease, warnings)
|
|
605
|
+
if (ctx.androidBuildType === "release") {
|
|
606
|
+
warnings.push(
|
|
607
|
+
'android.buildType "release" is deprecated: use `npm run android:release` and set buildType to "debug" or remove it.'
|
|
608
|
+
)
|
|
623
609
|
}
|
|
624
610
|
|
|
625
|
-
notes.push(`buildType detected: ${ctx.androidBuildType}`)
|
|
626
611
|
if (ctx.wv.android?.sdkPath) notes.push(`sdkPath: ${ctx.wv.android.sdkPath}`)
|
|
627
612
|
if (ctx.wv.android?.emulatorName) notes.push(`emulatorName: ${ctx.wv.android.emulatorName}`)
|
|
628
613
|
if (ctx.wv.android?.buildOptimisation) notes.push("buildOptimisation: enabled")
|
|
@@ -636,41 +621,32 @@ function handle_get_build_flow({ platform, mode, symptom } = {}) {
|
|
|
636
621
|
)
|
|
637
622
|
}
|
|
638
623
|
|
|
639
|
-
const isRelease = m === "release"
|
|
624
|
+
const isRelease = m === "release" || ctx.iosBuildType === "release"
|
|
640
625
|
flow_key = isRelease ? "ios_release" : "ios_debug"
|
|
641
|
-
|
|
642
|
-
if (
|
|
626
|
+
appIdentityWarnings(ctx, isRelease, warnings)
|
|
627
|
+
if (ctx.iosBuildType === "release") {
|
|
643
628
|
warnings.push(
|
|
644
|
-
'
|
|
629
|
+
'ios.buildType "release" is deprecated: use `npm run ios:release` and set buildType to "debug" or remove it.'
|
|
645
630
|
)
|
|
646
631
|
}
|
|
647
|
-
if (!ctx.wv.ios?.
|
|
648
|
-
warnings.push("WEBVIEW_CONFIG.ios.simulatorName not set — simulator launch step will fail.")
|
|
649
|
-
}
|
|
650
|
-
if (ctx.hasGoogleSignIn && !ctx.wv.ios?.simulatorName) {
|
|
632
|
+
if (isRelease && !ctx.wv.ios?.developmentTeam && !ctx.wv.ios?.provisioningProfile) {
|
|
651
633
|
warnings.push(
|
|
652
|
-
"
|
|
634
|
+
"ios.developmentTeam not set. A release fails with IOS-010 unless credentials/ios/ holds a .p12 and its provisioning profile."
|
|
653
635
|
)
|
|
654
636
|
}
|
|
655
|
-
if (
|
|
637
|
+
if (ctx.hasGoogleSignIn && !ctx.wv.googleSignIn?.iosClientId) {
|
|
656
638
|
warnings.push(
|
|
657
|
-
"
|
|
639
|
+
"googleSignIn.enabled=true but iosClientId may be missing. Check WEBVIEW_CONFIG.googleSignIn.iosClientId."
|
|
658
640
|
)
|
|
659
641
|
}
|
|
660
642
|
if (isRelease && ctx.hasNotifications) {
|
|
661
643
|
warnings.push(
|
|
662
|
-
"Push notifications are enabled
|
|
644
|
+
"Push notifications are enabled: review the generated aps-environment entitlement and the release provisioning profile."
|
|
663
645
|
)
|
|
664
646
|
}
|
|
665
647
|
|
|
666
|
-
notes.push(`buildType detected: ${ctx.wv.ios?.buildType || "not set (defaults to debug)"}`)
|
|
667
|
-
if (isRelease) {
|
|
668
|
-
notes.push(
|
|
669
|
-
"ios.buildType does not select the App Store build configuration; buildApp:ios still performs a Debug build. Archive the generated project with the scheme's Release action."
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
|
-
if (ctx.wv.ios?.appBundleId) notes.push(`appBundleId: ${ctx.wv.ios.appBundleId}`)
|
|
673
648
|
if (ctx.wv.ios?.simulatorName) notes.push(`simulatorName: ${ctx.wv.ios.simulatorName}`)
|
|
649
|
+
if (ctx.wv.ios?.developmentTeam) notes.push(`developmentTeam: ${ctx.wv.ios.developmentTeam}`)
|
|
674
650
|
if (ctx.hasGoogleSignIn) notes.push("googleSignIn: enabled")
|
|
675
651
|
} else {
|
|
676
652
|
return { error: `Unknown platform "${platform}". Use: web | android | ios` }
|