@tamer4lynx/cli 0.0.3 → 0.0.5

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