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
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
|
-
const os = require("os");
|
|
5
4
|
const {
|
|
6
|
-
detectAndroidSdk,
|
|
7
|
-
listAvds,
|
|
8
|
-
expandHome,
|
|
9
5
|
ANDROID_SDK_HINT
|
|
10
6
|
} = require("../utils.js");
|
|
7
|
+
const {
|
|
8
|
+
resolveNativeConfig
|
|
9
|
+
} = require("../nativeConfig.js");
|
|
11
10
|
|
|
12
11
|
/** An error carrying the one action that fixes it, shown as the diagnostic's hint. */
|
|
13
12
|
function withHint(message, hint) {
|
|
@@ -15,94 +14,87 @@ function withHint(message, hint) {
|
|
|
15
14
|
hint
|
|
16
15
|
});
|
|
17
16
|
}
|
|
18
|
-
const tilde = dir => dir.startsWith(os.homedir()) ? `~${dir.slice(os.homedir().length)}` : dir;
|
|
19
17
|
function createConfigPhase(ctx) {
|
|
20
18
|
const {
|
|
21
19
|
configPath,
|
|
22
20
|
progress
|
|
23
21
|
} = ctx;
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read config.json and resolve what this build uses (nativeConfig.js):
|
|
25
|
+
* release or debug, the start URL and its guard, the app id and the
|
|
26
|
+
* version. The resolved config is written to a temp file for Gradle;
|
|
27
|
+
* call `cleanup()` when done.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} [options]
|
|
30
|
+
* @param {boolean} [options.release] - `--release`
|
|
31
|
+
* @param {string} [options.url] - `--url`
|
|
32
|
+
* @param {string} [options.devHost] - the host the device reaches the dev server on
|
|
33
|
+
*/
|
|
34
|
+
async function initializeConfig({
|
|
35
|
+
release: releaseFlag = false,
|
|
36
|
+
url,
|
|
37
|
+
devHost
|
|
38
|
+
} = {}) {
|
|
25
39
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
26
40
|
const config = JSON.parse(configFile);
|
|
27
41
|
const {
|
|
28
|
-
WEBVIEW_CONFIG,
|
|
42
|
+
WEBVIEW_CONFIG: baseConfig,
|
|
29
43
|
BUILD_OUTPUT_PATH
|
|
30
44
|
} = config;
|
|
31
|
-
if (!
|
|
45
|
+
if (!baseConfig || Object.keys(baseConfig).length === 0) {
|
|
32
46
|
throw new Error("WebView Config missing in " + configPath);
|
|
33
47
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
const log = (message, type) => progress.log(message, type);
|
|
49
|
+
const resolved = resolveNativeConfig("android", {
|
|
50
|
+
release: releaseFlag,
|
|
51
|
+
url,
|
|
52
|
+
devHost,
|
|
53
|
+
config,
|
|
54
|
+
log
|
|
55
|
+
});
|
|
56
|
+
if (resolved.release) {
|
|
57
|
+
progress.log("Release build - a signed AAB will be generated", "info");
|
|
58
|
+
progress.log(`Release start URL: ${resolved.startUrl}`, "info");
|
|
40
59
|
}
|
|
41
60
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
config,
|
|
62
|
+
WEBVIEW_CONFIG: resolved.WEBVIEW_CONFIG,
|
|
63
|
+
BUILD_OUTPUT_PATH,
|
|
64
|
+
release: resolved.release,
|
|
65
|
+
version: resolved.version,
|
|
66
|
+
applicationId: resolved.appId,
|
|
67
|
+
usesDevServer: resolved.usesDevServer,
|
|
68
|
+
resolvedConfigPath: resolved.configPath,
|
|
69
|
+
cleanup: resolved.cleanup
|
|
44
70
|
};
|
|
45
71
|
}
|
|
46
|
-
function validateAndroidTools(androidConfig) {
|
|
47
|
-
progress.status("validating tools");
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Check the SDK tools a build runs, once ensureAndroidSdk has installed
|
|
75
|
+
* them: adb answers, and the emulator binary is there when one is used.
|
|
76
|
+
*
|
|
77
|
+
* @param {{ sdkPath: string }} androidConfig - sdkPath already resolved (resolveSdkPath)
|
|
78
|
+
* @param {{ needEmulator?: boolean }} [options]
|
|
79
|
+
*/
|
|
80
|
+
function validateAndroidTools(androidConfig, {
|
|
81
|
+
needEmulator = true
|
|
82
|
+
} = {}) {
|
|
83
|
+
progress.status("validating tools");
|
|
60
84
|
const ANDROID_SDK = androidConfig.sdkPath;
|
|
61
85
|
const ADB_PATH = `${ANDROID_SDK}/platform-tools/adb`;
|
|
62
86
|
const EMULATOR_PATH = `${ANDROID_SDK}/emulator/emulator`;
|
|
63
|
-
if (!fs.existsSync(
|
|
64
|
-
throw withHint(`Android SDK path does not exist: ${ANDROID_SDK}`, ANDROID_SDK_HINT);
|
|
65
|
-
}
|
|
66
|
-
if (!fs.existsSync(ADB_PATH)) {
|
|
67
|
-
throw withHint(`adb not found at ${ADB_PATH}`, "Install Android SDK Platform-Tools from Android Studio's SDK Manager.");
|
|
68
|
-
}
|
|
87
|
+
if (!fs.existsSync(ADB_PATH)) throw withHint(`adb not found at ${ADB_PATH}`, ANDROID_SDK_HINT);
|
|
69
88
|
try {
|
|
70
|
-
ctx.
|
|
89
|
+
ctx.runFile(ADB_PATH, ["version"], {
|
|
71
90
|
timeout: 15000
|
|
72
91
|
});
|
|
73
92
|
} catch (error) {
|
|
74
93
|
if (error.hint) throw error;
|
|
75
94
|
throw new Error(`ADB is not working properly: ${error.message}`);
|
|
76
95
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!fs.existsSync(EMULATOR_PATH)) {
|
|
80
|
-
throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
ctx.runCommand(`${EMULATOR_PATH} -version`);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`Emulator is not working properly: ${error.message}`);
|
|
86
|
-
}
|
|
87
|
-
if (!androidConfig.emulatorName) {
|
|
88
|
-
const [first] = listAvds(ANDROID_SDK);
|
|
89
|
-
if (!first) {
|
|
90
|
-
throw withHint("No Android emulator found", "Create one in Android Studio's Device Manager, or set WEBVIEW_CONFIG.android.emulatorName.");
|
|
91
|
-
}
|
|
92
|
-
androidConfig.emulatorName = first;
|
|
93
|
-
progress.log(`Using the "${first}" emulator`, "note");
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const avdOutput = ctx.runCommand(`${EMULATOR_PATH} -list-avds`);
|
|
97
|
-
if (!avdOutput.includes(androidConfig.emulatorName)) {
|
|
98
|
-
throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);
|
|
99
|
-
}
|
|
100
|
-
progress.log(`Emulator "${androidConfig.emulatorName}" exists`, "success");
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`Error checking emulator AVD: ${error.message}`);
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
progress.log("Skipping emulator validation for release build", "info");
|
|
96
|
+
if (needEmulator && !fs.existsSync(EMULATOR_PATH)) {
|
|
97
|
+
throw withHint(`Emulator not found at ${EMULATOR_PATH}`, ANDROID_SDK_HINT);
|
|
106
98
|
}
|
|
107
99
|
return {
|
|
108
100
|
ANDROID_SDK,
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.chooseAndroidTarget = chooseAndroidTarget;
|
|
7
|
+
exports.devServerReachPlan = devServerReachPlan;
|
|
8
|
+
exports.launchArgs = launchArgs;
|
|
9
|
+
exports.parseAdbDevices = parseAdbDevices;
|
|
10
|
+
exports.reachDevServer = reachDevServer;
|
|
11
|
+
exports.readyPhones = readyPhones;
|
|
12
|
+
var _utils = require("../utils.js");
|
|
13
|
+
// Where a debug build runs, and how it reaches the dev server.
|
|
14
|
+
//
|
|
15
|
+
// The emulator is the default. `--device` picks a connected phone instead,
|
|
16
|
+
// and in a terminal with both available the person is asked, with the last
|
|
17
|
+
// pick (.catalyst/local.json) as the default. A phone is never picked just
|
|
18
|
+
// because it is plugged in.
|
|
19
|
+
//
|
|
20
|
+
// Debug builds load http://localhost:<port>. On the emulator and on a USB
|
|
21
|
+
// phone alike, localhost is the device itself, so every port the app uses
|
|
22
|
+
// is forwarded to this machine with `adb reverse` after each install.
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse `adb devices -l`.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} output
|
|
28
|
+
* @returns {{ serial: string, state: string, emulator: boolean, model: string | null }[]}
|
|
29
|
+
*/
|
|
30
|
+
function parseAdbDevices(output) {
|
|
31
|
+
return String(output || "").split("\n").map(line => line.trim()).filter(line => line && !line.startsWith("List of devices") && !line.startsWith("*")).map(line => {
|
|
32
|
+
const [serial, state, ...rest] = line.split(/\s+/);
|
|
33
|
+
const props = Object.fromEntries(rest.filter(token => token.includes(":")).map(token => [token.slice(0, token.indexOf(":")), token.slice(token.indexOf(":") + 1)]));
|
|
34
|
+
return {
|
|
35
|
+
serial,
|
|
36
|
+
state,
|
|
37
|
+
emulator: serial.startsWith("emulator-"),
|
|
38
|
+
model: props.model ? props.model.replace(/_/g, " ") : null
|
|
39
|
+
};
|
|
40
|
+
}).filter(device => device.serial && device.state);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Phones adb can install on: not an emulator, and authorized ("device"). */
|
|
44
|
+
function readyPhones(devices) {
|
|
45
|
+
return devices.filter(device => !device.emulator && device.state === "device");
|
|
46
|
+
}
|
|
47
|
+
const phoneLabel = device => device.model ? `${device.model} (${device.serial})` : device.serial;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Choose where a debug build runs.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} options
|
|
53
|
+
* @param {boolean} [options.wantDevice] - `--device`
|
|
54
|
+
* @param {ReturnType<typeof parseAdbDevices>} options.devices
|
|
55
|
+
* @param {{ target: string, serial?: string, avd?: string } | null} [options.last] - getLastTarget("android")
|
|
56
|
+
* @param {Record<string, string | undefined>} [options.env] - ANDROID_SERIAL picks a phone without a terminal
|
|
57
|
+
* @param {boolean} [options.isTTY]
|
|
58
|
+
* @param {typeof selectChoice} [options.select]
|
|
59
|
+
* @param {string} [options.emulatorHint] - shown beside the emulator choice
|
|
60
|
+
* @returns {Promise<{ target: "emulator" } | { target: "device", serial: string, model: string | null }>}
|
|
61
|
+
*/
|
|
62
|
+
async function chooseAndroidTarget({
|
|
63
|
+
wantDevice = false,
|
|
64
|
+
devices,
|
|
65
|
+
last = null,
|
|
66
|
+
env = process.env,
|
|
67
|
+
isTTY,
|
|
68
|
+
select = _utils.select,
|
|
69
|
+
emulatorHint
|
|
70
|
+
}) {
|
|
71
|
+
const phones = readyPhones(devices);
|
|
72
|
+
const asDevice = device => ({
|
|
73
|
+
target: "device",
|
|
74
|
+
serial: device.serial,
|
|
75
|
+
model: device.model
|
|
76
|
+
});
|
|
77
|
+
const lastPhone = last?.target === "device" ? phones.find(device => device.serial === last.serial) : undefined;
|
|
78
|
+
if (wantDevice) {
|
|
79
|
+
if (env.ANDROID_SERIAL) {
|
|
80
|
+
const named = phones.find(device => device.serial === env.ANDROID_SERIAL);
|
|
81
|
+
if (!named) {
|
|
82
|
+
throw (0, _utils.nativeError)("ANDROID-011", `ANDROID_SERIAL=${env.ANDROID_SERIAL} is not a connected phone`);
|
|
83
|
+
}
|
|
84
|
+
return asDevice(named);
|
|
85
|
+
}
|
|
86
|
+
if (!phones.length) {
|
|
87
|
+
const unauthorized = devices.filter(device => !device.emulator && device.state === "unauthorized");
|
|
88
|
+
throw (0, _utils.nativeError)("ANDROID-011", unauthorized.length ? `${unauthorized.map(device => device.serial).join(", ")} has not allowed this computer yet; accept the prompt on the phone` : "adb devices lists no phone");
|
|
89
|
+
}
|
|
90
|
+
if (phones.length === 1) return asDevice(phones[0]);
|
|
91
|
+
const question = `Several Android phones are connected (${phones.map(device => device.serial).join(", ")}). Which one`;
|
|
92
|
+
if (!isTTY) {
|
|
93
|
+
throw (0, _utils.needsAnswerError)(question, {
|
|
94
|
+
hint: "Set ANDROID_SERIAL to the serial of the phone to use (adb devices lists them), or run the command in a terminal to pick one."
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const serial = await select(question, phones.map(device => ({
|
|
98
|
+
label: phoneLabel(device),
|
|
99
|
+
value: device.serial
|
|
100
|
+
})), {
|
|
101
|
+
defaultValue: (lastPhone || phones[0]).serial,
|
|
102
|
+
isTTY
|
|
103
|
+
});
|
|
104
|
+
return asDevice(phones.find(device => device.serial === serial));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// No flag: the emulator, unless a person here picks a connected phone.
|
|
108
|
+
if (!phones.length || !isTTY) return {
|
|
109
|
+
target: "emulator"
|
|
110
|
+
};
|
|
111
|
+
const pick = await select("Run the app on", [{
|
|
112
|
+
label: "Emulator",
|
|
113
|
+
value: "emulator",
|
|
114
|
+
hint: emulatorHint
|
|
115
|
+
}, ...phones.map(device => ({
|
|
116
|
+
label: phoneLabel(device),
|
|
117
|
+
value: device.serial,
|
|
118
|
+
hint: "connected phone"
|
|
119
|
+
}))], {
|
|
120
|
+
defaultValue: lastPhone ? lastPhone.serial : "emulator",
|
|
121
|
+
isTTY
|
|
122
|
+
});
|
|
123
|
+
if (pick === "emulator") return {
|
|
124
|
+
target: "emulator"
|
|
125
|
+
};
|
|
126
|
+
return asDevice(phones.find(device => device.serial === pick));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The adb calls that let the app on `serial` reach the dev server: stop the
|
|
131
|
+
* running app first (its in-app server scans device ports 3000 to 3099 and
|
|
132
|
+
* would hold the port a reverse needs), then reverse every port.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} serial
|
|
135
|
+
* @param {string} packageName - the installed (debug) package
|
|
136
|
+
* @param {number[]} ports - devServerPorts()
|
|
137
|
+
* @returns {string[][]} adb argv lists, in order
|
|
138
|
+
*/
|
|
139
|
+
function devServerReachPlan(serial, packageName, ports) {
|
|
140
|
+
return [["-s", serial, "shell", "am", "force-stop", packageName], ...ports.map(port => ["-s", serial, "reverse", `tcp:${port}`, `tcp:${port}`])];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run devServerReachPlan with `adb(args)`. Stopping the app may fail (it may
|
|
145
|
+
* not be running); a failed reverse is an error, since the app would load a
|
|
146
|
+
* page with nothing behind it.
|
|
147
|
+
*
|
|
148
|
+
* @param {(args: string[]) => unknown} adb
|
|
149
|
+
* @param {string} serial
|
|
150
|
+
* @param {string} packageName
|
|
151
|
+
* @param {number[]} ports
|
|
152
|
+
*/
|
|
153
|
+
function reachDevServer(adb, serial, packageName, ports) {
|
|
154
|
+
const [stop, ...reverses] = devServerReachPlan(serial, packageName, ports);
|
|
155
|
+
try {
|
|
156
|
+
adb(stop);
|
|
157
|
+
} catch {
|
|
158
|
+
// Not running is fine.
|
|
159
|
+
}
|
|
160
|
+
for (const args of reverses) {
|
|
161
|
+
try {
|
|
162
|
+
adb(args);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const detail = String(error.stderr || error.message || "").trim().split("\n")[0];
|
|
165
|
+
throw Object.assign(new Error(`adb ${args.slice(2).join(" ")} failed on ${serial}: ${detail}`), {
|
|
166
|
+
stage: "Connecting the device to the dev server",
|
|
167
|
+
hint: `Run adb -s ${serial} reverse --list to see what holds the port, quit it, and build again.`
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** adb argv that opens the app's launcher activity. */
|
|
174
|
+
function launchArgs(serial, packageName) {
|
|
175
|
+
return ["-s", serial, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1"];
|
|
176
|
+
}
|