@tamer4lynx/cli 0.0.2 → 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 +477 -1781
- 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";
|
|
@@ -213,6 +213,7 @@ function findRepoRoot(start2) {
|
|
|
213
213
|
}
|
|
214
214
|
function findDevAppPackage(projectRoot) {
|
|
215
215
|
const candidates = [
|
|
216
|
+
path2.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-app"),
|
|
216
217
|
path2.join(projectRoot, "node_modules", "tamer-dev-app"),
|
|
217
218
|
path2.join(projectRoot, "packages", "tamer-dev-app"),
|
|
218
219
|
path2.join(path2.dirname(projectRoot), "tamer-dev-app")
|
|
@@ -226,6 +227,7 @@ function findDevAppPackage(projectRoot) {
|
|
|
226
227
|
}
|
|
227
228
|
function findDevClientPackage(projectRoot) {
|
|
228
229
|
const candidates = [
|
|
230
|
+
path2.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client"),
|
|
229
231
|
path2.join(projectRoot, "node_modules", "tamer-dev-client"),
|
|
230
232
|
path2.join(projectRoot, "packages", "tamer-dev-client"),
|
|
231
233
|
path2.join(path2.dirname(projectRoot), "tamer-dev-client")
|
|
@@ -264,11 +266,8 @@ function resolveHostPaths(cwd = process.cwd()) {
|
|
|
264
266
|
const lynxBundlePath = path2.join(lynxProjectDir, bundleRoot, bundleFile);
|
|
265
267
|
const androidDir = path2.join(projectRoot, androidDirRel);
|
|
266
268
|
const devMode = resolveDevMode(config);
|
|
267
|
-
const devAppDir = devMode === "embedded" ? findDevAppPackage(projectRoot) : null;
|
|
268
269
|
const devClientPkg = findDevClientPackage(projectRoot);
|
|
269
|
-
const
|
|
270
|
-
const devClientBundleInApp = devAppDir ? path2.join(devAppDir, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : null;
|
|
271
|
-
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;
|
|
272
271
|
return {
|
|
273
272
|
projectRoot,
|
|
274
273
|
androidDir,
|
|
@@ -321,33 +320,6 @@ function resolveIconPaths(projectRoot, config) {
|
|
|
321
320
|
}
|
|
322
321
|
return Object.keys(out).length ? out : null;
|
|
323
322
|
}
|
|
324
|
-
function resolveDevAppPaths(repoRoot) {
|
|
325
|
-
const devAppDir = path2.join(repoRoot, "packages", "tamer-dev-app");
|
|
326
|
-
const configPath = path2.join(devAppDir, "tamer.config.json");
|
|
327
|
-
if (!fs2.existsSync(configPath)) {
|
|
328
|
-
throw new Error("packages/tamer-dev-app/tamer.config.json not found.");
|
|
329
|
-
}
|
|
330
|
-
const config = JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
331
|
-
const packageName = config.android?.packageName ?? "com.nanofuxion.tamerdevapp";
|
|
332
|
-
const androidDirRel = config.paths?.androidDir ?? "android";
|
|
333
|
-
const androidDir = path2.join(devAppDir, androidDirRel);
|
|
334
|
-
const devClientDir = findDevClientPackage(repoRoot) ?? path2.join(repoRoot, "packages", "tamer-dev-client");
|
|
335
|
-
const lynxBundlePath = path2.join(devClientDir, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle");
|
|
336
|
-
return {
|
|
337
|
-
projectRoot: devAppDir,
|
|
338
|
-
androidDir,
|
|
339
|
-
iosDir: path2.join(devAppDir, "ios"),
|
|
340
|
-
androidAppDir: path2.join(androidDir, "app"),
|
|
341
|
-
androidAssetsDir: path2.join(androidDir, "app", "src", "main", "assets"),
|
|
342
|
-
androidKotlinDir: path2.join(androidDir, "app", "src", "main", "kotlin", packageName.replace(/\./g, "/")),
|
|
343
|
-
lynxProjectDir: devClientDir,
|
|
344
|
-
lynxBundlePath,
|
|
345
|
-
lynxBundleFile: "dev-client.lynx.bundle",
|
|
346
|
-
devMode: "embedded",
|
|
347
|
-
devClientBundlePath: void 0,
|
|
348
|
-
config
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
323
|
|
|
352
324
|
// src/explorer/ref.ts
|
|
353
325
|
var LYNX_RAW_BASE = "https://raw.githubusercontent.com/lynx-family/lynx/develop/explorer";
|
|
@@ -919,30 +891,13 @@ function readAndSubstituteTemplate(templatePath, vars) {
|
|
|
919
891
|
raw
|
|
920
892
|
);
|
|
921
893
|
}
|
|
922
|
-
function findRepoRoot2(start2) {
|
|
923
|
-
let dir = path3.resolve(start2);
|
|
924
|
-
const root = path3.parse(dir).root;
|
|
925
|
-
while (dir !== root) {
|
|
926
|
-
const pkgPath = path3.join(dir, "package.json");
|
|
927
|
-
if (fs3.existsSync(pkgPath)) {
|
|
928
|
-
try {
|
|
929
|
-
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
|
|
930
|
-
if (pkg.workspaces) return dir;
|
|
931
|
-
} catch {
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
dir = path3.dirname(dir);
|
|
935
|
-
}
|
|
936
|
-
return start2;
|
|
937
|
-
}
|
|
938
894
|
var create = async (opts = {}) => {
|
|
939
895
|
const target = opts.target ?? "host";
|
|
940
896
|
const origCwd = process.cwd();
|
|
941
897
|
if (target === "dev-app") {
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
console.error("\u274C packages/tamer-dev-app/tamer.config.json not found.");
|
|
898
|
+
const devAppDir = findDevAppPackage(origCwd) ?? findDevAppPackage(findRepoRoot(origCwd));
|
|
899
|
+
if (!devAppDir || !fs3.existsSync(path3.join(devAppDir, "tamer.config.json"))) {
|
|
900
|
+
console.error("\u274C tamer-dev-app not found. Add @tamer4lynx/tamer-dev-app to dependencies.");
|
|
946
901
|
process.exit(1);
|
|
947
902
|
}
|
|
948
903
|
process.chdir(devAppDir);
|
|
@@ -980,7 +935,7 @@ var create = async (opts = {}) => {
|
|
|
980
935
|
const assetsDir = path3.join(mainDir, "assets");
|
|
981
936
|
const themesDir = path3.join(mainDir, "res", "values");
|
|
982
937
|
const gradleDir = path3.join(rootDir, "gradle");
|
|
983
|
-
function
|
|
938
|
+
function writeFile2(filePath, content, options) {
|
|
984
939
|
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
985
940
|
fs3.writeFileSync(
|
|
986
941
|
filePath,
|
|
@@ -993,7 +948,7 @@ var create = async (opts = {}) => {
|
|
|
993
948
|
fs3.rmSync(rootDir, { recursive: true, force: true });
|
|
994
949
|
}
|
|
995
950
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
996
|
-
|
|
951
|
+
writeFile2(
|
|
997
952
|
path3.join(gradleDir, "libs.versions.toml"),
|
|
998
953
|
`
|
|
999
954
|
[versions]
|
|
@@ -1052,7 +1007,7 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
|
|
|
1052
1007
|
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
|
1053
1008
|
`
|
|
1054
1009
|
);
|
|
1055
|
-
|
|
1010
|
+
writeFile2(
|
|
1056
1011
|
path3.join(rootDir, "settings.gradle.kts"),
|
|
1057
1012
|
`
|
|
1058
1013
|
pluginManagement {
|
|
@@ -1086,7 +1041,7 @@ println("If you have native modules please run tamer android link")
|
|
|
1086
1041
|
// GENERATED AUTOLINK END
|
|
1087
1042
|
`
|
|
1088
1043
|
);
|
|
1089
|
-
|
|
1044
|
+
writeFile2(
|
|
1090
1045
|
path3.join(rootDir, "build.gradle.kts"),
|
|
1091
1046
|
`
|
|
1092
1047
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
@@ -1098,7 +1053,7 @@ plugins {
|
|
|
1098
1053
|
}
|
|
1099
1054
|
`
|
|
1100
1055
|
);
|
|
1101
|
-
|
|
1056
|
+
writeFile2(
|
|
1102
1057
|
path3.join(rootDir, "gradle.properties"),
|
|
1103
1058
|
`
|
|
1104
1059
|
org.gradle.jvmargs=-Xmx2048m
|
|
@@ -1107,7 +1062,7 @@ kotlin.code.style=official
|
|
|
1107
1062
|
android.enableJetifier=true
|
|
1108
1063
|
`
|
|
1109
1064
|
);
|
|
1110
|
-
|
|
1065
|
+
writeFile2(
|
|
1111
1066
|
path3.join(appDir, "build.gradle.kts"),
|
|
1112
1067
|
`
|
|
1113
1068
|
plugins {
|
|
@@ -1197,7 +1152,7 @@ dependencies {
|
|
|
1197
1152
|
}
|
|
1198
1153
|
`
|
|
1199
1154
|
);
|
|
1200
|
-
|
|
1155
|
+
writeFile2(
|
|
1201
1156
|
path3.join(themesDir, "themes.xml"),
|
|
1202
1157
|
`
|
|
1203
1158
|
<resources>
|
|
@@ -1231,7 +1186,7 @@ dependencies {
|
|
|
1231
1186
|
<uses-permission android:name="android.permission.CAMERA" />` : ` <uses-permission android:name="android.permission.INTERNET" />`;
|
|
1232
1187
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
1233
1188
|
const manifestIconAttrs = iconPaths ? ' android:icon="@mipmap/ic_launcher"\n android:roundIcon="@mipmap/ic_launcher"\n' : "";
|
|
1234
|
-
|
|
1189
|
+
writeFile2(
|
|
1235
1190
|
path3.join(mainDir, "AndroidManifest.xml"),
|
|
1236
1191
|
`
|
|
1237
1192
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -1245,7 +1200,7 @@ ${manifestIconAttrs} android:usesCleartextTraffic="true"
|
|
|
1245
1200
|
</manifest>
|
|
1246
1201
|
`
|
|
1247
1202
|
);
|
|
1248
|
-
|
|
1203
|
+
writeFile2(
|
|
1249
1204
|
path3.join(kotlinGeneratedDir, "GeneratedLynxExtensions.kt"),
|
|
1250
1205
|
`
|
|
1251
1206
|
package ${packageName}.generated
|
|
@@ -1282,7 +1237,7 @@ object GeneratedLynxExtensions {
|
|
|
1282
1237
|
]) {
|
|
1283
1238
|
const srcPath = path3.join(templateDir, src);
|
|
1284
1239
|
if (fs3.existsSync(srcPath)) {
|
|
1285
|
-
|
|
1240
|
+
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1286
1241
|
}
|
|
1287
1242
|
}
|
|
1288
1243
|
} else {
|
|
@@ -1290,9 +1245,9 @@ object GeneratedLynxExtensions {
|
|
|
1290
1245
|
fetchAndPatchApplication(vars),
|
|
1291
1246
|
fetchAndPatchTemplateProvider(vars)
|
|
1292
1247
|
]);
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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));
|
|
1296
1251
|
if (hasDevLauncher) {
|
|
1297
1252
|
if (devClientPkg) {
|
|
1298
1253
|
const templateDir = path3.join(devClientPkg, "android", "templates");
|
|
@@ -1303,15 +1258,15 @@ object GeneratedLynxExtensions {
|
|
|
1303
1258
|
]) {
|
|
1304
1259
|
const srcPath = path3.join(templateDir, src);
|
|
1305
1260
|
if (fs3.existsSync(srcPath)) {
|
|
1306
|
-
|
|
1261
|
+
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1307
1262
|
}
|
|
1308
1263
|
}
|
|
1309
1264
|
} else {
|
|
1310
|
-
|
|
1265
|
+
writeFile2(path3.join(kotlinDir, "ProjectActivity.kt"), getProjectActivity(vars));
|
|
1311
1266
|
const devClientManagerSource = getDevClientManager(vars);
|
|
1312
1267
|
if (devClientManagerSource) {
|
|
1313
|
-
|
|
1314
|
-
|
|
1268
|
+
writeFile2(path3.join(kotlinDir, "DevClientManager.kt"), devClientManagerSource);
|
|
1269
|
+
writeFile2(path3.join(kotlinDir, "DevServerPrefs.kt"), getDevServerPrefs(vars));
|
|
1315
1270
|
}
|
|
1316
1271
|
}
|
|
1317
1272
|
}
|
|
@@ -1348,7 +1303,7 @@ object GeneratedLynxExtensions {
|
|
|
1348
1303
|
if (androidSdk) {
|
|
1349
1304
|
try {
|
|
1350
1305
|
const sdkDirContent = `sdk.dir=${androidSdk.replace(/\\/g, "/")}`;
|
|
1351
|
-
|
|
1306
|
+
writeFile2(path3.join(rootDir, "local.properties"), sdkDirContent);
|
|
1352
1307
|
console.log("\u{1F4E6} Created local.properties from tamer.config.json.");
|
|
1353
1308
|
} catch (err) {
|
|
1354
1309
|
console.error(`\u274C Failed to create local.properties: ${err.message}`);
|
|
@@ -1697,6 +1652,17 @@ ${createDelayedBody}
|
|
|
1697
1652
|
}
|
|
1698
1653
|
|
|
1699
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
|
+
};
|
|
1700
1666
|
var REQUIRED_CATALOG_ENTRIES = {
|
|
1701
1667
|
"androidx.biometric": {
|
|
1702
1668
|
versionRef: "biometric",
|
|
@@ -1761,12 +1727,11 @@ ${replacementBlock}
|
|
|
1761
1727
|
androidPackages.forEach((pkg) => {
|
|
1762
1728
|
const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
|
|
1763
1729
|
const sourceDir = pkg.config.android?.sourceDir || "android";
|
|
1764
|
-
const projectPath = path6.
|
|
1765
|
-
const relativePath = path6.relative(appAndroidPath, projectPath).replace(/\\/g, "/");
|
|
1730
|
+
const projectPath = path6.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
|
|
1766
1731
|
scriptContent += `
|
|
1767
1732
|
include(":${gradleProjectName}")`;
|
|
1768
1733
|
scriptContent += `
|
|
1769
|
-
project(":${gradleProjectName}").projectDir = file("${
|
|
1734
|
+
project(":${gradleProjectName}").projectDir = file("${projectPath}")`;
|
|
1770
1735
|
});
|
|
1771
1736
|
} else {
|
|
1772
1737
|
scriptContent += `
|
|
@@ -1844,7 +1809,19 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
1844
1809
|
}
|
|
1845
1810
|
function run() {
|
|
1846
1811
|
console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
|
|
1847
|
-
|
|
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
|
+
}
|
|
1848
1825
|
if (packages.length > 0) {
|
|
1849
1826
|
console.log(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
|
|
1850
1827
|
} else {
|
|
@@ -2013,7 +1990,10 @@ async function syncDevClient(opts) {
|
|
|
2013
1990
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2014
1991
|
process.exit(1);
|
|
2015
1992
|
}
|
|
2016
|
-
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";
|
|
2017
1997
|
const devServer = config.devServer ? {
|
|
2018
1998
|
host: config.devServer.host ?? "10.0.2.2",
|
|
2019
1999
|
port: config.devServer.port ?? config.devServer.httpPort ?? 3e3
|
|
@@ -2027,8 +2007,6 @@ async function syncDevClient(opts) {
|
|
|
2027
2007
|
const appDir = path8.join(rootDir, "app");
|
|
2028
2008
|
const mainDir = path8.join(appDir, "src", "main");
|
|
2029
2009
|
const manifestPath = path8.join(mainDir, "AndroidManifest.xml");
|
|
2030
|
-
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
2031
|
-
const hasDevClient = devMode === "embedded" && devClientPkg;
|
|
2032
2010
|
if (hasDevClient) {
|
|
2033
2011
|
const templateDir = path8.join(devClientPkg, "android", "templates");
|
|
2034
2012
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
@@ -2087,71 +2065,41 @@ $1$2`);
|
|
|
2087
2065
|
);
|
|
2088
2066
|
}
|
|
2089
2067
|
fs8.writeFileSync(manifestPath, manifest);
|
|
2090
|
-
console.log(
|
|
2068
|
+
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2091
2069
|
}
|
|
2092
2070
|
}
|
|
2093
2071
|
var syncDevClient_default = syncDevClient;
|
|
2094
2072
|
|
|
2095
2073
|
// src/android/bundle.ts
|
|
2096
|
-
function findRepoRoot3(start2) {
|
|
2097
|
-
let dir = path9.resolve(start2);
|
|
2098
|
-
const root = path9.parse(dir).root;
|
|
2099
|
-
while (dir !== root) {
|
|
2100
|
-
const pkgPath = path9.join(dir, "package.json");
|
|
2101
|
-
if (fs9.existsSync(pkgPath)) {
|
|
2102
|
-
try {
|
|
2103
|
-
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf8"));
|
|
2104
|
-
if (pkg.workspaces) return dir;
|
|
2105
|
-
} catch {
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
dir = path9.dirname(dir);
|
|
2109
|
-
}
|
|
2110
|
-
return start2;
|
|
2111
|
-
}
|
|
2112
2074
|
async function bundleAndDeploy(opts = {}) {
|
|
2113
|
-
const target = opts.target ?? "host";
|
|
2114
2075
|
const release = opts.release === true;
|
|
2115
|
-
const origCwd = process.cwd();
|
|
2116
2076
|
let resolved;
|
|
2117
2077
|
try {
|
|
2118
|
-
|
|
2119
|
-
const repoRoot = findRepoRoot3(origCwd);
|
|
2120
|
-
resolved = resolveDevAppPaths(repoRoot);
|
|
2121
|
-
const devAppDir = resolved.projectRoot;
|
|
2122
|
-
const androidDir = resolved.androidDir;
|
|
2123
|
-
if (!fs9.existsSync(androidDir)) {
|
|
2124
|
-
console.log("\u{1F4F1} Creating Tamer Dev App Android project...");
|
|
2125
|
-
await create_default({ target: "dev-app" });
|
|
2126
|
-
}
|
|
2127
|
-
process.chdir(devAppDir);
|
|
2128
|
-
} else {
|
|
2129
|
-
resolved = resolveHostPaths();
|
|
2130
|
-
}
|
|
2078
|
+
resolved = resolveHostPaths();
|
|
2131
2079
|
} catch (error) {
|
|
2132
2080
|
console.error(`\u274C Error loading configuration: ${error.message}`);
|
|
2133
2081
|
process.exit(1);
|
|
2134
2082
|
}
|
|
2135
|
-
const { lynxProjectDir, lynxBundlePath, androidAssetsDir, devClientBundlePath
|
|
2083
|
+
const { projectRoot, lynxProjectDir, lynxBundlePath, androidAssetsDir, devClientBundlePath } = resolved;
|
|
2084
|
+
const devClientPkg = findDevClientPackage(projectRoot);
|
|
2085
|
+
const includeDevClient = !release && !!devClientPkg;
|
|
2136
2086
|
const destinationDir = androidAssetsDir;
|
|
2137
2087
|
autolink_default();
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
if (target === "dev-app") {
|
|
2152
|
-
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.");
|
|
2153
2101
|
}
|
|
2154
|
-
if (
|
|
2102
|
+
if (includeDevClient && devClientBundlePath && !fs9.existsSync(devClientBundlePath)) {
|
|
2155
2103
|
const devClientDir = path9.dirname(path9.dirname(devClientBundlePath));
|
|
2156
2104
|
try {
|
|
2157
2105
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
@@ -2164,13 +2112,13 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2164
2112
|
}
|
|
2165
2113
|
try {
|
|
2166
2114
|
fs9.mkdirSync(destinationDir, { recursive: true });
|
|
2167
|
-
if (
|
|
2115
|
+
if (release) {
|
|
2168
2116
|
const devClientAsset = path9.join(destinationDir, "dev-client.lynx.bundle");
|
|
2169
2117
|
if (fs9.existsSync(devClientAsset)) {
|
|
2170
2118
|
fs9.rmSync(devClientAsset);
|
|
2171
2119
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2172
2120
|
}
|
|
2173
|
-
} else if (
|
|
2121
|
+
} else if (includeDevClient && devClientBundlePath && fs9.existsSync(devClientBundlePath)) {
|
|
2174
2122
|
fs9.copyFileSync(devClientBundlePath, path9.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2175
2123
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2176
2124
|
}
|
|
@@ -2189,29 +2137,16 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2189
2137
|
var bundle_default = bundleAndDeploy;
|
|
2190
2138
|
|
|
2191
2139
|
// src/android/build.ts
|
|
2192
|
-
import fs10 from "fs";
|
|
2193
2140
|
import path10 from "path";
|
|
2194
2141
|
import { execSync as execSync3 } from "child_process";
|
|
2195
|
-
function findRepoRoot4(start2) {
|
|
2196
|
-
let dir = path10.resolve(start2);
|
|
2197
|
-
const root = path10.parse(dir).root;
|
|
2198
|
-
while (dir !== root) {
|
|
2199
|
-
const pkgPath = path10.join(dir, "package.json");
|
|
2200
|
-
if (fs10.existsSync(pkgPath)) {
|
|
2201
|
-
try {
|
|
2202
|
-
const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf8"));
|
|
2203
|
-
if (pkg.workspaces) return dir;
|
|
2204
|
-
} catch {
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
dir = path10.dirname(dir);
|
|
2208
|
-
}
|
|
2209
|
-
return start2;
|
|
2210
|
-
}
|
|
2211
2142
|
async function buildApk(opts = {}) {
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2143
|
+
let resolved;
|
|
2144
|
+
try {
|
|
2145
|
+
resolved = resolveHostPaths();
|
|
2146
|
+
} catch (error) {
|
|
2147
|
+
throw error;
|
|
2148
|
+
}
|
|
2149
|
+
await bundle_default({ release: opts.release });
|
|
2215
2150
|
const androidDir = resolved.androidDir;
|
|
2216
2151
|
const gradlew = path10.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2217
2152
|
const variant = opts.release ? "Release" : "Debug";
|
|
@@ -2238,12 +2173,12 @@ async function buildApk(opts = {}) {
|
|
|
2238
2173
|
var build_default = buildApk;
|
|
2239
2174
|
|
|
2240
2175
|
// src/ios/create.ts
|
|
2241
|
-
import
|
|
2176
|
+
import fs11 from "fs";
|
|
2242
2177
|
import path12 from "path";
|
|
2243
2178
|
|
|
2244
2179
|
// src/ios/getPod.ts
|
|
2245
2180
|
import { execSync as execSync4 } from "child_process";
|
|
2246
|
-
import
|
|
2181
|
+
import fs10 from "fs";
|
|
2247
2182
|
import path11 from "path";
|
|
2248
2183
|
function isCocoaPodsInstalled() {
|
|
2249
2184
|
try {
|
|
@@ -2267,7 +2202,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2267
2202
|
try {
|
|
2268
2203
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2269
2204
|
const podfilePath = path11.join(rootDir, "Podfile");
|
|
2270
|
-
if (!
|
|
2205
|
+
if (!fs10.existsSync(podfilePath)) {
|
|
2271
2206
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2272
2207
|
}
|
|
2273
2208
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2285,14 +2220,14 @@ async function setupCocoaPods(rootDir) {
|
|
|
2285
2220
|
// src/ios/create.ts
|
|
2286
2221
|
import { randomBytes } from "crypto";
|
|
2287
2222
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2288
|
-
const raw =
|
|
2223
|
+
const raw = fs11.readFileSync(templatePath, "utf-8");
|
|
2289
2224
|
return Object.entries(vars).reduce(
|
|
2290
2225
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2291
2226
|
raw
|
|
2292
2227
|
);
|
|
2293
2228
|
}
|
|
2294
2229
|
var create2 = () => {
|
|
2295
|
-
const
|
|
2230
|
+
const generateId = () => randomBytes(12).toString("hex").toUpperCase();
|
|
2296
2231
|
let appName;
|
|
2297
2232
|
let bundleId;
|
|
2298
2233
|
let config;
|
|
@@ -2312,49 +2247,49 @@ var create2 = () => {
|
|
|
2312
2247
|
const projectDir = path12.join(rootDir, appName);
|
|
2313
2248
|
const xcodeprojDir = path12.join(rootDir, `${appName}.xcodeproj`);
|
|
2314
2249
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2315
|
-
function
|
|
2316
|
-
|
|
2317
|
-
|
|
2250
|
+
function writeFile2(filePath, content) {
|
|
2251
|
+
fs11.mkdirSync(path12.dirname(filePath), { recursive: true });
|
|
2252
|
+
fs11.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2318
2253
|
}
|
|
2319
|
-
if (
|
|
2254
|
+
if (fs11.existsSync(rootDir)) {
|
|
2320
2255
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2321
|
-
|
|
2256
|
+
fs11.rmSync(rootDir, { recursive: true, force: true });
|
|
2322
2257
|
}
|
|
2323
2258
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2324
2259
|
const ids = {
|
|
2325
|
-
project:
|
|
2326
|
-
mainGroup:
|
|
2327
|
-
appGroup:
|
|
2328
|
-
productsGroup:
|
|
2329
|
-
frameworksGroup:
|
|
2330
|
-
appFile:
|
|
2331
|
-
appDelegateRef:
|
|
2332
|
-
sceneDelegateRef:
|
|
2333
|
-
sceneDelegateBaseRef:
|
|
2334
|
-
viewControllerRef:
|
|
2335
|
-
assetsRef:
|
|
2336
|
-
lynxProviderRef:
|
|
2337
|
-
lynxInitRef:
|
|
2338
|
-
bridgingHeaderRef:
|
|
2339
|
-
nativeTarget:
|
|
2340
|
-
appDelegateBuildFile:
|
|
2341
|
-
sceneDelegateBuildFile:
|
|
2342
|
-
sceneDelegateSourceBuildFile:
|
|
2343
|
-
viewControllerBuildFile:
|
|
2344
|
-
lynxProviderBuildFile:
|
|
2345
|
-
lynxInitBuildFile:
|
|
2346
|
-
assetsBuildFile:
|
|
2347
|
-
frameworksBuildPhase:
|
|
2348
|
-
resourcesBuildPhase:
|
|
2349
|
-
sourcesBuildPhase:
|
|
2350
|
-
projectBuildConfigList:
|
|
2351
|
-
targetBuildConfigList:
|
|
2352
|
-
projectDebugConfig:
|
|
2353
|
-
projectReleaseConfig:
|
|
2354
|
-
targetDebugConfig:
|
|
2355
|
-
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()
|
|
2356
2291
|
};
|
|
2357
|
-
|
|
2292
|
+
writeFile2(path12.join(rootDir, "Podfile"), `
|
|
2358
2293
|
source 'https://cdn.cocoapods.org/'
|
|
2359
2294
|
|
|
2360
2295
|
platform :ios, '13.0'
|
|
@@ -2417,12 +2352,12 @@ end
|
|
|
2417
2352
|
const templateDir = path12.join(hostPkg, "ios", "templates");
|
|
2418
2353
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2419
2354
|
const srcPath = path12.join(templateDir, f);
|
|
2420
|
-
if (
|
|
2421
|
-
|
|
2355
|
+
if (fs11.existsSync(srcPath)) {
|
|
2356
|
+
writeFile2(path12.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2422
2357
|
}
|
|
2423
2358
|
}
|
|
2424
2359
|
} else {
|
|
2425
|
-
|
|
2360
|
+
writeFile2(path12.join(projectDir, "AppDelegate.swift"), `
|
|
2426
2361
|
import UIKit
|
|
2427
2362
|
|
|
2428
2363
|
@UIApplicationMain
|
|
@@ -2437,7 +2372,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2437
2372
|
}
|
|
2438
2373
|
}
|
|
2439
2374
|
`);
|
|
2440
|
-
|
|
2375
|
+
writeFile2(path12.join(projectDir, "SceneDelegate.swift"), `
|
|
2441
2376
|
import UIKit
|
|
2442
2377
|
|
|
2443
2378
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2451,7 +2386,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2451
2386
|
}
|
|
2452
2387
|
}
|
|
2453
2388
|
`);
|
|
2454
|
-
|
|
2389
|
+
writeFile2(path12.join(projectDir, "ViewController.swift"), `
|
|
2455
2390
|
import UIKit
|
|
2456
2391
|
import Lynx
|
|
2457
2392
|
import tamerinsets
|
|
@@ -2521,7 +2456,7 @@ class ViewController: UIViewController {
|
|
|
2521
2456
|
}
|
|
2522
2457
|
}
|
|
2523
2458
|
`);
|
|
2524
|
-
|
|
2459
|
+
writeFile2(path12.join(projectDir, "LynxProvider.swift"), `
|
|
2525
2460
|
import Foundation
|
|
2526
2461
|
|
|
2527
2462
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2540,7 +2475,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2540
2475
|
}
|
|
2541
2476
|
}
|
|
2542
2477
|
`);
|
|
2543
|
-
|
|
2478
|
+
writeFile2(path12.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2544
2479
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2545
2480
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2546
2481
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2580,7 +2515,7 @@ final class LynxInitProcessor {
|
|
|
2580
2515
|
}
|
|
2581
2516
|
`);
|
|
2582
2517
|
}
|
|
2583
|
-
|
|
2518
|
+
writeFile2(path12.join(projectDir, bridgingHeader), `
|
|
2584
2519
|
#import <Lynx/LynxConfig.h>
|
|
2585
2520
|
#import <Lynx/LynxEnv.h>
|
|
2586
2521
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2589,7 +2524,7 @@ final class LynxInitProcessor {
|
|
|
2589
2524
|
#import <SDWebImage/SDWebImage.h>
|
|
2590
2525
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2591
2526
|
`);
|
|
2592
|
-
|
|
2527
|
+
writeFile2(path12.join(projectDir, "Info.plist"), `
|
|
2593
2528
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2594
2529
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2595
2530
|
<plist version="1.0">
|
|
@@ -2648,38 +2583,38 @@ final class LynxInitProcessor {
|
|
|
2648
2583
|
</plist>
|
|
2649
2584
|
`);
|
|
2650
2585
|
const appIconDir = path12.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
2651
|
-
|
|
2586
|
+
fs11.mkdirSync(appIconDir, { recursive: true });
|
|
2652
2587
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
2653
2588
|
if (iconPaths?.ios) {
|
|
2654
|
-
const entries =
|
|
2589
|
+
const entries = fs11.readdirSync(iconPaths.ios, { withFileTypes: true });
|
|
2655
2590
|
for (const e of entries) {
|
|
2656
2591
|
const dest = path12.join(appIconDir, e.name);
|
|
2657
2592
|
if (e.isDirectory()) {
|
|
2658
|
-
|
|
2593
|
+
fs11.cpSync(path12.join(iconPaths.ios, e.name), dest, { recursive: true });
|
|
2659
2594
|
} else {
|
|
2660
|
-
|
|
2595
|
+
fs11.copyFileSync(path12.join(iconPaths.ios, e.name), dest);
|
|
2661
2596
|
}
|
|
2662
2597
|
}
|
|
2663
2598
|
console.log("\u2705 Copied iOS icon from tamer.config.json icon.ios");
|
|
2664
2599
|
} else if (iconPaths?.source) {
|
|
2665
2600
|
const ext = path12.extname(iconPaths.source) || ".png";
|
|
2666
2601
|
const icon1024 = `Icon-1024${ext}`;
|
|
2667
|
-
|
|
2668
|
-
|
|
2602
|
+
fs11.copyFileSync(iconPaths.source, path12.join(appIconDir, icon1024));
|
|
2603
|
+
writeFile2(path12.join(appIconDir, "Contents.json"), JSON.stringify({
|
|
2669
2604
|
images: [{ filename: icon1024, idiom: "universal", platform: "ios", size: "1024x1024" }],
|
|
2670
2605
|
info: { author: "xcode", version: 1 }
|
|
2671
2606
|
}, null, 2));
|
|
2672
2607
|
console.log("\u2705 Copied app icon from tamer.config.json icon.source");
|
|
2673
2608
|
} else {
|
|
2674
|
-
|
|
2609
|
+
writeFile2(path12.join(appIconDir, "Contents.json"), `
|
|
2675
2610
|
{
|
|
2676
2611
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
2677
2612
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
2678
2613
|
}
|
|
2679
2614
|
`);
|
|
2680
2615
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2616
|
+
fs11.mkdirSync(xcodeprojDir, { recursive: true });
|
|
2617
|
+
writeFile2(path12.join(xcodeprojDir, "project.pbxproj"), `
|
|
2683
2618
|
// !$*UTF8*$!
|
|
2684
2619
|
{
|
|
2685
2620
|
archiveVersion = 1;
|
|
@@ -2965,7 +2900,7 @@ final class LynxInitProcessor {
|
|
|
2965
2900
|
var create_default2 = create2;
|
|
2966
2901
|
|
|
2967
2902
|
// src/ios/autolink.ts
|
|
2968
|
-
import
|
|
2903
|
+
import fs12 from "fs";
|
|
2969
2904
|
import path13 from "path";
|
|
2970
2905
|
import { execSync as execSync5 } from "child_process";
|
|
2971
2906
|
var autolink2 = () => {
|
|
@@ -2979,11 +2914,11 @@ var autolink2 = () => {
|
|
|
2979
2914
|
const projectRoot = resolved.projectRoot;
|
|
2980
2915
|
const iosProjectPath = resolved.iosDir;
|
|
2981
2916
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
2982
|
-
if (!
|
|
2917
|
+
if (!fs12.existsSync(filePath)) {
|
|
2983
2918
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
2984
2919
|
return;
|
|
2985
2920
|
}
|
|
2986
|
-
let fileContent =
|
|
2921
|
+
let fileContent = fs12.readFileSync(filePath, "utf8");
|
|
2987
2922
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2988
2923
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2989
2924
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3008,15 +2943,15 @@ ${replacementBlock}
|
|
|
3008
2943
|
${replacementBlock}
|
|
3009
2944
|
`;
|
|
3010
2945
|
}
|
|
3011
|
-
|
|
2946
|
+
fs12.writeFileSync(filePath, fileContent, "utf8");
|
|
3012
2947
|
console.log(`\u2705 Updated autolinked section in ${path13.basename(filePath)}`);
|
|
3013
2948
|
}
|
|
3014
2949
|
function resolvePodName(pkg) {
|
|
3015
2950
|
const podspecDir = pkg.config.ios?.podspecPath || ".";
|
|
3016
2951
|
const fullPodspecDir = path13.join(pkg.packagePath, podspecDir);
|
|
3017
|
-
if (
|
|
2952
|
+
if (fs12.existsSync(fullPodspecDir)) {
|
|
3018
2953
|
try {
|
|
3019
|
-
const files =
|
|
2954
|
+
const files = fs12.readdirSync(fullPodspecDir);
|
|
3020
2955
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
3021
2956
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
3022
2957
|
} catch {
|
|
@@ -3050,7 +2985,7 @@ ${replacementBlock}
|
|
|
3050
2985
|
candidatePaths.push(path13.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
3051
2986
|
}
|
|
3052
2987
|
candidatePaths.push(path13.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
3053
|
-
const found = candidatePaths.find((p) =>
|
|
2988
|
+
const found = candidatePaths.find((p) => fs12.existsSync(p));
|
|
3054
2989
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
3055
2990
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
3056
2991
|
function updateImportsSection(filePath, pkgs) {
|
|
@@ -3065,7 +3000,7 @@ ${replacementBlock}
|
|
|
3065
3000
|
const podName = resolvePodName(pkg);
|
|
3066
3001
|
return `import ${podName}`;
|
|
3067
3002
|
}).join("\n");
|
|
3068
|
-
const fileContent =
|
|
3003
|
+
const fileContent = fs12.readFileSync(filePath, "utf8");
|
|
3069
3004
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
3070
3005
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
3071
3006
|
return;
|
|
@@ -3102,7 +3037,7 @@ ${after}`;
|
|
|
3102
3037
|
${fileContent}`;
|
|
3103
3038
|
}
|
|
3104
3039
|
}
|
|
3105
|
-
|
|
3040
|
+
fs12.writeFileSync(filePath, newContent, "utf8");
|
|
3106
3041
|
console.log(`\u2705 Updated imports in ${path13.basename(filePath)}`);
|
|
3107
3042
|
}
|
|
3108
3043
|
updateImportsSection(lynxInitPath, iosPackages);
|
|
@@ -3133,10 +3068,10 @@ ${fileContent}`;
|
|
|
3133
3068
|
candidates.push(path13.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
3134
3069
|
}
|
|
3135
3070
|
candidates.push(path13.join(iosProjectPath, "Info.plist"));
|
|
3136
|
-
return candidates.find((p) =>
|
|
3071
|
+
return candidates.find((p) => fs12.existsSync(p)) ?? null;
|
|
3137
3072
|
}
|
|
3138
3073
|
function readPlistXml(plistPath) {
|
|
3139
|
-
return
|
|
3074
|
+
return fs12.readFileSync(plistPath, "utf8");
|
|
3140
3075
|
}
|
|
3141
3076
|
function syncInfoPlistPermissions(packages) {
|
|
3142
3077
|
const plistPath = findInfoPlist();
|
|
@@ -3167,7 +3102,7 @@ ${fileContent}`;
|
|
|
3167
3102
|
added++;
|
|
3168
3103
|
}
|
|
3169
3104
|
if (added > 0) {
|
|
3170
|
-
|
|
3105
|
+
fs12.writeFileSync(plistPath, plist, "utf8");
|
|
3171
3106
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
3172
3107
|
}
|
|
3173
3108
|
}
|
|
@@ -3214,12 +3149,12 @@ ${schemesXml}
|
|
|
3214
3149
|
$1`
|
|
3215
3150
|
);
|
|
3216
3151
|
}
|
|
3217
|
-
|
|
3152
|
+
fs12.writeFileSync(plistPath, plist, "utf8");
|
|
3218
3153
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
3219
3154
|
}
|
|
3220
3155
|
function runPodInstall(forcePath) {
|
|
3221
3156
|
const podfilePath = forcePath ?? path13.join(iosProjectPath, "Podfile");
|
|
3222
|
-
if (!
|
|
3157
|
+
if (!fs12.existsSync(podfilePath)) {
|
|
3223
3158
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
3224
3159
|
return;
|
|
3225
3160
|
}
|
|
@@ -3248,7 +3183,7 @@ $1`
|
|
|
3248
3183
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
3249
3184
|
if (appNameFromConfig) {
|
|
3250
3185
|
const appPodfile = path13.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
3251
|
-
if (
|
|
3186
|
+
if (fs12.existsSync(appPodfile)) {
|
|
3252
3187
|
runPodInstall(appPodfile);
|
|
3253
3188
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
3254
3189
|
return;
|
|
@@ -3262,12 +3197,12 @@ $1`
|
|
|
3262
3197
|
var autolink_default2 = autolink2;
|
|
3263
3198
|
|
|
3264
3199
|
// src/ios/bundle.ts
|
|
3265
|
-
import
|
|
3200
|
+
import fs14 from "fs";
|
|
3266
3201
|
import path15 from "path";
|
|
3267
3202
|
import { execSync as execSync6 } from "child_process";
|
|
3268
3203
|
|
|
3269
3204
|
// src/ios/syncHost.ts
|
|
3270
|
-
import
|
|
3205
|
+
import fs13 from "fs";
|
|
3271
3206
|
import path14 from "path";
|
|
3272
3207
|
import crypto from "crypto";
|
|
3273
3208
|
function deterministicUUID(seed) {
|
|
@@ -3316,7 +3251,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3316
3251
|
`;
|
|
3317
3252
|
}
|
|
3318
3253
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3319
|
-
let content =
|
|
3254
|
+
let content = fs13.readFileSync(pbxprojPath, "utf8");
|
|
3320
3255
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3321
3256
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3322
3257
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3353,11 +3288,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3353
3288
|
);
|
|
3354
3289
|
content = content.replace(groupPattern, `$1
|
|
3355
3290
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3356
|
-
|
|
3291
|
+
fs13.writeFileSync(pbxprojPath, content, "utf8");
|
|
3357
3292
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3358
3293
|
}
|
|
3359
3294
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3360
|
-
let content =
|
|
3295
|
+
let content = fs13.readFileSync(pbxprojPath, "utf8");
|
|
3361
3296
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3362
3297
|
if (new RegExp(`path = ${escaped};`).test(content)) return;
|
|
3363
3298
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3382,11 +3317,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3382
3317
|
);
|
|
3383
3318
|
content = content.replace(groupPattern, `$1
|
|
3384
3319
|
${fileRefUUID} /* ${filename} */,`);
|
|
3385
|
-
|
|
3320
|
+
fs13.writeFileSync(pbxprojPath, content, "utf8");
|
|
3386
3321
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3387
3322
|
}
|
|
3388
3323
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3389
|
-
let content =
|
|
3324
|
+
let content = fs13.readFileSync(pbxprojPath, "utf8");
|
|
3390
3325
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3391
3326
|
if (new RegExp(`path = ${escaped};`).test(content)) return;
|
|
3392
3327
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3411,12 +3346,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3411
3346
|
);
|
|
3412
3347
|
content = content.replace(groupPattern, `$1
|
|
3413
3348
|
${fileRefUUID} /* ${filename} */,`);
|
|
3414
|
-
|
|
3349
|
+
fs13.writeFileSync(pbxprojPath, content, "utf8");
|
|
3415
3350
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3416
3351
|
}
|
|
3417
3352
|
function writeFile(filePath, content) {
|
|
3418
|
-
|
|
3419
|
-
|
|
3353
|
+
fs13.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
3354
|
+
fs13.writeFileSync(filePath, content, "utf8");
|
|
3420
3355
|
}
|
|
3421
3356
|
function getAppDelegateSwift() {
|
|
3422
3357
|
return `import UIKit
|
|
@@ -3626,8 +3561,8 @@ class ViewController: UIViewController {
|
|
|
3626
3561
|
`;
|
|
3627
3562
|
}
|
|
3628
3563
|
function patchInfoPlist(infoPlistPath) {
|
|
3629
|
-
if (!
|
|
3630
|
-
let content =
|
|
3564
|
+
if (!fs13.existsSync(infoPlistPath)) return;
|
|
3565
|
+
let content = fs13.readFileSync(infoPlistPath, "utf8");
|
|
3631
3566
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3632
3567
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3633
3568
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3659,7 +3594,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3659
3594
|
</plist>`);
|
|
3660
3595
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3661
3596
|
}
|
|
3662
|
-
|
|
3597
|
+
fs13.writeFileSync(infoPlistPath, content, "utf8");
|
|
3663
3598
|
}
|
|
3664
3599
|
function getSimpleLynxProviderSwift() {
|
|
3665
3600
|
return `import Foundation
|
|
@@ -3685,8 +3620,8 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3685
3620
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3686
3621
|
if (devClientPkg) {
|
|
3687
3622
|
const tplPath = path14.join(devClientPkg, "ios", "templates", templateName);
|
|
3688
|
-
if (
|
|
3689
|
-
let content =
|
|
3623
|
+
if (fs13.existsSync(tplPath)) {
|
|
3624
|
+
let content = fs13.readFileSync(tplPath, "utf8");
|
|
3690
3625
|
for (const [k, v] of Object.entries(vars)) {
|
|
3691
3626
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3692
3627
|
}
|
|
@@ -3698,15 +3633,15 @@ function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {})
|
|
|
3698
3633
|
function syncHostIos(opts) {
|
|
3699
3634
|
const resolved = resolveHostPaths();
|
|
3700
3635
|
const appName = resolved.config.ios?.appName;
|
|
3701
|
-
const devMode = resolveDevMode(resolved.config);
|
|
3702
3636
|
const release = opts?.release === true;
|
|
3703
|
-
const
|
|
3637
|
+
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
3638
|
+
const useDevClient = opts?.includeDevClient ?? (!release && !!devClientPkg);
|
|
3704
3639
|
if (!appName) {
|
|
3705
3640
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3706
3641
|
}
|
|
3707
3642
|
const projectDir = path14.join(resolved.iosDir, appName);
|
|
3708
3643
|
const infoPlistPath = path14.join(projectDir, "Info.plist");
|
|
3709
|
-
if (!
|
|
3644
|
+
if (!fs13.existsSync(projectDir)) {
|
|
3710
3645
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3711
3646
|
}
|
|
3712
3647
|
const pbxprojPath = path14.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
@@ -3715,35 +3650,35 @@ function syncHostIos(opts) {
|
|
|
3715
3650
|
patchInfoPlist(infoPlistPath);
|
|
3716
3651
|
writeFile(path14.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
|
|
3717
3652
|
writeFile(path14.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
|
|
3718
|
-
if (!
|
|
3719
|
-
|
|
3653
|
+
if (!fs13.existsSync(launchScreenPath)) {
|
|
3654
|
+
fs13.mkdirSync(baseLprojDir, { recursive: true });
|
|
3720
3655
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3721
3656
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3722
3657
|
}
|
|
3723
3658
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "SceneDelegate.swift");
|
|
3724
3659
|
if (useDevClient) {
|
|
3725
|
-
const
|
|
3660
|
+
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3726
3661
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3727
3662
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3728
3663
|
writeFile(path14.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3729
3664
|
writeFile(path14.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3730
3665
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3731
|
-
const devTPContent = readTemplateOrFallback(
|
|
3666
|
+
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3732
3667
|
if (devTPContent) {
|
|
3733
3668
|
writeFile(path14.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3734
3669
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3735
3670
|
}
|
|
3736
|
-
const projectVCContent = readTemplateOrFallback(
|
|
3671
|
+
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3737
3672
|
if (projectVCContent) {
|
|
3738
3673
|
writeFile(path14.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3739
3674
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3740
3675
|
}
|
|
3741
|
-
const devCMContent = readTemplateOrFallback(
|
|
3676
|
+
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3742
3677
|
if (devCMContent) {
|
|
3743
3678
|
writeFile(path14.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3744
3679
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3745
3680
|
}
|
|
3746
|
-
const qrContent = readTemplateOrFallback(
|
|
3681
|
+
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3747
3682
|
if (qrContent) {
|
|
3748
3683
|
writeFile(path14.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3749
3684
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
@@ -3760,7 +3695,6 @@ var syncHost_default = syncHostIos;
|
|
|
3760
3695
|
|
|
3761
3696
|
// src/ios/bundle.ts
|
|
3762
3697
|
function bundleAndDeploy2(opts = {}) {
|
|
3763
|
-
const target = opts.target ?? "host";
|
|
3764
3698
|
const release = opts.release === true;
|
|
3765
3699
|
let resolved;
|
|
3766
3700
|
try {
|
|
@@ -3772,16 +3706,13 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3772
3706
|
console.error(`\u274C Error loading configuration: ${error.message}`);
|
|
3773
3707
|
process.exit(1);
|
|
3774
3708
|
}
|
|
3709
|
+
const devClientPkg = findDevClientPackage(resolved.projectRoot);
|
|
3710
|
+
const includeDevClient = !release && !!devClientPkg;
|
|
3775
3711
|
const appName = resolved.config.ios.appName;
|
|
3776
3712
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
3777
3713
|
const destinationDir = path15.join(resolved.iosDir, appName);
|
|
3778
3714
|
const destinationBundlePath = path15.join(destinationDir, resolved.lynxBundleFile);
|
|
3779
|
-
|
|
3780
|
-
if (target === "dev-app") {
|
|
3781
|
-
console.error("\u274C iOS dev-app target not yet implemented.");
|
|
3782
|
-
process.exit(1);
|
|
3783
|
-
}
|
|
3784
|
-
syncHost_default({ release });
|
|
3715
|
+
syncHost_default({ release, includeDevClient });
|
|
3785
3716
|
autolink_default2();
|
|
3786
3717
|
try {
|
|
3787
3718
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -3792,11 +3723,11 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3792
3723
|
process.exit(1);
|
|
3793
3724
|
}
|
|
3794
3725
|
try {
|
|
3795
|
-
if (!
|
|
3726
|
+
if (!fs14.existsSync(sourceBundlePath)) {
|
|
3796
3727
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
3797
3728
|
process.exit(1);
|
|
3798
3729
|
}
|
|
3799
|
-
if (!
|
|
3730
|
+
if (!fs14.existsSync(destinationDir)) {
|
|
3800
3731
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
3801
3732
|
process.exit(1);
|
|
3802
3733
|
}
|
|
@@ -3805,39 +3736,29 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3805
3736
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
3806
3737
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
3807
3738
|
const pbxprojPath = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3808
|
-
if (
|
|
3739
|
+
if (fs14.existsSync(pbxprojPath)) {
|
|
3809
3740
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
3810
|
-
for (const entry of
|
|
3811
|
-
if (skip.has(entry) ||
|
|
3741
|
+
for (const entry of fs14.readdirSync(distDir)) {
|
|
3742
|
+
if (skip.has(entry) || fs14.statSync(path15.join(distDir, entry)).isDirectory()) continue;
|
|
3812
3743
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
3813
3744
|
}
|
|
3814
3745
|
}
|
|
3815
|
-
if (
|
|
3746
|
+
if (includeDevClient && devClientPkg) {
|
|
3816
3747
|
const devClientBundle = path15.join(destinationDir, "dev-client.lynx.bundle");
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
const pbxprojPath2 = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3831
|
-
if (fs15.existsSync(pbxprojPath2)) {
|
|
3832
|
-
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
} else {
|
|
3837
|
-
if (!fs15.existsSync(devClientBundle)) {
|
|
3838
|
-
fs15.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");
|
|
3839
3761
|
}
|
|
3840
|
-
console.log("\u2139\uFE0F Skipped dev-client bundle (release build)");
|
|
3841
3762
|
}
|
|
3842
3763
|
}
|
|
3843
3764
|
} catch (error) {
|
|
@@ -3849,1327 +3770,103 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
3849
3770
|
var bundle_default2 = bundleAndDeploy2;
|
|
3850
3771
|
|
|
3851
3772
|
// src/ios/build.ts
|
|
3852
|
-
import
|
|
3853
|
-
import path17 from "path";
|
|
3854
|
-
import { execSync as execSync8 } from "child_process";
|
|
3855
|
-
|
|
3856
|
-
// src/ios/syncDevClient.ts
|
|
3857
|
-
import fs16 from "fs";
|
|
3773
|
+
import fs15 from "fs";
|
|
3858
3774
|
import path16 from "path";
|
|
3859
3775
|
import { execSync as execSync7 } from "child_process";
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
(
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
return randomBytes2(12).toString("hex").toUpperCase();
|
|
3873
|
-
}
|
|
3874
|
-
function writeFile2(filePath, content) {
|
|
3875
|
-
fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
3876
|
-
fs16.writeFileSync(filePath, content, "utf8");
|
|
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
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
} catch {
|
|
3786
|
+
}
|
|
3787
|
+
return null;
|
|
3877
3788
|
}
|
|
3878
|
-
function
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
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);
|
|
3895
3824
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
presentProjectViewController()
|
|
3901
|
-
}
|
|
3902
|
-
return true
|
|
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);
|
|
3903
3829
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
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.)');
|
|
3910
3838
|
}
|
|
3839
|
+
}
|
|
3911
3840
|
}
|
|
3912
|
-
|
|
3913
|
-
}
|
|
3914
|
-
function getDevLauncherViewControllerSwift() {
|
|
3915
|
-
return `import UIKit
|
|
3916
|
-
import Lynx
|
|
3917
|
-
import tamerdevclient
|
|
3918
|
-
import tamerinsets
|
|
3919
|
-
|
|
3920
|
-
class DevLauncherViewController: UIViewController {
|
|
3921
|
-
private var lynxView: LynxView?
|
|
3922
|
-
|
|
3923
|
-
override func viewDidLoad() {
|
|
3924
|
-
super.viewDidLoad()
|
|
3925
|
-
view.backgroundColor = .black
|
|
3926
|
-
edgesForExtendedLayout = .all
|
|
3927
|
-
extendedLayoutIncludesOpaqueBars = true
|
|
3928
|
-
additionalSafeAreaInsets = .zero
|
|
3929
|
-
view.insetsLayoutMarginsFromSafeArea = false
|
|
3930
|
-
view.preservesSuperviewLayoutMargins = false
|
|
3931
|
-
viewRespectsSystemMinimumLayoutMargins = false
|
|
3932
|
-
setupLynxView()
|
|
3933
|
-
setupDevClientModule()
|
|
3934
|
-
}
|
|
3935
|
-
|
|
3936
|
-
override func viewDidLayoutSubviews() {
|
|
3937
|
-
super.viewDidLayoutSubviews()
|
|
3938
|
-
if let lynxView = lynxView {
|
|
3939
|
-
applyFullscreenLayout(to: lynxView)
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
3841
|
+
var build_default2 = buildIpa;
|
|
3942
3842
|
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
private func applyFullscreenLayout(to lynxView: LynxView) {
|
|
3972
|
-
let bounds = fullscreenBounds()
|
|
3973
|
-
let size = bounds.size
|
|
3974
|
-
lynxView.frame = bounds
|
|
3975
|
-
lynxView.updateScreenMetrics(withWidth: size.width, height: size.height)
|
|
3976
|
-
lynxView.updateViewport(withPreferredLayoutWidth: size.width, preferredLayoutHeight: size.height, needLayout: true)
|
|
3977
|
-
lynxView.preferredLayoutWidth = size.width
|
|
3978
|
-
lynxView.preferredLayoutHeight = size.height
|
|
3979
|
-
lynxView.layoutWidthMode = .exact
|
|
3980
|
-
lynxView.layoutHeightMode = .exact
|
|
3981
|
-
logViewport("devclient apply", lynxView: lynxView)
|
|
3982
|
-
}
|
|
3983
|
-
|
|
3984
|
-
private func fullscreenBounds() -> CGRect {
|
|
3985
|
-
let bounds = view.bounds
|
|
3986
|
-
if bounds.width > 0, bounds.height > 0 {
|
|
3987
|
-
return bounds
|
|
3988
|
-
}
|
|
3989
|
-
return UIScreen.main.bounds
|
|
3990
|
-
}
|
|
3991
|
-
|
|
3992
|
-
private func logViewport(_ label: String, lynxView: LynxView) {
|
|
3993
|
-
let rootWidth = lynxView.rootWidth()
|
|
3994
|
-
let rootHeight = lynxView.rootHeight()
|
|
3995
|
-
let intrinsic = lynxView.intrinsicContentSize
|
|
3996
|
-
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))
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
private func setupDevClientModule() {
|
|
4000
|
-
DevClientModule.presentQRScanner = { [weak self] completion in
|
|
4001
|
-
let scanner = QRScannerViewController()
|
|
4002
|
-
scanner.onResult = { url in
|
|
4003
|
-
scanner.dismiss(animated: true) { completion(url) }
|
|
4004
|
-
}
|
|
4005
|
-
scanner.modalPresentationStyle = .fullScreen
|
|
4006
|
-
self?.present(scanner, animated: true)
|
|
4007
|
-
}
|
|
4008
|
-
|
|
4009
|
-
DevClientModule.reloadProjectHandler = { [weak self] in
|
|
4010
|
-
guard let self = self else { return }
|
|
4011
|
-
let projectVC = ProjectViewController()
|
|
4012
|
-
projectVC.modalPresentationStyle = .fullScreen
|
|
4013
|
-
self.present(projectVC, animated: true)
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
`;
|
|
4018
|
-
}
|
|
4019
|
-
function getProjectViewControllerSwift() {
|
|
4020
|
-
return `import UIKit
|
|
4021
|
-
import Lynx
|
|
4022
|
-
import tamerinsets
|
|
4023
|
-
|
|
4024
|
-
class ProjectViewController: UIViewController {
|
|
4025
|
-
private var lynxView: LynxView?
|
|
4026
|
-
private var devClientManager: DevClientManager?
|
|
4027
|
-
|
|
4028
|
-
override func viewDidLoad() {
|
|
4029
|
-
super.viewDidLoad()
|
|
4030
|
-
view.backgroundColor = .black
|
|
4031
|
-
edgesForExtendedLayout = .all
|
|
4032
|
-
extendedLayoutIncludesOpaqueBars = true
|
|
4033
|
-
additionalSafeAreaInsets = .zero
|
|
4034
|
-
view.insetsLayoutMarginsFromSafeArea = false
|
|
4035
|
-
view.preservesSuperviewLayoutMargins = false
|
|
4036
|
-
viewRespectsSystemMinimumLayoutMargins = false
|
|
4037
|
-
setupLynxView()
|
|
4038
|
-
devClientManager = DevClientManager(onReload: { [weak self] in
|
|
4039
|
-
self?.reloadLynxView()
|
|
4040
|
-
})
|
|
4041
|
-
devClientManager?.connect()
|
|
4042
|
-
}
|
|
4043
|
-
|
|
4044
|
-
override func viewDidLayoutSubviews() {
|
|
4045
|
-
super.viewDidLayoutSubviews()
|
|
4046
|
-
if let lynxView = lynxView {
|
|
4047
|
-
applyFullscreenLayout(to: lynxView)
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
|
|
4051
|
-
override func viewSafeAreaInsetsDidChange() {
|
|
4052
|
-
super.viewSafeAreaInsetsDidChange()
|
|
4053
|
-
TamerInsetsModule.reRequestInsets()
|
|
4054
|
-
}
|
|
4055
|
-
|
|
4056
|
-
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
|
4057
|
-
|
|
4058
|
-
private func buildLynxView() -> LynxView {
|
|
4059
|
-
let size = fullscreenBounds().size
|
|
4060
|
-
let lv = LynxView { builder in
|
|
4061
|
-
builder.config = LynxConfig(provider: DevTemplateProvider())
|
|
4062
|
-
builder.screenSize = size
|
|
4063
|
-
builder.fontScale = 1.0
|
|
4064
|
-
}
|
|
4065
|
-
lv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
4066
|
-
lv.insetsLayoutMarginsFromSafeArea = false
|
|
4067
|
-
lv.preservesSuperviewLayoutMargins = false
|
|
4068
|
-
applyFullscreenLayout(to: lv)
|
|
4069
|
-
return lv
|
|
4070
|
-
}
|
|
4071
|
-
|
|
4072
|
-
private func setupLynxView() {
|
|
4073
|
-
let lv = buildLynxView()
|
|
4074
|
-
view.addSubview(lv)
|
|
4075
|
-
lv.loadTemplate(fromURL: "main.lynx.bundle", initData: nil)
|
|
4076
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self, weak lv] in
|
|
4077
|
-
guard let self, let lv else { return }
|
|
4078
|
-
self.logViewport("project post-load", lynxView: lv)
|
|
4079
|
-
self.applyFullscreenLayout(to: lv)
|
|
4080
|
-
}
|
|
4081
|
-
self.lynxView = lv
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
|
-
private func reloadLynxView() {
|
|
4085
|
-
lynxView?.removeFromSuperview()
|
|
4086
|
-
lynxView = nil
|
|
4087
|
-
setupLynxView()
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
|
-
private func applyFullscreenLayout(to lynxView: LynxView) {
|
|
4091
|
-
let bounds = fullscreenBounds()
|
|
4092
|
-
let size = bounds.size
|
|
4093
|
-
lynxView.frame = bounds
|
|
4094
|
-
lynxView.updateScreenMetrics(withWidth: size.width, height: size.height)
|
|
4095
|
-
lynxView.updateViewport(withPreferredLayoutWidth: size.width, preferredLayoutHeight: size.height, needLayout: true)
|
|
4096
|
-
lynxView.preferredLayoutWidth = size.width
|
|
4097
|
-
lynxView.preferredLayoutHeight = size.height
|
|
4098
|
-
lynxView.layoutWidthMode = .exact
|
|
4099
|
-
lynxView.layoutHeightMode = .exact
|
|
4100
|
-
logViewport("project apply", lynxView: lynxView)
|
|
4101
|
-
}
|
|
4102
|
-
|
|
4103
|
-
private func fullscreenBounds() -> CGRect {
|
|
4104
|
-
let bounds = view.bounds
|
|
4105
|
-
if bounds.width > 0, bounds.height > 0 {
|
|
4106
|
-
return bounds
|
|
4107
|
-
}
|
|
4108
|
-
return UIScreen.main.bounds
|
|
4109
|
-
}
|
|
4110
|
-
|
|
4111
|
-
private func logViewport(_ label: String, lynxView: LynxView) {
|
|
4112
|
-
let rootWidth = lynxView.rootWidth()
|
|
4113
|
-
let rootHeight = lynxView.rootHeight()
|
|
4114
|
-
let intrinsic = lynxView.intrinsicContentSize
|
|
4115
|
-
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))
|
|
4116
|
-
}
|
|
4117
|
-
|
|
4118
|
-
override func viewWillDisappear(_ animated: Bool) {
|
|
4119
|
-
super.viewWillDisappear(animated)
|
|
4120
|
-
if isBeingDismissed || isMovingFromParent {
|
|
4121
|
-
devClientManager?.disconnect()
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
`;
|
|
4126
|
-
}
|
|
4127
|
-
function getDevTemplateProviderSwift() {
|
|
4128
|
-
return `import Foundation
|
|
4129
|
-
import Lynx
|
|
4130
|
-
import tamerdevclient
|
|
4131
|
-
|
|
4132
|
-
class DevTemplateProvider: NSObject, LynxTemplateProvider {
|
|
4133
|
-
private static let devClientBundle = "dev-client.lynx.bundle"
|
|
4134
|
-
|
|
4135
|
-
func loadTemplate(withUrl url: String!, onComplete callback: LynxTemplateLoadBlock!) {
|
|
4136
|
-
DispatchQueue.global(qos: .background).async {
|
|
4137
|
-
// dev-client.lynx.bundle always loads from the embedded asset
|
|
4138
|
-
if url == Self.devClientBundle || url?.hasSuffix("/" + Self.devClientBundle) == true {
|
|
4139
|
-
self.loadFromBundle(url: Self.devClientBundle, callback: callback)
|
|
4140
|
-
return
|
|
4141
|
-
}
|
|
4142
|
-
|
|
4143
|
-
// Try the dev server first
|
|
4144
|
-
if let devUrl = DevServerPrefs.getUrl(), !devUrl.isEmpty {
|
|
4145
|
-
let origin: String
|
|
4146
|
-
if let parsed = URL(string: devUrl) {
|
|
4147
|
-
let scheme = parsed.scheme ?? "http"
|
|
4148
|
-
let host = parsed.host ?? "localhost"
|
|
4149
|
-
let port = parsed.port.map { ":\\($0)" } ?? ""
|
|
4150
|
-
origin = "\\(scheme)://\\(host)\\(port)"
|
|
4151
|
-
} else {
|
|
4152
|
-
origin = devUrl
|
|
4153
|
-
}
|
|
4154
|
-
|
|
4155
|
-
let candidates = ["/\\(url!)", "/tamer-dev-app/\\(url!)"]
|
|
4156
|
-
for candidate in candidates {
|
|
4157
|
-
if let data = self.httpFetch(url: origin + candidate) {
|
|
4158
|
-
callback?(data, nil)
|
|
4159
|
-
return
|
|
4160
|
-
}
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
|
|
4164
|
-
// Fall back to embedded bundle
|
|
4165
|
-
self.loadFromBundle(url: url, callback: callback)
|
|
4166
|
-
}
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
private func loadFromBundle(url: String?, callback: LynxTemplateLoadBlock!) {
|
|
4170
|
-
guard let url = url,
|
|
4171
|
-
let bundleUrl = Bundle.main.url(forResource: url, withExtension: nil),
|
|
4172
|
-
let data = try? Data(contentsOf: bundleUrl) else {
|
|
4173
|
-
let err = NSError(domain: "DevTemplateProvider", code: 404,
|
|
4174
|
-
userInfo: [NSLocalizedDescriptionKey: "Bundle not found: \\(url ?? "nil")"])
|
|
4175
|
-
callback?(nil, err)
|
|
4176
|
-
return
|
|
4177
|
-
}
|
|
4178
|
-
callback?(data, nil)
|
|
4179
|
-
}
|
|
4180
|
-
|
|
4181
|
-
private func httpFetch(url: String) -> Data? {
|
|
4182
|
-
guard let u = URL(string: url) else { return nil }
|
|
4183
|
-
var req = URLRequest(url: u)
|
|
4184
|
-
req.timeoutInterval = 10
|
|
4185
|
-
var result: Data?
|
|
4186
|
-
let sem = DispatchSemaphore(value: 0)
|
|
4187
|
-
URLSession.shared.dataTask(with: req) { data, response, _ in
|
|
4188
|
-
if let http = response as? HTTPURLResponse, http.statusCode == 200 {
|
|
4189
|
-
result = data
|
|
4190
|
-
}
|
|
4191
|
-
sem.signal()
|
|
4192
|
-
}.resume()
|
|
4193
|
-
sem.wait()
|
|
4194
|
-
return result
|
|
4195
|
-
}
|
|
4196
|
-
}
|
|
4197
|
-
`;
|
|
4198
|
-
}
|
|
4199
|
-
function getDevClientManagerSwift() {
|
|
4200
|
-
return `import Foundation
|
|
4201
|
-
import tamerdevclient
|
|
4202
|
-
|
|
4203
|
-
class DevClientManager {
|
|
4204
|
-
private var webSocketTask: URLSessionWebSocketTask?
|
|
4205
|
-
private let onReload: () -> Void
|
|
4206
|
-
private var session: URLSession?
|
|
4207
|
-
|
|
4208
|
-
init(onReload: @escaping () -> Void) {
|
|
4209
|
-
self.onReload = onReload
|
|
4210
|
-
}
|
|
4211
|
-
|
|
4212
|
-
func connect() {
|
|
4213
|
-
guard let devUrl = DevServerPrefs.getUrl(), !devUrl.isEmpty else { return }
|
|
4214
|
-
guard let base = URL(string: devUrl) else { return }
|
|
4215
|
-
|
|
4216
|
-
let scheme = (base.scheme == "https") ? "wss" : "ws"
|
|
4217
|
-
let host = base.host ?? "localhost"
|
|
4218
|
-
let port = base.port.map { ":\\($0)" } ?? ""
|
|
4219
|
-
let rawPath = base.path.isEmpty ? "/" : base.path
|
|
4220
|
-
let dir = rawPath.hasSuffix("/") ? rawPath : rawPath + "/"
|
|
4221
|
-
guard let wsUrl = URL(string: "\\(scheme)://\\(host)\\(port)\\(dir)__hmr") else { return }
|
|
4222
|
-
|
|
4223
|
-
session = URLSession(configuration: .default)
|
|
4224
|
-
let task = session!.webSocketTask(with: wsUrl)
|
|
4225
|
-
webSocketTask = task
|
|
4226
|
-
task.resume()
|
|
4227
|
-
receive()
|
|
4228
|
-
}
|
|
4229
|
-
|
|
4230
|
-
private func receive() {
|
|
4231
|
-
webSocketTask?.receive { [weak self] result in
|
|
4232
|
-
guard let self = self else { return }
|
|
4233
|
-
switch result {
|
|
4234
|
-
case .success(let msg):
|
|
4235
|
-
if case .string(let text) = msg, text.contains("\\"type\\":\\"reload\\"") {
|
|
4236
|
-
DispatchQueue.main.async { self.onReload() }
|
|
4237
|
-
}
|
|
4238
|
-
self.receive()
|
|
4239
|
-
case .failure:
|
|
4240
|
-
break
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
|
-
|
|
4245
|
-
func disconnect() {
|
|
4246
|
-
webSocketTask?.cancel(with: .normalClosure, reason: nil)
|
|
4247
|
-
webSocketTask = nil
|
|
4248
|
-
session = nil
|
|
4249
|
-
}
|
|
4250
|
-
}
|
|
4251
|
-
`;
|
|
4252
|
-
}
|
|
4253
|
-
function getQRScannerViewControllerSwift() {
|
|
4254
|
-
return `import UIKit
|
|
4255
|
-
import AVFoundation
|
|
4256
|
-
|
|
4257
|
-
class QRScannerViewController: UIViewController {
|
|
4258
|
-
var onResult: ((String?) -> Void)?
|
|
4259
|
-
|
|
4260
|
-
private var captureSession: AVCaptureSession?
|
|
4261
|
-
private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
4262
|
-
|
|
4263
|
-
override func viewDidLoad() {
|
|
4264
|
-
super.viewDidLoad()
|
|
4265
|
-
view.backgroundColor = .black
|
|
4266
|
-
setupCamera()
|
|
4267
|
-
addCancelButton()
|
|
4268
|
-
}
|
|
4269
|
-
|
|
4270
|
-
private func setupCamera() {
|
|
4271
|
-
let session = AVCaptureSession()
|
|
4272
|
-
guard let device = AVCaptureDevice.default(for: .video),
|
|
4273
|
-
let input = try? AVCaptureDeviceInput(device: device) else {
|
|
4274
|
-
onResult?(nil)
|
|
4275
|
-
return
|
|
4276
|
-
}
|
|
4277
|
-
|
|
4278
|
-
let output = AVCaptureMetadataOutput()
|
|
4279
|
-
session.addInput(input)
|
|
4280
|
-
session.addOutput(output)
|
|
4281
|
-
output.setMetadataObjectsDelegate(self, queue: .main)
|
|
4282
|
-
output.metadataObjectTypes = [.qr]
|
|
4283
|
-
|
|
4284
|
-
let preview = AVCaptureVideoPreviewLayer(session: session)
|
|
4285
|
-
preview.frame = view.layer.bounds
|
|
4286
|
-
preview.videoGravity = .resizeAspectFill
|
|
4287
|
-
view.layer.insertSublayer(preview, at: 0)
|
|
4288
|
-
previewLayer = preview
|
|
4289
|
-
|
|
4290
|
-
DispatchQueue.global(qos: .userInitiated).async { session.startRunning() }
|
|
4291
|
-
captureSession = session
|
|
4292
|
-
}
|
|
4293
|
-
|
|
4294
|
-
private func addCancelButton() {
|
|
4295
|
-
let btn = UIButton(type: .system)
|
|
4296
|
-
btn.setTitle("Cancel", for: .normal)
|
|
4297
|
-
btn.setTitleColor(.white, for: .normal)
|
|
4298
|
-
btn.titleLabel?.font = .systemFont(ofSize: 18, weight: .medium)
|
|
4299
|
-
btn.addTarget(self, action: #selector(cancel), for: .touchUpInside)
|
|
4300
|
-
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
4301
|
-
view.addSubview(btn)
|
|
4302
|
-
NSLayoutConstraint.activate([
|
|
4303
|
-
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
4304
|
-
btn.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24),
|
|
4305
|
-
])
|
|
4306
|
-
}
|
|
4307
|
-
|
|
4308
|
-
@objc private func cancel() {
|
|
4309
|
-
captureSession?.stopRunning()
|
|
4310
|
-
onResult?(nil)
|
|
4311
|
-
}
|
|
4312
|
-
|
|
4313
|
-
override func viewWillAppear(_ animated: Bool) {
|
|
4314
|
-
super.viewWillAppear(animated)
|
|
4315
|
-
if captureSession?.isRunning == false {
|
|
4316
|
-
DispatchQueue.global(qos: .userInitiated).async { self.captureSession?.startRunning() }
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
|
|
4320
|
-
override func viewWillDisappear(_ animated: Bool) {
|
|
4321
|
-
super.viewWillDisappear(animated)
|
|
4322
|
-
captureSession?.stopRunning()
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4325
|
-
|
|
4326
|
-
extension QRScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
|
|
4327
|
-
func metadataOutput(_ output: AVCaptureMetadataOutput,
|
|
4328
|
-
didOutput objects: [AVMetadataObject],
|
|
4329
|
-
from connection: AVCaptureConnection) {
|
|
4330
|
-
captureSession?.stopRunning()
|
|
4331
|
-
if let obj = objects.first as? AVMetadataMachineReadableCodeObject,
|
|
4332
|
-
let value = obj.stringValue {
|
|
4333
|
-
onResult?(value)
|
|
4334
|
-
}
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
`;
|
|
4338
|
-
}
|
|
4339
|
-
function getLynxInitProcessorSwift() {
|
|
4340
|
-
return `// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
4341
|
-
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
4342
|
-
// LICENSE file in the root directory of this source tree.
|
|
4343
|
-
|
|
4344
|
-
import Foundation
|
|
4345
|
-
|
|
4346
|
-
// GENERATED IMPORTS START
|
|
4347
|
-
// This section is automatically generated by Tamer4Lynx.
|
|
4348
|
-
// Manual edits will be overwritten.
|
|
4349
|
-
// GENERATED IMPORTS END
|
|
4350
|
-
|
|
4351
|
-
final class LynxInitProcessor {
|
|
4352
|
-
static let shared = LynxInitProcessor()
|
|
4353
|
-
private init() {}
|
|
4354
|
-
|
|
4355
|
-
func setupEnvironment() {
|
|
4356
|
-
TamerIconElement.registerFonts()
|
|
4357
|
-
setupLynxEnv()
|
|
4358
|
-
setupLynxService()
|
|
4359
|
-
}
|
|
4360
|
-
|
|
4361
|
-
private func setupLynxEnv() {
|
|
4362
|
-
let env = LynxEnv.sharedInstance()
|
|
4363
|
-
let globalConfig = LynxConfig(provider: env.config.templateProvider)
|
|
4364
|
-
|
|
4365
|
-
// GENERATED AUTOLINK START
|
|
4366
|
-
|
|
4367
|
-
// GENERATED AUTOLINK END
|
|
4368
|
-
|
|
4369
|
-
env.prepareConfig(globalConfig)
|
|
4370
|
-
}
|
|
4371
|
-
|
|
4372
|
-
private func setupLynxService() {
|
|
4373
|
-
let webPCoder = SDImageWebPCoder.shared
|
|
4374
|
-
SDImageCodersManager.shared.addCoder(webPCoder)
|
|
4375
|
-
}
|
|
4376
|
-
}
|
|
4377
|
-
`;
|
|
4378
|
-
}
|
|
4379
|
-
function getBridgingHeader() {
|
|
4380
|
-
return `#import <Lynx/LynxConfig.h>
|
|
4381
|
-
#import <Lynx/LynxEnv.h>
|
|
4382
|
-
#import <Lynx/LynxTemplateProvider.h>
|
|
4383
|
-
#import <Lynx/LynxView.h>
|
|
4384
|
-
#import <Lynx/LynxModule.h>
|
|
4385
|
-
#import <SDWebImage/SDWebImage.h>
|
|
4386
|
-
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
4387
|
-
`;
|
|
4388
|
-
}
|
|
4389
|
-
function getInfoPlist() {
|
|
4390
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4391
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4392
|
-
<plist version="1.0">
|
|
4393
|
-
<dict>
|
|
4394
|
-
<key>CFBundleDevelopmentRegion</key>
|
|
4395
|
-
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
4396
|
-
<key>CFBundleExecutable</key>
|
|
4397
|
-
<string>$(EXECUTABLE_NAME)</string>
|
|
4398
|
-
<key>CFBundleIdentifier</key>
|
|
4399
|
-
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
4400
|
-
<key>CFBundleInfoDictionaryVersion</key>
|
|
4401
|
-
<string>6.0</string>
|
|
4402
|
-
<key>CFBundleName</key>
|
|
4403
|
-
<string>$(PRODUCT_NAME)</string>
|
|
4404
|
-
<key>CFBundlePackageType</key>
|
|
4405
|
-
<string>APPL</string>
|
|
4406
|
-
<key>CFBundleShortVersionString</key>
|
|
4407
|
-
<string>1.0</string>
|
|
4408
|
-
<key>CFBundleVersion</key>
|
|
4409
|
-
<string>1</string>
|
|
4410
|
-
<key>UILaunchStoryboardName</key>
|
|
4411
|
-
<string>LaunchScreen</string>
|
|
4412
|
-
<key>CFBundleURLTypes</key>
|
|
4413
|
-
<array>
|
|
4414
|
-
<dict>
|
|
4415
|
-
<key>CFBundleURLSchemes</key>
|
|
4416
|
-
<array>
|
|
4417
|
-
<string>tamerdevapp</string>
|
|
4418
|
-
</array>
|
|
4419
|
-
</dict>
|
|
4420
|
-
</array>
|
|
4421
|
-
<key>NSCameraUsageDescription</key>
|
|
4422
|
-
<string>Used to scan QR codes for connecting to the dev server</string>
|
|
4423
|
-
<key>NSLocalNetworkUsageDescription</key>
|
|
4424
|
-
<string>Used to discover Tamer dev servers on your local network</string>
|
|
4425
|
-
<key>NSBonjourServices</key>
|
|
4426
|
-
<array>
|
|
4427
|
-
<string>_tamer._tcp.</string>
|
|
4428
|
-
</array>
|
|
4429
|
-
<key>NSAppTransportSecurity</key>
|
|
4430
|
-
<dict>
|
|
4431
|
-
<key>NSAllowsArbitraryLoads</key>
|
|
4432
|
-
<true/>
|
|
4433
|
-
</dict>
|
|
4434
|
-
<key>UIRequiredDeviceCapabilities</key>
|
|
4435
|
-
<array>
|
|
4436
|
-
<string>armv7</string>
|
|
4437
|
-
</array>
|
|
4438
|
-
<key>UISupportedInterfaceOrientations</key>
|
|
4439
|
-
<array>
|
|
4440
|
-
<string>UIInterfaceOrientationPortrait</string>
|
|
4441
|
-
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
4442
|
-
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
4443
|
-
</array>
|
|
4444
|
-
</dict>
|
|
4445
|
-
</plist>
|
|
4446
|
-
`;
|
|
4447
|
-
}
|
|
4448
|
-
function getPodfile() {
|
|
4449
|
-
return `source 'https://cdn.cocoapods.org/'
|
|
4450
|
-
|
|
4451
|
-
platform :ios, '13.0'
|
|
4452
|
-
|
|
4453
|
-
target '${APP_NAME}' do
|
|
4454
|
-
pod 'Lynx', '3.6.0', :subspecs => [
|
|
4455
|
-
'Framework',
|
|
4456
|
-
], :modular_headers => true
|
|
4457
|
-
|
|
4458
|
-
pod 'PrimJS', '3.6.1', :subspecs => ['quickjs', 'napi']
|
|
4459
|
-
|
|
4460
|
-
pod 'LynxService', '3.6.0', :subspecs => [
|
|
4461
|
-
'Image',
|
|
4462
|
-
'Log',
|
|
4463
|
-
'Http',
|
|
4464
|
-
]
|
|
4465
|
-
pod 'SDWebImage','5.15.5'
|
|
4466
|
-
pod 'SDWebImageWebPCoder', '0.11.0'
|
|
4467
|
-
|
|
4468
|
-
# GENERATED AUTOLINK DEPENDENCIES START
|
|
4469
|
-
# This section is automatically generated by Tamer4Lynx.
|
|
4470
|
-
# Manual edits will be overwritten.
|
|
4471
|
-
# GENERATED AUTOLINK DEPENDENCIES END
|
|
4472
|
-
end
|
|
4473
|
-
|
|
4474
|
-
post_install do |installer|
|
|
4475
|
-
installer.pods_project.targets.each do |target|
|
|
4476
|
-
target.build_configurations.each do |config|
|
|
4477
|
-
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'gnu++17'
|
|
4478
|
-
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
|
4479
|
-
config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
|
|
4480
|
-
end
|
|
4481
|
-
|
|
4482
|
-
if target.name == 'Lynx'
|
|
4483
|
-
target.build_configurations.each do |config|
|
|
4484
|
-
flags = [
|
|
4485
|
-
'-Wno-vla-extension',
|
|
4486
|
-
'-Wno-vla',
|
|
4487
|
-
'-Wno-error=vla-extension',
|
|
4488
|
-
'-Wno-deprecated-declarations',
|
|
4489
|
-
'-Wno-deprecated',
|
|
4490
|
-
'-Wno-deprecated-implementations',
|
|
4491
|
-
'-Wno-macro-redefined',
|
|
4492
|
-
'-Wno-enum-compare',
|
|
4493
|
-
'-Wno-enum-compare-conditional',
|
|
4494
|
-
'-Wno-enum-conversion'
|
|
4495
|
-
].join(' ')
|
|
4496
|
-
|
|
4497
|
-
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = "$(inherited) #{flags}"
|
|
4498
|
-
config.build_settings['OTHER_CFLAGS'] = "$(inherited) #{flags}"
|
|
4499
|
-
config.build_settings['CLANG_WARN_VLA'] = 'NO'
|
|
4500
|
-
config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO'
|
|
4501
|
-
config.build_settings['CLANG_WARN_ENUM_CONVERSION'] = 'NO'
|
|
4502
|
-
end
|
|
4503
|
-
end
|
|
4504
|
-
end
|
|
4505
|
-
end
|
|
4506
|
-
`;
|
|
4507
|
-
}
|
|
4508
|
-
function getMainStoryboard() {
|
|
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" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"
|
|
4513
|
-
initialViewController="BYZ-38-t0r">
|
|
4514
|
-
<dependencies>
|
|
4515
|
-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
|
4516
|
-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
4517
|
-
</dependencies>
|
|
4518
|
-
<scenes>
|
|
4519
|
-
<scene sceneID="tne-QT-ifu">
|
|
4520
|
-
<objects>
|
|
4521
|
-
<viewController id="BYZ-38-t0r" customClass="DevLauncherViewController"
|
|
4522
|
-
customModuleProvider="target" sceneMemberID="viewController">
|
|
4523
|
-
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
|
4524
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4525
|
-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
4526
|
-
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
|
4527
|
-
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
|
4528
|
-
</view>
|
|
4529
|
-
</viewController>
|
|
4530
|
-
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr"
|
|
4531
|
-
sceneMemberID="firstResponder"/>
|
|
4532
|
-
</objects>
|
|
4533
|
-
</scene>
|
|
4534
|
-
</scenes>
|
|
4535
|
-
</document>
|
|
4536
|
-
`;
|
|
4537
|
-
}
|
|
4538
|
-
function getLaunchScreenStoryboard2() {
|
|
4539
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4540
|
-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0"
|
|
4541
|
-
toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none"
|
|
4542
|
-
useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES"
|
|
4543
|
-
colorMatched="YES" initialViewController="01J-lp-oVM">
|
|
4544
|
-
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
|
4545
|
-
<dependencies>
|
|
4546
|
-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
|
4547
|
-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
4548
|
-
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
4549
|
-
</dependencies>
|
|
4550
|
-
<scenes>
|
|
4551
|
-
<scene sceneID="EHf-IW-A2E">
|
|
4552
|
-
<objects>
|
|
4553
|
-
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
|
4554
|
-
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
4555
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4556
|
-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
4557
|
-
<subviews>
|
|
4558
|
-
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bg9-1M-mhb">
|
|
4559
|
-
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
|
|
4560
|
-
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
4561
|
-
</view>
|
|
4562
|
-
</subviews>
|
|
4563
|
-
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
4564
|
-
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
4565
|
-
<constraints>
|
|
4566
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="3M4-v9-a3l"/>
|
|
4567
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="Sbc-LM-HvA"/>
|
|
4568
|
-
<constraint firstItem="Bg9-1M-mhb" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="cJ0-4h-f4M"/>
|
|
4569
|
-
<constraint firstAttribute="trailing" secondItem="Bg9-1M-mhb" secondAttribute="trailing" id="g0s-pf-rxW"/>
|
|
4570
|
-
</constraints>
|
|
4571
|
-
</view>
|
|
4572
|
-
</viewController>
|
|
4573
|
-
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
4574
|
-
</objects>
|
|
4575
|
-
</scene>
|
|
4576
|
-
</scenes>
|
|
4577
|
-
</document>
|
|
4578
|
-
`;
|
|
4579
|
-
}
|
|
4580
|
-
function generatePbxproj(ids) {
|
|
4581
|
-
return `// !$*UTF8*$!
|
|
4582
|
-
{
|
|
4583
|
-
archiveVersion = 1;
|
|
4584
|
-
classes = {};
|
|
4585
|
-
objectVersion = 56;
|
|
4586
|
-
objects = {
|
|
4587
|
-
/* Begin PBXBuildFile section */
|
|
4588
|
-
${ids.appDelegateBuildFile} /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.appDelegateRef}; };
|
|
4589
|
-
${ids.devLauncherBuildFile} /* DevLauncherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.devLauncherRef}; };
|
|
4590
|
-
${ids.projectVCBuildFile} /* ProjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.projectVCRef}; };
|
|
4591
|
-
${ids.templateProviderBuildFile} /* DevTemplateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.templateProviderRef}; };
|
|
4592
|
-
${ids.devClientManagerBuildFile} /* DevClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.devClientManagerRef}; };
|
|
4593
|
-
${ids.devServerPrefsBuildFile} /* DevServerPrefs in Sources (via DevClientModule) */ = {isa = PBXBuildFile; fileRef = ${ids.devClientModuleRef}; };
|
|
4594
|
-
${ids.qrScannerBuildFile} /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.qrScannerRef}; };
|
|
4595
|
-
${ids.lynxInitBuildFile} /* LynxInitProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${ids.lynxInitRef}; };
|
|
4596
|
-
${ids.mainStoryboardBuildFile} /* Base in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.mainStoryboardBaseRef}; };
|
|
4597
|
-
${ids.launchStoryboardBuildFile} /* Base in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.launchStoryboardBaseRef}; };
|
|
4598
|
-
${ids.assetsBuildFile} /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.assetsRef}; };
|
|
4599
|
-
${ids.bundleBuildFile} /* dev-client.lynx.bundle in Resources */ = {isa = PBXBuildFile; fileRef = ${ids.bundleRef}; };
|
|
4600
|
-
/* End PBXBuildFile section */
|
|
4601
|
-
|
|
4602
|
-
/* Begin PBXFileReference section */
|
|
4603
|
-
${ids.appFile} /* ${APP_NAME}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "${APP_NAME}.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
4604
|
-
${ids.appDelegateRef} /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate.swift"; sourceTree = "<group>"; };
|
|
4605
|
-
${ids.devLauncherRef} /* DevLauncherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevLauncherViewController.swift"; sourceTree = "<group>"; };
|
|
4606
|
-
${ids.projectVCRef} /* ProjectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectViewController.swift"; sourceTree = "<group>"; };
|
|
4607
|
-
${ids.templateProviderRef} /* DevTemplateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevTemplateProvider.swift"; sourceTree = "<group>"; };
|
|
4608
|
-
${ids.devClientManagerRef} /* DevClientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevClientManager.swift"; sourceTree = "<group>"; };
|
|
4609
|
-
${ids.devClientModuleRef} /* DevClientModule (via pod) */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevClientModule.swift"; sourceTree = "<group>"; };
|
|
4610
|
-
${ids.qrScannerRef} /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRScannerViewController.swift"; sourceTree = "<group>"; };
|
|
4611
|
-
${ids.lynxInitRef} /* LynxInitProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LynxInitProcessor.swift"; sourceTree = "<group>"; };
|
|
4612
|
-
${ids.bridgingHeaderRef} /* ${BRIDGING_HEADER} */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "${BRIDGING_HEADER}"; sourceTree = "<group>"; };
|
|
4613
|
-
${ids.mainStoryboardBaseRef} /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Main.storyboard"; sourceTree = "<group>"; };
|
|
4614
|
-
${ids.launchStoryboardBaseRef} /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/LaunchScreen.storyboard"; sourceTree = "<group>"; };
|
|
4615
|
-
${ids.assetsRef} /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Assets.xcassets"; sourceTree = "<group>"; };
|
|
4616
|
-
${ids.bundleRef} /* dev-client.lynx.bundle */ = {isa = PBXFileReference; lastKnownFileType = "file"; path = "dev-client.lynx.bundle"; sourceTree = "<group>"; };
|
|
4617
|
-
/* End PBXFileReference section */
|
|
4618
|
-
|
|
4619
|
-
/* Begin PBXFrameworksBuildPhase section */
|
|
4620
|
-
${ids.frameworksBuildPhase} /* Frameworks */ = {
|
|
4621
|
-
isa = PBXFrameworksBuildPhase;
|
|
4622
|
-
buildActionMask = 2147483647;
|
|
4623
|
-
files = (
|
|
4624
|
-
);
|
|
4625
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4626
|
-
};
|
|
4627
|
-
/* End PBXFrameworksBuildPhase section */
|
|
4628
|
-
|
|
4629
|
-
/* Begin PBXGroup section */
|
|
4630
|
-
${ids.mainGroup} = {
|
|
4631
|
-
isa = PBXGroup;
|
|
4632
|
-
children = (
|
|
4633
|
-
${ids.appGroup} /* ${APP_NAME} */,
|
|
4634
|
-
${ids.productsGroup} /* Products */,
|
|
4635
|
-
${ids.frameworksGroup} /* Frameworks */,
|
|
4636
|
-
);
|
|
4637
|
-
sourceTree = "<group>";
|
|
4638
|
-
};
|
|
4639
|
-
${ids.productsGroup} /* Products */ = {
|
|
4640
|
-
isa = PBXGroup;
|
|
4641
|
-
children = (
|
|
4642
|
-
${ids.appFile} /* ${APP_NAME}.app */,
|
|
4643
|
-
);
|
|
4644
|
-
name = Products;
|
|
4645
|
-
sourceTree = "<group>";
|
|
4646
|
-
};
|
|
4647
|
-
${ids.frameworksGroup} /* Frameworks */ = {
|
|
4648
|
-
isa = PBXGroup;
|
|
4649
|
-
children = (
|
|
4650
|
-
);
|
|
4651
|
-
name = Frameworks;
|
|
4652
|
-
sourceTree = "<group>";
|
|
4653
|
-
};
|
|
4654
|
-
${ids.appGroup} /* ${APP_NAME} */ = {
|
|
4655
|
-
isa = PBXGroup;
|
|
4656
|
-
children = (
|
|
4657
|
-
${ids.appDelegateRef} /* AppDelegate.swift */,
|
|
4658
|
-
${ids.devLauncherRef} /* DevLauncherViewController.swift */,
|
|
4659
|
-
${ids.projectVCRef} /* ProjectViewController.swift */,
|
|
4660
|
-
${ids.templateProviderRef} /* DevTemplateProvider.swift */,
|
|
4661
|
-
${ids.devClientManagerRef} /* DevClientManager.swift */,
|
|
4662
|
-
${ids.devClientModuleRef} /* DevClientModule.swift */,
|
|
4663
|
-
${ids.qrScannerRef} /* QRScannerViewController.swift */,
|
|
4664
|
-
${ids.lynxInitRef} /* LynxInitProcessor.swift */,
|
|
4665
|
-
${ids.bridgingHeaderRef} /* ${BRIDGING_HEADER} */,
|
|
4666
|
-
${ids.mainStoryboardRef} /* Main.storyboard */,
|
|
4667
|
-
${ids.launchStoryboardRef} /* LaunchScreen.storyboard */,
|
|
4668
|
-
${ids.assetsRef} /* Assets.xcassets */,
|
|
4669
|
-
${ids.bundleRef} /* dev-client.lynx.bundle */,
|
|
4670
|
-
);
|
|
4671
|
-
path = "${APP_NAME}";
|
|
4672
|
-
sourceTree = "<group>";
|
|
4673
|
-
};
|
|
4674
|
-
/* End PBXGroup section */
|
|
4675
|
-
|
|
4676
|
-
/* Begin PBXNativeTarget section */
|
|
4677
|
-
${ids.nativeTarget} /* ${APP_NAME} */ = {
|
|
4678
|
-
isa = PBXNativeTarget;
|
|
4679
|
-
buildConfigurationList = ${ids.targetBuildConfigList};
|
|
4680
|
-
buildPhases = (
|
|
4681
|
-
${ids.sourcesBuildPhase} /* Sources */,
|
|
4682
|
-
${ids.frameworksBuildPhase} /* Frameworks */,
|
|
4683
|
-
${ids.resourcesBuildPhase} /* Resources */,
|
|
4684
|
-
${ids.fontCopyScriptPhase} /* [Tamer] Copy Icon Fonts */,
|
|
4685
|
-
);
|
|
4686
|
-
buildRules = (
|
|
4687
|
-
);
|
|
4688
|
-
dependencies = (
|
|
4689
|
-
);
|
|
4690
|
-
name = "${APP_NAME}";
|
|
4691
|
-
productName = "${APP_NAME}";
|
|
4692
|
-
productReference = ${ids.appFile};
|
|
4693
|
-
productType = "com.apple.product-type.application";
|
|
4694
|
-
};
|
|
4695
|
-
/* End PBXNativeTarget section */
|
|
4696
|
-
|
|
4697
|
-
/* Begin PBXProject section */
|
|
4698
|
-
${ids.project} /* Project object */ = {
|
|
4699
|
-
isa = PBXProject;
|
|
4700
|
-
attributes = {
|
|
4701
|
-
LastUpgradeCheck = 1530;
|
|
4702
|
-
};
|
|
4703
|
-
buildConfigurationList = ${ids.projectBuildConfigList};
|
|
4704
|
-
compatibilityVersion = "Xcode 14.0";
|
|
4705
|
-
developmentRegion = en;
|
|
4706
|
-
hasScannedForEncodings = 0;
|
|
4707
|
-
knownRegions = (
|
|
4708
|
-
en,
|
|
4709
|
-
Base,
|
|
4710
|
-
);
|
|
4711
|
-
mainGroup = ${ids.mainGroup};
|
|
4712
|
-
productRefGroup = ${ids.productsGroup} /* Products */;
|
|
4713
|
-
projectDirPath = "";
|
|
4714
|
-
projectRoot = "";
|
|
4715
|
-
targets = (
|
|
4716
|
-
${ids.nativeTarget} /* ${APP_NAME} */,
|
|
4717
|
-
);
|
|
4718
|
-
};
|
|
4719
|
-
/* End PBXProject section */
|
|
4720
|
-
|
|
4721
|
-
/* Begin PBXResourcesBuildPhase section */
|
|
4722
|
-
${ids.resourcesBuildPhase} /* Resources */ = {
|
|
4723
|
-
isa = PBXResourcesBuildPhase;
|
|
4724
|
-
buildActionMask = 2147483647;
|
|
4725
|
-
files = (
|
|
4726
|
-
${ids.assetsBuildFile} /* Assets.xcassets in Resources */,
|
|
4727
|
-
${ids.mainStoryboardBuildFile} /* Base in Resources */,
|
|
4728
|
-
${ids.launchStoryboardBuildFile} /* Base in Resources */,
|
|
4729
|
-
${ids.bundleBuildFile} /* dev-client.lynx.bundle in Resources */,
|
|
4730
|
-
);
|
|
4731
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4732
|
-
};
|
|
4733
|
-
/* End PBXResourcesBuildPhase section */
|
|
4734
|
-
|
|
4735
|
-
/* Begin PBXShellScriptBuildPhase section */
|
|
4736
|
-
${ids.fontCopyScriptPhase} /* [Tamer] Copy Icon Fonts */ = {
|
|
4737
|
-
isa = PBXShellScriptBuildPhase;
|
|
4738
|
-
buildActionMask = 2147483647;
|
|
4739
|
-
files = (
|
|
4740
|
-
);
|
|
4741
|
-
inputPaths = (
|
|
4742
|
-
);
|
|
4743
|
-
name = "[Tamer] Copy Icon Fonts";
|
|
4744
|
-
outputPaths = (
|
|
4745
|
-
);
|
|
4746
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4747
|
-
shellPath = /bin/sh;
|
|
4748
|
-
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";
|
|
4749
|
-
};
|
|
4750
|
-
/* End PBXShellScriptBuildPhase section */
|
|
4751
|
-
|
|
4752
|
-
/* Begin PBXSourcesBuildPhase section */
|
|
4753
|
-
${ids.sourcesBuildPhase} /* Sources */ = {
|
|
4754
|
-
isa = PBXSourcesBuildPhase;
|
|
4755
|
-
buildActionMask = 2147483647;
|
|
4756
|
-
files = (
|
|
4757
|
-
${ids.appDelegateBuildFile} /* AppDelegate.swift in Sources */,
|
|
4758
|
-
${ids.devLauncherBuildFile} /* DevLauncherViewController.swift in Sources */,
|
|
4759
|
-
${ids.projectVCBuildFile} /* ProjectViewController.swift in Sources */,
|
|
4760
|
-
${ids.templateProviderBuildFile} /* DevTemplateProvider.swift in Sources */,
|
|
4761
|
-
${ids.devClientManagerBuildFile} /* DevClientManager.swift in Sources */,
|
|
4762
|
-
${ids.qrScannerBuildFile} /* QRScannerViewController.swift in Sources */,
|
|
4763
|
-
${ids.lynxInitBuildFile} /* LynxInitProcessor.swift in Sources */,
|
|
4764
|
-
);
|
|
4765
|
-
runOnlyForDeploymentPostprocessing = 0;
|
|
4766
|
-
};
|
|
4767
|
-
/* End PBXSourcesBuildPhase section */
|
|
4768
|
-
|
|
4769
|
-
/* Begin PBXVariantGroup section */
|
|
4770
|
-
${ids.mainStoryboardRef} /* Main.storyboard */ = {
|
|
4771
|
-
isa = PBXVariantGroup;
|
|
4772
|
-
children = (
|
|
4773
|
-
${ids.mainStoryboardBaseRef} /* Base */,
|
|
4774
|
-
);
|
|
4775
|
-
name = "Main.storyboard";
|
|
4776
|
-
sourceTree = "<group>";
|
|
4777
|
-
};
|
|
4778
|
-
${ids.launchStoryboardRef} /* LaunchScreen.storyboard */ = {
|
|
4779
|
-
isa = PBXVariantGroup;
|
|
4780
|
-
children = (
|
|
4781
|
-
${ids.launchStoryboardBaseRef} /* Base */,
|
|
4782
|
-
);
|
|
4783
|
-
name = "LaunchScreen.storyboard";
|
|
4784
|
-
sourceTree = "<group>";
|
|
4785
|
-
};
|
|
4786
|
-
/* End PBXVariantGroup section */
|
|
4787
|
-
|
|
4788
|
-
/* Begin XCBuildConfiguration section */
|
|
4789
|
-
${ids.projectDebugConfig} /* Debug */ = {
|
|
4790
|
-
isa = XCBuildConfiguration;
|
|
4791
|
-
buildSettings = {
|
|
4792
|
-
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
4793
|
-
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
|
4794
|
-
CLANG_CXX_LIBRARY = "libc++";
|
|
4795
|
-
CLANG_ENABLE_MODULES = YES;
|
|
4796
|
-
CLANG_ENABLE_OBJC_ARC = YES;
|
|
4797
|
-
COPY_PHASE_STRIP = NO;
|
|
4798
|
-
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
4799
|
-
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
4800
|
-
GCC_NO_COMMON_BLOCKS = YES;
|
|
4801
|
-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
4802
|
-
SDKROOT = iphoneos;
|
|
4803
|
-
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
|
4804
|
-
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
4805
|
-
};
|
|
4806
|
-
name = Debug;
|
|
4807
|
-
};
|
|
4808
|
-
${ids.projectReleaseConfig} /* Release */ = {
|
|
4809
|
-
isa = XCBuildConfiguration;
|
|
4810
|
-
buildSettings = {
|
|
4811
|
-
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
4812
|
-
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
|
4813
|
-
CLANG_CXX_LIBRARY = "libc++";
|
|
4814
|
-
CLANG_ENABLE_MODULES = YES;
|
|
4815
|
-
CLANG_ENABLE_OBJC_ARC = YES;
|
|
4816
|
-
COPY_PHASE_STRIP = NO;
|
|
4817
|
-
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
4818
|
-
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
4819
|
-
GCC_NO_COMMON_BLOCKS = YES;
|
|
4820
|
-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
4821
|
-
SDKROOT = iphoneos;
|
|
4822
|
-
SWIFT_COMPILATION_MODE = wholemodule;
|
|
4823
|
-
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
|
4824
|
-
};
|
|
4825
|
-
name = Release;
|
|
4826
|
-
};
|
|
4827
|
-
${ids.targetDebugConfig} /* Debug */ = {
|
|
4828
|
-
isa = XCBuildConfiguration;
|
|
4829
|
-
buildSettings = {
|
|
4830
|
-
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
4831
|
-
CURRENT_PROJECT_VERSION = 1;
|
|
4832
|
-
INFOPLIST_FILE = "${APP_NAME}/Info.plist";
|
|
4833
|
-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
4834
|
-
MARKETING_VERSION = "1.0";
|
|
4835
|
-
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}";
|
|
4836
|
-
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
4837
|
-
SWIFT_OBJC_BRIDGING_HEADER = "${APP_NAME}/${BRIDGING_HEADER}";
|
|
4838
|
-
SWIFT_VERSION = 5.0;
|
|
4839
|
-
TARGETED_DEVICE_FAMILY = "1,2";
|
|
4840
|
-
};
|
|
4841
|
-
name = Debug;
|
|
4842
|
-
};
|
|
4843
|
-
${ids.targetReleaseConfig} /* Release */ = {
|
|
4844
|
-
isa = XCBuildConfiguration;
|
|
4845
|
-
buildSettings = {
|
|
4846
|
-
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
4847
|
-
CURRENT_PROJECT_VERSION = 1;
|
|
4848
|
-
INFOPLIST_FILE = "${APP_NAME}/Info.plist";
|
|
4849
|
-
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
4850
|
-
MARKETING_VERSION = "1.0";
|
|
4851
|
-
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}";
|
|
4852
|
-
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
4853
|
-
SWIFT_OBJC_BRIDGING_HEADER = "${APP_NAME}/${BRIDGING_HEADER}";
|
|
4854
|
-
SWIFT_VERSION = 5.0;
|
|
4855
|
-
TARGETED_DEVICE_FAMILY = "1,2";
|
|
4856
|
-
};
|
|
4857
|
-
name = Release;
|
|
4858
|
-
};
|
|
4859
|
-
/* End XCBuildConfiguration section */
|
|
4860
|
-
|
|
4861
|
-
/* Begin XCConfigurationList section */
|
|
4862
|
-
${ids.projectBuildConfigList} = {
|
|
4863
|
-
isa = XCConfigurationList;
|
|
4864
|
-
buildConfigurations = (
|
|
4865
|
-
${ids.projectDebugConfig} /* Debug */,
|
|
4866
|
-
${ids.projectReleaseConfig} /* Release */,
|
|
4867
|
-
);
|
|
4868
|
-
defaultConfigurationIsVisible = 0;
|
|
4869
|
-
defaultConfigurationName = Release;
|
|
4870
|
-
};
|
|
4871
|
-
${ids.targetBuildConfigList} = {
|
|
4872
|
-
isa = XCConfigurationList;
|
|
4873
|
-
buildConfigurations = (
|
|
4874
|
-
${ids.targetDebugConfig} /* Debug */,
|
|
4875
|
-
${ids.targetReleaseConfig} /* Release */,
|
|
4876
|
-
);
|
|
4877
|
-
defaultConfigurationIsVisible = 0;
|
|
4878
|
-
defaultConfigurationName = Release;
|
|
4879
|
-
};
|
|
4880
|
-
/* End XCConfigurationList section */
|
|
4881
|
-
};
|
|
4882
|
-
rootObject = ${ids.project} /* Project object */;
|
|
4883
|
-
}
|
|
4884
|
-
`;
|
|
4885
|
-
}
|
|
4886
|
-
async function createDevAppProject(iosDir, repoRoot) {
|
|
4887
|
-
const projectDir = path16.join(iosDir, APP_NAME);
|
|
4888
|
-
const xcodeprojDir = path16.join(iosDir, `${APP_NAME}.xcodeproj`);
|
|
4889
|
-
if (fs16.existsSync(iosDir)) {
|
|
4890
|
-
fs16.rmSync(iosDir, { recursive: true, force: true });
|
|
4891
|
-
}
|
|
4892
|
-
console.log(`\u{1F680} Creating TamerDevApp iOS project at: ${iosDir}`);
|
|
4893
|
-
const ids = {};
|
|
4894
|
-
const idKeys = [
|
|
4895
|
-
"project",
|
|
4896
|
-
"mainGroup",
|
|
4897
|
-
"appGroup",
|
|
4898
|
-
"productsGroup",
|
|
4899
|
-
"frameworksGroup",
|
|
4900
|
-
"appFile",
|
|
4901
|
-
"appDelegateRef",
|
|
4902
|
-
"devLauncherRef",
|
|
4903
|
-
"projectVCRef",
|
|
4904
|
-
"templateProviderRef",
|
|
4905
|
-
"devClientManagerRef",
|
|
4906
|
-
"devClientModuleRef",
|
|
4907
|
-
"qrScannerRef",
|
|
4908
|
-
"lynxInitRef",
|
|
4909
|
-
"bridgingHeaderRef",
|
|
4910
|
-
"mainStoryboardRef",
|
|
4911
|
-
"mainStoryboardBaseRef",
|
|
4912
|
-
"launchStoryboardRef",
|
|
4913
|
-
"launchStoryboardBaseRef",
|
|
4914
|
-
"assetsRef",
|
|
4915
|
-
"bundleRef",
|
|
4916
|
-
"nativeTarget",
|
|
4917
|
-
"appDelegateBuildFile",
|
|
4918
|
-
"devLauncherBuildFile",
|
|
4919
|
-
"projectVCBuildFile",
|
|
4920
|
-
"templateProviderBuildFile",
|
|
4921
|
-
"devClientManagerBuildFile",
|
|
4922
|
-
"devServerPrefsBuildFile",
|
|
4923
|
-
"qrScannerBuildFile",
|
|
4924
|
-
"lynxInitBuildFile",
|
|
4925
|
-
"mainStoryboardBuildFile",
|
|
4926
|
-
"launchStoryboardBuildFile",
|
|
4927
|
-
"assetsBuildFile",
|
|
4928
|
-
"bundleBuildFile",
|
|
4929
|
-
"frameworksBuildPhase",
|
|
4930
|
-
"resourcesBuildPhase",
|
|
4931
|
-
"sourcesBuildPhase",
|
|
4932
|
-
"fontCopyScriptPhase",
|
|
4933
|
-
"projectBuildConfigList",
|
|
4934
|
-
"targetBuildConfigList",
|
|
4935
|
-
"projectDebugConfig",
|
|
4936
|
-
"projectReleaseConfig",
|
|
4937
|
-
"targetDebugConfig",
|
|
4938
|
-
"targetReleaseConfig"
|
|
4939
|
-
];
|
|
4940
|
-
for (const k of idKeys) ids[k] = generateId();
|
|
4941
|
-
writeFile2(path16.join(iosDir, "Podfile"), getPodfile());
|
|
4942
|
-
writeFile2(path16.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift2());
|
|
4943
|
-
const devClientPkg = findDevClientPackage(repoRoot);
|
|
4944
|
-
const templateDir = devClientPkg ? path16.join(devClientPkg, "ios", "templates") : null;
|
|
4945
|
-
const templateVars = { PROJECT_BUNDLE_SEGMENT: "tamer-dev-app" };
|
|
4946
|
-
const templateFiles = [
|
|
4947
|
-
"DevLauncherViewController.swift",
|
|
4948
|
-
"ProjectViewController.swift",
|
|
4949
|
-
"DevTemplateProvider.swift",
|
|
4950
|
-
"DevClientManager.swift",
|
|
4951
|
-
"QRScannerViewController.swift",
|
|
4952
|
-
"LynxInitProcessor.swift"
|
|
4953
|
-
];
|
|
4954
|
-
for (const f of templateFiles) {
|
|
4955
|
-
const src = templateDir ? path16.join(templateDir, f) : null;
|
|
4956
|
-
if (src && fs16.existsSync(src)) {
|
|
4957
|
-
writeFile2(path16.join(projectDir, f), readAndSubstituteTemplate4(src, templateVars));
|
|
4958
|
-
} else {
|
|
4959
|
-
const fallback = (() => {
|
|
4960
|
-
switch (f) {
|
|
4961
|
-
case "DevLauncherViewController.swift":
|
|
4962
|
-
return getDevLauncherViewControllerSwift();
|
|
4963
|
-
case "ProjectViewController.swift":
|
|
4964
|
-
return getProjectViewControllerSwift();
|
|
4965
|
-
case "DevTemplateProvider.swift":
|
|
4966
|
-
return getDevTemplateProviderSwift();
|
|
4967
|
-
case "DevClientManager.swift":
|
|
4968
|
-
return getDevClientManagerSwift();
|
|
4969
|
-
case "QRScannerViewController.swift":
|
|
4970
|
-
return getQRScannerViewControllerSwift();
|
|
4971
|
-
case "LynxInitProcessor.swift":
|
|
4972
|
-
return getLynxInitProcessorSwift();
|
|
4973
|
-
default:
|
|
4974
|
-
return "";
|
|
4975
|
-
}
|
|
4976
|
-
})();
|
|
4977
|
-
if (fallback) writeFile2(path16.join(projectDir, f), fallback);
|
|
4978
|
-
}
|
|
4979
|
-
}
|
|
4980
|
-
writeFile2(path16.join(projectDir, BRIDGING_HEADER), getBridgingHeader());
|
|
4981
|
-
writeFile2(path16.join(projectDir, "Info.plist"), getInfoPlist());
|
|
4982
|
-
writeFile2(path16.join(projectDir, "Base.lproj", "Main.storyboard"), getMainStoryboard());
|
|
4983
|
-
writeFile2(path16.join(projectDir, "Base.lproj", "LaunchScreen.storyboard"), getLaunchScreenStoryboard2());
|
|
4984
|
-
writeFile2(
|
|
4985
|
-
path16.join(projectDir, "Assets.xcassets", "AppIcon.appiconset", "Contents.json"),
|
|
4986
|
-
JSON.stringify({ images: [{ idiom: "universal", platform: "ios", size: "1024x1024" }], info: { author: "xcode", version: 1 } }, null, 2)
|
|
4987
|
-
);
|
|
4988
|
-
writeFile2(
|
|
4989
|
-
path16.join(projectDir, "Assets.xcassets", "Contents.json"),
|
|
4990
|
-
JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2)
|
|
4991
|
-
);
|
|
4992
|
-
writeFile2(path16.join(projectDir, "dev-client.lynx.bundle"), "");
|
|
4993
|
-
fs16.mkdirSync(xcodeprojDir, { recursive: true });
|
|
4994
|
-
writeFile2(path16.join(xcodeprojDir, "project.pbxproj"), generatePbxproj(ids));
|
|
4995
|
-
console.log(`\u2705 TamerDevApp iOS project created at ${iosDir}`);
|
|
4996
|
-
await setupCocoaPods(iosDir);
|
|
4997
|
-
}
|
|
4998
|
-
async function syncDevClientIos() {
|
|
4999
|
-
let resolved;
|
|
5000
|
-
let repoRoot;
|
|
5001
|
-
try {
|
|
5002
|
-
repoRoot = findRepoRoot(process.cwd());
|
|
5003
|
-
resolved = resolveDevAppPaths(repoRoot);
|
|
5004
|
-
} catch (e) {
|
|
5005
|
-
console.error(`\u274C ${e.message}`);
|
|
5006
|
-
process.exit(1);
|
|
5007
|
-
}
|
|
5008
|
-
const iosDir = resolved.iosDir;
|
|
5009
|
-
const workspacePath = path16.join(iosDir, `${APP_NAME}.xcworkspace`);
|
|
5010
|
-
const projectDir = path16.join(iosDir, APP_NAME);
|
|
5011
|
-
const hasCommittedSource = fs16.existsSync(path16.join(projectDir, "AppDelegate.swift"));
|
|
5012
|
-
if (!hasCommittedSource) {
|
|
5013
|
-
await createDevAppProject(iosDir, repoRoot);
|
|
5014
|
-
} else if (!fs16.existsSync(workspacePath)) {
|
|
5015
|
-
await setupCocoaPods(iosDir);
|
|
5016
|
-
console.log(`\u2139\uFE0F iOS dev-app project exists; ran pod install`);
|
|
5017
|
-
} else {
|
|
5018
|
-
console.log(`\u2139\uFE0F iOS dev-app project already exists at ${iosDir}`);
|
|
5019
|
-
}
|
|
5020
|
-
const prev = process.cwd();
|
|
5021
|
-
process.chdir(resolved.projectRoot);
|
|
5022
|
-
try {
|
|
5023
|
-
autolink_default2();
|
|
5024
|
-
} finally {
|
|
5025
|
-
process.chdir(prev);
|
|
5026
|
-
}
|
|
5027
|
-
const devClientDir = resolved.lynxProjectDir;
|
|
5028
|
-
console.log("\u{1F4E6} Building dev-client Lynx bundle...");
|
|
5029
|
-
execSync7("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
5030
|
-
const bundleSrc = resolved.lynxBundlePath;
|
|
5031
|
-
const bundleDst = path16.join(iosDir, APP_NAME, "dev-client.lynx.bundle");
|
|
5032
|
-
if (fs16.existsSync(bundleSrc)) {
|
|
5033
|
-
fs16.copyFileSync(bundleSrc, bundleDst);
|
|
5034
|
-
console.log(`\u2728 Copied dev-client.lynx.bundle to iOS project`);
|
|
5035
|
-
} else {
|
|
5036
|
-
console.warn(`\u26A0\uFE0F Bundle not found at ${bundleSrc}`);
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
var syncDevClient_default2 = syncDevClientIos;
|
|
5040
|
-
|
|
5041
|
-
// src/ios/build.ts
|
|
5042
|
-
var DEV_APP_NAME = "TamerDevApp";
|
|
5043
|
-
var SIMULATOR_ID = "A07F36D8-873A-41E0-8B90-3DF328A6B614";
|
|
5044
|
-
function findBootedSimulator() {
|
|
5045
|
-
try {
|
|
5046
|
-
const out = execSync8("xcrun simctl list devices --json", { encoding: "utf8" });
|
|
5047
|
-
const json = JSON.parse(out);
|
|
5048
|
-
for (const runtimes of Object.values(json.devices)) {
|
|
5049
|
-
for (const device of runtimes) {
|
|
5050
|
-
if (device.state === "Booted") return device.udid;
|
|
5051
|
-
}
|
|
5052
|
-
}
|
|
5053
|
-
} catch {
|
|
5054
|
-
}
|
|
5055
|
-
return null;
|
|
5056
|
-
}
|
|
5057
|
-
async function buildIpa(opts = {}) {
|
|
5058
|
-
const target = opts.target ?? "host";
|
|
5059
|
-
const resolved = resolveHostPaths();
|
|
5060
|
-
if (!resolved.config.ios?.appName) {
|
|
5061
|
-
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
5062
|
-
}
|
|
5063
|
-
if (target === "dev-app") {
|
|
5064
|
-
await buildIosDevApp(opts.install, opts.release);
|
|
5065
|
-
return;
|
|
5066
|
-
}
|
|
5067
|
-
const appName = resolved.config.ios.appName;
|
|
5068
|
-
const bundleId = resolved.config.ios.bundleId;
|
|
5069
|
-
const iosDir = resolved.iosDir;
|
|
5070
|
-
const configuration = opts.release ? "Release" : "Debug";
|
|
5071
|
-
bundle_default2({ target, release: opts.release });
|
|
5072
|
-
const scheme = appName;
|
|
5073
|
-
const workspacePath = path17.join(iosDir, `${appName}.xcworkspace`);
|
|
5074
|
-
const projectPath = path17.join(iosDir, `${appName}.xcodeproj`);
|
|
5075
|
-
const xcproject = fs17.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
5076
|
-
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
5077
|
-
const derivedDataPath = path17.join(iosDir, "build");
|
|
5078
|
-
const sdk = opts.install ? "iphonesimulator" : "iphoneos";
|
|
5079
|
-
console.log(`
|
|
5080
|
-
\u{1F528} Building ${configuration} (${sdk})...`);
|
|
5081
|
-
execSync8(
|
|
5082
|
-
`xcodebuild ${flag} "${xcproject}" -scheme "${scheme}" -configuration ${configuration} -sdk ${sdk} -derivedDataPath "${derivedDataPath}"`,
|
|
5083
|
-
{ stdio: "inherit", cwd: iosDir }
|
|
5084
|
-
);
|
|
5085
|
-
console.log(`\u2705 Build completed.`);
|
|
5086
|
-
if (opts.install) {
|
|
5087
|
-
const appGlob = path17.join(
|
|
5088
|
-
derivedDataPath,
|
|
5089
|
-
"Build",
|
|
5090
|
-
"Products",
|
|
5091
|
-
`${configuration}-iphonesimulator`,
|
|
5092
|
-
`${appName}.app`
|
|
5093
|
-
);
|
|
5094
|
-
if (!fs17.existsSync(appGlob)) {
|
|
5095
|
-
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
5096
|
-
process.exit(1);
|
|
5097
|
-
}
|
|
5098
|
-
const udid = findBootedSimulator();
|
|
5099
|
-
if (!udid) {
|
|
5100
|
-
console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
|
|
5101
|
-
process.exit(1);
|
|
5102
|
-
}
|
|
5103
|
-
console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
|
|
5104
|
-
execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
|
|
5105
|
-
if (bundleId) {
|
|
5106
|
-
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
5107
|
-
execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
|
|
5108
|
-
console.log("\u2705 App launched.");
|
|
5109
|
-
} else {
|
|
5110
|
-
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
5111
|
-
}
|
|
5112
|
-
}
|
|
5113
|
-
}
|
|
5114
|
-
async function buildIosDevApp(install, release) {
|
|
5115
|
-
const repoRoot = findRepoRoot(process.cwd());
|
|
5116
|
-
const resolved = resolveDevAppPaths(repoRoot);
|
|
5117
|
-
const iosDir = resolved.iosDir;
|
|
5118
|
-
const configuration = release ? "Release" : "Debug";
|
|
5119
|
-
await syncDevClient_default2();
|
|
5120
|
-
const workspacePath = path17.join(iosDir, `${DEV_APP_NAME}.xcworkspace`);
|
|
5121
|
-
const projectPath = path17.join(iosDir, `${DEV_APP_NAME}.xcodeproj`);
|
|
5122
|
-
const xcproject = fs17.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
5123
|
-
const flag = xcproject.endsWith(".xcworkspace") ? "workspace" : "project";
|
|
5124
|
-
console.log(`
|
|
5125
|
-
\u{1F528} Building TamerDevApp for simulator (${configuration})...`);
|
|
5126
|
-
execSync8(
|
|
5127
|
-
`xcodebuild -${flag} "${xcproject}" -scheme "${DEV_APP_NAME}" -configuration ${configuration} -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.5" -derivedDataPath build`,
|
|
5128
|
-
{ stdio: "inherit", cwd: iosDir }
|
|
5129
|
-
);
|
|
5130
|
-
console.log("\u2705 TamerDevApp built successfully.");
|
|
5131
|
-
if (install) {
|
|
5132
|
-
const appPath = path17.join(iosDir, "build", "Build", "Products", `${configuration}-iphonesimulator`, `${DEV_APP_NAME}.app`);
|
|
5133
|
-
console.log("\n\u{1F4F2} Installing to simulator...");
|
|
5134
|
-
try {
|
|
5135
|
-
execSync8(`xcrun simctl boot "${SIMULATOR_ID}" 2>/dev/null`);
|
|
5136
|
-
} catch {
|
|
5137
|
-
}
|
|
5138
|
-
execSync8(`xcrun simctl install "${SIMULATOR_ID}" "${appPath}"`, { stdio: "inherit" });
|
|
5139
|
-
execSync8(`xcrun simctl launch "${SIMULATOR_ID}" "com.nanofuxion.tamerdevapp"`, { stdio: "inherit" });
|
|
5140
|
-
execSync8("open -a Simulator", { stdio: "inherit" });
|
|
5141
|
-
console.log("\u2705 TamerDevApp launched in simulator.");
|
|
5142
|
-
}
|
|
5143
|
-
}
|
|
5144
|
-
var build_default2 = buildIpa;
|
|
5145
|
-
|
|
5146
|
-
// src/common/init.ts
|
|
5147
|
-
import fs18 from "fs";
|
|
5148
|
-
import path18 from "path";
|
|
5149
|
-
import readline from "readline";
|
|
5150
|
-
var rl = readline.createInterface({
|
|
5151
|
-
input: process.stdin,
|
|
5152
|
-
output: process.stdout,
|
|
5153
|
-
terminal: false
|
|
5154
|
-
});
|
|
5155
|
-
function ask(question) {
|
|
5156
|
-
return new Promise((resolve) => {
|
|
5157
|
-
rl.question(question, (answer) => resolve(answer.trim()));
|
|
5158
|
-
});
|
|
5159
|
-
}
|
|
5160
|
-
async function init() {
|
|
5161
|
-
console.log("Tamer4Lynx Init: Let's set up your tamer.config.json\n");
|
|
5162
|
-
const androidAppName = await ask("Android app name: ");
|
|
5163
|
-
const androidPackageName = await ask("Android package name (e.g. com.example.app): ");
|
|
5164
|
-
let androidSdk = await ask("Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME): ");
|
|
5165
|
-
if (androidSdk.startsWith("$") && /^[A-Z0-9_]+$/.test(androidSdk.slice(1))) {
|
|
5166
|
-
const envVar = androidSdk.slice(1);
|
|
5167
|
-
const envValue = process.env[envVar];
|
|
5168
|
-
if (envValue) {
|
|
5169
|
-
androidSdk = envValue;
|
|
5170
|
-
console.log(`Resolved ${androidSdk} from $${envVar}`);
|
|
5171
|
-
} else {
|
|
5172
|
-
console.warn(`Environment variable $${envVar} not found. SDK path will be left as-is.`);
|
|
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.`);
|
|
5173
3870
|
}
|
|
5174
3871
|
}
|
|
5175
3872
|
const useSame = await ask("Use same name and bundle ID for iOS as Android? (y/N): ");
|
|
@@ -5196,8 +3893,8 @@ async function init() {
|
|
|
5196
3893
|
paths: { androidDir: "android", iosDir: "ios" }
|
|
5197
3894
|
};
|
|
5198
3895
|
if (lynxProject) config.lynxProject = lynxProject;
|
|
5199
|
-
const configPath =
|
|
5200
|
-
|
|
3896
|
+
const configPath = path17.join(process.cwd(), "tamer.config.json");
|
|
3897
|
+
fs16.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
5201
3898
|
console.log(`
|
|
5202
3899
|
\u2705 Generated tamer.config.json at ${configPath}`);
|
|
5203
3900
|
rl.close();
|
|
@@ -5205,8 +3902,8 @@ async function init() {
|
|
|
5205
3902
|
var init_default = init;
|
|
5206
3903
|
|
|
5207
3904
|
// src/common/create.ts
|
|
5208
|
-
import
|
|
5209
|
-
import
|
|
3905
|
+
import fs17 from "fs";
|
|
3906
|
+
import path18 from "path";
|
|
5210
3907
|
import readline2 from "readline";
|
|
5211
3908
|
var rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5212
3909
|
function ask2(question) {
|
|
@@ -5236,13 +3933,13 @@ async function create3() {
|
|
|
5236
3933
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
5237
3934
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
5238
3935
|
const cwd = process.cwd();
|
|
5239
|
-
const root =
|
|
5240
|
-
if (
|
|
3936
|
+
const root = path18.join(cwd, extName);
|
|
3937
|
+
if (fs17.existsSync(root)) {
|
|
5241
3938
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
5242
3939
|
rl2.close();
|
|
5243
3940
|
process.exit(1);
|
|
5244
3941
|
}
|
|
5245
|
-
|
|
3942
|
+
fs17.mkdirSync(root, { recursive: true });
|
|
5246
3943
|
const lynxExt = {
|
|
5247
3944
|
platforms: {
|
|
5248
3945
|
android: {
|
|
@@ -5257,7 +3954,7 @@ async function create3() {
|
|
|
5257
3954
|
web: {}
|
|
5258
3955
|
}
|
|
5259
3956
|
};
|
|
5260
|
-
|
|
3957
|
+
fs17.writeFileSync(path18.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
5261
3958
|
const pkg = {
|
|
5262
3959
|
name: extName,
|
|
5263
3960
|
version: "0.0.1",
|
|
@@ -5270,17 +3967,17 @@ async function create3() {
|
|
|
5270
3967
|
engines: { node: ">=18" }
|
|
5271
3968
|
};
|
|
5272
3969
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5273
|
-
|
|
3970
|
+
fs17.writeFileSync(path18.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5274
3971
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5275
3972
|
if (includeModule) {
|
|
5276
|
-
|
|
5277
|
-
|
|
3973
|
+
fs17.mkdirSync(path18.join(root, "src"), { recursive: true });
|
|
3974
|
+
fs17.writeFileSync(path18.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5278
3975
|
export declare class ${simpleModuleName} {
|
|
5279
3976
|
// Add your module methods here
|
|
5280
3977
|
}
|
|
5281
3978
|
`);
|
|
5282
|
-
|
|
5283
|
-
|
|
3979
|
+
fs17.mkdirSync(path18.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
3980
|
+
fs17.writeFileSync(path18.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5284
3981
|
id("com.android.library")
|
|
5285
3982
|
id("org.jetbrains.kotlin.android")
|
|
5286
3983
|
}
|
|
@@ -5301,7 +3998,7 @@ dependencies {
|
|
|
5301
3998
|
implementation(libs.lynx.jssdk)
|
|
5302
3999
|
}
|
|
5303
4000
|
`);
|
|
5304
|
-
|
|
4001
|
+
fs17.writeFileSync(path18.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5305
4002
|
<manifest />
|
|
5306
4003
|
`);
|
|
5307
4004
|
const ktContent = `package ${packageName}
|
|
@@ -5318,8 +4015,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5318
4015
|
}
|
|
5319
4016
|
}
|
|
5320
4017
|
`;
|
|
5321
|
-
|
|
5322
|
-
|
|
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 });
|
|
5323
4020
|
const podspec = `Pod::Spec.new do |s|
|
|
5324
4021
|
s.name = '${extName}'
|
|
5325
4022
|
s.version = '0.0.1'
|
|
@@ -5333,7 +4030,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5333
4030
|
s.dependency 'Lynx'
|
|
5334
4031
|
end
|
|
5335
4032
|
`;
|
|
5336
|
-
|
|
4033
|
+
fs17.writeFileSync(path18.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5337
4034
|
const swiftContent = `import Foundation
|
|
5338
4035
|
|
|
5339
4036
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5342,16 +4039,16 @@ end
|
|
|
5342
4039
|
}
|
|
5343
4040
|
}
|
|
5344
4041
|
`;
|
|
5345
|
-
|
|
4042
|
+
fs17.writeFileSync(path18.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5346
4043
|
}
|
|
5347
|
-
|
|
4044
|
+
fs17.writeFileSync(path18.join(root, "index.js"), `'use strict';
|
|
5348
4045
|
module.exports = {};
|
|
5349
4046
|
`);
|
|
5350
|
-
|
|
4047
|
+
fs17.writeFileSync(path18.join(root, "tsconfig.json"), JSON.stringify({
|
|
5351
4048
|
compilerOptions: { target: "ES2020", module: "ESNext", moduleResolution: "bundler", strict: true },
|
|
5352
4049
|
include: ["src"]
|
|
5353
4050
|
}, null, 2));
|
|
5354
|
-
|
|
4051
|
+
fs17.writeFileSync(path18.join(root, "README.md"), `# ${extName}
|
|
5355
4052
|
|
|
5356
4053
|
Lynx extension for ${extName}.
|
|
5357
4054
|
|
|
@@ -5376,8 +4073,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5376
4073
|
var create_default3 = create3;
|
|
5377
4074
|
|
|
5378
4075
|
// src/common/codegen.ts
|
|
5379
|
-
import
|
|
5380
|
-
import
|
|
4076
|
+
import fs18 from "fs";
|
|
4077
|
+
import path19 from "path";
|
|
5381
4078
|
function codegen() {
|
|
5382
4079
|
const cwd = process.cwd();
|
|
5383
4080
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5385,9 +4082,9 @@ function codegen() {
|
|
|
5385
4082
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5386
4083
|
process.exit(1);
|
|
5387
4084
|
}
|
|
5388
|
-
const srcDir =
|
|
5389
|
-
const generatedDir =
|
|
5390
|
-
|
|
4085
|
+
const srcDir = path19.join(cwd, "src");
|
|
4086
|
+
const generatedDir = path19.join(cwd, "generated");
|
|
4087
|
+
fs18.mkdirSync(generatedDir, { recursive: true });
|
|
5391
4088
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5392
4089
|
const modules = extractLynxModules(dtsFiles);
|
|
5393
4090
|
if (modules.length === 0) {
|
|
@@ -5397,28 +4094,28 @@ function codegen() {
|
|
|
5397
4094
|
for (const mod of modules) {
|
|
5398
4095
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5399
4096
|
`;
|
|
5400
|
-
const outPath =
|
|
5401
|
-
|
|
4097
|
+
const outPath = path19.join(generatedDir, `${mod}.ts`);
|
|
4098
|
+
fs18.writeFileSync(outPath, tsContent);
|
|
5402
4099
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5403
4100
|
}
|
|
5404
4101
|
if (config.android) {
|
|
5405
|
-
const androidGenerated =
|
|
5406
|
-
|
|
4102
|
+
const androidGenerated = path19.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
4103
|
+
fs18.mkdirSync(androidGenerated, { recursive: true });
|
|
5407
4104
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5408
4105
|
}
|
|
5409
4106
|
if (config.ios) {
|
|
5410
|
-
const iosGenerated =
|
|
5411
|
-
|
|
4107
|
+
const iosGenerated = path19.join(cwd, "ios", "generated");
|
|
4108
|
+
fs18.mkdirSync(iosGenerated, { recursive: true });
|
|
5412
4109
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5413
4110
|
}
|
|
5414
4111
|
console.log("\u2728 Codegen complete.");
|
|
5415
4112
|
}
|
|
5416
4113
|
function findDtsFiles(dir) {
|
|
5417
4114
|
const result = [];
|
|
5418
|
-
if (!
|
|
5419
|
-
const entries =
|
|
4115
|
+
if (!fs18.existsSync(dir)) return result;
|
|
4116
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
5420
4117
|
for (const e of entries) {
|
|
5421
|
-
const full =
|
|
4118
|
+
const full = path19.join(dir, e.name);
|
|
5422
4119
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5423
4120
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5424
4121
|
}
|
|
@@ -5428,7 +4125,7 @@ function extractLynxModules(files) {
|
|
|
5428
4125
|
const modules = [];
|
|
5429
4126
|
const seen = /* @__PURE__ */ new Set();
|
|
5430
4127
|
for (const file of files) {
|
|
5431
|
-
const content =
|
|
4128
|
+
const content = fs18.readFileSync(file, "utf8");
|
|
5432
4129
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5433
4130
|
let m;
|
|
5434
4131
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5444,10 +4141,10 @@ var codegen_default = codegen;
|
|
|
5444
4141
|
|
|
5445
4142
|
// src/common/devServer.ts
|
|
5446
4143
|
import { spawn } from "child_process";
|
|
5447
|
-
import
|
|
4144
|
+
import fs19 from "fs";
|
|
5448
4145
|
import http from "http";
|
|
5449
4146
|
import os3 from "os";
|
|
5450
|
-
import
|
|
4147
|
+
import path20 from "path";
|
|
5451
4148
|
import { WebSocketServer } from "ws";
|
|
5452
4149
|
var DEFAULT_PORT = 3e3;
|
|
5453
4150
|
function getLanIp() {
|
|
@@ -5465,13 +4162,13 @@ async function startDevServer(opts) {
|
|
|
5465
4162
|
const verbose = opts?.verbose ?? false;
|
|
5466
4163
|
const resolved = resolveHostPaths();
|
|
5467
4164
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5468
|
-
const distDir =
|
|
4165
|
+
const distDir = path20.dirname(lynxBundlePath);
|
|
5469
4166
|
const port = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
5470
4167
|
let buildProcess = null;
|
|
5471
4168
|
function detectPackageManager2(cwd) {
|
|
5472
|
-
const dir =
|
|
5473
|
-
if (
|
|
5474
|
-
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"] };
|
|
5475
4172
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5476
4173
|
}
|
|
5477
4174
|
function runBuild() {
|
|
@@ -5493,20 +4190,20 @@ async function startDevServer(opts) {
|
|
|
5493
4190
|
});
|
|
5494
4191
|
});
|
|
5495
4192
|
}
|
|
5496
|
-
const projectName =
|
|
4193
|
+
const projectName = path20.basename(lynxProjectDir);
|
|
5497
4194
|
const basePath = `/${projectName}`;
|
|
5498
4195
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5499
4196
|
let iconFilePath = null;
|
|
5500
|
-
if (iconPaths?.source &&
|
|
4197
|
+
if (iconPaths?.source && fs19.statSync(iconPaths.source).isFile()) {
|
|
5501
4198
|
iconFilePath = iconPaths.source;
|
|
5502
4199
|
} else if (iconPaths?.android) {
|
|
5503
|
-
const androidIcon =
|
|
5504
|
-
if (
|
|
4200
|
+
const androidIcon = path20.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
4201
|
+
if (fs19.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5505
4202
|
} else if (iconPaths?.ios) {
|
|
5506
|
-
const iosIcon =
|
|
5507
|
-
if (
|
|
4203
|
+
const iosIcon = path20.join(iconPaths.ios, "Icon-1024.png");
|
|
4204
|
+
if (fs19.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5508
4205
|
}
|
|
5509
|
-
const iconExt = iconFilePath ?
|
|
4206
|
+
const iconExt = iconFilePath ? path20.extname(iconFilePath) || ".png" : "";
|
|
5510
4207
|
const iconMime = {
|
|
5511
4208
|
".png": "image/png",
|
|
5512
4209
|
".jpg": "image/jpeg",
|
|
@@ -5545,7 +4242,7 @@ async function startDevServer(opts) {
|
|
|
5545
4242
|
return;
|
|
5546
4243
|
}
|
|
5547
4244
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5548
|
-
|
|
4245
|
+
fs19.readFile(iconFilePath, (err, data) => {
|
|
5549
4246
|
if (err) {
|
|
5550
4247
|
res.writeHead(404);
|
|
5551
4248
|
res.end();
|
|
@@ -5563,14 +4260,14 @@ async function startDevServer(opts) {
|
|
|
5563
4260
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5564
4261
|
}
|
|
5565
4262
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5566
|
-
const filePath =
|
|
5567
|
-
const distResolved =
|
|
5568
|
-
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) {
|
|
5569
4266
|
res.writeHead(403);
|
|
5570
4267
|
res.end();
|
|
5571
4268
|
return;
|
|
5572
4269
|
}
|
|
5573
|
-
|
|
4270
|
+
fs19.readFile(filePath, (err, data) => {
|
|
5574
4271
|
if (err) {
|
|
5575
4272
|
res.writeHead(404);
|
|
5576
4273
|
res.end("Not found");
|
|
@@ -5624,10 +4321,10 @@ async function startDevServer(opts) {
|
|
|
5624
4321
|
}
|
|
5625
4322
|
if (chokidar) {
|
|
5626
4323
|
const watchPaths = [
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
].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));
|
|
5631
4328
|
if (watchPaths.length > 0) {
|
|
5632
4329
|
const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5633
4330
|
watcher.on("change", async () => {
|
|
@@ -5706,10 +4403,10 @@ async function start(opts) {
|
|
|
5706
4403
|
var start_default = start;
|
|
5707
4404
|
|
|
5708
4405
|
// src/common/injectHost.ts
|
|
5709
|
-
import
|
|
5710
|
-
import
|
|
4406
|
+
import fs20 from "fs";
|
|
4407
|
+
import path21 from "path";
|
|
5711
4408
|
function readAndSubstitute(templatePath, vars) {
|
|
5712
|
-
const raw =
|
|
4409
|
+
const raw = fs20.readFileSync(templatePath, "utf-8");
|
|
5713
4410
|
return Object.entries(vars).reduce(
|
|
5714
4411
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5715
4412
|
raw
|
|
@@ -5730,32 +4427,32 @@ async function injectHostAndroid(opts) {
|
|
|
5730
4427
|
process.exit(1);
|
|
5731
4428
|
}
|
|
5732
4429
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5733
|
-
const rootDir =
|
|
4430
|
+
const rootDir = path21.join(projectRoot, androidDir);
|
|
5734
4431
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5735
|
-
const javaDir =
|
|
5736
|
-
const kotlinDir =
|
|
5737
|
-
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)) {
|
|
5738
4435
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5739
4436
|
process.exit(1);
|
|
5740
4437
|
}
|
|
5741
|
-
const templateDir =
|
|
4438
|
+
const templateDir = path21.join(hostPkg, "android", "templates");
|
|
5742
4439
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5743
4440
|
const files = [
|
|
5744
|
-
{ src: "App.java", dst:
|
|
5745
|
-
{ src: "TemplateProvider.java", dst:
|
|
5746
|
-
{ 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") }
|
|
5747
4444
|
];
|
|
5748
4445
|
for (const { src, dst } of files) {
|
|
5749
|
-
const srcPath =
|
|
5750
|
-
if (!
|
|
5751
|
-
if (
|
|
5752
|
-
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)`);
|
|
5753
4450
|
continue;
|
|
5754
4451
|
}
|
|
5755
4452
|
const content = readAndSubstitute(srcPath, vars);
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
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)}`);
|
|
5759
4456
|
}
|
|
5760
4457
|
}
|
|
5761
4458
|
async function injectHostIos(opts) {
|
|
@@ -5773,13 +4470,13 @@ async function injectHostIos(opts) {
|
|
|
5773
4470
|
process.exit(1);
|
|
5774
4471
|
}
|
|
5775
4472
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5776
|
-
const rootDir =
|
|
5777
|
-
const projectDir =
|
|
5778
|
-
if (!
|
|
4473
|
+
const rootDir = path21.join(projectRoot, iosDir);
|
|
4474
|
+
const projectDir = path21.join(rootDir, appName);
|
|
4475
|
+
if (!fs20.existsSync(projectDir)) {
|
|
5779
4476
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5780
4477
|
process.exit(1);
|
|
5781
4478
|
}
|
|
5782
|
-
const templateDir =
|
|
4479
|
+
const templateDir = path21.join(hostPkg, "ios", "templates");
|
|
5783
4480
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5784
4481
|
const files = [
|
|
5785
4482
|
"AppDelegate.swift",
|
|
@@ -5789,23 +4486,23 @@ async function injectHostIos(opts) {
|
|
|
5789
4486
|
"LynxInitProcessor.swift"
|
|
5790
4487
|
];
|
|
5791
4488
|
for (const f of files) {
|
|
5792
|
-
const srcPath =
|
|
5793
|
-
const dstPath =
|
|
5794
|
-
if (!
|
|
5795
|
-
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) {
|
|
5796
4493
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5797
4494
|
continue;
|
|
5798
4495
|
}
|
|
5799
4496
|
const content = readAndSubstitute(srcPath, vars);
|
|
5800
|
-
|
|
4497
|
+
fs20.writeFileSync(dstPath, content);
|
|
5801
4498
|
console.log(`\u2705 Injected ${f}`);
|
|
5802
4499
|
}
|
|
5803
4500
|
}
|
|
5804
4501
|
|
|
5805
4502
|
// src/common/buildEmbeddable.ts
|
|
5806
|
-
import
|
|
5807
|
-
import
|
|
5808
|
-
import { execSync as
|
|
4503
|
+
import fs21 from "fs";
|
|
4504
|
+
import path22 from "path";
|
|
4505
|
+
import { execSync as execSync8 } from "child_process";
|
|
5809
4506
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5810
4507
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
5811
4508
|
var GRADLE_VERSION = "8.14.2";
|
|
@@ -5879,14 +4576,14 @@ object LynxEmbeddable {
|
|
|
5879
4576
|
}
|
|
5880
4577
|
`;
|
|
5881
4578
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
5882
|
-
const libDir =
|
|
5883
|
-
const libSrcMain =
|
|
5884
|
-
const assetsDir =
|
|
5885
|
-
const kotlinDir =
|
|
5886
|
-
const generatedDir =
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
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 });
|
|
5890
4587
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5891
4588
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5892
4589
|
const settingsContent = `pluginManagement {
|
|
@@ -5906,7 +4603,7 @@ include(":lib")
|
|
|
5906
4603
|
${androidModules.map((p) => {
|
|
5907
4604
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
5908
4605
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
5909
|
-
const absPath =
|
|
4606
|
+
const absPath = path22.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
5910
4607
|
return `include(":${gradleName}")
|
|
5911
4608
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
5912
4609
|
}).join("\n")}
|
|
@@ -5953,10 +4650,10 @@ dependencies {
|
|
|
5953
4650
|
${libDeps}
|
|
5954
4651
|
}
|
|
5955
4652
|
`;
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
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"),
|
|
5960
4657
|
`plugins {
|
|
5961
4658
|
alias(libs.plugins.android.library) apply false
|
|
5962
4659
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -5964,26 +4661,26 @@ ${libDeps}
|
|
|
5964
4661
|
}
|
|
5965
4662
|
`
|
|
5966
4663
|
);
|
|
5967
|
-
|
|
5968
|
-
|
|
4664
|
+
fs21.writeFileSync(
|
|
4665
|
+
path22.join(androidDir, "gradle.properties"),
|
|
5969
4666
|
`org.gradle.jvmargs=-Xmx2048m
|
|
5970
4667
|
android.useAndroidX=true
|
|
5971
4668
|
kotlin.code.style=official
|
|
5972
4669
|
`
|
|
5973
4670
|
);
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
4671
|
+
fs21.writeFileSync(path22.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
4672
|
+
fs21.writeFileSync(
|
|
4673
|
+
path22.join(libSrcMain, "AndroidManifest.xml"),
|
|
5977
4674
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
5978
4675
|
);
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
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"),
|
|
5983
4680
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
5984
4681
|
);
|
|
5985
|
-
|
|
5986
|
-
|
|
4682
|
+
fs21.writeFileSync(
|
|
4683
|
+
path22.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
5987
4684
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
5988
4685
|
);
|
|
5989
4686
|
}
|
|
@@ -5991,21 +4688,21 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5991
4688
|
const resolved = resolveHostPaths();
|
|
5992
4689
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
5993
4690
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
5994
|
-
|
|
5995
|
-
if (!
|
|
4691
|
+
execSync8("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
4692
|
+
if (!fs21.existsSync(lynxBundlePath)) {
|
|
5996
4693
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
5997
4694
|
process.exit(1);
|
|
5998
4695
|
}
|
|
5999
|
-
const outDir =
|
|
6000
|
-
|
|
6001
|
-
const distDir =
|
|
4696
|
+
const outDir = path22.join(projectRoot, EMBEDDABLE_DIR);
|
|
4697
|
+
fs21.mkdirSync(outDir, { recursive: true });
|
|
4698
|
+
const distDir = path22.dirname(lynxBundlePath);
|
|
6002
4699
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
6003
4700
|
const modules = discoverModules(projectRoot);
|
|
6004
4701
|
const androidModules = modules.filter((m) => m.config.android);
|
|
6005
4702
|
const abiFilters = resolveAbiFilters(config);
|
|
6006
|
-
const androidDir =
|
|
6007
|
-
if (
|
|
6008
|
-
|
|
4703
|
+
const androidDir = path22.join(outDir, "android");
|
|
4704
|
+
if (fs21.existsSync(androidDir)) fs21.rmSync(androidDir, { recursive: true });
|
|
4705
|
+
fs21.mkdirSync(androidDir, { recursive: true });
|
|
6009
4706
|
generateAndroidLibrary(
|
|
6010
4707
|
outDir,
|
|
6011
4708
|
androidDir,
|
|
@@ -6015,23 +4712,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6015
4712
|
modules,
|
|
6016
4713
|
abiFilters
|
|
6017
4714
|
);
|
|
6018
|
-
const gradlewPath =
|
|
4715
|
+
const gradlewPath = path22.join(androidDir, "gradlew");
|
|
6019
4716
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
6020
4717
|
const existingGradleDirs = [
|
|
6021
|
-
|
|
6022
|
-
devAppDir ?
|
|
4718
|
+
path22.join(projectRoot, "android"),
|
|
4719
|
+
devAppDir ? path22.join(devAppDir, "android") : null
|
|
6023
4720
|
].filter(Boolean);
|
|
6024
4721
|
let hasWrapper = false;
|
|
6025
4722
|
for (const d of existingGradleDirs) {
|
|
6026
|
-
if (
|
|
4723
|
+
if (fs21.existsSync(path22.join(d, "gradlew"))) {
|
|
6027
4724
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
6028
|
-
const src =
|
|
6029
|
-
if (
|
|
6030
|
-
const dest =
|
|
6031
|
-
if (
|
|
6032
|
-
|
|
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 });
|
|
6033
4730
|
} else {
|
|
6034
|
-
|
|
4731
|
+
fs21.copyFileSync(src, dest);
|
|
6035
4732
|
}
|
|
6036
4733
|
}
|
|
6037
4734
|
}
|
|
@@ -6045,15 +4742,15 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6045
4742
|
}
|
|
6046
4743
|
try {
|
|
6047
4744
|
console.log("\u{1F4E6} Building Android AAR...");
|
|
6048
|
-
|
|
4745
|
+
execSync8("./gradlew :lib:assembleRelease", { cwd: androidDir, stdio: "inherit" });
|
|
6049
4746
|
} catch (e) {
|
|
6050
4747
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6051
4748
|
throw e;
|
|
6052
4749
|
}
|
|
6053
|
-
const aarSrc =
|
|
6054
|
-
const aarDest =
|
|
6055
|
-
if (
|
|
6056
|
-
|
|
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);
|
|
6057
4754
|
console.log(` - tamer-embeddable.aar`);
|
|
6058
4755
|
}
|
|
6059
4756
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6064,7 +4761,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6064
4761
|
// LynxEmbeddable.init(applicationContext)
|
|
6065
4762
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6066
4763
|
`;
|
|
6067
|
-
|
|
4764
|
+
fs21.writeFileSync(path22.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6068
4765
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6069
4766
|
const readme = `# Embeddable Lynx Bundle
|
|
6070
4767
|
|
|
@@ -6095,7 +4792,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6095
4792
|
|
|
6096
4793
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6097
4794
|
`;
|
|
6098
|
-
|
|
4795
|
+
fs21.writeFileSync(path22.join(outDir, "README.md"), readme);
|
|
6099
4796
|
console.log(`
|
|
6100
4797
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6101
4798
|
console.log(" - main.lynx.bundle");
|
|
@@ -6107,20 +4804,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6107
4804
|
console.log(" - README.md");
|
|
6108
4805
|
}
|
|
6109
4806
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6110
|
-
const iosDir =
|
|
6111
|
-
const podDir =
|
|
6112
|
-
const resourcesDir =
|
|
6113
|
-
|
|
6114
|
-
|
|
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));
|
|
6115
4812
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6116
4813
|
const podDeps = iosModules.map((p) => {
|
|
6117
4814
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6118
|
-
const podspecDir =
|
|
6119
|
-
if (!
|
|
6120
|
-
const files =
|
|
4815
|
+
const podspecDir = path22.join(p.packagePath, podspecPath);
|
|
4816
|
+
if (!fs21.existsSync(podspecDir)) return null;
|
|
4817
|
+
const files = fs21.readdirSync(podspecDir);
|
|
6121
4818
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6122
4819
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6123
|
-
const absPath =
|
|
4820
|
+
const absPath = path22.resolve(podspecDir);
|
|
6124
4821
|
return { podName, absPath };
|
|
6125
4822
|
}).filter(Boolean);
|
|
6126
4823
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6160,9 +4857,9 @@ end
|
|
|
6160
4857
|
});
|
|
6161
4858
|
const swiftImports = iosModules.map((p) => {
|
|
6162
4859
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6163
|
-
const podspecDir =
|
|
6164
|
-
if (!
|
|
6165
|
-
const files =
|
|
4860
|
+
const podspecDir = path22.join(p.packagePath, podspecPath);
|
|
4861
|
+
if (!fs21.existsSync(podspecDir)) return null;
|
|
4862
|
+
const files = fs21.readdirSync(podspecDir);
|
|
6166
4863
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6167
4864
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6168
4865
|
}).filter(Boolean);
|
|
@@ -6181,17 +4878,17 @@ ${regBlock}
|
|
|
6181
4878
|
}
|
|
6182
4879
|
}
|
|
6183
4880
|
`;
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
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);
|
|
6187
4884
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6188
4885
|
|
|
6189
4886
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6190
4887
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6191
4888
|
`;
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
4889
|
+
fs21.writeFileSync(path22.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
4890
|
+
fs21.writeFileSync(
|
|
4891
|
+
path22.join(outDir, "snippet-ios.swift"),
|
|
6195
4892
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6196
4893
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6197
4894
|
`
|
|
@@ -6199,9 +4896,9 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6199
4896
|
}
|
|
6200
4897
|
|
|
6201
4898
|
// src/common/add.ts
|
|
6202
|
-
import
|
|
6203
|
-
import
|
|
6204
|
-
import { execSync as
|
|
4899
|
+
import fs22 from "fs";
|
|
4900
|
+
import path23 from "path";
|
|
4901
|
+
import { execSync as execSync9 } from "child_process";
|
|
6205
4902
|
var CORE_PACKAGES = [
|
|
6206
4903
|
"@tamer4lynx/tamer-app-shell",
|
|
6207
4904
|
"@tamer4lynx/tamer-screen",
|
|
@@ -6213,15 +4910,15 @@ var CORE_PACKAGES = [
|
|
|
6213
4910
|
"@tamer4lynx/tamer-icons"
|
|
6214
4911
|
];
|
|
6215
4912
|
function detectPackageManager(cwd) {
|
|
6216
|
-
const dir =
|
|
6217
|
-
if (
|
|
6218
|
-
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";
|
|
6219
4916
|
return "npm";
|
|
6220
4917
|
}
|
|
6221
4918
|
function runInstall(cwd, packages, pm) {
|
|
6222
4919
|
const args = pm === "npm" ? ["install", ...packages] : ["add", ...packages];
|
|
6223
4920
|
const cmd = pm === "npm" ? "npm" : pm === "pnpm" ? "pnpm" : "bun";
|
|
6224
|
-
|
|
4921
|
+
execSync9(`${cmd} ${args.join(" ")}`, { stdio: "inherit", cwd });
|
|
6225
4922
|
}
|
|
6226
4923
|
function addCore() {
|
|
6227
4924
|
const { lynxProjectDir } = resolveHostPaths();
|
|
@@ -6267,12 +4964,12 @@ android.command("create").option("-t, --target <target>", "Create target: host (
|
|
|
6267
4964
|
android.command("link").description("Link native modules to the Android project").action(() => {
|
|
6268
4965
|
autolink_default();
|
|
6269
4966
|
});
|
|
6270
|
-
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) => {
|
|
6271
4968
|
validateDebugRelease(opts.debug, opts.release);
|
|
6272
4969
|
const release = opts.release === true;
|
|
6273
|
-
await bundle_default({
|
|
4970
|
+
await bundle_default({ release });
|
|
6274
4971
|
});
|
|
6275
|
-
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 () => {
|
|
6276
4973
|
const opts = androidBuildCmd.opts();
|
|
6277
4974
|
validateDebugRelease(opts.debug, opts.release);
|
|
6278
4975
|
const release = opts.release === true;
|
|
@@ -6280,7 +4977,7 @@ var androidBuildCmd = android.command("build").option("-i, --install", "Install
|
|
|
6280
4977
|
await buildEmbeddable({ release: true });
|
|
6281
4978
|
return;
|
|
6282
4979
|
}
|
|
6283
|
-
await build_default({ install: opts.install,
|
|
4980
|
+
await build_default({ install: opts.install, release });
|
|
6284
4981
|
});
|
|
6285
4982
|
android.command("sync").description("Sync dev client files (TemplateProvider, MainActivity, DevClientManager) from tamer.config.json").action(async () => {
|
|
6286
4983
|
await syncDevClient_default();
|
|
@@ -6298,12 +4995,12 @@ ios.command("inject").option("-f, --force", "Overwrite existing files").descript
|
|
|
6298
4995
|
ios.command("link").description("Link native modules to the iOS project").action(() => {
|
|
6299
4996
|
autolink_default2();
|
|
6300
4997
|
});
|
|
6301
|
-
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) => {
|
|
6302
4999
|
validateDebugRelease(opts.debug, opts.release);
|
|
6303
5000
|
const release = opts.release === true;
|
|
6304
|
-
bundle_default2({
|
|
5001
|
+
bundle_default2({ release });
|
|
6305
5002
|
});
|
|
6306
|
-
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 () => {
|
|
6307
5004
|
const opts = iosBuildCmd.opts();
|
|
6308
5005
|
validateDebugRelease(opts.debug, opts.release);
|
|
6309
5006
|
const release = opts.release === true;
|
|
@@ -6311,7 +5008,7 @@ var iosBuildCmd = ios.command("build").option("-t, --target <target>", "Build ta
|
|
|
6311
5008
|
await buildEmbeddable({ release: true });
|
|
6312
5009
|
return;
|
|
6313
5010
|
}
|
|
6314
|
-
await build_default2({
|
|
5011
|
+
await build_default2({ install: opts.install, release });
|
|
6315
5012
|
});
|
|
6316
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(() => {
|
|
6317
5014
|
const opts = linkCmd.opts();
|
|
@@ -6337,7 +5034,7 @@ var linkCmd = program.command("link").option("-i, --ios", "Link iOS native modul
|
|
|
6337
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) => {
|
|
6338
5035
|
await start_default({ verbose: opts.verbose });
|
|
6339
5036
|
});
|
|
6340
|
-
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 () => {
|
|
6341
5038
|
const opts = buildCmd.opts();
|
|
6342
5039
|
validateDebugRelease(opts.debug, opts.release);
|
|
6343
5040
|
const release = opts.release === true;
|
|
@@ -6347,23 +5044,22 @@ var buildCmd = program.command("build").option("-p, --platform <platform>", "and
|
|
|
6347
5044
|
}
|
|
6348
5045
|
const p = opts.platform?.toLowerCase();
|
|
6349
5046
|
const platform = p === "ios" || p === "android" ? p : "all";
|
|
6350
|
-
const target = opts.target ?? "dev-app";
|
|
6351
5047
|
if (platform === "android" || platform === "all") {
|
|
6352
|
-
await build_default({ install: opts.install,
|
|
5048
|
+
await build_default({ install: opts.install, release });
|
|
6353
5049
|
}
|
|
6354
5050
|
if (platform === "ios" || platform === "all") {
|
|
6355
|
-
await build_default2({
|
|
5051
|
+
await build_default2({ install: opts.install, release });
|
|
6356
5052
|
}
|
|
6357
5053
|
});
|
|
6358
|
-
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
|
|
6359
|
-
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]");
|
|
6360
5056
|
const p = opts.platform?.toLowerCase();
|
|
6361
5057
|
const platform = p === "ios" || p === "android" ? p : "all";
|
|
6362
5058
|
if (platform === "android" || platform === "all") {
|
|
6363
|
-
await build_default({ install: opts.install,
|
|
5059
|
+
await build_default({ install: opts.install, release: false });
|
|
6364
5060
|
}
|
|
6365
5061
|
if (platform === "ios" || platform === "all") {
|
|
6366
|
-
await build_default2({
|
|
5062
|
+
await build_default2({ install: opts.install, release: false });
|
|
6367
5063
|
}
|
|
6368
5064
|
});
|
|
6369
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));
|
|
@@ -6373,10 +5069,10 @@ program.command("codegen").description("Generate code from @lynxmodule declarati
|
|
|
6373
5069
|
codegen_default();
|
|
6374
5070
|
});
|
|
6375
5071
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
6376
|
-
const configPath =
|
|
5072
|
+
const configPath = path24.join(process.cwd(), "tamer.config.json");
|
|
6377
5073
|
let config = {};
|
|
6378
|
-
if (
|
|
6379
|
-
config = JSON.parse(
|
|
5074
|
+
if (fs23.existsSync(configPath)) {
|
|
5075
|
+
config = JSON.parse(fs23.readFileSync(configPath, "utf8"));
|
|
6380
5076
|
}
|
|
6381
5077
|
if (config.autolink) {
|
|
6382
5078
|
delete config.autolink;
|
|
@@ -6385,7 +5081,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
6385
5081
|
config.autolink = true;
|
|
6386
5082
|
console.log("Autolink enabled in tamer.config.json");
|
|
6387
5083
|
}
|
|
6388
|
-
|
|
5084
|
+
fs23.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
6389
5085
|
console.log(`Updated ${configPath}`);
|
|
6390
5086
|
});
|
|
6391
5087
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|