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
package/mcp_v2/tools/config.js
CHANGED
|
@@ -111,22 +111,92 @@ function handle_check_config({ project_path, platform = "both" } = {}) {
|
|
|
111
111
|
|
|
112
112
|
const wc = config.WEBVIEW_CONFIG
|
|
113
113
|
|
|
114
|
-
// ──
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
// ── App identity and production URL (catalyst-core 1.3.0+) ─────────────────
|
|
115
|
+
// appId and appName apply to both platforms and win over the per-platform
|
|
116
|
+
// keys. url is the origin a release build loads; debug builds ignore it.
|
|
117
|
+
const PLACEHOLDER_APP_IDS = [
|
|
118
|
+
"io.yourname.androidproject",
|
|
119
|
+
"com.catalyst.framework.app",
|
|
120
|
+
"com.debug.webview",
|
|
121
|
+
]
|
|
122
|
+
const legacyAppId = wc.android?.packageName || wc.ios?.appBundleId || wc.ios?.bundleIdentifier
|
|
123
|
+
const appId = typeof wc.appId === "string" && wc.appId.trim() ? wc.appId.trim() : null
|
|
124
|
+
if (appId) {
|
|
125
|
+
if (
|
|
126
|
+
appId.toLowerCase().startsWith("com.example.") ||
|
|
127
|
+
PLACEHOLDER_APP_IDS.includes(appId.toLowerCase())
|
|
128
|
+
) {
|
|
129
|
+
warnings.push({
|
|
130
|
+
field: "WEBVIEW_CONFIG.appId",
|
|
131
|
+
message: `appId "${appId}" is a placeholder. Debug builds work, but a release build refuses it (NATIVE-003). Set your own reverse-DNS id, e.g. "com.yourcompany.app"; it cannot change after the first store upload.`,
|
|
132
|
+
})
|
|
133
|
+
} else {
|
|
134
|
+
passed.push({ field: "WEBVIEW_CONFIG.appId", value: appId })
|
|
135
|
+
}
|
|
136
|
+
} else if (!legacyAppId) {
|
|
137
|
+
warnings.push({
|
|
138
|
+
field: "WEBVIEW_CONFIG.appId",
|
|
119
139
|
message:
|
|
120
|
-
'WEBVIEW_CONFIG.
|
|
140
|
+
'WEBVIEW_CONFIG.appId not set. It is the Android application id and the iOS bundle id (e.g. "com.yourcompany.app"); a release build refuses to ship without one (NATIVE-003).',
|
|
121
141
|
})
|
|
122
|
-
}
|
|
123
|
-
|
|
142
|
+
}
|
|
143
|
+
if (typeof wc.appName === "string" && wc.appName.trim()) {
|
|
144
|
+
passed.push({ field: "WEBVIEW_CONFIG.appName", value: wc.appName })
|
|
145
|
+
}
|
|
146
|
+
if (wc.url) {
|
|
147
|
+
let parsed = null
|
|
148
|
+
try {
|
|
149
|
+
parsed = new URL(String(wc.url))
|
|
150
|
+
} catch {
|
|
151
|
+
parsed = null
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
!parsed ||
|
|
155
|
+
!["http:", "https:"].includes(parsed.protocol) ||
|
|
156
|
+
(parsed.pathname && parsed.pathname !== "/")
|
|
157
|
+
) {
|
|
158
|
+
issues.push({
|
|
159
|
+
field: "WEBVIEW_CONFIG.url",
|
|
160
|
+
severity: "error",
|
|
161
|
+
message: `WEBVIEW_CONFIG.url "${wc.url}" must be an http(s) origin without a path, e.g. "https://shop.acme.com". A release build fails with NATIVE-004.`,
|
|
162
|
+
})
|
|
163
|
+
} else if (
|
|
164
|
+
/^(localhost|127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)|\.local$/i.test(
|
|
165
|
+
parsed.hostname
|
|
166
|
+
) &&
|
|
167
|
+
wc.allowPrivateHost !== true
|
|
168
|
+
) {
|
|
169
|
+
warnings.push({
|
|
170
|
+
field: "WEBVIEW_CONFIG.url",
|
|
171
|
+
message: `WEBVIEW_CONFIG.url "${wc.url}" is a local or private host. A release build refuses it (NATIVE-001) unless WEBVIEW_CONFIG.allowPrivateHost is true.`,
|
|
172
|
+
})
|
|
173
|
+
} else {
|
|
174
|
+
passed.push({ field: "WEBVIEW_CONFIG.url", value: wc.url })
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
warnings.push({
|
|
178
|
+
field: "WEBVIEW_CONFIG.url",
|
|
179
|
+
message:
|
|
180
|
+
'WEBVIEW_CONFIG.url not set. Release builds load this production origin (e.g. "https://shop.acme.com"); without it they fall back to LOCAL_IP/port/useHttps. Debug builds do not need it.',
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── WEBVIEW_CONFIG.LOCAL_IP ───────────────────────────────────────────────
|
|
185
|
+
// Debug builds reach the dev server on their own (adb reverse on Android,
|
|
186
|
+
// localhost on the simulator, <mac>.local on an iPhone), so LOCAL_IP stays
|
|
187
|
+
// localhost. A LAN address saved by an older setup is put back to
|
|
188
|
+
// localhost by the first 1.3.0 build.
|
|
189
|
+
if (
|
|
190
|
+
wc.LOCAL_IP &&
|
|
191
|
+
!["localhost", "127.0.0.1"].includes(wc.LOCAL_IP) &&
|
|
192
|
+
/^\d{1,3}(\.\d{1,3}){3}$/.test(wc.LOCAL_IP)
|
|
193
|
+
) {
|
|
194
|
+
warnings.push({
|
|
124
195
|
field: "WEBVIEW_CONFIG.LOCAL_IP",
|
|
125
|
-
|
|
126
|
-
message: `WEBVIEW_CONFIG.LOCAL_IP is set to "${wc.LOCAL_IP}" — this will not work. The native emulator runs in a separate network namespace and cannot resolve localhost. Use your machine LAN IP (run "ifconfig | grep inet" to find it).`,
|
|
196
|
+
message: `WEBVIEW_CONFIG.LOCAL_IP is "${wc.LOCAL_IP}". Keep it "localhost": debug builds work out the dev server address themselves, and the first build on catalyst-core 1.3.0 resets a private address to localhost. Put the production address in WEBVIEW_CONFIG.url.`,
|
|
127
197
|
})
|
|
128
198
|
} else {
|
|
129
|
-
passed.push({ field: "WEBVIEW_CONFIG.LOCAL_IP", value: wc.LOCAL_IP })
|
|
199
|
+
passed.push({ field: "WEBVIEW_CONFIG.LOCAL_IP", value: wc.LOCAL_IP || "localhost (default)" })
|
|
130
200
|
}
|
|
131
201
|
|
|
132
202
|
// ── WEBVIEW_CONFIG.appInfo ────────────────────────────────────────────────
|
|
@@ -149,14 +219,18 @@ function handle_check_config({ project_path, platform = "both" } = {}) {
|
|
|
149
219
|
message:
|
|
150
220
|
"WEBVIEW_CONFIG.port is required. Must match NODE_SERVER_PORT (e.g. 3000). The native WebView connects to this port.",
|
|
151
221
|
})
|
|
152
|
-
} else if (
|
|
222
|
+
} else if (
|
|
223
|
+
!(typeof wc.port === "number" || (typeof wc.port === "string" && /^\d+$/.test(wc.port.trim()))) ||
|
|
224
|
+
!(Number(wc.port) >= 1 && Number(wc.port) <= 65535)
|
|
225
|
+
) {
|
|
226
|
+
// The template ships "3005": both shells read it as text.
|
|
153
227
|
issues.push({
|
|
154
228
|
field: "WEBVIEW_CONFIG.port",
|
|
155
229
|
severity: "error",
|
|
156
|
-
message: `WEBVIEW_CONFIG.port must be a number, got ${
|
|
230
|
+
message: `WEBVIEW_CONFIG.port must be a port number from 1 to 65535 (a number or a string such as "3005"), got ${JSON.stringify(wc.port)}.`,
|
|
157
231
|
})
|
|
158
232
|
} else {
|
|
159
|
-
if (config.NODE_SERVER_PORT !== undefined && wc.port !== config.NODE_SERVER_PORT) {
|
|
233
|
+
if (config.NODE_SERVER_PORT !== undefined && Number(wc.port) !== Number(config.NODE_SERVER_PORT)) {
|
|
160
234
|
warnings.push({
|
|
161
235
|
field: "WEBVIEW_CONFIG.port",
|
|
162
236
|
message: `WEBVIEW_CONFIG.port (${wc.port}) does not match NODE_SERVER_PORT (${config.NODE_SERVER_PORT}). WebView may connect to wrong port.`,
|
|
@@ -215,178 +289,111 @@ function handle_check_config({ project_path, platform = "both" } = {}) {
|
|
|
215
289
|
}
|
|
216
290
|
|
|
217
291
|
// ── Android fields ─────────────────────────────────────────────────────────
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
292
|
+
// Everything here is optional: buildApp:android finds or installs the SDK,
|
|
293
|
+
// JDK and emulator itself, after asking (or with --yes / --accept-licenses).
|
|
294
|
+
if ((platform === "android" || platform === "both") && wc.android) {
|
|
295
|
+
const a = wc.android
|
|
296
|
+
if (a.sdkPath && !fs.existsSync(a.sdkPath.replace(/^~(?=$|\/)/, process.env.HOME || "~"))) {
|
|
297
|
+
warnings.push({
|
|
298
|
+
field: "WEBVIEW_CONFIG.android.sdkPath",
|
|
299
|
+
message: `android.sdkPath "${a.sdkPath}" does not exist on this machine. Remove it to let the build use ANDROID_HOME, ANDROID_SDK_ROOT or the default SDK location (and install the SDK there).`,
|
|
300
|
+
})
|
|
301
|
+
} else if (a.sdkPath) {
|
|
302
|
+
passed.push({ field: "WEBVIEW_CONFIG.android.sdkPath", value: a.sdkPath })
|
|
303
|
+
}
|
|
304
|
+
if (a.emulatorName)
|
|
305
|
+
passed.push({ field: "WEBVIEW_CONFIG.android.emulatorName", value: a.emulatorName })
|
|
306
|
+
if (!wc.appName && a.appName)
|
|
307
|
+
passed.push({ field: "WEBVIEW_CONFIG.android.appName", value: a.appName })
|
|
308
|
+
if (!wc.appId && a.packageName) {
|
|
309
|
+
passed.push({ field: "WEBVIEW_CONFIG.android.packageName", value: a.packageName })
|
|
310
|
+
}
|
|
311
|
+
if (String(a.buildType || "").toLowerCase() === "release") {
|
|
312
|
+
warnings.push({
|
|
313
|
+
field: "WEBVIEW_CONFIG.android.buildType",
|
|
314
|
+
message:
|
|
315
|
+
'android.buildType "release" is deprecated. Use `npm run android:release` (catalyst buildApp:android --release) and set buildType back to "debug" or remove it.',
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
const keystoreBlocks = [a.keystore, a.keystoreConfig].filter(Boolean)
|
|
319
|
+
if (keystoreBlocks.some((block) => block.storePassword || block.keyPassword)) {
|
|
320
|
+
warnings.push({
|
|
321
|
+
field: "WEBVIEW_CONFIG.android.keystoreConfig",
|
|
322
|
+
message:
|
|
323
|
+
"Keystore passwords in config.json are not used for signing. The upload key lives in credentials/android/ (created by the first release build in a terminal; CI uses the CATALYST_ANDROID_CREDENTIALS secret from `catalyst credentials:export android`). Delete the passwords.",
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
if (a.cachePattern !== undefined && typeof a.cachePattern !== "string") {
|
|
327
|
+
warnings.push({
|
|
328
|
+
field: "WEBVIEW_CONFIG.android.cachePattern",
|
|
329
|
+
message: `android.cachePattern should be a comma-separated glob string (e.g. "*.css,*.js,*.png"), got ${typeof a.cachePattern}.`,
|
|
330
|
+
})
|
|
331
|
+
} else if (a.cachePattern) {
|
|
332
|
+
passed.push({ field: "WEBVIEW_CONFIG.android.cachePattern", value: a.cachePattern })
|
|
333
|
+
}
|
|
334
|
+
if (a.security && typeof a.security.allowBackup !== "boolean") {
|
|
335
|
+
warnings.push({
|
|
336
|
+
field: "WEBVIEW_CONFIG.android.security.allowBackup",
|
|
337
|
+
message:
|
|
338
|
+
"android.security.allowBackup should be a boolean. Controls Google cloud backup of app data.",
|
|
224
339
|
})
|
|
225
|
-
} else {
|
|
226
|
-
const a = wc.android
|
|
227
|
-
// sdkPath and emulatorName are hard required — build exits immediately without them
|
|
228
|
-
if (!a.sdkPath) {
|
|
229
|
-
issues.push({
|
|
230
|
-
field: "WEBVIEW_CONFIG.android.sdkPath",
|
|
231
|
-
severity: "error",
|
|
232
|
-
message:
|
|
233
|
-
"android.sdkPath is required. Set to absolute path of Android SDK (e.g. /Users/you/Android/). Build exits immediately without it.",
|
|
234
|
-
})
|
|
235
|
-
} else if (!fs.existsSync(a.sdkPath)) {
|
|
236
|
-
issues.push({
|
|
237
|
-
field: "WEBVIEW_CONFIG.android.sdkPath",
|
|
238
|
-
severity: "error",
|
|
239
|
-
message: `android.sdkPath "${a.sdkPath}" does not exist on this machine. Android build will fail immediately.`,
|
|
240
|
-
})
|
|
241
|
-
} else {
|
|
242
|
-
passed.push({ field: "WEBVIEW_CONFIG.android.sdkPath", value: a.sdkPath })
|
|
243
|
-
}
|
|
244
|
-
if (!a.emulatorName) {
|
|
245
|
-
issues.push({
|
|
246
|
-
field: "WEBVIEW_CONFIG.android.emulatorName",
|
|
247
|
-
severity: "error",
|
|
248
|
-
message:
|
|
249
|
-
"android.emulatorName is required for debug builds. Set to AVD name (run: emulator -list-avds). Build fails if emulator not found.",
|
|
250
|
-
})
|
|
251
|
-
} else {
|
|
252
|
-
passed.push({ field: "WEBVIEW_CONFIG.android.emulatorName", value: a.emulatorName })
|
|
253
|
-
}
|
|
254
|
-
// appName and packageName — optional but strongly recommended
|
|
255
|
-
if (!a.appName) {
|
|
256
|
-
warnings.push({
|
|
257
|
-
field: "WEBVIEW_CONFIG.android.appName",
|
|
258
|
-
message:
|
|
259
|
-
'android.appName not set. App will show "Catalyst Application" as display name on device.',
|
|
260
|
-
})
|
|
261
|
-
} else {
|
|
262
|
-
passed.push({ field: "WEBVIEW_CONFIG.android.appName", value: a.appName })
|
|
263
|
-
}
|
|
264
|
-
if (!a.packageName) {
|
|
265
|
-
warnings.push({
|
|
266
|
-
field: "WEBVIEW_CONFIG.android.packageName",
|
|
267
|
-
message:
|
|
268
|
-
"android.packageName not set (e.g. com.company.app). Will be derived from appName — may produce unexpected package ID.",
|
|
269
|
-
})
|
|
270
|
-
} else {
|
|
271
|
-
passed.push({ field: "WEBVIEW_CONFIG.android.packageName", value: a.packageName })
|
|
272
|
-
}
|
|
273
|
-
// buildType
|
|
274
|
-
if (a.buildType && !["debug", "release"].includes(a.buildType)) {
|
|
275
|
-
issues.push({
|
|
276
|
-
field: "WEBVIEW_CONFIG.android.buildType",
|
|
277
|
-
severity: "error",
|
|
278
|
-
message: `android.buildType must be "debug" or "release", got "${a.buildType}".`,
|
|
279
|
-
})
|
|
280
|
-
} else {
|
|
281
|
-
passed.push({
|
|
282
|
-
field: "WEBVIEW_CONFIG.android.buildType",
|
|
283
|
-
value: a.buildType || "debug (default)",
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
// release keystore check
|
|
287
|
-
if (a.buildType === "release" && !a.keystore && !a.keystoreConfig) {
|
|
288
|
-
issues.push({
|
|
289
|
-
field: "WEBVIEW_CONFIG.android.keystore",
|
|
290
|
-
severity: "error",
|
|
291
|
-
message:
|
|
292
|
-
'android.buildType is "release" but no keystore or keystoreConfig provided. Release build cannot sign the APK.',
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
if (a.cachePattern !== undefined && typeof a.cachePattern !== "string") {
|
|
296
|
-
warnings.push({
|
|
297
|
-
field: "WEBVIEW_CONFIG.android.cachePattern",
|
|
298
|
-
message: `android.cachePattern should be a comma-separated glob string (e.g. "*.css,*.js,*.png"), got ${typeof a.cachePattern}.`,
|
|
299
|
-
})
|
|
300
|
-
} else if (a.cachePattern) {
|
|
301
|
-
passed.push({ field: "WEBVIEW_CONFIG.android.cachePattern", value: a.cachePattern })
|
|
302
|
-
}
|
|
303
|
-
if (a.security && typeof a.security.allowBackup !== "boolean") {
|
|
304
|
-
warnings.push({
|
|
305
|
-
field: "WEBVIEW_CONFIG.android.security.allowBackup",
|
|
306
|
-
message:
|
|
307
|
-
"android.security.allowBackup should be a boolean. Controls Google cloud backup of app data.",
|
|
308
|
-
})
|
|
309
|
-
}
|
|
310
340
|
}
|
|
311
341
|
}
|
|
312
342
|
|
|
313
343
|
// ── iOS fields ─────────────────────────────────────────────────────────────
|
|
344
|
+
// buildApp:ios picks an existing iPhone simulator (ios.simulatorName
|
|
345
|
+
// first), creates "Catalyst iPhone" only when there is none, and
|
|
346
|
+
// finishes Xcode setup itself. Signing files live
|
|
347
|
+
// in credentials/ios/ (CI: CATALYST_IOS_CREDENTIALS), never in config.json.
|
|
314
348
|
if (platform === "ios" || platform === "both") {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
severity: "error",
|
|
319
|
-
message: "WEBVIEW_CONFIG.ios block missing. iOS build cannot start without it.",
|
|
320
|
-
})
|
|
321
|
-
} else {
|
|
322
|
-
const ios = wc.ios
|
|
323
|
-
// appBundleId — optional but strongly recommended (defaults to com.debug.webview)
|
|
324
|
-
if (!ios.appBundleId) {
|
|
349
|
+
const ios = wc.ios || {}
|
|
350
|
+
if (!wc.appId && ios.appBundleId) {
|
|
351
|
+
if (!/^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*){1,}$/i.test(ios.appBundleId)) {
|
|
325
352
|
warnings.push({
|
|
326
353
|
field: "WEBVIEW_CONFIG.ios.appBundleId",
|
|
327
|
-
message:
|
|
328
|
-
'ios.appBundleId not set. App will use "com.debug.webview" as bundle ID — fine for testing, must be set for distribution.',
|
|
354
|
+
message: `appBundleId "${ios.appBundleId}" may not be valid; expected reverse-DNS format like "com.company.app". Prefer WEBVIEW_CONFIG.appId.`,
|
|
329
355
|
})
|
|
330
356
|
} else {
|
|
331
|
-
|
|
332
|
-
warnings.push({
|
|
333
|
-
field: "WEBVIEW_CONFIG.ios.appBundleId",
|
|
334
|
-
message: `appBundleId "${ios.appBundleId}" may not be valid — expected reverse-DNS format like "com.company.app".`,
|
|
335
|
-
})
|
|
336
|
-
} else {
|
|
337
|
-
passed.push({ field: "WEBVIEW_CONFIG.ios.appBundleId", value: ios.appBundleId })
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// simulatorName — optional, auto-detected if missing
|
|
341
|
-
if (!ios.simulatorName) {
|
|
342
|
-
warnings.push({
|
|
343
|
-
field: "WEBVIEW_CONFIG.ios.simulatorName",
|
|
344
|
-
message:
|
|
345
|
-
'ios.simulatorName not set. Will use auto-detected booted simulator. Recommend setting explicitly (e.g. "iPhone 17 Pro"). Run: xcrun simctl list devices.',
|
|
346
|
-
})
|
|
347
|
-
} else {
|
|
348
|
-
passed.push({ field: "WEBVIEW_CONFIG.ios.simulatorName", value: ios.simulatorName })
|
|
349
|
-
}
|
|
350
|
-
// appName — optional, defaults to "Catalyst Application"
|
|
351
|
-
if (!ios.appName) {
|
|
352
|
-
warnings.push({
|
|
353
|
-
field: "WEBVIEW_CONFIG.ios.appName",
|
|
354
|
-
message:
|
|
355
|
-
'ios.appName not set. App will show "Catalyst Application" as display name on device.',
|
|
356
|
-
})
|
|
357
|
-
} else {
|
|
358
|
-
passed.push({ field: "WEBVIEW_CONFIG.ios.appName", value: ios.appName })
|
|
359
|
-
}
|
|
360
|
-
// buildType — case-sensitive (Debug/Release not debug/release)
|
|
361
|
-
if (ios.buildType && !["Debug", "Release"].includes(ios.buildType)) {
|
|
362
|
-
issues.push({
|
|
363
|
-
field: "WEBVIEW_CONFIG.ios.buildType",
|
|
364
|
-
severity: "error",
|
|
365
|
-
message: `ios.buildType must be "Debug" or "Release" (case-sensitive). Got: "${ios.buildType}". Common mistake: using lowercase "debug" breaks Xcode build.`,
|
|
366
|
-
})
|
|
367
|
-
} else {
|
|
368
|
-
passed.push({
|
|
369
|
-
field: "WEBVIEW_CONFIG.ios.buildType",
|
|
370
|
-
value: ios.buildType || "Debug (default)",
|
|
371
|
-
})
|
|
372
|
-
}
|
|
373
|
-
// physical device fields
|
|
374
|
-
if (ios.deviceUDID && !ios.developmentTeam) {
|
|
375
|
-
warnings.push({
|
|
376
|
-
field: "WEBVIEW_CONFIG.ios.developmentTeam",
|
|
377
|
-
message:
|
|
378
|
-
"ios.deviceUDID is set (physical device build) but ios.developmentTeam is missing. Physical device builds require Apple Developer Team ID for code signing.",
|
|
379
|
-
})
|
|
380
|
-
}
|
|
381
|
-
if (ios.cachePattern !== undefined && typeof ios.cachePattern !== "string") {
|
|
382
|
-
warnings.push({
|
|
383
|
-
field: "WEBVIEW_CONFIG.ios.cachePattern",
|
|
384
|
-
message: `ios.cachePattern should be a comma-separated glob string, got ${typeof ios.cachePattern}.`,
|
|
385
|
-
})
|
|
386
|
-
} else if (ios.cachePattern) {
|
|
387
|
-
passed.push({ field: "WEBVIEW_CONFIG.ios.cachePattern", value: ios.cachePattern })
|
|
357
|
+
passed.push({ field: "WEBVIEW_CONFIG.ios.appBundleId", value: ios.appBundleId })
|
|
388
358
|
}
|
|
389
359
|
}
|
|
360
|
+
if (ios.simulatorName)
|
|
361
|
+
passed.push({ field: "WEBVIEW_CONFIG.ios.simulatorName", value: ios.simulatorName })
|
|
362
|
+
if (String(ios.buildType || "").toLowerCase() === "release") {
|
|
363
|
+
warnings.push({
|
|
364
|
+
field: "WEBVIEW_CONFIG.ios.buildType",
|
|
365
|
+
message:
|
|
366
|
+
'ios.buildType "release" is deprecated. Use `npm run ios:release` (catalyst buildApp:ios --release) and set buildType back to "debug" or remove it.',
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
if (ios.developmentTeam) {
|
|
370
|
+
passed.push({ field: "WEBVIEW_CONFIG.ios.developmentTeam", value: ios.developmentTeam })
|
|
371
|
+
} else {
|
|
372
|
+
warnings.push({
|
|
373
|
+
field: "WEBVIEW_CONFIG.ios.developmentTeam",
|
|
374
|
+
message:
|
|
375
|
+
"ios.developmentTeam not set. The simulator does not need it, but a physical iPhone (--device) and a release with cloud or Xcode-account signing do (IOS-010). Set your 10-character Apple team ID.",
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
// iOS reads an array of patterns; a string becomes one literal
|
|
379
|
+
// pattern (not split on commas, unlike android.cachePattern).
|
|
380
|
+
const iosPattern = ios.cachePattern
|
|
381
|
+
const iosPatternOk = Array.isArray(iosPattern)
|
|
382
|
+
? iosPattern.every((item) => typeof item === "string")
|
|
383
|
+
: typeof iosPattern === "string"
|
|
384
|
+
if (iosPattern !== undefined && !iosPatternOk) {
|
|
385
|
+
warnings.push({
|
|
386
|
+
field: "WEBVIEW_CONFIG.ios.cachePattern",
|
|
387
|
+
message: `ios.cachePattern should be an array of glob strings (e.g. ["*.css", "*.js"]), got ${JSON.stringify(iosPattern)}.`,
|
|
388
|
+
})
|
|
389
|
+
} else if (typeof iosPattern === "string" && iosPattern.includes(",")) {
|
|
390
|
+
warnings.push({
|
|
391
|
+
field: "WEBVIEW_CONFIG.ios.cachePattern",
|
|
392
|
+
message: `ios.cachePattern "${iosPattern}" is one literal pattern on iOS, not a comma-separated list. Use an array, e.g. ["*.css", "*.js"].`,
|
|
393
|
+
})
|
|
394
|
+
} else if (iosPattern && iosPattern.length) {
|
|
395
|
+
passed.push({ field: "WEBVIEW_CONFIG.ios.cachePattern", value: iosPattern })
|
|
396
|
+
}
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
// ── splashScreen (inside WEBVIEW_CONFIG; the native builds read only that) ──
|
package/package.json
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { execFileSync } from "child_process"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import os from "os"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest"
|
|
7
|
+
|
|
8
|
+
// bin/catalyst.js runs on import, so it is exercised as a process: a copy sits
|
|
9
|
+
// in a temp package whose dist/native scripts just print the argv they get.
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const binSource = path.resolve(here, "../../bin/catalyst.js")
|
|
12
|
+
|
|
13
|
+
let root = ""
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), "catalyst-bin-flags-"))
|
|
17
|
+
fs.mkdirSync(path.join(root, "bin"))
|
|
18
|
+
fs.mkdirSync(path.join(root, "dist", "native"), { recursive: true })
|
|
19
|
+
fs.copyFileSync(binSource, path.join(root, "bin", "catalyst.js"))
|
|
20
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ type: "module", version: "0.0.0" }))
|
|
21
|
+
for (const name of [
|
|
22
|
+
"buildAppAndroid",
|
|
23
|
+
"buildAppIos",
|
|
24
|
+
"androidSetup",
|
|
25
|
+
"setupEmulatorIos",
|
|
26
|
+
"exportCredentials",
|
|
27
|
+
]) {
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
path.join(root, "dist", "native", `${name}.js`),
|
|
30
|
+
`console.log(JSON.stringify({ script: ${JSON.stringify(name)}, args: process.argv.slice(2) }))\n`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function run(args: string[]) {
|
|
40
|
+
const out = execFileSync(process.execPath, [path.join(root, "bin", "catalyst.js"), ...args], {
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
})
|
|
43
|
+
return out
|
|
44
|
+
.trim()
|
|
45
|
+
.split("\n")
|
|
46
|
+
.map((line) => JSON.parse(line))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("native build flags", () => {
|
|
50
|
+
it("forwards --release and --url to the platform script", () => {
|
|
51
|
+
expect(run(["buildApp:android", "--release", "--url", "https://shop.acme.com"])).toEqual([
|
|
52
|
+
{ script: "buildAppAndroid", args: ["--release", "--url", "https://shop.acme.com"] },
|
|
53
|
+
])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("does not dispatch on a --url value that names a command", () => {
|
|
57
|
+
expect(run(["--url", "build", "buildApp:android"])).toEqual([
|
|
58
|
+
{ script: "buildAppAndroid", args: ["--url", "build"] },
|
|
59
|
+
])
|
|
60
|
+
expect(run(["buildApp:android", "--url=serve"])).toEqual([
|
|
61
|
+
{ script: "buildAppAndroid", args: ["--url=serve"] },
|
|
62
|
+
])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("keeps flags given before the command away from node", () => {
|
|
66
|
+
expect(run(["--release", "buildApp:android", "--verbose"])).toEqual([
|
|
67
|
+
{ script: "buildAppAndroid", args: ["--release", "--verbose"] },
|
|
68
|
+
])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("forwards --device wherever it appears", () => {
|
|
72
|
+
expect(run(["--device", "buildApp:ios"])).toEqual([{ script: "buildAppIos", args: ["--device"] }])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("forwards --yes and --accept-licenses wherever they appear", () => {
|
|
76
|
+
expect(run(["--yes", "setupEmulator:android", "--accept-licenses"])).toEqual([
|
|
77
|
+
{ script: "androidSetup", args: ["--yes", "--accept-licenses"] },
|
|
78
|
+
])
|
|
79
|
+
expect(run(["setupEmulator", "--yes"])).toEqual([
|
|
80
|
+
{ script: "setupEmulatorIos", args: ["--yes"] },
|
|
81
|
+
{ script: "androidSetup", args: ["--yes"] },
|
|
82
|
+
])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it("passes the flags to both platforms for buildApp", () => {
|
|
86
|
+
expect(run(["buildApp", "--release", "--url=https://shop.acme.com"])).toEqual([
|
|
87
|
+
{ script: "buildAppIos", args: ["--release", "--url=https://shop.acme.com"] },
|
|
88
|
+
{ script: "buildAppAndroid", args: ["--release", "--url=https://shop.acme.com"] },
|
|
89
|
+
])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("dispatches credentials:export with its platform", () => {
|
|
93
|
+
expect(run(["credentials:export", "android"])).toEqual([
|
|
94
|
+
{ script: "exportCredentials", args: ["android"] },
|
|
95
|
+
])
|
|
96
|
+
})
|
|
97
|
+
})
|