@tamer4lynx/cli 0.0.3 → 0.0.4
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/README.md +11 -10
- package/dist/index.js +442 -1715
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,12 +7,12 @@ process.on("warning", (w) => {
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// index.ts
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import fs23 from "fs";
|
|
11
|
+
import path24 from "path";
|
|
12
12
|
import { program } from "commander";
|
|
13
13
|
|
|
14
14
|
// package.json
|
|
15
|
-
var version = "0.0.
|
|
15
|
+
var version = "0.0.4";
|
|
16
16
|
|
|
17
17
|
// src/android/create.ts
|
|
18
18
|
import fs3 from "fs";
|
|
@@ -266,11 +266,8 @@ function resolveHostPaths(cwd = process.cwd()) {
|
|
|
266
266
|
const lynxBundlePath = path2.join(lynxProjectDir, bundleRoot, bundleFile);
|
|
267
267
|
const androidDir = path2.join(projectRoot, androidDirRel);
|
|
268
268
|
const devMode = resolveDevMode(config);
|
|
269
|
-
const devAppDir = devMode === "embedded" ? findDevAppPackage(projectRoot) : null;
|
|
270
269
|
const devClientPkg = findDevClientPackage(projectRoot);
|
|
271
|
-
const
|
|
272
|
-
const devClientBundleInApp = devAppDir ? path2.join(devAppDir, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : null;
|
|
273
|
-
const devClientBundlePath = devMode === "embedded" ? devClientBundleInClient && fs2.existsSync(devClientBundleInClient) ? devClientBundleInClient : devClientBundleInApp ?? void 0 : void 0;
|
|
270
|
+
const devClientBundlePath = devClientPkg ? path2.join(devClientPkg, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : void 0;
|
|
274
271
|
return {
|
|
275
272
|
projectRoot,
|
|
276
273
|
androidDir,
|
|
@@ -323,41 +320,6 @@ function resolveIconPaths(projectRoot, config) {
|
|
|
323
320
|
}
|
|
324
321
|
return Object.keys(out).length ? out : null;
|
|
325
322
|
}
|
|
326
|
-
function resolveDevAppPaths(searchRoot) {
|
|
327
|
-
const devAppDir = findDevAppPackage(searchRoot) ?? findDevAppPackage(findRepoRoot(searchRoot));
|
|
328
|
-
if (!devAppDir) {
|
|
329
|
-
throw new Error("tamer-dev-app not found. Add @tamer4lynx/tamer-dev-app to dependencies, or run from the tamer4lynx monorepo.");
|
|
330
|
-
}
|
|
331
|
-
const configPath = path2.join(devAppDir, "tamer.config.json");
|
|
332
|
-
if (!fs2.existsSync(configPath)) {
|
|
333
|
-
throw new Error(`tamer.config.json not found in ${devAppDir}`);
|
|
334
|
-
}
|
|
335
|
-
const config = JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
336
|
-
const packageName = config.android?.packageName ?? "com.nanofuxion.tamerdevapp";
|
|
337
|
-
const androidDirRel = config.paths?.androidDir ?? "android";
|
|
338
|
-
const androidDir = path2.join(devAppDir, androidDirRel);
|
|
339
|
-
const inDevAppScoped = path2.join(devAppDir, "node_modules", "@tamer4lynx", "tamer-dev-client");
|
|
340
|
-
const inDevAppFlat = path2.join(devAppDir, "node_modules", "tamer-dev-client");
|
|
341
|
-
const devClientDir = findDevClientPackage(searchRoot) ?? findDevClientPackage(findRepoRoot(searchRoot)) ?? (fs2.existsSync(path2.join(inDevAppScoped, "package.json")) ? inDevAppScoped : null) ?? (fs2.existsSync(path2.join(inDevAppFlat, "package.json")) ? inDevAppFlat : null);
|
|
342
|
-
if (!devClientDir || !fs2.existsSync(devClientDir)) {
|
|
343
|
-
throw new Error("tamer-dev-client not found. Add @tamer4lynx/tamer-dev-client (or tamer-dev-app pulls it in).");
|
|
344
|
-
}
|
|
345
|
-
const lynxBundlePath = path2.join(devClientDir, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle");
|
|
346
|
-
return {
|
|
347
|
-
projectRoot: devAppDir,
|
|
348
|
-
androidDir,
|
|
349
|
-
iosDir: path2.join(devAppDir, "ios"),
|
|
350
|
-
androidAppDir: path2.join(androidDir, "app"),
|
|
351
|
-
androidAssetsDir: path2.join(androidDir, "app", "src", "main", "assets"),
|
|
352
|
-
androidKotlinDir: path2.join(androidDir, "app", "src", "main", "kotlin", packageName.replace(/\./g, "/")),
|
|
353
|
-
lynxProjectDir: devClientDir,
|
|
354
|
-
lynxBundlePath,
|
|
355
|
-
lynxBundleFile: "dev-client.lynx.bundle",
|
|
356
|
-
devMode: "embedded",
|
|
357
|
-
devClientBundlePath: void 0,
|
|
358
|
-
config
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
323
|
|
|
362
324
|
// src/explorer/ref.ts
|
|
363
325
|
var LYNX_RAW_BASE = "https://raw.githubusercontent.com/lynx-family/lynx/develop/explorer";
|
|
@@ -973,7 +935,7 @@ var create = async (opts = {}) => {
|
|
|
973
935
|
const assetsDir = path3.join(mainDir, "assets");
|
|
974
936
|
const themesDir = path3.join(mainDir, "res", "values");
|
|
975
937
|
const gradleDir = path3.join(rootDir, "gradle");
|
|
976
|
-
function
|
|
938
|
+
function writeFile2(filePath, content, options) {
|
|
977
939
|
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
978
940
|
fs3.writeFileSync(
|
|
979
941
|
filePath,
|
|
@@ -986,7 +948,7 @@ var create = async (opts = {}) => {
|
|
|
986
948
|
fs3.rmSync(rootDir, { recursive: true, force: true });
|
|
987
949
|
}
|
|
988
950
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
989
|
-
|
|
951
|
+
writeFile2(
|
|
990
952
|
path3.join(gradleDir, "libs.versions.toml"),
|
|
991
953
|
`
|
|
992
954
|
[versions]
|
|
@@ -1045,7 +1007,7 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
|
|
|
1045
1007
|
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
|
1046
1008
|
`
|
|
1047
1009
|
);
|
|
1048
|
-
|
|
1010
|
+
writeFile2(
|
|
1049
1011
|
path3.join(rootDir, "settings.gradle.kts"),
|
|
1050
1012
|
`
|
|
1051
1013
|
pluginManagement {
|
|
@@ -1079,7 +1041,7 @@ println("If you have native modules please run tamer android link")
|
|
|
1079
1041
|
// GENERATED AUTOLINK END
|
|
1080
1042
|
`
|
|
1081
1043
|
);
|
|
1082
|
-
|
|
1044
|
+
writeFile2(
|
|
1083
1045
|
path3.join(rootDir, "build.gradle.kts"),
|
|
1084
1046
|
`
|
|
1085
1047
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
@@ -1091,7 +1053,7 @@ plugins {
|
|
|
1091
1053
|
}
|
|
1092
1054
|
`
|
|
1093
1055
|
);
|
|
1094
|
-
|
|
1056
|
+
writeFile2(
|
|
1095
1057
|
path3.join(rootDir, "gradle.properties"),
|
|
1096
1058
|
`
|
|
1097
1059
|
org.gradle.jvmargs=-Xmx2048m
|
|
@@ -1100,7 +1062,7 @@ kotlin.code.style=official
|
|
|
1100
1062
|
android.enableJetifier=true
|
|
1101
1063
|
`
|
|
1102
1064
|
);
|
|
1103
|
-
|
|
1065
|
+
writeFile2(
|
|
1104
1066
|
path3.join(appDir, "build.gradle.kts"),
|
|
1105
1067
|
`
|
|
1106
1068
|
plugins {
|
|
@@ -1190,7 +1152,7 @@ dependencies {
|
|
|
1190
1152
|
}
|
|
1191
1153
|
`
|
|
1192
1154
|
);
|
|
1193
|
-
|
|
1155
|
+
writeFile2(
|
|
1194
1156
|
path3.join(themesDir, "themes.xml"),
|
|
1195
1157
|
`
|
|
1196
1158
|
<resources>
|
|
@@ -1224,7 +1186,7 @@ dependencies {
|
|
|
1224
1186
|
<uses-permission android:name="android.permission.CAMERA" />` : ` <uses-permission android:name="android.permission.INTERNET" />`;
|
|
1225
1187
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
1226
1188
|
const manifestIconAttrs = iconPaths ? ' android:icon="@mipmap/ic_launcher"\n android:roundIcon="@mipmap/ic_launcher"\n' : "";
|
|
1227
|
-
|
|
1189
|
+
writeFile2(
|
|
1228
1190
|
path3.join(mainDir, "AndroidManifest.xml"),
|
|
1229
1191
|
`
|
|
1230
1192
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -1238,7 +1200,7 @@ ${manifestIconAttrs} android:usesCleartextTraffic="true"
|
|
|
1238
1200
|
</manifest>
|
|
1239
1201
|
`
|
|
1240
1202
|
);
|
|
1241
|
-
|
|
1203
|
+
writeFile2(
|
|
1242
1204
|
path3.join(kotlinGeneratedDir, "GeneratedLynxExtensions.kt"),
|
|
1243
1205
|
`
|
|
1244
1206
|
package ${packageName}.generated
|
|
@@ -1275,7 +1237,7 @@ object GeneratedLynxExtensions {
|
|
|
1275
1237
|
]) {
|
|
1276
1238
|
const srcPath = path3.join(templateDir, src);
|
|
1277
1239
|
if (fs3.existsSync(srcPath)) {
|
|
1278
|
-
|
|
1240
|
+
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1279
1241
|
}
|
|
1280
1242
|
}
|
|
1281
1243
|
} else {
|
|
@@ -1283,9 +1245,9 @@ object GeneratedLynxExtensions {
|
|
|
1283
1245
|
fetchAndPatchApplication(vars),
|
|
1284
1246
|
fetchAndPatchTemplateProvider(vars)
|
|
1285
1247
|
]);
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1248
|
+
writeFile2(path3.join(javaDir, "App.java"), applicationSource);
|
|
1249
|
+
writeFile2(path3.join(javaDir, "TemplateProvider.java"), templateProviderSource);
|
|
1250
|
+
writeFile2(path3.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
|
|
1289
1251
|
if (hasDevLauncher) {
|
|
1290
1252
|
if (devClientPkg) {
|
|
1291
1253
|
const templateDir = path3.join(devClientPkg, "android", "templates");
|
|
@@ -1296,15 +1258,15 @@ object GeneratedLynxExtensions {
|
|
|
1296
1258
|
]) {
|
|
1297
1259
|
const srcPath = path3.join(templateDir, src);
|
|
1298
1260
|
if (fs3.existsSync(srcPath)) {
|
|
1299
|
-
|
|
1261
|
+
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1300
1262
|
}
|
|
1301
1263
|
}
|
|
1302
1264
|
} else {
|
|
1303
|
-
|
|
1265
|
+
writeFile2(path3.join(kotlinDir, "ProjectActivity.kt"), getProjectActivity(vars));
|
|
1304
1266
|
const devClientManagerSource = getDevClientManager(vars);
|
|
1305
1267
|
if (devClientManagerSource) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1268
|
+
writeFile2(path3.join(kotlinDir, "DevClientManager.kt"), devClientManagerSource);
|
|
1269
|
+
writeFile2(path3.join(kotlinDir, "DevServerPrefs.kt"), getDevServerPrefs(vars));
|
|
1308
1270
|
}
|
|
1309
1271
|
}
|
|
1310
1272
|
}
|
|
@@ -1341,7 +1303,7 @@ object GeneratedLynxExtensions {
|
|
|
1341
1303
|
if (androidSdk) {
|
|
1342
1304
|
try {
|
|
1343
1305
|
const sdkDirContent = `sdk.dir=${androidSdk.replace(/\\/g, "/")}`;
|
|
1344
|
-
|
|
1306
|
+
writeFile2(path3.join(rootDir, "local.properties"), sdkDirContent);
|
|
1345
1307
|
console.log("\u{1F4E6} Created local.properties from tamer.config.json.");
|
|
1346
1308
|
} catch (err) {
|
|
1347
1309
|
console.error(`\u274C Failed to create local.properties: ${err.message}`);
|
|
@@ -1690,6 +1652,17 @@ ${createDelayedBody}
|
|
|
1690
1652
|
}
|
|
1691
1653
|
|
|
1692
1654
|
// src/android/autolink.ts
|
|
1655
|
+
var TAMER_DEV_CLIENT_FALLBACK = {
|
|
1656
|
+
name: "@tamer4lynx/tamer-dev-client",
|
|
1657
|
+
packagePath: "",
|
|
1658
|
+
config: {
|
|
1659
|
+
android: {
|
|
1660
|
+
moduleClassName: "com.nanofuxion.tamerdevclient.DevClientModule",
|
|
1661
|
+
sourceDir: "android",
|
|
1662
|
+
permissions: ["CAMERA", "ACCESS_NETWORK_STATE", "ACCESS_WIFI_STATE"]
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1693
1666
|
var REQUIRED_CATALOG_ENTRIES = {
|
|
1694
1667
|
"androidx.biometric": {
|
|
1695
1668
|
versionRef: "biometric",
|
|
@@ -1754,12 +1727,11 @@ ${replacementBlock}
|
|
|
1754
1727
|
androidPackages.forEach((pkg) => {
|
|
1755
1728
|
const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
|
|
1756
1729
|
const sourceDir = pkg.config.android?.sourceDir || "android";
|
|
1757
|
-
const projectPath = path6.
|
|
1758
|
-
const relativePath = path6.relative(appAndroidPath, projectPath).replace(/\\/g, "/");
|
|
1730
|
+
const projectPath = path6.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
|
|
1759
1731
|
scriptContent += `
|
|
1760
1732
|
include(":${gradleProjectName}")`;
|
|
1761
1733
|
scriptContent += `
|
|
1762
|
-
project(":${gradleProjectName}").projectDir = file("${
|
|
1734
|
+
project(":${gradleProjectName}").projectDir = file("${projectPath}")`;
|
|
1763
1735
|
});
|
|
1764
1736
|
} else {
|
|
1765
1737
|
scriptContent += `
|
|
@@ -1837,7 +1809,19 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
1837
1809
|
}
|
|
1838
1810
|
function run() {
|
|
1839
1811
|
console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
|
|
1840
|
-
|
|
1812
|
+
let packages = discoverModules(projectRoot).filter((p) => p.config.android);
|
|
1813
|
+
const isDevApp = packageName === "com.nanofuxion.tamerdevapp";
|
|
1814
|
+
const devClientScoped = path6.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client");
|
|
1815
|
+
const devClientFlat = path6.join(projectRoot, "node_modules", "tamer-dev-client");
|
|
1816
|
+
const devClientPath = fs6.existsSync(path6.join(devClientScoped, "android")) ? devClientScoped : fs6.existsSync(path6.join(devClientFlat, "android")) ? devClientFlat : null;
|
|
1817
|
+
const hasDevClient = packages.some((p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client");
|
|
1818
|
+
if (isDevApp && devClientPath && !hasDevClient) {
|
|
1819
|
+
packages = [{
|
|
1820
|
+
...TAMER_DEV_CLIENT_FALLBACK,
|
|
1821
|
+
packagePath: devClientPath
|
|
1822
|
+
}, ...packages];
|
|
1823
|
+
console.log("\u2139\uFE0F Added tamer-dev-client (fallback for dev-app; lynx.ext.json missing in published package).");
|
|
1824
|
+
}
|
|
1841
1825
|
if (packages.length > 0) {
|
|
1842
1826
|
console.log(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
|
|
1843
1827
|
} else {
|
|
@@ -2006,7 +1990,10 @@ async function syncDevClient(opts) {
|
|
|
2006
1990
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2007
1991
|
process.exit(1);
|
|
2008
1992
|
}
|
|
2009
|
-
const
|
|
1993
|
+
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
1994
|
+
const includeDevClient = opts?.includeDevClient ?? (opts?.forceProduction ? false : !!devClientPkg);
|
|
1995
|
+
const hasDevClient = includeDevClient && devClientPkg;
|
|
1996
|
+
const devMode = hasDevClient ? "embedded" : "standalone";
|
|
2010
1997
|
const devServer = config.devServer ? {
|
|
2011
1998
|
host: config.devServer.host ?? "10.0.2.2",
|
|
2012
1999
|
port: config.devServer.port ?? config.devServer.httpPort ?? 3e3
|
|
@@ -2020,8 +2007,6 @@ async function syncDevClient(opts) {
|
|
|
2020
2007
|
const appDir = path8.join(rootDir, "app");
|
|
2021
2008
|
const mainDir = path8.join(appDir, "src", "main");
|
|
2022
2009
|
const manifestPath = path8.join(mainDir, "AndroidManifest.xml");
|
|
2023
|
-
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
2024
|
-
const hasDevClient = devMode === "embedded" && devClientPkg;
|
|
2025
2010
|
if (hasDevClient) {
|
|
2026
2011
|
const templateDir = path8.join(devClientPkg, "android", "templates");
|
|
2027
2012
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
@@ -2080,54 +2065,41 @@ $1$2`);
|
|
|
2080
2065
|
);
|
|
2081
2066
|
}
|
|
2082
2067
|
fs8.writeFileSync(manifestPath, manifest);
|
|
2083
|
-
console.log(
|
|
2068
|
+
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2084
2069
|
}
|
|
2085
2070
|
}
|
|
2086
2071
|
var syncDevClient_default = syncDevClient;
|
|
2087
2072
|
|
|
2088
2073
|
// src/android/bundle.ts
|
|
2089
2074
|
async function bundleAndDeploy(opts = {}) {
|
|
2090
|
-
const target = opts.target ?? "host";
|
|
2091
2075
|
const release = opts.release === true;
|
|
2092
|
-
const origCwd = process.cwd();
|
|
2093
2076
|
let resolved;
|
|
2094
2077
|
try {
|
|
2095
|
-
|
|
2096
|
-
resolved = resolveDevAppPaths(origCwd);
|
|
2097
|
-
const devAppDir = resolved.projectRoot;
|
|
2098
|
-
const androidDir = resolved.androidDir;
|
|
2099
|
-
if (!fs9.existsSync(androidDir)) {
|
|
2100
|
-
console.log("\u{1F4F1} Creating Tamer Dev App Android project...");
|
|
2101
|
-
await create_default({ target: "dev-app" });
|
|
2102
|
-
}
|
|
2103
|
-
process.chdir(devAppDir);
|
|
2104
|
-
} else {
|
|
2105
|
-
resolved = resolveHostPaths();
|
|
2106
|
-
}
|
|
2078
|
+
resolved = resolveHostPaths();
|
|
2107
2079
|
} catch (error) {
|
|
2108
2080
|
console.error(`\u274C Error loading configuration: ${error.message}`);
|
|
2109
2081
|
process.exit(1);
|
|
2110
2082
|
}
|
|
2111
|
-
const { lynxProjectDir, lynxBundlePath, androidAssetsDir, devClientBundlePath
|
|
2083
|
+
const { projectRoot, lynxProjectDir, lynxBundlePath, androidAssetsDir, devClientBundlePath } = resolved;
|
|
2084
|
+
const devClientPkg = findDevClientPackage(projectRoot);
|
|
2085
|
+
const includeDevClient = !release && !!devClientPkg;
|
|
2112
2086
|
const destinationDir = androidAssetsDir;
|
|
2113
2087
|
autolink_default();
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
if (target === "dev-app") {
|
|
2128
|
-
process.chdir(origCwd);
|
|
2088
|
+
await syncDevClient_default({ includeDevClient });
|
|
2089
|
+
const bundleExists = fs9.existsSync(lynxBundlePath);
|
|
2090
|
+
if (!bundleExists) {
|
|
2091
|
+
try {
|
|
2092
|
+
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
2093
|
+
execSync2("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
2094
|
+
console.log("\u2705 Build completed successfully.");
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
console.error("\u274C Build process failed.");
|
|
2097
|
+
process.exit(1);
|
|
2098
|
+
}
|
|
2099
|
+
} else {
|
|
2100
|
+
console.log("\u{1F4E6} Using pre-built Lynx bundle.");
|
|
2129
2101
|
}
|
|
2130
|
-
if (
|
|
2102
|
+
if (includeDevClient && devClientBundlePath && !fs9.existsSync(devClientBundlePath)) {
|
|
2131
2103
|
const devClientDir = path9.dirname(path9.dirname(devClientBundlePath));
|
|
2132
2104
|
try {
|
|
2133
2105
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
@@ -2140,13 +2112,13 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2140
2112
|
}
|
|
2141
2113
|
try {
|
|
2142
2114
|
fs9.mkdirSync(destinationDir, { recursive: true });
|
|
2143
|
-
if (
|
|
2115
|
+
if (release) {
|
|
2144
2116
|
const devClientAsset = path9.join(destinationDir, "dev-client.lynx.bundle");
|
|
2145
2117
|
if (fs9.existsSync(devClientAsset)) {
|
|
2146
2118
|
fs9.rmSync(devClientAsset);
|
|
2147
2119
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2148
2120
|
}
|
|
2149
|
-
} else if (
|
|
2121
|
+
} else if (includeDevClient && devClientBundlePath && fs9.existsSync(devClientBundlePath)) {
|
|
2150
2122
|
fs9.copyFileSync(devClientBundlePath, path9.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2151
2123
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2152
2124
|
}
|
|
@@ -2168,20 +2140,13 @@ var bundle_default = bundleAndDeploy;
|
|
|
2168
2140
|
import path10 from "path";
|
|
2169
2141
|
import { execSync as execSync3 } from "child_process";
|
|
2170
2142
|
async function buildApk(opts = {}) {
|
|
2171
|
-
const target = opts.target ?? "host";
|
|
2172
2143
|
let resolved;
|
|
2173
2144
|
try {
|
|
2174
|
-
resolved =
|
|
2145
|
+
resolved = resolveHostPaths();
|
|
2175
2146
|
} catch (error) {
|
|
2176
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
2177
|
-
if (target === "dev-app") {
|
|
2178
|
-
console.error(`\u274C ${msg}`);
|
|
2179
|
-
console.error(" Add @tamer4lynx/tamer-dev-app to dependencies, or use -t host to build your app.");
|
|
2180
|
-
process.exit(1);
|
|
2181
|
-
}
|
|
2182
2147
|
throw error;
|
|
2183
2148
|
}
|
|
2184
|
-
await bundle_default({
|
|
2149
|
+
await bundle_default({ release: opts.release });
|
|
2185
2150
|
const androidDir = resolved.androidDir;
|
|
2186
2151
|
const gradlew = path10.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2187
2152
|
const variant = opts.release ? "Release" : "Debug";
|
|
@@ -2262,7 +2227,7 @@ function readAndSubstituteTemplate3(templatePath, vars) {
|
|
|
2262
2227
|
);
|
|
2263
2228
|
}
|
|
2264
2229
|
var create2 = () => {
|
|
2265
|
-
const
|
|
2230
|
+
const generateId = () => randomBytes(12).toString("hex").toUpperCase();
|
|
2266
2231
|
let appName;
|
|
2267
2232
|
let bundleId;
|
|
2268
2233
|
let config;
|
|
@@ -2282,7 +2247,7 @@ var create2 = () => {
|
|
|
2282
2247
|
const projectDir = path12.join(rootDir, appName);
|
|
2283
2248
|
const xcodeprojDir = path12.join(rootDir, `${appName}.xcodeproj`);
|
|
2284
2249
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2285
|
-
function
|
|
2250
|
+
function writeFile2(filePath, content) {
|
|
2286
2251
|
fs11.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
2287
2252
|
fs11.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2288
2253
|
}
|
|
@@ -2292,39 +2257,39 @@ var create2 = () => {
|
|
|
2292
2257
|
}
|
|
2293
2258
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2294
2259
|
const ids = {
|
|
2295
|
-
project:
|
|
2296
|
-
mainGroup:
|
|
2297
|
-
appGroup:
|
|
2298
|
-
productsGroup:
|
|
2299
|
-
frameworksGroup:
|
|
2300
|
-
appFile:
|
|
2301
|
-
appDelegateRef:
|
|
2302
|
-
sceneDelegateRef:
|
|
2303
|
-
sceneDelegateBaseRef:
|
|
2304
|
-
viewControllerRef:
|
|
2305
|
-
assetsRef:
|
|
2306
|
-
lynxProviderRef:
|
|
2307
|
-
lynxInitRef:
|
|
2308
|
-
bridgingHeaderRef:
|
|
2309
|
-
nativeTarget:
|
|
2310
|
-
appDelegateBuildFile:
|
|
2311
|
-
sceneDelegateBuildFile:
|
|
2312
|
-
sceneDelegateSourceBuildFile:
|
|
2313
|
-
viewControllerBuildFile:
|
|
2314
|
-
lynxProviderBuildFile:
|
|
2315
|
-
lynxInitBuildFile:
|
|
2316
|
-
assetsBuildFile:
|
|
2317
|
-
frameworksBuildPhase:
|
|
2318
|
-
resourcesBuildPhase:
|
|
2319
|
-
sourcesBuildPhase:
|
|
2320
|
-
projectBuildConfigList:
|
|
2321
|
-
targetBuildConfigList:
|
|
2322
|
-
projectDebugConfig:
|
|
2323
|
-
projectReleaseConfig:
|
|
2324
|
-
targetDebugConfig:
|
|
2325
|
-
targetReleaseConfig:
|
|
2260
|
+
project: generateId(),
|
|
2261
|
+
mainGroup: generateId(),
|
|
2262
|
+
appGroup: generateId(),
|
|
2263
|
+
productsGroup: generateId(),
|
|
2264
|
+
frameworksGroup: generateId(),
|
|
2265
|
+
appFile: generateId(),
|
|
2266
|
+
appDelegateRef: generateId(),
|
|
2267
|
+
sceneDelegateRef: generateId(),
|
|
2268
|
+
sceneDelegateBaseRef: generateId(),
|
|
2269
|
+
viewControllerRef: generateId(),
|
|
2270
|
+
assetsRef: generateId(),
|
|
2271
|
+
lynxProviderRef: generateId(),
|
|
2272
|
+
lynxInitRef: generateId(),
|
|
2273
|
+
bridgingHeaderRef: generateId(),
|
|
2274
|
+
nativeTarget: generateId(),
|
|
2275
|
+
appDelegateBuildFile: generateId(),
|
|
2276
|
+
sceneDelegateBuildFile: generateId(),
|
|
2277
|
+
sceneDelegateSourceBuildFile: generateId(),
|
|
2278
|
+
viewControllerBuildFile: generateId(),
|
|
2279
|
+
lynxProviderBuildFile: generateId(),
|
|
2280
|
+
lynxInitBuildFile: generateId(),
|
|
2281
|
+
assetsBuildFile: generateId(),
|
|
2282
|
+
frameworksBuildPhase: generateId(),
|
|
2283
|
+
resourcesBuildPhase: generateId(),
|
|
2284
|
+
sourcesBuildPhase: generateId(),
|
|
2285
|
+
projectBuildConfigList: generateId(),
|
|
2286
|
+
targetBuildConfigList: generateId(),
|
|
2287
|
+
projectDebugConfig: generateId(),
|
|
2288
|
+
projectReleaseConfig: generateId(),
|
|
2289
|
+
targetDebugConfig: generateId(),
|
|
2290
|
+
targetReleaseConfig: generateId()
|
|
2326
2291
|
};
|
|
2327
|
-
|
|
2292
|
+
writeFile2(path12.join(rootDir, "Podfile"), `
|
|
2328
2293
|
source 'https://cdn.cocoapods.org/'
|
|
2329
2294
|
|
|
2330
2295
|
platform :ios, '13.0'
|
|
@@ -2388,11 +2353,11 @@ end
|
|
|
2388
2353
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2389
2354
|
const srcPath = path12.join(templateDir, f);
|
|
2390
2355
|
if (fs11.existsSync(srcPath)) {
|
|
2391
|
-
|
|
2356
|
+
writeFile2(path12.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2392
2357
|
}
|
|
2393
2358
|
}
|
|
2394
2359
|
} else {
|
|
2395
|
-
|
|
2360
|
+
writeFile2(path12.join(projectDir, "AppDelegate.swift"), `
|
|
2396
2361
|
import UIKit
|
|
2397
2362
|
|
|
2398
2363
|
@UIApplicationMain
|
|
@@ -2407,7 +2372,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2407
2372
|
}
|
|
2408
2373
|
}
|
|
2409
2374
|
`);
|
|
2410
|
-
|
|
2375
|
+
writeFile2(path12.join(projectDir, "SceneDelegate.swift"), `
|
|
2411
2376
|
import UIKit
|
|
2412
2377
|
|
|
2413
2378
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2421,7 +2386,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2421
2386
|
}
|
|
2422
2387
|
}
|
|
2423
2388
|
`);
|
|
2424
|
-
|
|
2389
|
+
writeFile2(path12.join(projectDir, "ViewController.swift"), `
|
|
2425
2390
|
import UIKit
|
|
2426
2391
|
import Lynx
|
|
2427
2392
|
import tamerinsets
|
|
@@ -2491,7 +2456,7 @@ class ViewController: UIViewController {
|
|
|
2491
2456
|
}
|
|
2492
2457
|
}
|
|
2493
2458
|
`);
|
|
2494
|
-
|
|
2459
|
+
writeFile2(path12.join(projectDir, "LynxProvider.swift"), `
|
|
2495
2460
|
import Foundation
|
|
2496
2461
|
|
|
2497
2462
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2510,7 +2475,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2510
2475
|
}
|
|
2511
2476
|
}
|
|
2512
2477
|
`);
|
|
2513
|
-
|
|
2478
|
+
writeFile2(path12.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2514
2479
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2515
2480
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2516
2481
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2550,7 +2515,7 @@ final class LynxInitProcessor {
|
|
|
2550
2515
|
}
|
|
2551
2516
|
`);
|
|
2552
2517
|
}
|
|
2553
|
-
|
|
2518
|
+
writeFile2(path12.join(projectDir, bridgingHeader), `
|
|
2554
2519
|
#import <Lynx/LynxConfig.h>
|
|
2555
2520
|
#import <Lynx/LynxEnv.h>
|
|
2556
2521
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2559,7 +2524,7 @@ final class LynxInitProcessor {
|
|
|
2559
2524
|
#import <SDWebImage/SDWebImage.h>
|
|
2560
2525
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2561
2526
|
`);
|
|
2562
|
-
|
|
2527
|
+
writeFile2(path12.join(projectDir, "Info.plist"), `
|
|
2563
2528
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2564
2529
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2565
2530
|
<plist version="1.0">
|
|
@@ -2635,13 +2600,13 @@ final class LynxInitProcessor {
|
|
|
2635
2600
|
const ext = path12.extname(iconPaths.source) || ".png";
|
|
2636
2601
|
const icon1024 = `Icon-1024${ext}`;
|
|
2637
2602
|
fs11.copyFileSync(iconPaths.source, path12.join(appIconDir, icon1024));
|
|
2638
|
-
|
|
2603
|
+
writeFile2(path12.join(appIconDir, "Contents.json"), JSON.stringify({
|
|
2639
2604
|
images: [{ filename: icon1024, idiom: "universal", platform: "ios", size: "1024x1024" }],
|
|
2640
2605
|
info: { author: "xcode", version: 1 }
|
|
2641
2606
|
}, null, 2));
|
|
2642
2607
|
console.log("\u2705 Copied app icon from tamer.config.json icon.source");
|
|
2643
2608
|
} else {
|
|
2644
|
-
|
|
2609
|
+
writeFile2(path12.join(appIconDir, "Contents.json"), `
|
|
2645
2610
|
{
|
|
2646
2611
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
2647
2612
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
@@ -2649,7 +2614,7 @@ final class LynxInitProcessor {
|
|
|
2649
2614
|
`);
|
|
2650
2615
|
}
|
|
2651
2616
|
fs11.mkdirSync(xcodeprojDir, { recursive: true });
|
|
2652
|
-
|
|
2617
|
+
writeFile2(path12.join(xcodeprojDir, "project.pbxproj"), `
|
|
2653
2618
|
// !$*UTF8*$!
|
|
2654
2619
|
{
|
|
2655
2620
|
archiveVersion = 1;
|
|
@@ -3668,9 +3633,9 @@ function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {})
|
|
|
3668
3633
|
function syncHostIos(opts) {
|
|
3669
3634
|
const resolved = resolveHostPaths();
|
|
3670
3635
|
const appName = resolved.config.ios?.appName;
|
|
3671
|
-
const devMode = resolveDevMode(resolved.config);
|
|
3672
3636
|
const release = opts?.release === true;
|
|
3673
|
-
const
|
|
3637
|
+
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
3638
|
+
const useDevClient = opts?.includeDevClient ?? (!release && !!devClientPkg);
|
|
3674
3639
|
if (!appName) {
|
|
3675
3640
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3676
3641
|
}
|
|
@@ -3692,28 +3657,28 @@ function syncHostIos(opts) {
|
|
|
3692
3657
|
}
|
|
3693
3658
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "SceneDelegate.swift");
|
|
3694
3659
|
if (useDevClient) {
|
|
3695
|
-
const
|
|
3660
|
+
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3696
3661
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3697
3662
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3698
3663
|
writeFile(path14.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3699
3664
|
writeFile(path14.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3700
3665
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3701
|
-
const devTPContent = readTemplateOrFallback(
|
|
3666
|
+
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3702
3667
|
if (devTPContent) {
|
|
3703
3668
|
writeFile(path14.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3704
3669
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3705
3670
|
}
|
|
3706
|
-
const projectVCContent = readTemplateOrFallback(
|
|
3671
|
+
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3707
3672
|
if (projectVCContent) {
|
|
3708
3673
|
writeFile(path14.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3709
3674
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3710
3675
|
}
|
|
3711
|
-
const devCMContent = readTemplateOrFallback(
|
|
3676
|
+
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3712
3677
|
if (devCMContent) {
|
|
3713
3678
|
writeFile(path14.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3714
3679
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3715
3680
|
}
|
|
3716
|
-
const qrContent = readTemplateOrFallback(
|
|
3681
|
+
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3717
3682
|
if (qrContent) {
|
|
3718
3683
|
writeFile(path14.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3719
3684
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
@@ -3730,7 +3695,6 @@ var syncHost_default = syncHostIos;
|
|
|
3730
3695
|
|
|
3731
3696
|
// src/ios/bundle.ts
|
|
3732
3697
|
function bundleAndDeploy2(opts = {}) {
|
|
3733
|
-
const target = opts.target ?? "host";
|
|
3734
3698
|
const release = opts.release === true;
|
|
3735
3699
|
let resolved;
|
|
3736
3700
|
try {
|
|
@@ -3742,16 +3706,13 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3742
3706
|
console.error(`\u274C Error loading configuration: ${error.message}`);
|
|
3743
3707
|
process.exit(1);
|
|
3744
3708
|
}
|
|
3709
|
+
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
3710
|
+
const includeDevClient = !release && !!devClientPkg;
|
|
3745
3711
|
const appName = resolved.config.ios.appName;
|
|
3746
3712
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
3747
3713
|
const destinationDir = path15.join(resolved.iosDir, appName);
|
|
3748
3714
|
const destinationBundlePath = path15.join(destinationDir, resolved.lynxBundleFile);
|
|
3749
|
-
|
|
3750
|
-
if (target === "dev-app") {
|
|
3751
|
-
console.error("\u274C iOS dev-app target not yet implemented.");
|
|
3752
|
-
process.exit(1);
|
|
3753
|
-
}
|
|
3754
|
-
syncHost_default({ release });
|
|
3715
|
+
syncHost_default({ release, includeDevClient });
|
|
3755
3716
|
autolink_default2();
|
|
3756
3717
|
try {
|
|
3757
3718
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -3782,32 +3743,22 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3782
3743
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
3783
3744
|
}
|
|
3784
3745
|
}
|
|
3785
|
-
if (
|
|
3746
|
+
if (includeDevClient && devClientPkg) {
|
|
3786
3747
|
const devClientBundle = path15.join(destinationDir, "dev-client.lynx.bundle");
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
const pbxprojPath2 = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3801
|
-
if (fs14.existsSync(pbxprojPath2)) {
|
|
3802
|
-
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
}
|
|
3806
|
-
} else {
|
|
3807
|
-
if (!fs14.existsSync(devClientBundle)) {
|
|
3808
|
-
fs14.writeFileSync(devClientBundle, "");
|
|
3748
|
+
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
3749
|
+
try {
|
|
3750
|
+
execSync6("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
3751
|
+
} catch {
|
|
3752
|
+
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
3753
|
+
}
|
|
3754
|
+
const builtBundle = path15.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
3755
|
+
if (fs14.existsSync(builtBundle)) {
|
|
3756
|
+
fs14.copyFileSync(builtBundle, devClientBundle);
|
|
3757
|
+
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
3758
|
+
const pbxprojPath2 = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3759
|
+
if (fs14.existsSync(pbxprojPath2)) {
|
|
3760
|
+
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
3809
3761
|
}
|
|
3810
|
-
console.log("\u2139\uFE0F Skipped dev-client bundle (release build)");
|
|
3811
3762
|
}
|
|
3812
3763
|
}
|
|
3813
3764
|
} catch (error) {
|
|
@@ -3819,1363 +3770,140 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3819
3770
|
var bundle_default2 = bundleAndDeploy2;
|
|
3820
3771
|
|
|
3821
3772
|
// src/ios/build.ts
|
|
3822
|
-
import fs16 from "fs";
|
|
3823
|
-
import path17 from "path";
|
|
3824
|
-
import { execSync as execSync8 } from "child_process";
|
|
3825
|
-
|
|
3826
|
-
// src/ios/syncDevClient.ts
|
|
3827
3773
|
import fs15 from "fs";
|
|
3828
3774
|
import path16 from "path";
|
|
3829
3775
|
import { execSync as execSync7 } from "child_process";
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
(
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
}
|
|
3838
|
-
var APP_NAME = "TamerDevApp";
|
|
3839
|
-
var BUNDLE_ID = "com.nanofuxion.tamerdevapp";
|
|
3840
|
-
var BRIDGING_HEADER = `${APP_NAME}-Bridging-Header.h`;
|
|
3841
|
-
function generateId() {
|
|
3842
|
-
return randomBytes2(12).toString("hex").toUpperCase();
|
|
3843
|
-
}
|
|
3844
|
-
function writeFile2(filePath, content) {
|
|
3845
|
-
fs15.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
3846
|
-
fs15.writeFileSync(filePath, content, "utf8");
|
|
3847
|
-
}
|
|
3848
|
-
function getAppDelegateSwift2() {
|
|
3849
|
-
return `import UIKit
|
|
3850
|
-
|
|
3851
|
-
@UIApplicationMain
|
|
3852
|
-
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
3853
|
-
var window: UIWindow?
|
|
3854
|
-
|
|
3855
|
-
func application(
|
|
3856
|
-
_ application: UIApplication,
|
|
3857
|
-
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
3858
|
-
) -> Bool {
|
|
3859
|
-
LynxInitProcessor.shared.setupEnvironment()
|
|
3860
|
-
|
|
3861
|
-
window = UIWindow(frame: UIScreen.main.bounds)
|
|
3862
|
-
window?.rootViewController = DevLauncherViewController()
|
|
3863
|
-
window?.makeKeyAndVisible()
|
|
3864
|
-
return true
|
|
3865
|
-
}
|
|
3866
|
-
|
|
3867
|
-
func application(_ app: UIApplication, open url: URL,
|
|
3868
|
-
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
3869
|
-
if url.scheme == "tamerdevapp", let host = url.host, host == "project" {
|
|
3870
|
-
presentProjectViewController()
|
|
3871
|
-
}
|
|
3872
|
-
return true
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
@objc func presentProjectViewController() {
|
|
3876
|
-
guard let root = window?.rootViewController else { return }
|
|
3877
|
-
let projectVC = ProjectViewController()
|
|
3878
|
-
projectVC.modalPresentationStyle = .fullScreen
|
|
3879
|
-
root.present(projectVC, animated: true)
|
|
3776
|
+
function findBootedSimulator() {
|
|
3777
|
+
try {
|
|
3778
|
+
const out = execSync7("xcrun simctl list devices --json", { encoding: "utf8" });
|
|
3779
|
+
const json = JSON.parse(out);
|
|
3780
|
+
for (const runtimes of Object.values(json.devices)) {
|
|
3781
|
+
for (const device of runtimes) {
|
|
3782
|
+
if (device.state === "Booted") return device.udid;
|
|
3783
|
+
}
|
|
3880
3784
|
}
|
|
3785
|
+
} catch {
|
|
3786
|
+
}
|
|
3787
|
+
return null;
|
|
3881
3788
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3789
|
+
async function buildIpa(opts = {}) {
|
|
3790
|
+
const resolved = resolveHostPaths();
|
|
3791
|
+
if (!resolved.config.ios?.appName) {
|
|
3792
|
+
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3793
|
+
}
|
|
3794
|
+
const appName = resolved.config.ios.appName;
|
|
3795
|
+
const bundleId = resolved.config.ios.bundleId;
|
|
3796
|
+
const iosDir = resolved.iosDir;
|
|
3797
|
+
const configuration = opts.release ? "Release" : "Debug";
|
|
3798
|
+
bundle_default2({ release: opts.release });
|
|
3799
|
+
const scheme = appName;
|
|
3800
|
+
const workspacePath = path16.join(iosDir, `${appName}.xcworkspace`);
|
|
3801
|
+
const projectPath = path16.join(iosDir, `${appName}.xcodeproj`);
|
|
3802
|
+
const xcproject = fs15.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
3803
|
+
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
3804
|
+
const derivedDataPath = path16.join(iosDir, "build");
|
|
3805
|
+
const sdk = opts.install ? "iphonesimulator" : "iphoneos";
|
|
3806
|
+
console.log(`
|
|
3807
|
+
\u{1F528} Building ${configuration} (${sdk})...`);
|
|
3808
|
+
execSync7(
|
|
3809
|
+
`xcodebuild ${flag} "${xcproject}" -scheme "${scheme}" -configuration ${configuration} -sdk ${sdk} -derivedDataPath "${derivedDataPath}"`,
|
|
3810
|
+
{ stdio: "inherit", cwd: iosDir }
|
|
3811
|
+
);
|
|
3812
|
+
console.log(`\u2705 Build completed.`);
|
|
3813
|
+
if (opts.install) {
|
|
3814
|
+
const appGlob = path16.join(
|
|
3815
|
+
derivedDataPath,
|
|
3816
|
+
"Build",
|
|
3817
|
+
"Products",
|
|
3818
|
+
`${configuration}-iphonesimulator`,
|
|
3819
|
+
`${appName}.app`
|
|
3820
|
+
);
|
|
3821
|
+
if (!fs15.existsSync(appGlob)) {
|
|
3822
|
+
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
3823
|
+
process.exit(1);
|
|
3904
3824
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
applyFullscreenLayout(to: lynxView)
|
|
3910
|
-
}
|
|
3825
|
+
const udid = findBootedSimulator();
|
|
3826
|
+
if (!udid) {
|
|
3827
|
+
console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
|
|
3828
|
+
process.exit(1);
|
|
3911
3829
|
}
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3830
|
+
console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
|
|
3831
|
+
execSync7(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
|
|
3832
|
+
if (bundleId) {
|
|
3833
|
+
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
3834
|
+
execSync7(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
|
|
3835
|
+
console.log("\u2705 App launched.");
|
|
3836
|
+
} else {
|
|
3837
|
+
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
3916
3838
|
}
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
var build_default2 = buildIpa;
|
|
3917
3842
|
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3843
|
+
// src/common/init.ts
|
|
3844
|
+
import fs16 from "fs";
|
|
3845
|
+
import path17 from "path";
|
|
3846
|
+
import readline from "readline";
|
|
3847
|
+
var rl = readline.createInterface({
|
|
3848
|
+
input: process.stdin,
|
|
3849
|
+
output: process.stdout,
|
|
3850
|
+
terminal: false
|
|
3851
|
+
});
|
|
3852
|
+
function ask(question) {
|
|
3853
|
+
return new Promise((resolve) => {
|
|
3854
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
async function init() {
|
|
3858
|
+
console.log("Tamer4Lynx Init: Let's set up your tamer.config.json\n");
|
|
3859
|
+
const androidAppName = await ask("Android app name: ");
|
|
3860
|
+
const androidPackageName = await ask("Android package name (e.g. com.example.app): ");
|
|
3861
|
+
let androidSdk = await ask("Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME): ");
|
|
3862
|
+
if (androidSdk.startsWith("$") && /^[A-Z0-9_]+$/.test(androidSdk.slice(1))) {
|
|
3863
|
+
const envVar = androidSdk.slice(1);
|
|
3864
|
+
const envValue = process.env[envVar];
|
|
3865
|
+
if (envValue) {
|
|
3866
|
+
androidSdk = envValue;
|
|
3867
|
+
console.log(`Resolved ${androidSdk} from $${envVar}`);
|
|
3868
|
+
} else {
|
|
3869
|
+
console.warn(`Environment variable $${envVar} not found. SDK path will be left as-is.`);
|
|
3939
3870
|
}
|
|
3871
|
+
}
|
|
3872
|
+
const useSame = await ask("Use same name and bundle ID for iOS as Android? (y/N): ");
|
|
3873
|
+
let iosAppName;
|
|
3874
|
+
let iosBundleId;
|
|
3875
|
+
if (/^y(es)?$/i.test(useSame)) {
|
|
3876
|
+
iosAppName = androidAppName;
|
|
3877
|
+
iosBundleId = androidPackageName;
|
|
3878
|
+
} else {
|
|
3879
|
+
iosAppName = await ask("iOS app name: ");
|
|
3880
|
+
iosBundleId = await ask("iOS bundle ID (e.g. com.example.app): ");
|
|
3881
|
+
}
|
|
3882
|
+
const lynxProject = await ask("Lynx project path (relative to project root, e.g. packages/example) [optional]: ");
|
|
3883
|
+
const config = {
|
|
3884
|
+
android: {
|
|
3885
|
+
appName: androidAppName || void 0,
|
|
3886
|
+
packageName: androidPackageName || void 0,
|
|
3887
|
+
sdk: androidSdk || void 0
|
|
3888
|
+
},
|
|
3889
|
+
ios: {
|
|
3890
|
+
appName: iosAppName || void 0,
|
|
3891
|
+
bundleId: iosBundleId || void 0
|
|
3892
|
+
},
|
|
3893
|
+
paths: { androidDir: "android", iosDir: "ios" }
|
|
3894
|
+
};
|
|
3895
|
+
if (lynxProject) config.lynxProject = lynxProject;
|
|
3896
|
+
const configPath = path17.join(process.cwd(), "tamer.config.json");
|
|
3897
|
+
fs16.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3898
|
+
console.log(`
|
|
3899
|
+
\u2705 Generated tamer.config.json at ${configPath}`);
|
|
3900
|
+
rl.close();
|
|
3901
|
+
}
|
|
3902
|
+
var init_default = init;
|
|
3940
3903
|
|
|
3941
|
-
|
|
3942
|
-
let bounds = fullscreenBounds()
|
|
3943
|
-
let size = bounds.size
|
|
3944
|
-
lynxView.frame = bounds
|
|
3945
|
-
lynxView.updateScreenMetrics(withWidth: size.width, height: size.height)
|
|
3946
|
-
lynxView.updateViewport(withPreferredLayoutWidth: size.width, preferredLayoutHeight: size.height, needLayout: true)
|
|
3947
|
-
lynxView.preferredLayoutWidth = size.width
|
|
3948
|
-
lynxView.preferredLayoutHeight = size.height
|
|
3949
|
-
lynxView.layoutWidthMode = .exact
|
|
3950
|
-
lynxView.layoutHeightMode = .exact
|
|
3951
|
-
logViewport("devclient apply", lynxView: lynxView)
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
|
-
private func fullscreenBounds() -> CGRect {
|
|
3955
|
-
let bounds = view.bounds
|
|
3956
|
-
if bounds.width > 0, bounds.height > 0 {
|
|
3957
|
-
return bounds
|
|
3958
|
-
}
|
|
3959
|
-
return UIScreen.main.bounds
|
|
3960
|
-
}
|
|
3961
|
-
|
|
3962
|
-
private func logViewport(_ label: String, lynxView: LynxView) {
|
|
3963
|
-
let rootWidth = lynxView.rootWidth()
|
|
3964
|
-
let rootHeight = lynxView.rootHeight()
|
|
3965
|
-
let intrinsic = lynxView.intrinsicContentSize
|
|
3966
|
-
NSLog("[DevLauncher] %@ view=%@ safe=%@ lynxFrame=%@ lynxBounds=%@ root=%0.2fx%0.2f intrinsic=%@", label, NSCoder.string(for: view.bounds), NSCoder.string(for: view.safeAreaInsets), NSCoder.string(for: lynxView.frame), NSCoder.string(for: lynxView.bounds), rootWidth, rootHeight, NSCoder.string(for: intrinsic))
|
|
3967
|
-
}
|
|
3968
|
-
|
|
3969
|
-
private func setupDevClientModule() {
|
|
3970
|
-
DevClientModule.presentQRScanner = { [weak self] completion in
|
|
3971
|
-
let scanner = QRScannerViewController()
|
|
3972
|
-
scanner.onResult = { url in
|
|
3973
|
-
scanner.dismiss(animated: true) { completion(url) }
|
|
3974
|
-
}
|
|
3975
|
-
scanner.modalPresentationStyle = .fullScreen
|
|
3976
|
-
self?.present(scanner, animated: true)
|
|
3977
|
-
}
|
|
3978
|
-
|
|
3979
|
-
DevClientModule.reloadProjectHandler = { [weak self] in
|
|
3980
|
-
guard let self = self else { return }
|
|
3981
|
-
let projectVC = ProjectViewController()
|
|
3982
|
-
projectVC.modalPresentationStyle = .fullScreen
|
|
3983
|
-
self.present(projectVC, animated: true)
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
}
|
|
3987
|
-
`;
|
|
3988
|
-
}
|
|
3989
|
-
function getProjectViewControllerSwift() {
|
|
3990
|
-
return `import UIKit
|
|
3991
|
-
import Lynx
|
|
3992
|
-
import tamerinsets
|
|
3993
|
-
|
|
3994
|
-
class ProjectViewController: UIViewController {
|
|
3995
|
-
private var lynxView: LynxView?
|
|
3996
|
-
private var devClientManager: DevClientManager?
|
|
3997
|
-
|
|
3998
|
-
override func viewDidLoad() {
|
|
3999
|
-
super.viewDidLoad()
|
|
4000
|
-
view.backgroundColor = .black
|
|
4001
|
-
edgesForExtendedLayout = .all
|
|
4002
|
-
extendedLayoutIncludesOpaqueBars = true
|
|
4003
|
-
additionalSafeAreaInsets = .zero
|
|
4004
|
-
view.insetsLayoutMarginsFromSafeArea = false
|
|
4005
|
-
view.preservesSuperviewLayoutMargins = false
|
|
4006
|
-
viewRespectsSystemMinimumLayoutMargins = false
|
|
4007
|
-
setupLynxView()
|
|
4008
|
-
devClientManager = DevClientManager(onReload: { [weak self] in
|
|
4009
|
-
self?.reloadLynxView()
|
|
4010
|
-
})
|
|
4011
|
-
devClientManager?.connect()
|
|
4012
|
-
}
|
|
4013
|
-
|
|
4014
|
-
override func viewDidLayoutSubviews() {
|
|
4015
|
-
super.viewDidLayoutSubviews()
|
|
4016
|
-
if let lynxView = lynxView {
|
|
4017
|
-
applyFullscreenLayout(to: lynxView)
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
|
|
4021
|
-
override func viewSafeAreaInsetsDidChange() {
|
|
4022
|
-
super.viewSafeAreaInsetsDidChange()
|
|
4023
|
-
TamerInsetsModule.reRequestInsets()
|
|
4024
|
-
}
|
|
4025
|
-
|
|
4026
|
-
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
|
4027
|
-
|
|
4028
|
-
private func buildLynxView() -> LynxView {
|
|
4029
|
-
let size = fullscreenBounds().size
|
|
4030
|
-
let lv = LynxView { builder in
|
|
4031
|
-
builder.config = LynxConfig(provider: DevTemplateProvider())
|
|
4032
|
-
builder.screenSize = size
|
|
4033
|
-
builder.fontScale = 1.0
|
|
4034
|
-
}
|
|
4035
|
-
lv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
4036
|
-
lv.insetsLayoutMarginsFromSafeArea = false
|
|
4037
|
-
lv.preservesSuperviewLayoutMargins = false
|
|
4038
|
-
applyFullscreenLayout(to: lv)
|
|
4039
|
-
return lv
|
|
4040
|
-
}
|
|
4041
|
-
|
|
4042
|
-
private func setupLynxView() {
|
|
4043
|
-
let lv = buildLynxView()
|
|
4044
|
-
view.addSubview(lv)
|
|
4045
|
-
lv.loadTemplate(fromURL: "main.lynx.bundle", initData: nil)
|
|
4046
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self, weak lv] in
|
|
4047
|
-
guard let self, let lv else { return }
|
|
4048
|
-
self.logViewport("project post-load", lynxView: lv)
|
|
4049
|
-
self.applyFullscreenLayout(to: lv)
|
|
4050
|
-
}
|
|
4051
|
-
self.lynxView = lv
|
|
4052
|
-
}
|
|
4053
|
-
|
|
4054
|
-
private func reloadLynxView() {
|
|
4055
|
-
lynxView?.removeFromSuperview()
|
|
4056
|
-
lynxView = nil
|
|
4057
|
-
setupLynxView()
|
|
4058
|
-
}
|
|
4059
|
-
|
|
4060
|
-
private func applyFullscreenLayout(to lynxView: LynxView) {
|
|
4061
|
-
let bounds = fullscreenBounds()
|
|
4062
|
-
let size = bounds.size
|
|
4063
|
-
lynxView.frame = bounds
|
|
4064
|
-
lynxView.updateScreenMetrics(withWidth: size.width, height: size.height)
|
|
4065
|
-
lynxView.updateViewport(withPreferredLayoutWidth: size.width, preferredLayoutHeight: size.height, needLayout: true)
|
|
4066
|
-
lynxView.preferredLayoutWidth = size.width
|
|
4067
|
-
lynxView.preferredLayoutHeight = size.height
|
|
4068
|
-
lynxView.layoutWidthMode = .exact
|
|
4069
|
-
lynxView.layoutHeightMode = .exact
|
|
4070
|
-
logViewport("project apply", lynxView: lynxView)
|
|
4071
|
-
}
|
|
4072
|
-
|
|
4073
|
-
private func fullscreenBounds() -> CGRect {
|
|
4074
|
-
let bounds = view.bounds
|
|
4075
|
-
if bounds.width > 0, bounds.height > 0 {
|
|
4076
|
-
return bounds
|
|
4077
|
-
}
|
|
4078
|
-
return UIScreen.main.bounds
|
|
4079
|
-
}
|
|
4080
|
-
|
|
4081
|
-
private func logViewport(_ label: String, lynxView: LynxView) {
|
|
4082
|
-
let rootWidth = lynxView.rootWidth()
|
|
4083
|
-
let rootHeight = lynxView.rootHeight()
|
|
4084
|
-
let intrinsic = lynxView.intrinsicContentSize
|
|
4085
|
-
NSLog("[ProjectVC] %@ view=%@ safe=%@ lynxFrame=%@ lynxBounds=%@ root=%0.2fx%0.2f intrinsic=%@", label, NSCoder.string(for: view.bounds), NSCoder.string(for: view.safeAreaInsets), NSCoder.string(for: lynxView.frame), NSCoder.string(for: lynxView.bounds), rootWidth, rootHeight, NSCoder.string(for: intrinsic))
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
override func viewWillDisappear(_ animated: Bool) {
|
|
4089
|
-
super.viewWillDisappear(animated)
|
|
4090
|
-
if isBeingDismissed || isMovingFromParent {
|
|
4091
|
-
devClientManager?.disconnect()
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
}
|
|
4095
|
-
`;
|
|
4096
|
-
}
|
|
4097
|
-
function getDevTemplateProviderSwift() {
|
|
4098
|
-
return `import Foundation
|
|
4099
|
-
import Lynx
|
|
4100
|
-
import tamerdevclient
|
|
4101
|
-
|
|
4102
|
-
class DevTemplateProvider: NSObject, LynxTemplateProvider {
|
|
4103
|
-
private static let devClientBundle = "dev-client.lynx.bundle"
|
|
4104
|
-
|
|
4105
|
-
func loadTemplate(withUrl url: String!, onComplete callback: LynxTemplateLoadBlock!) {
|
|
4106
|
-
DispatchQueue.global(qos: .background).async {
|
|
4107
|
-
// dev-client.lynx.bundle always loads from the embedded asset
|
|
4108
|
-
if url == Self.devClientBundle || url?.hasSuffix("/" + Self.devClientBundle) == true {
|
|
4109
|
-
self.loadFromBundle(url: Self.devClientBundle, callback: callback)
|
|
4110
|
-
return
|
|
4111
|
-
}
|
|
4112
|
-
|
|
4113
|
-
// Try the dev server first
|
|
4114
|
-
if let devUrl = DevServerPrefs.getUrl(), !devUrl.isEmpty {
|
|
4115
|
-
let origin: String
|
|
4116
|
-
if let parsed = URL(string: devUrl) {
|
|
4117
|
-
let scheme = parsed.scheme ?? "http"
|
|
4118
|
-
let host = parsed.host ?? "localhost"
|
|
4119
|
-
let port = parsed.port.map { ":\\($0)" } ?? ""
|
|
4120
|
-
origin = "\\(scheme)://\\(host)\\(port)"
|
|
4121
|
-
} else {
|
|
4122
|
-
origin = devUrl
|
|
4123
|
-
}
|
|
4124
|
-
|
|
4125
|
-
let candidates = ["/\\(url!)", "/tamer-dev-app/\\(url!)"]
|
|
4126
|
-
for candidate in candidates {
|
|
4127
|
-
if let data = self.httpFetch(url: origin + candidate) {
|
|
4128
|
-
callback?(data, nil)
|
|
4129
|
-
return
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4133
|
-
|
|
4134
|
-
// Fall back to embedded bundle
|
|
4135
|
-
self.loadFromBundle(url: url, callback: callback)
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
|
|
4139
|
-
private func loadFromBundle(url: String?, callback: LynxTemplateLoadBlock!) {
|
|
4140
|
-
guard let url = url,
|
|
4141
|
-
let bundleUrl = Bundle.main.url(forResource: url, withExtension: nil),
|
|
4142
|
-
let data = try? Data(contentsOf: bundleUrl) else {
|
|
4143
|
-
let err = NSError(domain: "DevTemplateProvider", code: 404,
|
|
4144
|
-
userInfo: [NSLocalizedDescriptionKey: "Bundle not found: \\(url ?? "nil")"])
|
|
4145
|
-
callback?(nil, err)
|
|
4146
|
-
return
|
|
4147
|
-
}
|
|
4148
|
-
callback?(data, nil)
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
private func httpFetch(url: String) -> Data? {
|
|
4152
|
-
guard let u = URL(string: url) else { return nil }
|
|
4153
|
-
var req = URLRequest(url: u)
|
|
4154
|
-
req.timeoutInterval = 10
|
|
4155
|
-
var result: Data?
|
|
4156
|
-
let sem = DispatchSemaphore(value: 0)
|
|
4157
|
-
URLSession.shared.dataTask(with: req) { data, response, _ in
|
|
4158
|
-
if let http = response as? HTTPURLResponse, http.statusCode == 200 {
|
|
4159
|
-
result = data
|
|
4160
|
-
}
|
|
4161
|
-
sem.signal()
|
|
4162
|
-
}.resume()
|
|
4163
|
-
sem.wait()
|
|
4164
|
-
return result
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
`;
|
|
4168
|
-
}
|
|
4169
|
-
function getDevClientManagerSwift() {
|
|
4170
|
-
return `import Foundation
|
|
4171
|
-
import tamerdevclient
|
|
4172
|
-
|
|
4173
|
-
class DevClientManager {
|
|
4174
|
-
private var webSocketTask: URLSessionWebSocketTask?
|
|
4175
|
-
private let onReload: () -> Void
|
|
4176
|
-
private var session: URLSession?
|
|
4177
|
-
|
|
4178
|
-
init(onReload: @escaping () -> Void) {
|
|
4179
|
-
self.onReload = onReload
|
|
4180
|
-
}
|
|
4181
|
-
|
|
4182
|
-
func connect() {
|
|
4183
|
-
guard let devUrl = DevServerPrefs.getUrl(), !devUrl.isEmpty else { return }
|
|
4184
|
-
guard let base = URL(string: devUrl) else { return }
|
|
4185
|
-
|
|
4186
|
-
let scheme = (base.scheme == "https") ? "wss" : "ws"
|
|
4187
|
-
let host = base.host ?? "localhost"
|
|
4188
|
-
let port = base.port.map { ":\\($0)" } ?? ""
|
|
4189
|
-
let rawPath = base.path.isEmpty ? "/" : base.path
|
|
4190
|
-
let dir = rawPath.hasSuffix("/") ? rawPath : rawPath + "/"
|
|
4191
|
-
guard let wsUrl = URL(string: "\\(scheme)://\\(host)\\(port)\\(dir)__hmr") else { return }
|
|
4192
|
-
|
|
4193
|
-
session = URLSession(configuration: .default)
|
|
4194
|
-
let task = session!.webSocketTask(with: wsUrl)
|
|
4195
|
-
webSocketTask = task
|
|
4196
|
-
task.resume()
|
|
4197
|
-
receive()
|
|
4198
|
-
}
|
|
4199
|
-
|
|
4200
|
-
private func receive() {
|
|
4201
|
-
webSocketTask?.receive { [weak self] result in
|
|
4202
|
-
guard let self = self else { return }
|
|
4203
|
-
switch result {
|
|
4204
|
-
case .success(let msg):
|
|
4205
|
-
if case .string(let text) = msg, text.contains("\\"type\\":\\"reload\\"") {
|
|
4206
|
-
DispatchQueue.main.async { self.onReload() }
|
|
4207
|
-
}
|
|
4208
|
-
self.receive()
|
|
4209
|
-
case .failure:
|
|
4210
|
-
break
|
|
4211
|
-
}
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
|
|
4215
|
-
func disconnect() {
|
|
4216
|
-
webSocketTask?.cancel(with: .normalClosure, reason: nil)
|
|
4217
|
-
webSocketTask = nil
|
|
4218
|
-
session = nil
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
`;
|
|
4222
|
-
}
|
|
4223
|
-
function getQRScannerViewControllerSwift() {
|
|
4224
|
-
return `import UIKit
|
|
4225
|
-
import AVFoundation
|
|
4226
|
-
|
|
4227
|
-
class QRScannerViewController: UIViewController {
|
|
4228
|
-
var onResult: ((String?) -> Void)?
|
|
4229
|
-
|
|
4230
|
-
private var captureSession: AVCaptureSession?
|
|
4231
|
-
private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
4232
|
-
|
|
4233
|
-
override func viewDidLoad() {
|
|
4234
|
-
super.viewDidLoad()
|
|
4235
|
-
view.backgroundColor = .black
|
|
4236
|
-
setupCamera()
|
|
4237
|
-
addCancelButton()
|
|
4238
|
-
}
|
|
4239
|
-
|
|
4240
|
-
private func setupCamera() {
|
|
4241
|
-
let session = AVCaptureSession()
|
|
4242
|
-
guard let device = AVCaptureDevice.default(for: .video),
|
|
4243
|
-
let input = try? AVCaptureDeviceInput(device: device) else {
|
|
4244
|
-
onResult?(nil)
|
|
4245
|
-
return
|
|
4246
|
-
}
|
|
4247
|
-
|
|
4248
|
-
let output = AVCaptureMetadataOutput()
|
|
4249
|
-
session.addInput(input)
|
|
4250
|
-
session.addOutput(output)
|
|
4251
|
-
output.setMetadataObjectsDelegate(self, queue: .main)
|
|
4252
|
-
output.metadataObjectTypes = [.qr]
|
|
4253
|
-
|
|
4254
|
-
let preview = AVCaptureVideoPreviewLayer(session: session)
|
|
4255
|
-
preview.frame = view.layer.bounds
|
|
4256
|
-
preview.videoGravity = .resizeAspectFill
|
|
4257
|
-
view.layer.insertSublayer(preview, at: 0)
|
|
4258
|
-
previewLayer = preview
|
|
4259
|
-
|
|
4260
|
-
DispatchQueue.global(qos: .userInitiated).async { session.startRunning() }
|
|
4261
|
-
captureSession = session
|
|
4262
|
-
}
|
|
4263
|
-
|
|
4264
|
-
private func addCancelButton() {
|
|
4265
|
-
let btn = UIButton(type: .system)
|
|
4266
|
-
btn.setTitle("Cancel", for: .normal)
|
|
4267
|
-
btn.setTitleColor(.white, for: .normal)
|
|
4268
|
-
btn.titleLabel?.font = .systemFont(ofSize: 18, weight: .medium)
|
|
4269
|
-
btn.addTarget(self, action: #selector(cancel), for: .touchUpInside)
|
|
4270
|
-
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
4271
|
-
view.addSubview(btn)
|
|
4272
|
-
NSLayoutConstraint.activate([
|
|
4273
|
-
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
4274
|
-
btn.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24),
|
|
4275
|
-
])
|
|
4276
|
-
}
|
|
4277
|
-
|
|
4278
|
-
@objc private func cancel() {
|
|
4279
|
-
captureSession?.stopRunning()
|
|
4280
|
-
onResult?(nil)
|
|
4281
|
-
}
|
|
4282
|
-
|
|
4283
|
-
override func viewWillAppear(_ animated: Bool) {
|
|
4284
|
-
super.viewWillAppear(animated)
|
|
4285
|
-
if captureSession?.isRunning == false {
|
|
4286
|
-
DispatchQueue.global(qos: .userInitiated).async { self.captureSession?.startRunning() }
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
|
|
4290
|
-
override func viewWillDisappear(_ animated: Bool) {
|
|
4291
|
-
super.viewWillDisappear(animated)
|
|
4292
|
-
captureSession?.stopRunning()
|
|
4293
|
-
}
|
|
4294
|
-
}
|
|
4295
|
-
|
|
4296
|
-
extension QRScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
|
|
4297
|
-
func metadataOutput(_ output: AVCaptureMetadataOutput,
|
|
4298
|
-
didOutput objects: [AVMetadataObject],
|
|
4299
|
-
from connection: AVCaptureConnection) {
|
|
4300
|
-
captureSession?.stopRunning()
|
|
4301
|
-
if let obj = objects.first as? AVMetadataMachineReadableCodeObject,
|
|
4302
|
-
let value = obj.stringValue {
|
|
4303
|
-
onResult?(value)
|
|
4304
|
-
}
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
`;
|
|
4308
|
-
}
|
|
4309
|
-
function getLynxInitProcessorSwift() {
|
|
4310
|
-
return `// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
4311
|
-
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
4312
|
-
// LICENSE file in the root directory of this source tree.
|
|
4313
|
-
|
|
4314
|
-
import Foundation
|
|
4315
|
-
|
|
4316
|
-
// GENERATED IMPORTS START
|
|
4317
|
-
// This section is automatically generated by Tamer4Lynx.
|
|
4318
|
-
// Manual edits will be overwritten.
|
|
4319
|
-
// GENERATED IMPORTS END
|
|
4320
|
-
|
|
4321
|
-
final class LynxInitProcessor {
|
|
4322
|
-
static let shared = LynxInitProcessor()
|
|
4323
|
-
private init() {}
|
|
4324
|
-
|
|
4325
|
-
func setupEnvironment() {
|
|
4326
|
-
TamerIconElement.registerFonts()
|
|
4327
|
-
setupLynxEnv()
|
|
4328
|
-
setupLynxService()
|
|
4329
|
-
}
|
|
4330
|
-
|
|
4331
|
-
private func setupLynxEnv() {
|
|
4332
|
-
let env = LynxEnv.sharedInstance()
|
|
4333
|
-
let globalConfig = LynxConfig(provider: env.config.templateProvider)
|
|
4334
|
-
|
|
4335
|
-
// GENERATED AUTOLINK START
|
|
4336
|
-
|
|
4337
|
-
// GENERATED AUTOLINK END
|
|
4338
|
-
|
|
4339
|
-
env.prepareConfig(globalConfig)
|
|
4340
|
-
}
|
|
4341
|
-
|
|
4342
|
-
private func setupLynxService() {
|
|
4343
|
-
let webPCoder = SDImageWebPCoder.shared
|
|
4344
|
-
SDImageCodersManager.shared.addCoder(webPCoder)
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
`;
|
|
4348
|
-
}
|
|
4349
|
-
function getBridgingHeader() {
|
|
4350
|
-
return `#import <Lynx/LynxConfig.h>
|
|
4351
|
-
#import <Lynx/LynxEnv.h>
|
|
4352
|
-
#import <Lynx/LynxTemplateProvider.h>
|
|
4353
|
-
#import <Lynx/LynxView.h>
|
|
4354
|
-
#import <Lynx/LynxModule.h>
|
|
4355
|
-
#import <SDWebImage/SDWebImage.h>
|
|
4356
|
-
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
4357
|
-
`;
|
|
4358
|
-
}
|
|
4359
|
-
function getInfoPlist() {
|
|
4360
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4361
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4362
|
-
<plist version="1.0">
|
|
4363
|
-
<dict>
|
|
4364
|
-
<key>CFBundleDevelopmentRegion</key>
|
|
4365
|
-
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
4366
|
-
<key>CFBundleExecutable</key>
|
|
4367
|
-
<string>$(EXECUTABLE_NAME)</string>
|
|
4368
|
-
<key>CFBundleIdentifier</key>
|
|
4369
|
-
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
4370
|
-
<key>CFBundleInfoDictionaryVersion</key>
|
|
4371
|
-
<string>6.0</string>
|
|
4372
|
-
<key>CFBundleName</key>
|
|
4373
|
-
<string>$(PRODUCT_NAME)</string>
|
|
4374
|
-
<key>CFBundlePackageType</key>
|
|
4375
|
-
<string>APPL</string>
|
|
4376
|
-
<key>CFBundleShortVersionString</key>
|
|
4377
|
-
<string>1.0</string>
|
|
4378
|
-
<key>CFBundleVersion</key>
|
|
4379
|
-
<string>1</string>
|
|
4380
|
-
<key>UILaunchStoryboardName</key>
|
|
4381
|
-
<string>LaunchScreen</string>
|
|
4382
|
-
<key>CFBundleURLTypes</key>
|
|
4383
|
-
<array>
|
|
4384
|
-
<dict>
|
|
4385
|
-
<key>CFBundleURLSchemes</key>
|
|
4386
|
-
<array>
|
|
4387
|
-
<string>tamerdevapp</string>
|
|
4388
|
-
</array>
|
|
4389
|
-
</dict>
|
|
4390
|
-
</array>
|
|
4391
|
-
<key>NSCameraUsageDescription</key>
|
|
4392
|
-
<string>Used to scan QR codes for connecting to the dev server</string>
|
|
4393
|
-
<key>NSLocalNetworkUsageDescription</key>
|
|
4394
|
-
<string>Used to discover Tamer dev servers on your local network</string>
|
|
4395
|
-
<key>NSBonjourServices</key>
|
|
4396
|
-
<array>
|
|
4397
|
-
<string>_tamer._tcp.</string>
|
|
4398
|
-
</array>
|
|
4399
|
-
<key>NSAppTransportSecurity</key>
|
|
4400
|
-
<dict>
|
|
4401
|
-
<key>NSAllowsArbitraryLoads</key>
|
|
4402
|
-
<true/>
|
|
4403
|
-
</dict>
|
|
4404
|
-
<key>UIRequiredDeviceCapabilities</key>
|
|
4405
|
-
<array>
|
|
4406
|
-
<string>armv7</string>
|
|
4407
|
-
</array>
|
|
4408
|
-
<key>UISupportedInterfaceOrientations</key>
|
|
4409
|
-
<array>
|
|
4410
|
-
<string>UIInterfaceOrientationPortrait</string>
|
|
4411
|
-
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
4412
|
-
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
4413
|
-
</array>
|
|
4414
|
-
</dict>
|
|
4415
|
-
</plist>
|
|
4416
|
-
`;
|
|
4417
|
-
}
|
|
4418
|
-
function getPodfile() {
|
|
4419
|
-
return `source 'https://cdn.cocoapods.org/'
|
|
4420
|
-
|
|
4421
|
-
platform :ios, '13.0'
|
|
4422
|
-
|
|
4423
|
-
target '${APP_NAME}' do
|
|
4424
|
-
pod 'Lynx', '3.6.0', :subspecs => [
|
|
4425
|
-
'Framework',
|
|
4426
|
-
], :modular_headers => true
|
|
4427
|
-
|
|
4428
|
-
pod 'PrimJS', '3.6.1', :subspecs => ['quickjs', 'napi']
|
|
4429
|
-
|
|
4430
|
-
pod 'LynxService', '3.6.0', :subspecs => [
|
|
4431
|
-
'Image',
|
|
4432
|
-
'Log',
|
|
4433
|
-
'Http',
|
|
4434
|
-
]
|
|
4435
|
-
pod 'SDWebImage','5.15.5'
|
|
4436
|
-
pod 'SDWebImageWebPCoder', '0.11.0'
|
|
4437
|
-
|
|
4438
|
-
# GENERATED AUTOLINK DEPENDENCIES START
|
|
4439
|
-
# This section is automatically generated by Tamer4Lynx.
|
|
4440
|
-
# Manual edits will be overwritten.
|
|
4441
|
-
# GENERATED AUTOLINK DEPENDENCIES END
|
|
4442
|
-
end
|
|
4443
|
-
|
|
4444
|
-
post_install do |installer|
|
|
4445
|
-
installer.pods_project.targets.each do |target|
|
|
4446
|
-
target.build_configurations.each do |config|
|
|
4447
|
-
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'gnu++17'
|
|
4448
|
-
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
|
4449
|
-
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
|
|
4450
|
-
end
|
|
4451
|
-
|
|
4452
|
-
if target.name == 'Lynx'
|
|
4453
|
-
target.build_configurations.each do |config|
|
|
4454
|
-
flags = [
|
|
4455
|
-
'-Wno-vla-extension',
|
|
4456
|
-
'-Wno-vla',
|
|
4457
|
-
'-Wno-error=vla-extension',
|
|
4458
|
-
'-Wno-deprecated-declarations',
|
|
4459
|
-
'-Wno-deprecated',
|
|
4460
|
-
'-Wno-deprecated-implementations',
|
|
4461
|
-
'-Wno-macro-redefined',
|
|
4462
|
-
'-Wno-enum-compare',
|
|
4463
|
-
'-Wno-enum-compare-conditional',
|
|
4464
|
-
'-Wno-enum-conversion'
|
|
4465
|
-
].join(' ')
|
|
4466
|
-
|
|
4467
|
-
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = "$(inherited) #{flags}"
|
|
4468
|
-
config.build_settings['OTHER_CFLAGS'] = "$(inherited) #{flags}"
|
|
4469
|
-
config.build_settings['CLANG_WARN_VLA'] = 'NO'
|
|
4470
|
-
config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO'
|
|
4471
|
-
config.build_settings['CLANG_WARN_ENUM_CONVERSION'] = 'NO'
|
|
4472
|
-
end
|
|
4473
|
-
end
|
|
4474
|
-
end
|
|
4475
|
-
end
|
|
4476
|
-
`;
|
|
4477
|
-
}
|
|
4478
|
-
function getMainStoryboard() {
|
|
4479
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4480
|
-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0"
|
|
4481
|
-
toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none"
|
|
4482
|
-
useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"
|
|
4483
|
-
initialViewController="BYZ-38-t0r">
|
|
4484
|
-
<dependencies>
|
|
4485
|
-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
|
4486
|
-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
4487
|
-
</dependencies>
|
|
4488
|
-
<scenes>
|
|
4489
|
-
<scene sceneID="tne-QT-ifu">
|
|
4490
|
-
<objects>
|
|
4491
|
-
<viewController id="BYZ-38-t0r" customClass="DevLauncherViewController"
|
|
4492
|
-
customModuleProvider="target" sceneMemberID="viewController">
|
|
4493
|
-
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
|
4494
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4495
|
-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
4496
|
-
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
|
4497
|
-
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
|
4498
|
-
</view>
|
|
4499
|
-
</viewController>
|
|
4500
|
-
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr"
|
|
4501
|
-
sceneMemberID="firstResponder"/>
|
|
4502
|
-
</objects>
|
|
4503
|
-
</scene>
|
|
4504
|
-
</scenes>
|
|
4505
|
-
</document>
|
|
4506
|
-
`;
|
|
4507
|
-
}
|
|
4508
|
-
function getLaunchScreenStoryboard2() {
|
|
4509
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4510
|
-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0"
|
|
4511
|
-
toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none"
|
|
4512
|
-
useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES"
|
|
4513
|
-
colorMatched="YES" initialViewController="01J-lp-oVM">
|
|
4514
|
-
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
|
4515
|
-
<dependencies>
|
|
4516
|
-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
|
4517
|
-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
4518
|
-
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
4519
|
-
</dependencies>
|
|
4520
|
-
<scenes>
|
|
4521
|
-
<scene sceneID="EHf-IW-A2E">
|
|
4522
|
-
<objects>
|
|
4523
|
-
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
|
4524
|
-
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
4525
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4526
|
-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
4527
|
-
<subviews>
|
|
4528
|
-
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bg9-1M-mhb">
|
|
4529
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4530
|
-
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
4531
|
-
</view>
|
|
4532
|
-
</subviews>
|
|
4533
|
-
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
4534
|
-
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
4535
|
-
<constraints>
|
|
4536
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="3M4-v9-a3l"/>
|
|
4537
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="Sbc-LM-HvA"/>
|
|
4538
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="cJ0-4h-f4M"/>
|
|
4539
|
-
<constraint firstAttribute="trailing" secondItem="Bg9-1M-mhb" secondAttribute="trailing" id="g0s-pf-rxW"/>
|
|
4540
|
-
</constraints>
|
|
4541
|
-
</view>
|
|
4542
|
-
</viewController>
|
|
4543
|
-
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
4544
|
-
</objects>
|
|
4545
|
-
</scene>
|
|
4546
|
-
</scenes>
|
|
4547
|
-
</document>
|
|
4548
|
-
`;
|
|
4549
|
-
}
|
|
4550
|
-
function generatePbxproj(ids) {
|
|
4551
|
-
return `// !$*UTF8*$!
|
|
4552
|
-
{
|
|
4553
|
-
archiveVersion = 1;
|
|
4554
|
-
classes = {};
|
|
4555
|
-
objectVersion = 56;
|
|
4556
|
-
objects = {
|
|
4557
|
-
/* Begin PBXBuildFile section */
|
|
4558
|
-
${ids.appDelegateBuildFile} /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.appDelegateRef}; };
|
|
4559
|
-
${ids.devLauncherBuildFile} /* DevLauncherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.devLauncherRef}; };
|
|
4560
|
-
${ids.projectVCBuildFile} /* ProjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.projectVCRef}; };
|
|
4561
|
-
${ids.templateProviderBuildFile} /* DevTemplateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.templateProviderRef}; };
|
|
4562
|
-
${ids.devClientManagerBuildFile} /* DevClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.devClientManagerRef}; };
|
|
4563
|
-
${ids.devServerPrefsBuildFile} /* DevServerPrefs in Sources (via DevClientModule) */ = {isa = PBXBuildFile; fileRef = ${ids.devClientModuleRef}; };
|
|
4564
|
-
${ids.qrScannerBuildFile} /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.qrScannerRef}; };
|
|
4565
|
-
${ids.lynxInitBuildFile} /* LynxInitProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.lynxInitRef}; };
|
|
4566
|
-
${ids.mainStoryboardBuildFile} /* Base in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.mainStoryboardBaseRef}; };
|
|
4567
|
-
${ids.launchStoryboardBuildFile} /* Base in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.launchStoryboardBaseRef}; };
|
|
4568
|
-
${ids.assetsBuildFile} /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.assetsRef}; };
|
|
4569
|
-
${ids.bundleBuildFile} /* dev-client.lynx.bundle in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.bundleRef}; };
|
|
4570
|
-
/* End PBXBuildFile section */
|
|
4571
|
-
|
|
4572
|
-
/* Begin PBXFileReference section */
|
|
4573
|
-
${ids.appFile} /* ${APP_NAME}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "${APP_NAME}.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
4574
|
-
${ids.appDelegateRef} /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate.swift"; sourceTree = "<group>"; };
|
|
4575
|
-
${ids.devLauncherRef} /* DevLauncherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevLauncherViewController.swift"; sourceTree = "<group>"; };
|
|
4576
|
-
${ids.projectVCRef} /* ProjectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectViewController.swift"; sourceTree = "<group>"; };
|
|
4577
|
-
${ids.templateProviderRef} /* DevTemplateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevTemplateProvider.swift"; sourceTree = "<group>"; };
|
|
4578
|
-
${ids.devClientManagerRef} /* DevClientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevClientManager.swift"; sourceTree = "<group>"; };
|
|
4579
|
-
${ids.devClientModuleRef} /* DevClientModule (via pod) */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevClientModule.swift"; sourceTree = "<group>"; };
|
|
4580
|
-
${ids.qrScannerRef} /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRScannerViewController.swift"; sourceTree = "<group>"; };
|
|
4581
|
-
${ids.lynxInitRef} /* LynxInitProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LynxInitProcessor.swift"; sourceTree = "<group>"; };
|
|
4582
|
-
${ids.bridgingHeaderRef} /* ${BRIDGING_HEADER} */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "${BRIDGING_HEADER}"; sourceTree = "<group>"; };
|
|
4583
|
-
${ids.mainStoryboardBaseRef} /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Main.storyboard"; sourceTree = "<group>"; };
|
|
4584
|
-
${ids.launchStoryboardBaseRef} /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/LaunchScreen.storyboard"; sourceTree = "<group>"; };
|
|
4585
|
-
${ids.assetsRef} /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Assets.xcassets"; sourceTree = "<group>"; };
|
|
4586
|
-
${ids.bundleRef} /* dev-client.lynx.bundle */ = {isa = PBXFileReference; lastKnownFileType = "file"; path = "dev-client.lynx.bundle"; sourceTree = "<group>"; };
|
|
4587
|
-
/* End PBXFileReference section */
|
|
4588
|
-
|
|
4589
|
-
/* Begin PBXFrameworksBuildPhase section */
|
|
4590
|
-
${ids.frameworksBuildPhase} /* Frameworks */ = {
|
|
4591
|
-
isa = PBXFrameworksBuildPhase;
|
|
4592
|
-
buildActionMask = 2147483647;
|
|
4593
|
-
files = (
|
|
4594
|
-
);
|
|
4595
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4596
|
-
};
|
|
4597
|
-
/* End PBXFrameworksBuildPhase section */
|
|
4598
|
-
|
|
4599
|
-
/* Begin PBXGroup section */
|
|
4600
|
-
${ids.mainGroup} = {
|
|
4601
|
-
isa = PBXGroup;
|
|
4602
|
-
children = (
|
|
4603
|
-
${ids.appGroup} /* ${APP_NAME} */,
|
|
4604
|
-
${ids.productsGroup} /* Products */,
|
|
4605
|
-
${ids.frameworksGroup} /* Frameworks */,
|
|
4606
|
-
);
|
|
4607
|
-
sourceTree = "<group>";
|
|
4608
|
-
};
|
|
4609
|
-
${ids.productsGroup} /* Products */ = {
|
|
4610
|
-
isa = PBXGroup;
|
|
4611
|
-
children = (
|
|
4612
|
-
${ids.appFile} /* ${APP_NAME}.app */,
|
|
4613
|
-
);
|
|
4614
|
-
name = Products;
|
|
4615
|
-
sourceTree = "<group>";
|
|
4616
|
-
};
|
|
4617
|
-
${ids.frameworksGroup} /* Frameworks */ = {
|
|
4618
|
-
isa = PBXGroup;
|
|
4619
|
-
children = (
|
|
4620
|
-
);
|
|
4621
|
-
name = Frameworks;
|
|
4622
|
-
sourceTree = "<group>";
|
|
4623
|
-
};
|
|
4624
|
-
${ids.appGroup} /* ${APP_NAME} */ = {
|
|
4625
|
-
isa = PBXGroup;
|
|
4626
|
-
children = (
|
|
4627
|
-
${ids.appDelegateRef} /* AppDelegate.swift */,
|
|
4628
|
-
${ids.devLauncherRef} /* DevLauncherViewController.swift */,
|
|
4629
|
-
${ids.projectVCRef} /* ProjectViewController.swift */,
|
|
4630
|
-
${ids.templateProviderRef} /* DevTemplateProvider.swift */,
|
|
4631
|
-
${ids.devClientManagerRef} /* DevClientManager.swift */,
|
|
4632
|
-
${ids.devClientModuleRef} /* DevClientModule.swift */,
|
|
4633
|
-
${ids.qrScannerRef} /* QRScannerViewController.swift */,
|
|
4634
|
-
${ids.lynxInitRef} /* LynxInitProcessor.swift */,
|
|
4635
|
-
${ids.bridgingHeaderRef} /* ${BRIDGING_HEADER} */,
|
|
4636
|
-
${ids.mainStoryboardRef} /* Main.storyboard */,
|
|
4637
|
-
${ids.launchStoryboardRef} /* LaunchScreen.storyboard */,
|
|
4638
|
-
${ids.assetsRef} /* Assets.xcassets */,
|
|
4639
|
-
${ids.bundleRef} /* dev-client.lynx.bundle */,
|
|
4640
|
-
);
|
|
4641
|
-
path = "${APP_NAME}";
|
|
4642
|
-
sourceTree = "<group>";
|
|
4643
|
-
};
|
|
4644
|
-
/* End PBXGroup section */
|
|
4645
|
-
|
|
4646
|
-
/* Begin PBXNativeTarget section */
|
|
4647
|
-
${ids.nativeTarget} /* ${APP_NAME} */ = {
|
|
4648
|
-
isa = PBXNativeTarget;
|
|
4649
|
-
buildConfigurationList = ${ids.targetBuildConfigList};
|
|
4650
|
-
buildPhases = (
|
|
4651
|
-
${ids.sourcesBuildPhase} /* Sources */,
|
|
4652
|
-
${ids.frameworksBuildPhase} /* Frameworks */,
|
|
4653
|
-
${ids.resourcesBuildPhase} /* Resources */,
|
|
4654
|
-
${ids.fontCopyScriptPhase} /* [Tamer] Copy Icon Fonts */,
|
|
4655
|
-
);
|
|
4656
|
-
buildRules = (
|
|
4657
|
-
);
|
|
4658
|
-
dependencies = (
|
|
4659
|
-
);
|
|
4660
|
-
name = "${APP_NAME}";
|
|
4661
|
-
productName = "${APP_NAME}";
|
|
4662
|
-
productReference = ${ids.appFile};
|
|
4663
|
-
productType = "com.apple.product-type.application";
|
|
4664
|
-
};
|
|
4665
|
-
/* End PBXNativeTarget section */
|
|
4666
|
-
|
|
4667
|
-
/* Begin PBXProject section */
|
|
4668
|
-
${ids.project} /* Project object */ = {
|
|
4669
|
-
isa = PBXProject;
|
|
4670
|
-
attributes = {
|
|
4671
|
-
LastUpgradeCheck = 1530;
|
|
4672
|
-
};
|
|
4673
|
-
buildConfigurationList = ${ids.projectBuildConfigList};
|
|
4674
|
-
compatibilityVersion = "Xcode 14.0";
|
|
4675
|
-
developmentRegion = en;
|
|
4676
|
-
hasScannedForEncodings = 0;
|
|
4677
|
-
knownRegions = (
|
|
4678
|
-
en,
|
|
4679
|
-
Base,
|
|
4680
|
-
);
|
|
4681
|
-
mainGroup = ${ids.mainGroup};
|
|
4682
|
-
productRefGroup = ${ids.productsGroup} /* Products */;
|
|
4683
|
-
projectDirPath = "";
|
|
4684
|
-
projectRoot = "";
|
|
4685
|
-
targets = (
|
|
4686
|
-
${ids.nativeTarget} /* ${APP_NAME} */,
|
|
4687
|
-
);
|
|
4688
|
-
};
|
|
4689
|
-
/* End PBXProject section */
|
|
4690
|
-
|
|
4691
|
-
/* Begin PBXResourcesBuildPhase section */
|
|
4692
|
-
${ids.resourcesBuildPhase} /* Resources */ = {
|
|
4693
|
-
isa = PBXResourcesBuildPhase;
|
|
4694
|
-
buildActionMask = 2147483647;
|
|
4695
|
-
files = (
|
|
4696
|
-
${ids.assetsBuildFile} /* Assets.xcassets in Resources */,
|
|
4697
|
-
${ids.mainStoryboardBuildFile} /* Base in Resources */,
|
|
4698
|
-
${ids.launchStoryboardBuildFile} /* Base in Resources */,
|
|
4699
|
-
${ids.bundleBuildFile} /* dev-client.lynx.bundle in Resources */,
|
|
4700
|
-
);
|
|
4701
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4702
|
-
};
|
|
4703
|
-
/* End PBXResourcesBuildPhase section */
|
|
4704
|
-
|
|
4705
|
-
/* Begin PBXShellScriptBuildPhase section */
|
|
4706
|
-
${ids.fontCopyScriptPhase} /* [Tamer] Copy Icon Fonts */ = {
|
|
4707
|
-
isa = PBXShellScriptBuildPhase;
|
|
4708
|
-
buildActionMask = 2147483647;
|
|
4709
|
-
files = (
|
|
4710
|
-
);
|
|
4711
|
-
inputPaths = (
|
|
4712
|
-
);
|
|
4713
|
-
name = "[Tamer] Copy Icon Fonts";
|
|
4714
|
-
outputPaths = (
|
|
4715
|
-
);
|
|
4716
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4717
|
-
shellPath = /bin/sh;
|
|
4718
|
-
shellScript = "FONTS_SRC=\\"${SRCROOT}/../../tamer-icons/fonts\\"\\nif [ -d \\"$FONTS_SRC\\" ]; then\\n cp -f \\"$FONTS_SRC/MaterialSymbolsOutlined.ttf\\" \\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/\\" 2>/dev/null || true\\n cp -f \\"$FONTS_SRC/fa-solid-900.ttf\\" \\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/\\" 2>/dev/null || true\\nfi\\nCP_SRC=\\"${SRCROOT}/../../tamer-icons/android/src/main/assets/fonts/material-codepoints.txt\\"\\n[ -f \\"$CP_SRC\\" ] && cp -f \\"$CP_SRC\\" \\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/\\" 2>/dev/null || true\\n";
|
|
4719
|
-
};
|
|
4720
|
-
/* End PBXShellScriptBuildPhase section */
|
|
4721
|
-
|
|
4722
|
-
/* Begin PBXSourcesBuildPhase section */
|
|
4723
|
-
${ids.sourcesBuildPhase} /* Sources */ = {
|
|
4724
|
-
isa = PBXSourcesBuildPhase;
|
|
4725
|
-
buildActionMask = 2147483647;
|
|
4726
|
-
files = (
|
|
4727
|
-
${ids.appDelegateBuildFile} /* AppDelegate.swift in Sources */,
|
|
4728
|
-
${ids.devLauncherBuildFile} /* DevLauncherViewController.swift in Sources */,
|
|
4729
|
-
${ids.projectVCBuildFile} /* ProjectViewController.swift in Sources */,
|
|
4730
|
-
${ids.templateProviderBuildFile} /* DevTemplateProvider.swift in Sources */,
|
|
4731
|
-
${ids.devClientManagerBuildFile} /* DevClientManager.swift in Sources */,
|
|
4732
|
-
${ids.qrScannerBuildFile} /* QRScannerViewController.swift in Sources */,
|
|
4733
|
-
${ids.lynxInitBuildFile} /* LynxInitProcessor.swift in Sources */,
|
|
4734
|
-
);
|
|
4735
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4736
|
-
};
|
|
4737
|
-
/* End PBXSourcesBuildPhase section */
|
|
4738
|
-
|
|
4739
|
-
/* Begin PBXVariantGroup section */
|
|
4740
|
-
${ids.mainStoryboardRef} /* Main.storyboard */ = {
|
|
4741
|
-
isa = PBXVariantGroup;
|
|
4742
|
-
children = (
|
|
4743
|
-
${ids.mainStoryboardBaseRef} /* Base */,
|
|
4744
|
-
);
|
|
4745
|
-
name = "Main.storyboard";
|
|
4746
|
-
sourceTree = "<group>";
|
|
4747
|
-
};
|
|
4748
|
-
${ids.launchStoryboardRef} /* LaunchScreen.storyboard */ = {
|
|
4749
|
-
isa = PBXVariantGroup;
|
|
4750
|
-
children = (
|
|
4751
|
-
${ids.launchStoryboardBaseRef} /* Base */,
|
|
4752
|
-
);
|
|
4753
|
-
name = "LaunchScreen.storyboard";
|
|
4754
|
-
sourceTree = "<group>";
|
|
4755
|
-
};
|
|
4756
|
-
/* End PBXVariantGroup section */
|
|
4757
|
-
|
|
4758
|
-
/* Begin XCBuildConfiguration section */
|
|
4759
|
-
${ids.projectDebugConfig} /* Debug */ = {
|
|
4760
|
-
isa = XCBuildConfiguration;
|
|
4761
|
-
buildSettings = {
|
|
4762
|
-
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
4763
|
-
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
|
4764
|
-
CLANG_CXX_LIBRARY = "libc++";
|
|
4765
|
-
CLANG_ENABLE_MODULES = YES;
|
|
4766
|
-
CLANG_ENABLE_OBJC_ARC = YES;
|
|
4767
|
-
COPY_PHASE_STRIP = NO;
|
|
4768
|
-
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
4769
|
-
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
4770
|
-
GCC_NO_COMMON_BLOCKS = YES;
|
|
4771
|
-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
4772
|
-
SDKROOT = iphoneos;
|
|
4773
|
-
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
|
4774
|
-
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
4775
|
-
};
|
|
4776
|
-
name = Debug;
|
|
4777
|
-
};
|
|
4778
|
-
${ids.projectReleaseConfig} /* Release */ = {
|
|
4779
|
-
isa = XCBuildConfiguration;
|
|
4780
|
-
buildSettings = {
|
|
4781
|
-
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
4782
|
-
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
|
4783
|
-
CLANG_CXX_LIBRARY = "libc++";
|
|
4784
|
-
CLANG_ENABLE_MODULES = YES;
|
|
4785
|
-
CLANG_ENABLE_OBJC_ARC = YES;
|
|
4786
|
-
COPY_PHASE_STRIP = NO;
|
|
4787
|
-
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
4788
|
-
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
4789
|
-
GCC_NO_COMMON_BLOCKS = YES;
|
|
4790
|
-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
4791
|
-
SDKROOT = iphoneos;
|
|
4792
|
-
SWIFT_COMPILATION_MODE = wholemodule;
|
|
4793
|
-
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
|
4794
|
-
};
|
|
4795
|
-
name = Release;
|
|
4796
|
-
};
|
|
4797
|
-
${ids.targetDebugConfig} /* Debug */ = {
|
|
4798
|
-
isa = XCBuildConfiguration;
|
|
4799
|
-
buildSettings = {
|
|
4800
|
-
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
4801
|
-
CURRENT_PROJECT_VERSION = 1;
|
|
4802
|
-
INFOPLIST_FILE = "${APP_NAME}/Info.plist";
|
|
4803
|
-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
4804
|
-
MARKETING_VERSION = "1.0";
|
|
4805
|
-
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}";
|
|
4806
|
-
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
4807
|
-
SWIFT_OBJC_BRIDGING_HEADER = "${APP_NAME}/${BRIDGING_HEADER}";
|
|
4808
|
-
SWIFT_VERSION = 5.0;
|
|
4809
|
-
TARGETED_DEVICE_FAMILY = "1,2";
|
|
4810
|
-
};
|
|
4811
|
-
name = Debug;
|
|
4812
|
-
};
|
|
4813
|
-
${ids.targetReleaseConfig} /* Release */ = {
|
|
4814
|
-
isa = XCBuildConfiguration;
|
|
4815
|
-
buildSettings = {
|
|
4816
|
-
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
4817
|
-
CURRENT_PROJECT_VERSION = 1;
|
|
4818
|
-
INFOPLIST_FILE = "${APP_NAME}/Info.plist";
|
|
4819
|
-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
4820
|
-
MARKETING_VERSION = "1.0";
|
|
4821
|
-
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}";
|
|
4822
|
-
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
4823
|
-
SWIFT_OBJC_BRIDGING_HEADER = "${APP_NAME}/${BRIDGING_HEADER}";
|
|
4824
|
-
SWIFT_VERSION = 5.0;
|
|
4825
|
-
TARGETED_DEVICE_FAMILY = "1,2";
|
|
4826
|
-
};
|
|
4827
|
-
name = Release;
|
|
4828
|
-
};
|
|
4829
|
-
/* End XCBuildConfiguration section */
|
|
4830
|
-
|
|
4831
|
-
/* Begin XCConfigurationList section */
|
|
4832
|
-
${ids.projectBuildConfigList} = {
|
|
4833
|
-
isa = XCConfigurationList;
|
|
4834
|
-
buildConfigurations = (
|
|
4835
|
-
${ids.projectDebugConfig} /* Debug */,
|
|
4836
|
-
${ids.projectReleaseConfig} /* Release */,
|
|
4837
|
-
);
|
|
4838
|
-
defaultConfigurationIsVisible = 0;
|
|
4839
|
-
defaultConfigurationName = Release;
|
|
4840
|
-
};
|
|
4841
|
-
${ids.targetBuildConfigList} = {
|
|
4842
|
-
isa = XCConfigurationList;
|
|
4843
|
-
buildConfigurations = (
|
|
4844
|
-
${ids.targetDebugConfig} /* Debug */,
|
|
4845
|
-
${ids.targetReleaseConfig} /* Release */,
|
|
4846
|
-
);
|
|
4847
|
-
defaultConfigurationIsVisible = 0;
|
|
4848
|
-
defaultConfigurationName = Release;
|
|
4849
|
-
};
|
|
4850
|
-
/* End XCConfigurationList section */
|
|
4851
|
-
};
|
|
4852
|
-
rootObject = ${ids.project} /* Project object */;
|
|
4853
|
-
}
|
|
4854
|
-
`;
|
|
4855
|
-
}
|
|
4856
|
-
async function createDevAppProject(iosDir, repoRoot) {
|
|
4857
|
-
const projectDir = path16.join(iosDir, APP_NAME);
|
|
4858
|
-
const xcodeprojDir = path16.join(iosDir, `${APP_NAME}.xcodeproj`);
|
|
4859
|
-
if (fs15.existsSync(iosDir)) {
|
|
4860
|
-
fs15.rmSync(iosDir, { recursive: true, force: true });
|
|
4861
|
-
}
|
|
4862
|
-
console.log(`\u{1F680} Creating TamerDevApp iOS project at: ${iosDir}`);
|
|
4863
|
-
const ids = {};
|
|
4864
|
-
const idKeys = [
|
|
4865
|
-
"project",
|
|
4866
|
-
"mainGroup",
|
|
4867
|
-
"appGroup",
|
|
4868
|
-
"productsGroup",
|
|
4869
|
-
"frameworksGroup",
|
|
4870
|
-
"appFile",
|
|
4871
|
-
"appDelegateRef",
|
|
4872
|
-
"devLauncherRef",
|
|
4873
|
-
"projectVCRef",
|
|
4874
|
-
"templateProviderRef",
|
|
4875
|
-
"devClientManagerRef",
|
|
4876
|
-
"devClientModuleRef",
|
|
4877
|
-
"qrScannerRef",
|
|
4878
|
-
"lynxInitRef",
|
|
4879
|
-
"bridgingHeaderRef",
|
|
4880
|
-
"mainStoryboardRef",
|
|
4881
|
-
"mainStoryboardBaseRef",
|
|
4882
|
-
"launchStoryboardRef",
|
|
4883
|
-
"launchStoryboardBaseRef",
|
|
4884
|
-
"assetsRef",
|
|
4885
|
-
"bundleRef",
|
|
4886
|
-
"nativeTarget",
|
|
4887
|
-
"appDelegateBuildFile",
|
|
4888
|
-
"devLauncherBuildFile",
|
|
4889
|
-
"projectVCBuildFile",
|
|
4890
|
-
"templateProviderBuildFile",
|
|
4891
|
-
"devClientManagerBuildFile",
|
|
4892
|
-
"devServerPrefsBuildFile",
|
|
4893
|
-
"qrScannerBuildFile",
|
|
4894
|
-
"lynxInitBuildFile",
|
|
4895
|
-
"mainStoryboardBuildFile",
|
|
4896
|
-
"launchStoryboardBuildFile",
|
|
4897
|
-
"assetsBuildFile",
|
|
4898
|
-
"bundleBuildFile",
|
|
4899
|
-
"frameworksBuildPhase",
|
|
4900
|
-
"resourcesBuildPhase",
|
|
4901
|
-
"sourcesBuildPhase",
|
|
4902
|
-
"fontCopyScriptPhase",
|
|
4903
|
-
"projectBuildConfigList",
|
|
4904
|
-
"targetBuildConfigList",
|
|
4905
|
-
"projectDebugConfig",
|
|
4906
|
-
"projectReleaseConfig",
|
|
4907
|
-
"targetDebugConfig",
|
|
4908
|
-
"targetReleaseConfig"
|
|
4909
|
-
];
|
|
4910
|
-
for (const k of idKeys) ids[k] = generateId();
|
|
4911
|
-
writeFile2(path16.join(iosDir, "Podfile"), getPodfile());
|
|
4912
|
-
writeFile2(path16.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift2());
|
|
4913
|
-
const devClientPkg = findDevClientPackage(repoRoot);
|
|
4914
|
-
const templateDir = devClientPkg ? path16.join(devClientPkg, "ios", "templates") : null;
|
|
4915
|
-
const templateVars = { PROJECT_BUNDLE_SEGMENT: "tamer-dev-app" };
|
|
4916
|
-
const templateFiles = [
|
|
4917
|
-
"DevLauncherViewController.swift",
|
|
4918
|
-
"ProjectViewController.swift",
|
|
4919
|
-
"DevTemplateProvider.swift",
|
|
4920
|
-
"DevClientManager.swift",
|
|
4921
|
-
"QRScannerViewController.swift",
|
|
4922
|
-
"LynxInitProcessor.swift"
|
|
4923
|
-
];
|
|
4924
|
-
for (const f of templateFiles) {
|
|
4925
|
-
const src = templateDir ? path16.join(templateDir, f) : null;
|
|
4926
|
-
if (src && fs15.existsSync(src)) {
|
|
4927
|
-
writeFile2(path16.join(projectDir, f), readAndSubstituteTemplate4(src, templateVars));
|
|
4928
|
-
} else {
|
|
4929
|
-
const fallback = (() => {
|
|
4930
|
-
switch (f) {
|
|
4931
|
-
case "DevLauncherViewController.swift":
|
|
4932
|
-
return getDevLauncherViewControllerSwift();
|
|
4933
|
-
case "ProjectViewController.swift":
|
|
4934
|
-
return getProjectViewControllerSwift();
|
|
4935
|
-
case "DevTemplateProvider.swift":
|
|
4936
|
-
return getDevTemplateProviderSwift();
|
|
4937
|
-
case "DevClientManager.swift":
|
|
4938
|
-
return getDevClientManagerSwift();
|
|
4939
|
-
case "QRScannerViewController.swift":
|
|
4940
|
-
return getQRScannerViewControllerSwift();
|
|
4941
|
-
case "LynxInitProcessor.swift":
|
|
4942
|
-
return getLynxInitProcessorSwift();
|
|
4943
|
-
default:
|
|
4944
|
-
return "";
|
|
4945
|
-
}
|
|
4946
|
-
})();
|
|
4947
|
-
if (fallback) writeFile2(path16.join(projectDir, f), fallback);
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
writeFile2(path16.join(projectDir, BRIDGING_HEADER), getBridgingHeader());
|
|
4951
|
-
writeFile2(path16.join(projectDir, "Info.plist"), getInfoPlist());
|
|
4952
|
-
writeFile2(path16.join(projectDir, "Base.lproj", "Main.storyboard"), getMainStoryboard());
|
|
4953
|
-
writeFile2(path16.join(projectDir, "Base.lproj", "LaunchScreen.storyboard"), getLaunchScreenStoryboard2());
|
|
4954
|
-
writeFile2(
|
|
4955
|
-
path16.join(projectDir, "Assets.xcassets", "AppIcon.appiconset", "Contents.json"),
|
|
4956
|
-
JSON.stringify({ images: [{ idiom: "universal", platform: "ios", size: "1024x1024" }], info: { author: "xcode", version: 1 } }, null, 2)
|
|
4957
|
-
);
|
|
4958
|
-
writeFile2(
|
|
4959
|
-
path16.join(projectDir, "Assets.xcassets", "Contents.json"),
|
|
4960
|
-
JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)
|
|
4961
|
-
);
|
|
4962
|
-
writeFile2(path16.join(projectDir, "dev-client.lynx.bundle"), "");
|
|
4963
|
-
fs15.mkdirSync(xcodeprojDir, { recursive: true });
|
|
4964
|
-
writeFile2(path16.join(xcodeprojDir, "project.pbxproj"), generatePbxproj(ids));
|
|
4965
|
-
console.log(`\u2705 TamerDevApp iOS project created at ${iosDir}`);
|
|
4966
|
-
await setupCocoaPods(iosDir);
|
|
4967
|
-
}
|
|
4968
|
-
async function syncDevClientIos() {
|
|
4969
|
-
let resolved;
|
|
4970
|
-
let repoRoot;
|
|
4971
|
-
try {
|
|
4972
|
-
resolved = resolveDevAppPaths(process.cwd());
|
|
4973
|
-
repoRoot = resolved.projectRoot;
|
|
4974
|
-
} catch (e) {
|
|
4975
|
-
console.error(`\u274C ${e.message}`);
|
|
4976
|
-
process.exit(1);
|
|
4977
|
-
}
|
|
4978
|
-
const iosDir = resolved.iosDir;
|
|
4979
|
-
const workspacePath = path16.join(iosDir, `${APP_NAME}.xcworkspace`);
|
|
4980
|
-
const projectDir = path16.join(iosDir, APP_NAME);
|
|
4981
|
-
const hasCommittedSource = fs15.existsSync(path16.join(projectDir, "AppDelegate.swift"));
|
|
4982
|
-
if (!hasCommittedSource) {
|
|
4983
|
-
await createDevAppProject(iosDir, repoRoot);
|
|
4984
|
-
} else if (!fs15.existsSync(workspacePath)) {
|
|
4985
|
-
await setupCocoaPods(iosDir);
|
|
4986
|
-
console.log(`\u2139\uFE0F iOS dev-app project exists; ran pod install`);
|
|
4987
|
-
} else {
|
|
4988
|
-
console.log(`\u2139\uFE0F iOS dev-app project already exists at ${iosDir}`);
|
|
4989
|
-
}
|
|
4990
|
-
const prev = process.cwd();
|
|
4991
|
-
process.chdir(resolved.projectRoot);
|
|
4992
|
-
try {
|
|
4993
|
-
autolink_default2();
|
|
4994
|
-
} finally {
|
|
4995
|
-
process.chdir(prev);
|
|
4996
|
-
}
|
|
4997
|
-
const devClientDir = resolved.lynxProjectDir;
|
|
4998
|
-
console.log("\u{1F4E6} Building dev-client Lynx bundle...");
|
|
4999
|
-
execSync7("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
5000
|
-
const bundleSrc = resolved.lynxBundlePath;
|
|
5001
|
-
const bundleDst = path16.join(iosDir, APP_NAME, "dev-client.lynx.bundle");
|
|
5002
|
-
if (fs15.existsSync(bundleSrc)) {
|
|
5003
|
-
fs15.copyFileSync(bundleSrc, bundleDst);
|
|
5004
|
-
console.log(`\u2728 Copied dev-client.lynx.bundle to iOS project`);
|
|
5005
|
-
} else {
|
|
5006
|
-
console.warn(`\u26A0\uFE0F Bundle not found at ${bundleSrc}`);
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
var syncDevClient_default2 = syncDevClientIos;
|
|
5010
|
-
|
|
5011
|
-
// src/ios/build.ts
|
|
5012
|
-
var DEV_APP_NAME = "TamerDevApp";
|
|
5013
|
-
var SIMULATOR_ID = "A07F36D8-873A-41E0-8B90-3DF328A6B614";
|
|
5014
|
-
function findBootedSimulator() {
|
|
5015
|
-
try {
|
|
5016
|
-
const out = execSync8("xcrun simctl list devices --json", { encoding: "utf8" });
|
|
5017
|
-
const json = JSON.parse(out);
|
|
5018
|
-
for (const runtimes of Object.values(json.devices)) {
|
|
5019
|
-
for (const device of runtimes) {
|
|
5020
|
-
if (device.state === "Booted") return device.udid;
|
|
5021
|
-
}
|
|
5022
|
-
}
|
|
5023
|
-
} catch {
|
|
5024
|
-
}
|
|
5025
|
-
return null;
|
|
5026
|
-
}
|
|
5027
|
-
async function buildIpa(opts = {}) {
|
|
5028
|
-
const target = opts.target ?? "host";
|
|
5029
|
-
const resolved = resolveHostPaths();
|
|
5030
|
-
if (!resolved.config.ios?.appName) {
|
|
5031
|
-
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
5032
|
-
}
|
|
5033
|
-
if (target === "dev-app") {
|
|
5034
|
-
await buildIosDevApp(opts.install, opts.release);
|
|
5035
|
-
return;
|
|
5036
|
-
}
|
|
5037
|
-
const appName = resolved.config.ios.appName;
|
|
5038
|
-
const bundleId = resolved.config.ios.bundleId;
|
|
5039
|
-
const iosDir = resolved.iosDir;
|
|
5040
|
-
const configuration = opts.release ? "Release" : "Debug";
|
|
5041
|
-
bundle_default2({ target, release: opts.release });
|
|
5042
|
-
const scheme = appName;
|
|
5043
|
-
const workspacePath = path17.join(iosDir, `${appName}.xcworkspace`);
|
|
5044
|
-
const projectPath = path17.join(iosDir, `${appName}.xcodeproj`);
|
|
5045
|
-
const xcproject = fs16.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
5046
|
-
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
5047
|
-
const derivedDataPath = path17.join(iosDir, "build");
|
|
5048
|
-
const sdk = opts.install ? "iphonesimulator" : "iphoneos";
|
|
5049
|
-
console.log(`
|
|
5050
|
-
\u{1F528} Building ${configuration} (${sdk})...`);
|
|
5051
|
-
execSync8(
|
|
5052
|
-
`xcodebuild ${flag} "${xcproject}" -scheme "${scheme}" -configuration ${configuration} -sdk ${sdk} -derivedDataPath "${derivedDataPath}"`,
|
|
5053
|
-
{ stdio: "inherit", cwd: iosDir }
|
|
5054
|
-
);
|
|
5055
|
-
console.log(`\u2705 Build completed.`);
|
|
5056
|
-
if (opts.install) {
|
|
5057
|
-
const appGlob = path17.join(
|
|
5058
|
-
derivedDataPath,
|
|
5059
|
-
"Build",
|
|
5060
|
-
"Products",
|
|
5061
|
-
`${configuration}-iphonesimulator`,
|
|
5062
|
-
`${appName}.app`
|
|
5063
|
-
);
|
|
5064
|
-
if (!fs16.existsSync(appGlob)) {
|
|
5065
|
-
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
5066
|
-
process.exit(1);
|
|
5067
|
-
}
|
|
5068
|
-
const udid = findBootedSimulator();
|
|
5069
|
-
if (!udid) {
|
|
5070
|
-
console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
|
|
5071
|
-
process.exit(1);
|
|
5072
|
-
}
|
|
5073
|
-
console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
|
|
5074
|
-
execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
|
|
5075
|
-
if (bundleId) {
|
|
5076
|
-
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
5077
|
-
execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
|
|
5078
|
-
console.log("\u2705 App launched.");
|
|
5079
|
-
} else {
|
|
5080
|
-
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
}
|
|
5084
|
-
async function buildIosDevApp(install, release) {
|
|
5085
|
-
const resolved = resolveDevAppPaths(process.cwd());
|
|
5086
|
-
const iosDir = resolved.iosDir;
|
|
5087
|
-
const configuration = release ? "Release" : "Debug";
|
|
5088
|
-
await syncDevClient_default2();
|
|
5089
|
-
const workspacePath = path17.join(iosDir, `${DEV_APP_NAME}.xcworkspace`);
|
|
5090
|
-
const projectPath = path17.join(iosDir, `${DEV_APP_NAME}.xcodeproj`);
|
|
5091
|
-
const xcproject = fs16.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
5092
|
-
const flag = xcproject.endsWith(".xcworkspace") ? "workspace" : "project";
|
|
5093
|
-
console.log(`
|
|
5094
|
-
\u{1F528} Building TamerDevApp for simulator (${configuration})...`);
|
|
5095
|
-
execSync8(
|
|
5096
|
-
`xcodebuild -${flag} "${xcproject}" -scheme "${DEV_APP_NAME}" -configuration ${configuration} -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.5" -derivedDataPath build`,
|
|
5097
|
-
{ stdio: "inherit", cwd: iosDir }
|
|
5098
|
-
);
|
|
5099
|
-
console.log("\u2705 TamerDevApp built successfully.");
|
|
5100
|
-
if (install) {
|
|
5101
|
-
const appPath = path17.join(iosDir, "build", "Build", "Products", `${configuration}-iphonesimulator`, `${DEV_APP_NAME}.app`);
|
|
5102
|
-
console.log("\n\u{1F4F2} Installing to simulator...");
|
|
5103
|
-
try {
|
|
5104
|
-
execSync8(`xcrun simctl boot "${SIMULATOR_ID}" 2>/dev/null`);
|
|
5105
|
-
} catch {
|
|
5106
|
-
}
|
|
5107
|
-
execSync8(`xcrun simctl install "${SIMULATOR_ID}" "${appPath}"`, { stdio: "inherit" });
|
|
5108
|
-
execSync8(`xcrun simctl launch "${SIMULATOR_ID}" "com.nanofuxion.tamerdevapp"`, { stdio: "inherit" });
|
|
5109
|
-
execSync8("open -a Simulator", { stdio: "inherit" });
|
|
5110
|
-
console.log("\u2705 TamerDevApp launched in simulator.");
|
|
5111
|
-
}
|
|
5112
|
-
}
|
|
5113
|
-
var build_default2 = buildIpa;
|
|
5114
|
-
|
|
5115
|
-
// src/common/init.ts
|
|
3904
|
+
// src/common/create.ts
|
|
5116
3905
|
import fs17 from "fs";
|
|
5117
3906
|
import path18 from "path";
|
|
5118
|
-
import readline from "readline";
|
|
5119
|
-
var rl = readline.createInterface({
|
|
5120
|
-
input: process.stdin,
|
|
5121
|
-
output: process.stdout,
|
|
5122
|
-
terminal: false
|
|
5123
|
-
});
|
|
5124
|
-
function ask(question) {
|
|
5125
|
-
return new Promise((resolve) => {
|
|
5126
|
-
rl.question(question, (answer) => resolve(answer.trim()));
|
|
5127
|
-
});
|
|
5128
|
-
}
|
|
5129
|
-
async function init() {
|
|
5130
|
-
console.log("Tamer4Lynx Init: Let's set up your tamer.config.json\n");
|
|
5131
|
-
const androidAppName = await ask("Android app name: ");
|
|
5132
|
-
const androidPackageName = await ask("Android package name (e.g. com.example.app): ");
|
|
5133
|
-
let androidSdk = await ask("Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME): ");
|
|
5134
|
-
if (androidSdk.startsWith("$") && /^[A-Z0-9_]+$/.test(androidSdk.slice(1))) {
|
|
5135
|
-
const envVar = androidSdk.slice(1);
|
|
5136
|
-
const envValue = process.env[envVar];
|
|
5137
|
-
if (envValue) {
|
|
5138
|
-
androidSdk = envValue;
|
|
5139
|
-
console.log(`Resolved ${androidSdk} from $${envVar}`);
|
|
5140
|
-
} else {
|
|
5141
|
-
console.warn(`Environment variable $${envVar} not found. SDK path will be left as-is.`);
|
|
5142
|
-
}
|
|
5143
|
-
}
|
|
5144
|
-
const useSame = await ask("Use same name and bundle ID for iOS as Android? (y/N): ");
|
|
5145
|
-
let iosAppName;
|
|
5146
|
-
let iosBundleId;
|
|
5147
|
-
if (/^y(es)?$/i.test(useSame)) {
|
|
5148
|
-
iosAppName = androidAppName;
|
|
5149
|
-
iosBundleId = androidPackageName;
|
|
5150
|
-
} else {
|
|
5151
|
-
iosAppName = await ask("iOS app name: ");
|
|
5152
|
-
iosBundleId = await ask("iOS bundle ID (e.g. com.example.app): ");
|
|
5153
|
-
}
|
|
5154
|
-
const lynxProject = await ask("Lynx project path (relative to project root, e.g. packages/example) [optional]: ");
|
|
5155
|
-
const config = {
|
|
5156
|
-
android: {
|
|
5157
|
-
appName: androidAppName || void 0,
|
|
5158
|
-
packageName: androidPackageName || void 0,
|
|
5159
|
-
sdk: androidSdk || void 0
|
|
5160
|
-
},
|
|
5161
|
-
ios: {
|
|
5162
|
-
appName: iosAppName || void 0,
|
|
5163
|
-
bundleId: iosBundleId || void 0
|
|
5164
|
-
},
|
|
5165
|
-
paths: { androidDir: "android", iosDir: "ios" }
|
|
5166
|
-
};
|
|
5167
|
-
if (lynxProject) config.lynxProject = lynxProject;
|
|
5168
|
-
const configPath = path18.join(process.cwd(), "tamer.config.json");
|
|
5169
|
-
fs17.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
5170
|
-
console.log(`
|
|
5171
|
-
\u2705 Generated tamer.config.json at ${configPath}`);
|
|
5172
|
-
rl.close();
|
|
5173
|
-
}
|
|
5174
|
-
var init_default = init;
|
|
5175
|
-
|
|
5176
|
-
// src/common/create.ts
|
|
5177
|
-
import fs18 from "fs";
|
|
5178
|
-
import path19 from "path";
|
|
5179
3907
|
import readline2 from "readline";
|
|
5180
3908
|
var rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5181
3909
|
function ask2(question) {
|
|
@@ -5205,13 +3933,13 @@ async function create3() {
|
|
|
5205
3933
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
5206
3934
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
5207
3935
|
const cwd = process.cwd();
|
|
5208
|
-
const root =
|
|
5209
|
-
if (
|
|
3936
|
+
const root = path18.join(cwd, extName);
|
|
3937
|
+
if (fs17.existsSync(root)) {
|
|
5210
3938
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
5211
3939
|
rl2.close();
|
|
5212
3940
|
process.exit(1);
|
|
5213
3941
|
}
|
|
5214
|
-
|
|
3942
|
+
fs17.mkdirSync(root, { recursive: true });
|
|
5215
3943
|
const lynxExt = {
|
|
5216
3944
|
platforms: {
|
|
5217
3945
|
android: {
|
|
@@ -5226,7 +3954,7 @@ async function create3() {
|
|
|
5226
3954
|
web: {}
|
|
5227
3955
|
}
|
|
5228
3956
|
};
|
|
5229
|
-
|
|
3957
|
+
fs17.writeFileSync(path18.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
5230
3958
|
const pkg = {
|
|
5231
3959
|
name: extName,
|
|
5232
3960
|
version: "0.0.1",
|
|
@@ -5239,17 +3967,17 @@ async function create3() {
|
|
|
5239
3967
|
engines: { node: ">=18" }
|
|
5240
3968
|
};
|
|
5241
3969
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5242
|
-
|
|
3970
|
+
fs17.writeFileSync(path18.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5243
3971
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5244
3972
|
if (includeModule) {
|
|
5245
|
-
|
|
5246
|
-
|
|
3973
|
+
fs17.mkdirSync(path18.join(root, "src"), { recursive: true });
|
|
3974
|
+
fs17.writeFileSync(path18.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5247
3975
|
export declare class ${simpleModuleName} {
|
|
5248
3976
|
// Add your module methods here
|
|
5249
3977
|
}
|
|
5250
3978
|
`);
|
|
5251
|
-
|
|
5252
|
-
|
|
3979
|
+
fs17.mkdirSync(path18.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
3980
|
+
fs17.writeFileSync(path18.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5253
3981
|
id("com.android.library")
|
|
5254
3982
|
id("org.jetbrains.kotlin.android")
|
|
5255
3983
|
}
|
|
@@ -5270,7 +3998,7 @@ dependencies {
|
|
|
5270
3998
|
implementation(libs.lynx.jssdk)
|
|
5271
3999
|
}
|
|
5272
4000
|
`);
|
|
5273
|
-
|
|
4001
|
+
fs17.writeFileSync(path18.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5274
4002
|
<manifest />
|
|
5275
4003
|
`);
|
|
5276
4004
|
const ktContent = `package ${packageName}
|
|
@@ -5287,8 +4015,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5287
4015
|
}
|
|
5288
4016
|
}
|
|
5289
4017
|
`;
|
|
5290
|
-
|
|
5291
|
-
|
|
4018
|
+
fs17.writeFileSync(path18.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
|
|
4019
|
+
fs17.mkdirSync(path18.join(root, "ios", extName, extName, "Classes"), { recursive: true });
|
|
5292
4020
|
const podspec = `Pod::Spec.new do |s|
|
|
5293
4021
|
s.name = '${extName}'
|
|
5294
4022
|
s.version = '0.0.1'
|
|
@@ -5302,7 +4030,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5302
4030
|
s.dependency 'Lynx'
|
|
5303
4031
|
end
|
|
5304
4032
|
`;
|
|
5305
|
-
|
|
4033
|
+
fs17.writeFileSync(path18.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5306
4034
|
const swiftContent = `import Foundation
|
|
5307
4035
|
|
|
5308
4036
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5311,16 +4039,16 @@ end
|
|
|
5311
4039
|
}
|
|
5312
4040
|
}
|
|
5313
4041
|
`;
|
|
5314
|
-
|
|
4042
|
+
fs17.writeFileSync(path18.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5315
4043
|
}
|
|
5316
|
-
|
|
4044
|
+
fs17.writeFileSync(path18.join(root, "index.js"), `'use strict';
|
|
5317
4045
|
module.exports = {};
|
|
5318
4046
|
`);
|
|
5319
|
-
|
|
4047
|
+
fs17.writeFileSync(path18.join(root, "tsconfig.json"), JSON.stringify({
|
|
5320
4048
|
compilerOptions: { target: "ES2020", module: "ESNext", moduleResolution: "bundler", strict: true },
|
|
5321
4049
|
include: ["src"]
|
|
5322
4050
|
}, null, 2));
|
|
5323
|
-
|
|
4051
|
+
fs17.writeFileSync(path18.join(root, "README.md"), `# ${extName}
|
|
5324
4052
|
|
|
5325
4053
|
Lynx extension for ${extName}.
|
|
5326
4054
|
|
|
@@ -5345,8 +4073,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5345
4073
|
var create_default3 = create3;
|
|
5346
4074
|
|
|
5347
4075
|
// src/common/codegen.ts
|
|
5348
|
-
import
|
|
5349
|
-
import
|
|
4076
|
+
import fs18 from "fs";
|
|
4077
|
+
import path19 from "path";
|
|
5350
4078
|
function codegen() {
|
|
5351
4079
|
const cwd = process.cwd();
|
|
5352
4080
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5354,9 +4082,9 @@ function codegen() {
|
|
|
5354
4082
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5355
4083
|
process.exit(1);
|
|
5356
4084
|
}
|
|
5357
|
-
const srcDir =
|
|
5358
|
-
const generatedDir =
|
|
5359
|
-
|
|
4085
|
+
const srcDir = path19.join(cwd, "src");
|
|
4086
|
+
const generatedDir = path19.join(cwd, "generated");
|
|
4087
|
+
fs18.mkdirSync(generatedDir, { recursive: true });
|
|
5360
4088
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5361
4089
|
const modules = extractLynxModules(dtsFiles);
|
|
5362
4090
|
if (modules.length === 0) {
|
|
@@ -5366,28 +4094,28 @@ function codegen() {
|
|
|
5366
4094
|
for (const mod of modules) {
|
|
5367
4095
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5368
4096
|
`;
|
|
5369
|
-
const outPath =
|
|
5370
|
-
|
|
4097
|
+
const outPath = path19.join(generatedDir, `${mod}.ts`);
|
|
4098
|
+
fs18.writeFileSync(outPath, tsContent);
|
|
5371
4099
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5372
4100
|
}
|
|
5373
4101
|
if (config.android) {
|
|
5374
|
-
const androidGenerated =
|
|
5375
|
-
|
|
4102
|
+
const androidGenerated = path19.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
4103
|
+
fs18.mkdirSync(androidGenerated, { recursive: true });
|
|
5376
4104
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5377
4105
|
}
|
|
5378
4106
|
if (config.ios) {
|
|
5379
|
-
const iosGenerated =
|
|
5380
|
-
|
|
4107
|
+
const iosGenerated = path19.join(cwd, "ios", "generated");
|
|
4108
|
+
fs18.mkdirSync(iosGenerated, { recursive: true });
|
|
5381
4109
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5382
4110
|
}
|
|
5383
4111
|
console.log("\u2728 Codegen complete.");
|
|
5384
4112
|
}
|
|
5385
4113
|
function findDtsFiles(dir) {
|
|
5386
4114
|
const result = [];
|
|
5387
|
-
if (!
|
|
5388
|
-
const entries =
|
|
4115
|
+
if (!fs18.existsSync(dir)) return result;
|
|
4116
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
5389
4117
|
for (const e of entries) {
|
|
5390
|
-
const full =
|
|
4118
|
+
const full = path19.join(dir, e.name);
|
|
5391
4119
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5392
4120
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5393
4121
|
}
|
|
@@ -5397,7 +4125,7 @@ function extractLynxModules(files) {
|
|
|
5397
4125
|
const modules = [];
|
|
5398
4126
|
const seen = /* @__PURE__ */ new Set();
|
|
5399
4127
|
for (const file of files) {
|
|
5400
|
-
const content =
|
|
4128
|
+
const content = fs18.readFileSync(file, "utf8");
|
|
5401
4129
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5402
4130
|
let m;
|
|
5403
4131
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5413,10 +4141,10 @@ var codegen_default = codegen;
|
|
|
5413
4141
|
|
|
5414
4142
|
// src/common/devServer.ts
|
|
5415
4143
|
import { spawn } from "child_process";
|
|
5416
|
-
import
|
|
4144
|
+
import fs19 from "fs";
|
|
5417
4145
|
import http from "http";
|
|
5418
4146
|
import os3 from "os";
|
|
5419
|
-
import
|
|
4147
|
+
import path20 from "path";
|
|
5420
4148
|
import { WebSocketServer } from "ws";
|
|
5421
4149
|
var DEFAULT_PORT = 3e3;
|
|
5422
4150
|
function getLanIp() {
|
|
@@ -5434,13 +4162,13 @@ async function startDevServer(opts) {
|
|
|
5434
4162
|
const verbose = opts?.verbose ?? false;
|
|
5435
4163
|
const resolved = resolveHostPaths();
|
|
5436
4164
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5437
|
-
const distDir =
|
|
4165
|
+
const distDir = path20.dirname(lynxBundlePath);
|
|
5438
4166
|
const port = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
5439
4167
|
let buildProcess = null;
|
|
5440
4168
|
function detectPackageManager2(cwd) {
|
|
5441
|
-
const dir =
|
|
5442
|
-
if (
|
|
5443
|
-
if (
|
|
4169
|
+
const dir = path20.resolve(cwd);
|
|
4170
|
+
if (fs19.existsSync(path20.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
|
|
4171
|
+
if (fs19.existsSync(path20.join(dir, "bun.lockb")) || fs19.existsSync(path20.join(dir, "bun.lock"))) return { cmd: "bun", args: ["run", "build"] };
|
|
5444
4172
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5445
4173
|
}
|
|
5446
4174
|
function runBuild() {
|
|
@@ -5462,20 +4190,20 @@ async function startDevServer(opts) {
|
|
|
5462
4190
|
});
|
|
5463
4191
|
});
|
|
5464
4192
|
}
|
|
5465
|
-
const projectName =
|
|
4193
|
+
const projectName = path20.basename(lynxProjectDir);
|
|
5466
4194
|
const basePath = `/${projectName}`;
|
|
5467
4195
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5468
4196
|
let iconFilePath = null;
|
|
5469
|
-
if (iconPaths?.source &&
|
|
4197
|
+
if (iconPaths?.source && fs19.statSync(iconPaths.source).isFile()) {
|
|
5470
4198
|
iconFilePath = iconPaths.source;
|
|
5471
4199
|
} else if (iconPaths?.android) {
|
|
5472
|
-
const androidIcon =
|
|
5473
|
-
if (
|
|
4200
|
+
const androidIcon = path20.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
4201
|
+
if (fs19.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5474
4202
|
} else if (iconPaths?.ios) {
|
|
5475
|
-
const iosIcon =
|
|
5476
|
-
if (
|
|
4203
|
+
const iosIcon = path20.join(iconPaths.ios, "Icon-1024.png");
|
|
4204
|
+
if (fs19.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5477
4205
|
}
|
|
5478
|
-
const iconExt = iconFilePath ?
|
|
4206
|
+
const iconExt = iconFilePath ? path20.extname(iconFilePath) || ".png" : "";
|
|
5479
4207
|
const iconMime = {
|
|
5480
4208
|
".png": "image/png",
|
|
5481
4209
|
".jpg": "image/jpeg",
|
|
@@ -5514,7 +4242,7 @@ async function startDevServer(opts) {
|
|
|
5514
4242
|
return;
|
|
5515
4243
|
}
|
|
5516
4244
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5517
|
-
|
|
4245
|
+
fs19.readFile(iconFilePath, (err, data) => {
|
|
5518
4246
|
if (err) {
|
|
5519
4247
|
res.writeHead(404);
|
|
5520
4248
|
res.end();
|
|
@@ -5532,14 +4260,14 @@ async function startDevServer(opts) {
|
|
|
5532
4260
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5533
4261
|
}
|
|
5534
4262
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5535
|
-
const filePath =
|
|
5536
|
-
const distResolved =
|
|
5537
|
-
if (!filePath.startsWith(distResolved +
|
|
4263
|
+
const filePath = path20.resolve(distDir, relPath);
|
|
4264
|
+
const distResolved = path20.resolve(distDir);
|
|
4265
|
+
if (!filePath.startsWith(distResolved + path20.sep) && filePath !== distResolved) {
|
|
5538
4266
|
res.writeHead(403);
|
|
5539
4267
|
res.end();
|
|
5540
4268
|
return;
|
|
5541
4269
|
}
|
|
5542
|
-
|
|
4270
|
+
fs19.readFile(filePath, (err, data) => {
|
|
5543
4271
|
if (err) {
|
|
5544
4272
|
res.writeHead(404);
|
|
5545
4273
|
res.end("Not found");
|
|
@@ -5593,10 +4321,10 @@ async function startDevServer(opts) {
|
|
|
5593
4321
|
}
|
|
5594
4322
|
if (chokidar) {
|
|
5595
4323
|
const watchPaths = [
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
].filter((p) =>
|
|
4324
|
+
path20.join(lynxProjectDir, "src"),
|
|
4325
|
+
path20.join(lynxProjectDir, "lynx.config.ts"),
|
|
4326
|
+
path20.join(lynxProjectDir, "lynx.config.js")
|
|
4327
|
+
].filter((p) => fs19.existsSync(p));
|
|
5600
4328
|
if (watchPaths.length > 0) {
|
|
5601
4329
|
const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5602
4330
|
watcher.on("change", async () => {
|
|
@@ -5675,10 +4403,10 @@ async function start(opts) {
|
|
|
5675
4403
|
var start_default = start;
|
|
5676
4404
|
|
|
5677
4405
|
// src/common/injectHost.ts
|
|
5678
|
-
import
|
|
5679
|
-
import
|
|
4406
|
+
import fs20 from "fs";
|
|
4407
|
+
import path21 from "path";
|
|
5680
4408
|
function readAndSubstitute(templatePath, vars) {
|
|
5681
|
-
const raw =
|
|
4409
|
+
const raw = fs20.readFileSync(templatePath, "utf-8");
|
|
5682
4410
|
return Object.entries(vars).reduce(
|
|
5683
4411
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5684
4412
|
raw
|
|
@@ -5699,32 +4427,32 @@ async function injectHostAndroid(opts) {
|
|
|
5699
4427
|
process.exit(1);
|
|
5700
4428
|
}
|
|
5701
4429
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5702
|
-
const rootDir =
|
|
4430
|
+
const rootDir = path21.join(projectRoot, androidDir);
|
|
5703
4431
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5704
|
-
const javaDir =
|
|
5705
|
-
const kotlinDir =
|
|
5706
|
-
if (!
|
|
4432
|
+
const javaDir = path21.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
4433
|
+
const kotlinDir = path21.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
4434
|
+
if (!fs20.existsSync(javaDir) || !fs20.existsSync(kotlinDir)) {
|
|
5707
4435
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5708
4436
|
process.exit(1);
|
|
5709
4437
|
}
|
|
5710
|
-
const templateDir =
|
|
4438
|
+
const templateDir = path21.join(hostPkg, "android", "templates");
|
|
5711
4439
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5712
4440
|
const files = [
|
|
5713
|
-
{ src: "App.java", dst:
|
|
5714
|
-
{ src: "TemplateProvider.java", dst:
|
|
5715
|
-
{ src: "MainActivity.kt", dst:
|
|
4441
|
+
{ src: "App.java", dst: path21.join(javaDir, "App.java") },
|
|
4442
|
+
{ src: "TemplateProvider.java", dst: path21.join(javaDir, "TemplateProvider.java") },
|
|
4443
|
+
{ src: "MainActivity.kt", dst: path21.join(kotlinDir, "MainActivity.kt") }
|
|
5716
4444
|
];
|
|
5717
4445
|
for (const { src, dst } of files) {
|
|
5718
|
-
const srcPath =
|
|
5719
|
-
if (!
|
|
5720
|
-
if (
|
|
5721
|
-
console.log(`\u23ED\uFE0F Skipping ${
|
|
4446
|
+
const srcPath = path21.join(templateDir, src);
|
|
4447
|
+
if (!fs20.existsSync(srcPath)) continue;
|
|
4448
|
+
if (fs20.existsSync(dst) && !opts?.force) {
|
|
4449
|
+
console.log(`\u23ED\uFE0F Skipping ${path21.basename(dst)} (use --force to overwrite)`);
|
|
5722
4450
|
continue;
|
|
5723
4451
|
}
|
|
5724
4452
|
const content = readAndSubstitute(srcPath, vars);
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
console.log(`\u2705 Injected ${
|
|
4453
|
+
fs20.mkdirSync(path21.dirname(dst), { recursive: true });
|
|
4454
|
+
fs20.writeFileSync(dst, content);
|
|
4455
|
+
console.log(`\u2705 Injected ${path21.basename(dst)}`);
|
|
5728
4456
|
}
|
|
5729
4457
|
}
|
|
5730
4458
|
async function injectHostIos(opts) {
|
|
@@ -5742,13 +4470,13 @@ async function injectHostIos(opts) {
|
|
|
5742
4470
|
process.exit(1);
|
|
5743
4471
|
}
|
|
5744
4472
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5745
|
-
const rootDir =
|
|
5746
|
-
const projectDir =
|
|
5747
|
-
if (!
|
|
4473
|
+
const rootDir = path21.join(projectRoot, iosDir);
|
|
4474
|
+
const projectDir = path21.join(rootDir, appName);
|
|
4475
|
+
if (!fs20.existsSync(projectDir)) {
|
|
5748
4476
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5749
4477
|
process.exit(1);
|
|
5750
4478
|
}
|
|
5751
|
-
const templateDir =
|
|
4479
|
+
const templateDir = path21.join(hostPkg, "ios", "templates");
|
|
5752
4480
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5753
4481
|
const files = [
|
|
5754
4482
|
"AppDelegate.swift",
|
|
@@ -5758,23 +4486,23 @@ async function injectHostIos(opts) {
|
|
|
5758
4486
|
"LynxInitProcessor.swift"
|
|
5759
4487
|
];
|
|
5760
4488
|
for (const f of files) {
|
|
5761
|
-
const srcPath =
|
|
5762
|
-
const dstPath =
|
|
5763
|
-
if (!
|
|
5764
|
-
if (
|
|
4489
|
+
const srcPath = path21.join(templateDir, f);
|
|
4490
|
+
const dstPath = path21.join(projectDir, f);
|
|
4491
|
+
if (!fs20.existsSync(srcPath)) continue;
|
|
4492
|
+
if (fs20.existsSync(dstPath) && !opts?.force) {
|
|
5765
4493
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5766
4494
|
continue;
|
|
5767
4495
|
}
|
|
5768
4496
|
const content = readAndSubstitute(srcPath, vars);
|
|
5769
|
-
|
|
4497
|
+
fs20.writeFileSync(dstPath, content);
|
|
5770
4498
|
console.log(`\u2705 Injected ${f}`);
|
|
5771
4499
|
}
|
|
5772
4500
|
}
|
|
5773
4501
|
|
|
5774
4502
|
// src/common/buildEmbeddable.ts
|
|
5775
|
-
import
|
|
5776
|
-
import
|
|
5777
|
-
import { execSync as
|
|
4503
|
+
import fs21 from "fs";
|
|
4504
|
+
import path22 from "path";
|
|
4505
|
+
import { execSync as execSync8 } from "child_process";
|
|
5778
4506
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5779
4507
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
5780
4508
|
var GRADLE_VERSION = "8.14.2";
|
|
@@ -5848,14 +4576,14 @@ object LynxEmbeddable {
|
|
|
5848
4576
|
}
|
|
5849
4577
|
`;
|
|
5850
4578
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
5851
|
-
const libDir =
|
|
5852
|
-
const libSrcMain =
|
|
5853
|
-
const assetsDir =
|
|
5854
|
-
const kotlinDir =
|
|
5855
|
-
const generatedDir =
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
4579
|
+
const libDir = path22.join(androidDir, "lib");
|
|
4580
|
+
const libSrcMain = path22.join(libDir, "src", "main");
|
|
4581
|
+
const assetsDir = path22.join(libSrcMain, "assets");
|
|
4582
|
+
const kotlinDir = path22.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
|
|
4583
|
+
const generatedDir = path22.join(kotlinDir, "generated");
|
|
4584
|
+
fs21.mkdirSync(path22.join(androidDir, "gradle"), { recursive: true });
|
|
4585
|
+
fs21.mkdirSync(generatedDir, { recursive: true });
|
|
4586
|
+
fs21.mkdirSync(assetsDir, { recursive: true });
|
|
5859
4587
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5860
4588
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5861
4589
|
const settingsContent = `pluginManagement {
|
|
@@ -5875,7 +4603,7 @@ include(":lib")
|
|
|
5875
4603
|
${androidModules.map((p) => {
|
|
5876
4604
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
5877
4605
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
5878
|
-
const absPath =
|
|
4606
|
+
const absPath = path22.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
5879
4607
|
return `include(":${gradleName}")
|
|
5880
4608
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
5881
4609
|
}).join("\n")}
|
|
@@ -5922,10 +4650,10 @@ dependencies {
|
|
|
5922
4650
|
${libDeps}
|
|
5923
4651
|
}
|
|
5924
4652
|
`;
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
4653
|
+
fs21.writeFileSync(path22.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
|
|
4654
|
+
fs21.writeFileSync(path22.join(androidDir, "settings.gradle.kts"), settingsContent);
|
|
4655
|
+
fs21.writeFileSync(
|
|
4656
|
+
path22.join(androidDir, "build.gradle.kts"),
|
|
5929
4657
|
`plugins {
|
|
5930
4658
|
alias(libs.plugins.android.library) apply false
|
|
5931
4659
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -5933,26 +4661,26 @@ ${libDeps}
|
|
|
5933
4661
|
}
|
|
5934
4662
|
`
|
|
5935
4663
|
);
|
|
5936
|
-
|
|
5937
|
-
|
|
4664
|
+
fs21.writeFileSync(
|
|
4665
|
+
path22.join(androidDir, "gradle.properties"),
|
|
5938
4666
|
`org.gradle.jvmargs=-Xmx2048m
|
|
5939
4667
|
android.useAndroidX=true
|
|
5940
4668
|
kotlin.code.style=official
|
|
5941
4669
|
`
|
|
5942
4670
|
);
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
4671
|
+
fs21.writeFileSync(path22.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
4672
|
+
fs21.writeFileSync(
|
|
4673
|
+
path22.join(libSrcMain, "AndroidManifest.xml"),
|
|
5946
4674
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
5947
4675
|
);
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
4676
|
+
fs21.copyFileSync(lynxBundlePath, path22.join(assetsDir, lynxBundleFile));
|
|
4677
|
+
fs21.writeFileSync(path22.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
|
|
4678
|
+
fs21.writeFileSync(
|
|
4679
|
+
path22.join(generatedDir, "GeneratedLynxExtensions.kt"),
|
|
5952
4680
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
5953
4681
|
);
|
|
5954
|
-
|
|
5955
|
-
|
|
4682
|
+
fs21.writeFileSync(
|
|
4683
|
+
path22.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
5956
4684
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
5957
4685
|
);
|
|
5958
4686
|
}
|
|
@@ -5960,21 +4688,21 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5960
4688
|
const resolved = resolveHostPaths();
|
|
5961
4689
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
5962
4690
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
5963
|
-
|
|
5964
|
-
if (!
|
|
4691
|
+
execSync8("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
4692
|
+
if (!fs21.existsSync(lynxBundlePath)) {
|
|
5965
4693
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
5966
4694
|
process.exit(1);
|
|
5967
4695
|
}
|
|
5968
|
-
const outDir =
|
|
5969
|
-
|
|
5970
|
-
const distDir =
|
|
4696
|
+
const outDir = path22.join(projectRoot, EMBEDDABLE_DIR);
|
|
4697
|
+
fs21.mkdirSync(outDir, { recursive: true });
|
|
4698
|
+
const distDir = path22.dirname(lynxBundlePath);
|
|
5971
4699
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
5972
4700
|
const modules = discoverModules(projectRoot);
|
|
5973
4701
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5974
4702
|
const abiFilters = resolveAbiFilters(config);
|
|
5975
|
-
const androidDir =
|
|
5976
|
-
if (
|
|
5977
|
-
|
|
4703
|
+
const androidDir = path22.join(outDir, "android");
|
|
4704
|
+
if (fs21.existsSync(androidDir)) fs21.rmSync(androidDir, { recursive: true });
|
|
4705
|
+
fs21.mkdirSync(androidDir, { recursive: true });
|
|
5978
4706
|
generateAndroidLibrary(
|
|
5979
4707
|
outDir,
|
|
5980
4708
|
androidDir,
|
|
@@ -5984,23 +4712,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5984
4712
|
modules,
|
|
5985
4713
|
abiFilters
|
|
5986
4714
|
);
|
|
5987
|
-
const gradlewPath =
|
|
4715
|
+
const gradlewPath = path22.join(androidDir, "gradlew");
|
|
5988
4716
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
5989
4717
|
const existingGradleDirs = [
|
|
5990
|
-
|
|
5991
|
-
devAppDir ?
|
|
4718
|
+
path22.join(projectRoot, "android"),
|
|
4719
|
+
devAppDir ? path22.join(devAppDir, "android") : null
|
|
5992
4720
|
].filter(Boolean);
|
|
5993
4721
|
let hasWrapper = false;
|
|
5994
4722
|
for (const d of existingGradleDirs) {
|
|
5995
|
-
if (
|
|
4723
|
+
if (fs21.existsSync(path22.join(d, "gradlew"))) {
|
|
5996
4724
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
5997
|
-
const src =
|
|
5998
|
-
if (
|
|
5999
|
-
const dest =
|
|
6000
|
-
if (
|
|
6001
|
-
|
|
4725
|
+
const src = path22.join(d, name);
|
|
4726
|
+
if (fs21.existsSync(src)) {
|
|
4727
|
+
const dest = path22.join(androidDir, name);
|
|
4728
|
+
if (fs21.statSync(src).isDirectory()) {
|
|
4729
|
+
fs21.cpSync(src, dest, { recursive: true });
|
|
6002
4730
|
} else {
|
|
6003
|
-
|
|
4731
|
+
fs21.copyFileSync(src, dest);
|
|
6004
4732
|
}
|
|
6005
4733
|
}
|
|
6006
4734
|
}
|
|
@@ -6014,15 +4742,15 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6014
4742
|
}
|
|
6015
4743
|
try {
|
|
6016
4744
|
console.log("\u{1F4E6} Building Android AAR...");
|
|
6017
|
-
|
|
4745
|
+
execSync8("./gradlew :lib:assembleRelease", { cwd: androidDir, stdio: "inherit" });
|
|
6018
4746
|
} catch (e) {
|
|
6019
4747
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6020
4748
|
throw e;
|
|
6021
4749
|
}
|
|
6022
|
-
const aarSrc =
|
|
6023
|
-
const aarDest =
|
|
6024
|
-
if (
|
|
6025
|
-
|
|
4750
|
+
const aarSrc = path22.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
|
|
4751
|
+
const aarDest = path22.join(outDir, "tamer-embeddable.aar");
|
|
4752
|
+
if (fs21.existsSync(aarSrc)) {
|
|
4753
|
+
fs21.copyFileSync(aarSrc, aarDest);
|
|
6026
4754
|
console.log(` - tamer-embeddable.aar`);
|
|
6027
4755
|
}
|
|
6028
4756
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6033,7 +4761,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6033
4761
|
// LynxEmbeddable.init(applicationContext)
|
|
6034
4762
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6035
4763
|
`;
|
|
6036
|
-
|
|
4764
|
+
fs21.writeFileSync(path22.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6037
4765
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6038
4766
|
const readme = `# Embeddable Lynx Bundle
|
|
6039
4767
|
|
|
@@ -6064,7 +4792,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6064
4792
|
|
|
6065
4793
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6066
4794
|
`;
|
|
6067
|
-
|
|
4795
|
+
fs21.writeFileSync(path22.join(outDir, "README.md"), readme);
|
|
6068
4796
|
console.log(`
|
|
6069
4797
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6070
4798
|
console.log(" - main.lynx.bundle");
|
|
@@ -6076,20 +4804,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6076
4804
|
console.log(" - README.md");
|
|
6077
4805
|
}
|
|
6078
4806
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6079
|
-
const iosDir =
|
|
6080
|
-
const podDir =
|
|
6081
|
-
const resourcesDir =
|
|
6082
|
-
|
|
6083
|
-
|
|
4807
|
+
const iosDir = path22.join(outDir, "ios");
|
|
4808
|
+
const podDir = path22.join(iosDir, "TamerEmbeddable");
|
|
4809
|
+
const resourcesDir = path22.join(podDir, "Resources");
|
|
4810
|
+
fs21.mkdirSync(resourcesDir, { recursive: true });
|
|
4811
|
+
fs21.copyFileSync(lynxBundlePath, path22.join(resourcesDir, lynxBundleFile));
|
|
6084
4812
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6085
4813
|
const podDeps = iosModules.map((p) => {
|
|
6086
4814
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6087
|
-
const podspecDir =
|
|
6088
|
-
if (!
|
|
6089
|
-
const files =
|
|
4815
|
+
const podspecDir = path22.join(p.packagePath, podspecPath);
|
|
4816
|
+
if (!fs21.existsSync(podspecDir)) return null;
|
|
4817
|
+
const files = fs21.readdirSync(podspecDir);
|
|
6090
4818
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6091
4819
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6092
|
-
const absPath =
|
|
4820
|
+
const absPath = path22.resolve(podspecDir);
|
|
6093
4821
|
return { podName, absPath };
|
|
6094
4822
|
}).filter(Boolean);
|
|
6095
4823
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6129,9 +4857,9 @@ end
|
|
|
6129
4857
|
});
|
|
6130
4858
|
const swiftImports = iosModules.map((p) => {
|
|
6131
4859
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6132
|
-
const podspecDir =
|
|
6133
|
-
if (!
|
|
6134
|
-
const files =
|
|
4860
|
+
const podspecDir = path22.join(p.packagePath, podspecPath);
|
|
4861
|
+
if (!fs21.existsSync(podspecDir)) return null;
|
|
4862
|
+
const files = fs21.readdirSync(podspecDir);
|
|
6135
4863
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6136
4864
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6137
4865
|
}).filter(Boolean);
|
|
@@ -6150,17 +4878,17 @@ ${regBlock}
|
|
|
6150
4878
|
}
|
|
6151
4879
|
}
|
|
6152
4880
|
`;
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
const absIosDir =
|
|
4881
|
+
fs21.writeFileSync(path22.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
|
|
4882
|
+
fs21.writeFileSync(path22.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
|
|
4883
|
+
const absIosDir = path22.resolve(iosDir);
|
|
6156
4884
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6157
4885
|
|
|
6158
4886
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6159
4887
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6160
4888
|
`;
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
4889
|
+
fs21.writeFileSync(path22.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
4890
|
+
fs21.writeFileSync(
|
|
4891
|
+
path22.join(outDir, "snippet-ios.swift"),
|
|
6164
4892
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6165
4893
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6166
4894
|
`
|
|
@@ -6168,9 +4896,9 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6168
4896
|
}
|
|
6169
4897
|
|
|
6170
4898
|
// src/common/add.ts
|
|
6171
|
-
import
|
|
6172
|
-
import
|
|
6173
|
-
import { execSync as
|
|
4899
|
+
import fs22 from "fs";
|
|
4900
|
+
import path23 from "path";
|
|
4901
|
+
import { execSync as execSync9 } from "child_process";
|
|
6174
4902
|
var CORE_PACKAGES = [
|
|
6175
4903
|
"@tamer4lynx/tamer-app-shell",
|
|
6176
4904
|
"@tamer4lynx/tamer-screen",
|
|
@@ -6182,15 +4910,15 @@ var CORE_PACKAGES = [
|
|
|
6182
4910
|
"@tamer4lynx/tamer-icons"
|
|
6183
4911
|
];
|
|
6184
4912
|
function detectPackageManager(cwd) {
|
|
6185
|
-
const dir =
|
|
6186
|
-
if (
|
|
6187
|
-
if (
|
|
4913
|
+
const dir = path23.resolve(cwd);
|
|
4914
|
+
if (fs22.existsSync(path23.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
4915
|
+
if (fs22.existsSync(path23.join(dir, "bun.lockb"))) return "bun";
|
|
6188
4916
|
return "npm";
|
|
6189
4917
|
}
|
|
6190
4918
|
function runInstall(cwd, packages, pm) {
|
|
6191
4919
|
const args = pm === "npm" ? ["install", ...packages] : ["add", ...packages];
|
|
6192
4920
|
const cmd = pm === "npm" ? "npm" : pm === "pnpm" ? "pnpm" : "bun";
|
|
6193
|
-
|
|
4921
|
+
execSync9(`${cmd} ${args.join(" ")}`, { stdio: "inherit", cwd });
|
|
6194
4922
|
}
|
|
6195
4923
|
function addCore() {
|
|
6196
4924
|
const { lynxProjectDir } = resolveHostPaths();
|
|
@@ -6236,12 +4964,12 @@ android.command("create").option("-t, --target <target>", "Create target: host (
|
|
|
6236
4964
|
android.command("link").description("Link native modules to the Android project").action(() => {
|
|
6237
4965
|
autolink_default();
|
|
6238
4966
|
});
|
|
6239
|
-
android.command("bundle").option("-
|
|
4967
|
+
android.command("bundle").option("-d, --debug", "Debug bundle with dev client embedded (default)").option("-r, --release", "Release bundle without dev client").description("Build Lynx bundle and copy to Android assets (runs autolink first)").action(async (opts) => {
|
|
6240
4968
|
validateDebugRelease(opts.debug, opts.release);
|
|
6241
4969
|
const release = opts.release === true;
|
|
6242
|
-
await bundle_default({
|
|
4970
|
+
await bundle_default({ release });
|
|
6243
4971
|
});
|
|
6244
|
-
var androidBuildCmd = android.command("build").option("-i, --install", "Install APK to connected device and launch app after building").option("-
|
|
4972
|
+
var androidBuildCmd = android.command("build").option("-i, --install", "Install APK to connected device and launch app after building").option("-e, --embeddable", "Build for embedding in existing app (host only). Use with --release for production-ready embeddable.").option("-d, --debug", "Debug APK with dev client embedded (default)").option("-r, --release", "Release APK without dev client").description("Build APK (autolink + bundle + gradle)").action(async () => {
|
|
6245
4973
|
const opts = androidBuildCmd.opts();
|
|
6246
4974
|
validateDebugRelease(opts.debug, opts.release);
|
|
6247
4975
|
const release = opts.release === true;
|
|
@@ -6249,7 +4977,7 @@ var androidBuildCmd = android.command("build").option("-i, --install", "Install
|
|
|
6249
4977
|
await buildEmbeddable({ release: true });
|
|
6250
4978
|
return;
|
|
6251
4979
|
}
|
|
6252
|
-
await build_default({ install: opts.install,
|
|
4980
|
+
await build_default({ install: opts.install, release });
|
|
6253
4981
|
});
|
|
6254
4982
|
android.command("sync").description("Sync dev client files (TemplateProvider, MainActivity, DevClientManager) from tamer.config.json").action(async () => {
|
|
6255
4983
|
await syncDevClient_default();
|
|
@@ -6267,12 +4995,12 @@ ios.command("inject").option("-f, --force", "Overwrite existing files").descript
|
|
|
6267
4995
|
ios.command("link").description("Link native modules to the iOS project").action(() => {
|
|
6268
4996
|
autolink_default2();
|
|
6269
4997
|
});
|
|
6270
|
-
ios.command("bundle").option("-
|
|
4998
|
+
ios.command("bundle").option("-d, --debug", "Debug bundle with dev client embedded (default)").option("-r, --release", "Release bundle without dev client").description("Build Lynx bundle and copy to iOS project (runs autolink first)").action((opts) => {
|
|
6271
4999
|
validateDebugRelease(opts.debug, opts.release);
|
|
6272
5000
|
const release = opts.release === true;
|
|
6273
|
-
bundle_default2({
|
|
5001
|
+
bundle_default2({ release });
|
|
6274
5002
|
});
|
|
6275
|
-
var iosBuildCmd = ios.command("build").option("-
|
|
5003
|
+
var iosBuildCmd = ios.command("build").option("-e, --embeddable", "Output bundle + code snippets to embeddable/ for adding LynxView to an existing app. Use with --release.").option("-i, --install", "Install and launch on booted simulator after building").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client").description("Build iOS app (autolink + bundle + xcodebuild)").action(async () => {
|
|
6276
5004
|
const opts = iosBuildCmd.opts();
|
|
6277
5005
|
validateDebugRelease(opts.debug, opts.release);
|
|
6278
5006
|
const release = opts.release === true;
|
|
@@ -6280,7 +5008,7 @@ var iosBuildCmd = ios.command("build").option("-t, --target <target>", "Build ta
|
|
|
6280
5008
|
await buildEmbeddable({ release: true });
|
|
6281
5009
|
return;
|
|
6282
5010
|
}
|
|
6283
|
-
await build_default2({
|
|
5011
|
+
await build_default2({ install: opts.install, release });
|
|
6284
5012
|
});
|
|
6285
5013
|
var linkCmd = program.command("link").option("-i, --ios", "Link iOS native modules").option("-a, --android", "Link Android native modules").option("-b, --both", "Link both iOS and Android native modules").option("-s, --silent", "Run in silent mode without outputting messages").description("Link native modules to the project").action(() => {
|
|
6286
5014
|
const opts = linkCmd.opts();
|
|
@@ -6306,7 +5034,7 @@ var linkCmd = program.command("link").option("-i, --ios", "Link iOS native modul
|
|
|
6306
5034
|
program.command("start").option("-v, --verbose", "Show all logs (native + JS); default shows JS only").description("Start dev server with HMR and WebSocket support (Expo-like)").action(async (opts) => {
|
|
6307
5035
|
await start_default({ verbose: opts.verbose });
|
|
6308
5036
|
});
|
|
6309
|
-
var buildCmd = program.command("build").option("-p, --platform <platform>", "android, ios, or all (default: all)", "all").option("-
|
|
5037
|
+
var buildCmd = program.command("build").option("-p, --platform <platform>", "android, ios, or all (default: all)", "all").option("-e, --embeddable", "Output bundle + code snippets to embeddable/ for adding LynxView to an existing app. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client").option("-i, --install", "Install after building").description("Build app (unified: delegates to android/ios build)").action(async () => {
|
|
6310
5038
|
const opts = buildCmd.opts();
|
|
6311
5039
|
validateDebugRelease(opts.debug, opts.release);
|
|
6312
5040
|
const release = opts.release === true;
|
|
@@ -6316,23 +5044,22 @@ var buildCmd = program.command("build").option("-p, --platform <platform>", "and
|
|
|
6316
5044
|
}
|
|
6317
5045
|
const p = opts.platform?.toLowerCase();
|
|
6318
5046
|
const platform = p === "ios" || p === "android" ? p : "all";
|
|
6319
|
-
const target = opts.target ?? "dev-app";
|
|
6320
5047
|
if (platform === "android" || platform === "all") {
|
|
6321
|
-
await build_default({ install: opts.install,
|
|
5048
|
+
await build_default({ install: opts.install, release });
|
|
6322
5049
|
}
|
|
6323
5050
|
if (platform === "ios" || platform === "all") {
|
|
6324
|
-
await build_default2({
|
|
5051
|
+
await build_default2({ install: opts.install, release });
|
|
6325
5052
|
}
|
|
6326
5053
|
});
|
|
6327
|
-
program.command("build-dev-app").option("-p, --platform <platform>", "Platform: android, ios, or all (default)", "all").option("-i, --install", "Install APK to connected device and launch app after building").description("(Deprecated) Use: t4l build
|
|
6328
|
-
console.warn("\u26A0\uFE0F build-dev-app is deprecated. Use: t4l build
|
|
5054
|
+
program.command("build-dev-app").option("-p, --platform <platform>", "Platform: android, ios, or all (default)", "all").option("-i, --install", "Install APK to connected device and launch app after building").description("(Deprecated) Use: t4l build -p android -d --install").action(async (opts) => {
|
|
5055
|
+
console.warn("\u26A0\uFE0F build-dev-app is deprecated. Use: t4l build -p android -d [--install]");
|
|
6329
5056
|
const p = opts.platform?.toLowerCase();
|
|
6330
5057
|
const platform = p === "ios" || p === "android" ? p : "all";
|
|
6331
5058
|
if (platform === "android" || platform === "all") {
|
|
6332
|
-
await build_default({ install: opts.install,
|
|
5059
|
+
await build_default({ install: opts.install, release: false });
|
|
6333
5060
|
}
|
|
6334
5061
|
if (platform === "ios" || platform === "all") {
|
|
6335
|
-
await build_default2({
|
|
5062
|
+
await build_default2({ install: opts.install, release: false });
|
|
6336
5063
|
}
|
|
6337
5064
|
});
|
|
6338
5065
|
program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project. Future: will track versions for compatibility (Expo-style).").action((packages) => add(packages));
|
|
@@ -6342,10 +5069,10 @@ program.command("codegen").description("Generate code from @lynxmodule declarati
|
|
|
6342
5069
|
codegen_default();
|
|
6343
5070
|
});
|
|
6344
5071
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
6345
|
-
const configPath =
|
|
5072
|
+
const configPath = path24.join(process.cwd(), "tamer.config.json");
|
|
6346
5073
|
let config = {};
|
|
6347
|
-
if (
|
|
6348
|
-
config = JSON.parse(
|
|
5074
|
+
if (fs23.existsSync(configPath)) {
|
|
5075
|
+
config = JSON.parse(fs23.readFileSync(configPath, "utf8"));
|
|
6349
5076
|
}
|
|
6350
5077
|
if (config.autolink) {
|
|
6351
5078
|
delete config.autolink;
|
|
@@ -6354,7 +5081,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
6354
5081
|
config.autolink = true;
|
|
6355
5082
|
console.log("Autolink enabled in tamer.config.json");
|
|
6356
5083
|
}
|
|
6357
|
-
|
|
5084
|
+
fs23.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
6358
5085
|
console.log(`Updated ${configPath}`);
|
|
6359
5086
|
});
|
|
6360
5087
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|