@tamer4lynx/cli 0.0.16 → 0.0.18

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 (2) hide show
  1. package/dist/index.js +963 -765
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -7,41 +7,64 @@ process.on("warning", (w) => {
7
7
  });
8
8
 
9
9
  // index.ts
10
- import fs29 from "fs";
11
- import path30 from "path";
12
- import { fileURLToPath } from "url";
10
+ import fs30 from "fs";
11
+ import path31 from "path";
13
12
  import { program } from "commander";
14
13
 
14
+ // src/common/cliVersion.ts
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import { fileURLToPath } from "url";
18
+ function getCliVersion() {
19
+ let dir = path.dirname(fileURLToPath(import.meta.url));
20
+ for (let i = 0; i < 12; i++) {
21
+ const pkgPath = path.join(dir, "package.json");
22
+ if (fs.existsSync(pkgPath)) {
23
+ try {
24
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
25
+ if (pkg.name === "@tamer4lynx/cli" && typeof pkg.version === "string") {
26
+ return pkg.version;
27
+ }
28
+ } catch {
29
+ }
30
+ }
31
+ const next = path.dirname(dir);
32
+ if (next === dir) break;
33
+ dir = next;
34
+ }
35
+ return "0.0.0";
36
+ }
37
+
15
38
  // src/android/create.ts
16
- import fs4 from "fs";
17
- import path4 from "path";
39
+ import fs5 from "fs";
40
+ import path5 from "path";
18
41
  import os2 from "os";
19
42
 
20
43
  // src/android/getGradle.ts
21
- import fs from "fs";
22
- import path from "path";
44
+ import fs2 from "fs";
45
+ import path2 from "path";
23
46
  import os from "os";
24
47
  import { execSync } from "child_process";
25
48
  import fetch2 from "node-fetch";
26
49
  import AdmZip from "adm-zip";
27
50
  async function downloadGradle(gradleVersion) {
28
51
  const gradleBaseUrl = `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`;
29
- const downloadDir = path.join(process.cwd(), "gradle");
30
- const zipPath = path.join(downloadDir, `gradle-${gradleVersion}.zip`);
31
- const extractedPath = path.join(downloadDir, `gradle-${gradleVersion}`);
32
- if (fs.existsSync(extractedPath)) {
52
+ const downloadDir = path2.join(process.cwd(), "gradle");
53
+ const zipPath = path2.join(downloadDir, `gradle-${gradleVersion}.zip`);
54
+ const extractedPath = path2.join(downloadDir, `gradle-${gradleVersion}`);
55
+ if (fs2.existsSync(extractedPath)) {
33
56
  console.log(`\u2705 Gradle ${gradleVersion} already exists at ${extractedPath}. Skipping download.`);
34
57
  return;
35
58
  }
36
- if (!fs.existsSync(downloadDir)) {
37
- fs.mkdirSync(downloadDir, { recursive: true });
59
+ if (!fs2.existsSync(downloadDir)) {
60
+ fs2.mkdirSync(downloadDir, { recursive: true });
38
61
  }
39
62
  console.log(`\u{1F4E5} Downloading Gradle ${gradleVersion} from ${gradleBaseUrl}...`);
40
63
  const response = await fetch2(gradleBaseUrl);
41
64
  if (!response.ok || !response.body) {
42
65
  throw new Error(`Failed to download: ${response.statusText}`);
43
66
  }
44
- const fileStream = fs.createWriteStream(zipPath);
67
+ const fileStream = fs2.createWriteStream(zipPath);
45
68
  await new Promise((resolve, reject) => {
46
69
  response.body?.pipe(fileStream);
47
70
  response.body?.on("error", reject);
@@ -55,26 +78,26 @@ async function downloadGradle(gradleVersion) {
55
78
  console.error(`\u274C Failed to extract Gradle zip: ${err}`);
56
79
  throw err;
57
80
  }
58
- fs.unlinkSync(zipPath);
81
+ fs2.unlinkSync(zipPath);
59
82
  console.log(`\u2705 Gradle ${gradleVersion} extracted to ${extractedPath}`);
60
83
  }
61
84
  async function setupGradleWrapper(rootDir, gradleVersion) {
62
85
  try {
63
86
  console.log("\u{1F4E6} Setting up Gradle wrapper...");
64
87
  await downloadGradle(gradleVersion);
65
- const gradleBinDir = path.join(
88
+ const gradleBinDir = path2.join(
66
89
  process.cwd(),
67
90
  "gradle",
68
91
  `gradle-${gradleVersion}`,
69
92
  "bin"
70
93
  );
71
94
  const gradleExecutable = os.platform() === "win32" ? "gradle.bat" : "gradle";
72
- const gradleExecutablePath = path.join(gradleBinDir, gradleExecutable);
73
- if (!fs.existsSync(gradleExecutablePath)) {
95
+ const gradleExecutablePath = path2.join(gradleBinDir, gradleExecutable);
96
+ if (!fs2.existsSync(gradleExecutablePath)) {
74
97
  throw new Error(`Gradle executable not found at ${gradleExecutablePath}`);
75
98
  }
76
99
  if (os.platform() !== "win32") {
77
- fs.chmodSync(gradleExecutablePath, "755");
100
+ fs2.chmodSync(gradleExecutablePath, "755");
78
101
  }
79
102
  console.log(`\u{1F680} Executing Gradle wrapper in: ${rootDir}`);
80
103
  execSync(`"${gradleExecutablePath}" wrapper`, {
@@ -89,8 +112,8 @@ async function setupGradleWrapper(rootDir, gradleVersion) {
89
112
  }
90
113
 
91
114
  // src/common/hostConfig.ts
92
- import fs2 from "fs";
93
- import path2 from "path";
115
+ import fs3 from "fs";
116
+ import path3 from "path";
94
117
  var DEFAULT_ABI_FILTERS = ["armeabi-v7a", "arm64-v8a"];
95
118
  var TAMER_CONFIG = "tamer.config.json";
96
119
  var LYNX_CONFIG_FILES = ["lynx.config.ts", "lynx.config.js", "lynx.config.mjs"];
@@ -99,27 +122,27 @@ var DEFAULT_IOS_DIR = "ios";
99
122
  var DEFAULT_BUNDLE_FILE = "main.lynx.bundle";
100
123
  var DEFAULT_BUNDLE_ROOT = "dist";
101
124
  function findProjectRoot(start2) {
102
- let dir = path2.resolve(start2);
103
- const root = path2.parse(dir).root;
125
+ let dir = path3.resolve(start2);
126
+ const root = path3.parse(dir).root;
104
127
  while (dir !== root) {
105
- const p = path2.join(dir, TAMER_CONFIG);
106
- if (fs2.existsSync(p)) return dir;
107
- dir = path2.dirname(dir);
128
+ const p = path3.join(dir, TAMER_CONFIG);
129
+ if (fs3.existsSync(p)) return dir;
130
+ dir = path3.dirname(dir);
108
131
  }
109
132
  return start2;
110
133
  }
111
134
  function loadTamerConfig(cwd) {
112
- const p = path2.join(cwd, TAMER_CONFIG);
113
- if (!fs2.existsSync(p)) return null;
135
+ const p = path3.join(cwd, TAMER_CONFIG);
136
+ if (!fs3.existsSync(p)) return null;
114
137
  try {
115
- return JSON.parse(fs2.readFileSync(p, "utf8"));
138
+ return JSON.parse(fs3.readFileSync(p, "utf8"));
116
139
  } catch {
117
140
  return null;
118
141
  }
119
142
  }
120
143
  function extractDistPathRoot(configPath) {
121
144
  try {
122
- const content = fs2.readFileSync(configPath, "utf8");
145
+ const content = fs3.readFileSync(configPath, "utf8");
123
146
  const rootMatch = content.match(/distPath\s*:\s*\{\s*root\s*:\s*['"]([^'"]+)['"]/);
124
147
  if (rootMatch?.[1]) return rootMatch[1];
125
148
  const rootMatch2 = content.match(/root\s*:\s*['"]([^'"]+)['"]/);
@@ -130,16 +153,16 @@ function extractDistPathRoot(configPath) {
130
153
  }
131
154
  function findLynxConfigInDir(dir) {
132
155
  for (const name of LYNX_CONFIG_FILES) {
133
- const p = path2.join(dir, name);
134
- if (fs2.existsSync(p)) return p;
156
+ const p = path3.join(dir, name);
157
+ if (fs3.existsSync(p)) return p;
135
158
  }
136
159
  return null;
137
160
  }
138
161
  function hasRspeedy(pkgDir) {
139
- const pkgJsonPath = path2.join(pkgDir, "package.json");
140
- if (!fs2.existsSync(pkgJsonPath)) return false;
162
+ const pkgJsonPath = path3.join(pkgDir, "package.json");
163
+ if (!fs3.existsSync(pkgJsonPath)) return false;
141
164
  try {
142
- const pkg = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf8"));
165
+ const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
143
166
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
144
167
  return Object.keys(deps).some((k) => k === "@lynx-js/rspeedy");
145
168
  } catch {
@@ -148,17 +171,17 @@ function hasRspeedy(pkgDir) {
148
171
  }
149
172
  function discoverLynxProject(cwd, explicitPath) {
150
173
  if (explicitPath) {
151
- const resolved = path2.isAbsolute(explicitPath) ? explicitPath : path2.join(cwd, explicitPath);
152
- if (fs2.existsSync(resolved)) {
174
+ const resolved = path3.isAbsolute(explicitPath) ? explicitPath : path3.join(cwd, explicitPath);
175
+ if (fs3.existsSync(resolved)) {
153
176
  const lynxConfig2 = findLynxConfigInDir(resolved);
154
177
  const bundleRoot = lynxConfig2 ? extractDistPathRoot(lynxConfig2) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
155
178
  return { dir: resolved, bundleRoot };
156
179
  }
157
180
  }
158
- const rootPkgPath = path2.join(cwd, "package.json");
159
- if (fs2.existsSync(rootPkgPath)) {
181
+ const rootPkgPath = path3.join(cwd, "package.json");
182
+ if (fs3.existsSync(rootPkgPath)) {
160
183
  try {
161
- const rootPkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf8"));
184
+ const rootPkg = JSON.parse(fs3.readFileSync(rootPkgPath, "utf8"));
162
185
  const lynxConfig2 = findLynxConfigInDir(cwd);
163
186
  if (lynxConfig2 || (rootPkg.dependencies?.["@lynx-js/rspeedy"] || rootPkg.devDependencies?.["@lynx-js/rspeedy"])) {
164
187
  const bundleRoot = lynxConfig2 ? extractDistPathRoot(lynxConfig2) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
@@ -169,12 +192,12 @@ function discoverLynxProject(cwd, explicitPath) {
169
192
  for (const ws of workspaces) {
170
193
  const isGlob = typeof ws === "string" && ws.includes("*");
171
194
  const dirsToCheck = isGlob ? (() => {
172
- const parentDir = path2.join(cwd, ws.replace(/\/\*$/, ""));
173
- if (!fs2.existsSync(parentDir)) return [];
174
- return fs2.readdirSync(parentDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path2.join(parentDir, e.name));
175
- })() : [path2.join(cwd, ws)];
195
+ const parentDir = path3.join(cwd, ws.replace(/\/\*$/, ""));
196
+ if (!fs3.existsSync(parentDir)) return [];
197
+ return fs3.readdirSync(parentDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path3.join(parentDir, e.name));
198
+ })() : [path3.join(cwd, ws)];
176
199
  for (const pkgDir of dirsToCheck) {
177
- if (!fs2.existsSync(pkgDir)) continue;
200
+ if (!fs3.existsSync(pkgDir)) continue;
178
201
  const lynxConfig3 = findLynxConfigInDir(pkgDir);
179
202
  if (lynxConfig3 || hasRspeedy(pkgDir)) {
180
203
  const bundleRoot = lynxConfig3 ? extractDistPathRoot(lynxConfig3) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
@@ -194,30 +217,30 @@ function discoverLynxProject(cwd, explicitPath) {
194
217
  return null;
195
218
  }
196
219
  function findRepoRoot(start2) {
197
- let dir = path2.resolve(start2);
198
- const root = path2.parse(dir).root;
220
+ let dir = path3.resolve(start2);
221
+ const root = path3.parse(dir).root;
199
222
  while (dir !== root) {
200
- const pkgPath = path2.join(dir, "package.json");
201
- if (fs2.existsSync(pkgPath)) {
223
+ const pkgPath = path3.join(dir, "package.json");
224
+ if (fs3.existsSync(pkgPath)) {
202
225
  try {
203
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
226
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
204
227
  if (pkg.workspaces) return dir;
205
228
  } catch {
206
229
  }
207
230
  }
208
- dir = path2.dirname(dir);
231
+ dir = path3.dirname(dir);
209
232
  }
210
233
  return start2;
211
234
  }
212
235
  function findDevAppPackage(projectRoot) {
213
236
  const candidates = [
214
- path2.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-app"),
215
- path2.join(projectRoot, "node_modules", "tamer-dev-app"),
216
- path2.join(projectRoot, "packages", "tamer-dev-app"),
217
- path2.join(path2.dirname(projectRoot), "tamer-dev-app")
237
+ path3.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-app"),
238
+ path3.join(projectRoot, "node_modules", "tamer-dev-app"),
239
+ path3.join(projectRoot, "packages", "tamer-dev-app"),
240
+ path3.join(path3.dirname(projectRoot), "tamer-dev-app")
218
241
  ];
219
242
  for (const pkg of candidates) {
220
- if (fs2.existsSync(pkg) && fs2.existsSync(path2.join(pkg, "package.json"))) {
243
+ if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
221
244
  return pkg;
222
245
  }
223
246
  }
@@ -225,13 +248,13 @@ function findDevAppPackage(projectRoot) {
225
248
  }
226
249
  function findDevClientPackage(projectRoot) {
227
250
  const candidates = [
228
- path2.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client"),
229
- path2.join(projectRoot, "node_modules", "tamer-dev-client"),
230
- path2.join(projectRoot, "packages", "tamer-dev-client"),
231
- path2.join(path2.dirname(projectRoot), "tamer-dev-client")
251
+ path3.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client"),
252
+ path3.join(projectRoot, "node_modules", "tamer-dev-client"),
253
+ path3.join(projectRoot, "packages", "tamer-dev-client"),
254
+ path3.join(path3.dirname(projectRoot), "tamer-dev-client")
232
255
  ];
233
256
  for (const pkg of candidates) {
234
- if (fs2.existsSync(pkg) && fs2.existsSync(path2.join(pkg, "package.json"))) {
257
+ if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
235
258
  return pkg;
236
259
  }
237
260
  }
@@ -239,11 +262,11 @@ function findDevClientPackage(projectRoot) {
239
262
  }
240
263
  function findTamerHostPackage(projectRoot) {
241
264
  const candidates = [
242
- path2.join(projectRoot, "node_modules", "tamer-host"),
243
- path2.join(projectRoot, "packages", "tamer-host")
265
+ path3.join(projectRoot, "node_modules", "tamer-host"),
266
+ path3.join(projectRoot, "packages", "tamer-host")
244
267
  ];
245
268
  for (const pkg of candidates) {
246
- if (fs2.existsSync(pkg) && fs2.existsSync(path2.join(pkg, "package.json"))) {
269
+ if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
247
270
  return pkg;
248
271
  }
249
272
  }
@@ -261,18 +284,18 @@ function resolveHostPaths(cwd = process.cwd()) {
261
284
  const lynxProjectDir = discovered?.dir ?? cwd;
262
285
  const bundleRoot = paths.lynxBundleRoot ?? discovered?.bundleRoot ?? DEFAULT_BUNDLE_ROOT;
263
286
  const bundleFile = paths.lynxBundleFile ?? DEFAULT_BUNDLE_FILE;
264
- const lynxBundlePath = path2.join(lynxProjectDir, bundleRoot, bundleFile);
265
- const androidDir = path2.join(projectRoot, androidDirRel);
287
+ const lynxBundlePath = path3.join(lynxProjectDir, bundleRoot, bundleFile);
288
+ const androidDir = path3.join(projectRoot, androidDirRel);
266
289
  const devMode = resolveDevMode(config);
267
290
  const devClientPkg = findDevClientPackage(projectRoot);
268
- const devClientBundlePath = devClientPkg ? path2.join(devClientPkg, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : void 0;
291
+ const devClientBundlePath = devClientPkg ? path3.join(devClientPkg, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : void 0;
269
292
  return {
270
293
  projectRoot,
271
294
  androidDir,
272
- iosDir: path2.join(projectRoot, iosDirRel),
273
- androidAppDir: path2.join(projectRoot, androidDirRel, "app"),
274
- androidAssetsDir: path2.join(projectRoot, androidDirRel, "app", "src", "main", "assets"),
275
- androidKotlinDir: path2.join(projectRoot, androidDirRel, "app", "src", "main", "kotlin", packageName.replace(/\./g, "/")),
295
+ iosDir: path3.join(projectRoot, iosDirRel),
296
+ androidAppDir: path3.join(projectRoot, androidDirRel, "app"),
297
+ androidAssetsDir: path3.join(projectRoot, androidDirRel, "app", "src", "main", "assets"),
298
+ androidKotlinDir: path3.join(projectRoot, androidDirRel, "app", "src", "main", "kotlin", packageName.replace(/\./g, "/")),
276
299
  lynxProjectDir,
277
300
  lynxBundlePath,
278
301
  lynxBundleFile: bundleFile,
@@ -347,32 +370,32 @@ function normalizeAndroidAdaptiveColor(input) {
347
370
  function resolveIconPaths(projectRoot, config) {
348
371
  const raw = config.icon;
349
372
  if (!raw) return null;
350
- const join = (p) => path2.isAbsolute(p) ? p : path2.join(projectRoot, p);
373
+ const join = (p) => path3.isAbsolute(p) ? p : path3.join(projectRoot, p);
351
374
  if (typeof raw === "string") {
352
375
  const p = join(raw);
353
- return fs2.existsSync(p) ? { source: p } : null;
376
+ return fs3.existsSync(p) ? { source: p } : null;
354
377
  }
355
378
  const out = {};
356
379
  if (raw.source) {
357
380
  const p = join(raw.source);
358
- if (fs2.existsSync(p)) out.source = p;
381
+ if (fs3.existsSync(p)) out.source = p;
359
382
  }
360
383
  if (raw.android) {
361
384
  const p = join(raw.android);
362
- if (fs2.existsSync(p)) out.android = p;
385
+ if (fs3.existsSync(p)) out.android = p;
363
386
  }
364
387
  if (raw.ios) {
365
388
  const p = join(raw.ios);
366
- if (fs2.existsSync(p)) out.ios = p;
389
+ if (fs3.existsSync(p)) out.ios = p;
367
390
  }
368
391
  const ad = raw.androidAdaptive;
369
392
  if (ad?.foreground) {
370
393
  const fg = join(ad.foreground);
371
- if (fs2.existsSync(fg)) {
394
+ if (fs3.existsSync(fg)) {
372
395
  let usedAdaptive = false;
373
396
  if (ad.background) {
374
397
  const bg = join(ad.background);
375
- if (fs2.existsSync(bg)) {
398
+ if (fs3.existsSync(bg)) {
376
399
  out.androidAdaptiveForeground = fg;
377
400
  out.androidAdaptiveBackground = bg;
378
401
  usedAdaptive = true;
@@ -395,19 +418,19 @@ function resolveIconPaths(projectRoot, config) {
395
418
  }
396
419
 
397
420
  // src/common/syncAppIcons.ts
398
- import fs3 from "fs";
399
- import path3 from "path";
421
+ import fs4 from "fs";
422
+ import path4 from "path";
400
423
  function purgeAdaptiveForegroundArtifacts(drawableDir) {
401
424
  for (const base of ["ic_launcher_fg_src", "ic_launcher_fg_bm", "ic_launcher_fg_sc"]) {
402
425
  for (const ext of [".png", ".webp", ".jpg", ".jpeg", ".xml"]) {
403
426
  try {
404
- fs3.unlinkSync(path3.join(drawableDir, base + ext));
427
+ fs4.unlinkSync(path4.join(drawableDir, base + ext));
405
428
  } catch {
406
429
  }
407
430
  }
408
431
  }
409
432
  try {
410
- fs3.unlinkSync(path3.join(drawableDir, "ic_launcher_foreground.xml"));
433
+ fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_foreground.xml"));
411
434
  } catch {
412
435
  }
413
436
  }
@@ -420,15 +443,15 @@ function writeAdaptiveForegroundLayer(drawableDir, fgSourcePath, fgExt, layout)
420
443
  purgeAdaptiveForegroundArtifacts(drawableDir);
421
444
  for (const ext of [".png", ".webp", ".jpg", ".jpeg"]) {
422
445
  try {
423
- fs3.unlinkSync(path3.join(drawableDir, `ic_launcher_foreground${ext}`));
446
+ fs4.unlinkSync(path4.join(drawableDir, `ic_launcher_foreground${ext}`));
424
447
  } catch {
425
448
  }
426
449
  }
427
450
  if (!layout) {
428
- fs3.copyFileSync(fgSourcePath, path3.join(drawableDir, `ic_launcher_foreground${fgExt}`));
451
+ fs4.copyFileSync(fgSourcePath, path4.join(drawableDir, `ic_launcher_foreground${fgExt}`));
429
452
  return;
430
453
  }
431
- fs3.copyFileSync(fgSourcePath, path3.join(drawableDir, `ic_launcher_fg_src${fgExt}`));
454
+ fs4.copyFileSync(fgSourcePath, path4.join(drawableDir, `ic_launcher_fg_src${fgExt}`));
432
455
  const CANVAS_DP = 108;
433
456
  const scale = layout.scale ?? 1;
434
457
  const shrinkDp = (1 - scale) / 2 * CANVAS_DP;
@@ -452,11 +475,11 @@ function writeAdaptiveForegroundLayer(drawableDir, fgSourcePath, fgExt, layout)
452
475
  android:bottom="${insetBottom}dp"
453
476
  android:drawable="@drawable/ic_launcher_fg_src" />
454
477
  </layer-list>`;
455
- fs3.writeFileSync(path3.join(drawableDir, "ic_launcher_foreground.xml"), layerXml);
478
+ fs4.writeFileSync(path4.join(drawableDir, "ic_launcher_foreground.xml"), layerXml);
456
479
  }
457
480
  function ensureAndroidManifestLauncherIcon(manifestPath) {
458
- if (!fs3.existsSync(manifestPath)) return;
459
- let content = fs3.readFileSync(manifestPath, "utf8");
481
+ if (!fs4.existsSync(manifestPath)) return;
482
+ let content = fs4.readFileSync(manifestPath, "utf8");
460
483
  if (content.includes("android:icon=")) return;
461
484
  const next = content.replace(/<application(\s[^>]*)>/, (full, attrs) => {
462
485
  if (String(attrs).includes("android:icon")) return full;
@@ -465,30 +488,30 @@ function ensureAndroidManifestLauncherIcon(manifestPath) {
465
488
  android:roundIcon="@mipmap/ic_launcher">`;
466
489
  });
467
490
  if (next !== content) {
468
- fs3.writeFileSync(manifestPath, next, "utf8");
491
+ fs4.writeFileSync(manifestPath, next, "utf8");
469
492
  console.log("\u2705 Added android:icon / roundIcon to AndroidManifest.xml");
470
493
  }
471
494
  }
472
495
  function applyAndroidLauncherIcons(resDir, iconPaths) {
473
496
  if (!iconPaths) return false;
474
- fs3.mkdirSync(resDir, { recursive: true });
497
+ fs4.mkdirSync(resDir, { recursive: true });
475
498
  const fgAd = iconPaths.androidAdaptiveForeground;
476
499
  const bgAd = iconPaths.androidAdaptiveBackground;
477
500
  const bgColor = iconPaths.androidAdaptiveBackgroundColor;
478
501
  if (fgAd && (bgAd || bgColor)) {
479
- const drawableDir = path3.join(resDir, "drawable");
480
- fs3.mkdirSync(drawableDir, { recursive: true });
481
- const fgExt = path3.extname(fgAd) || ".png";
502
+ const drawableDir = path4.join(resDir, "drawable");
503
+ fs4.mkdirSync(drawableDir, { recursive: true });
504
+ const fgExt = path4.extname(fgAd) || ".png";
482
505
  writeAdaptiveForegroundLayer(drawableDir, fgAd, fgExt, iconPaths.androidAdaptiveForegroundLayout);
483
506
  if (bgColor) {
484
507
  for (const ext of [".png", ".webp", ".jpg", ".jpeg"]) {
485
508
  try {
486
- fs3.unlinkSync(path3.join(drawableDir, `ic_launcher_background${ext}`));
509
+ fs4.unlinkSync(path4.join(drawableDir, `ic_launcher_background${ext}`));
487
510
  } catch {
488
511
  }
489
512
  }
490
513
  try {
491
- fs3.unlinkSync(path3.join(drawableDir, "ic_launcher_background.xml"));
514
+ fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_background.xml"));
492
515
  } catch {
493
516
  }
494
517
  const shapeXml = `<?xml version="1.0" encoding="utf-8"?>
@@ -496,44 +519,44 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
496
519
  <solid android:color="${bgColor}" />
497
520
  </shape>
498
521
  `;
499
- fs3.writeFileSync(path3.join(drawableDir, "ic_launcher_background.xml"), shapeXml);
522
+ fs4.writeFileSync(path4.join(drawableDir, "ic_launcher_background.xml"), shapeXml);
500
523
  } else {
501
524
  try {
502
- fs3.unlinkSync(path3.join(drawableDir, "ic_launcher_background.xml"));
525
+ fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_background.xml"));
503
526
  } catch {
504
527
  }
505
- const bgExt = path3.extname(bgAd) || ".png";
506
- fs3.copyFileSync(bgAd, path3.join(drawableDir, `ic_launcher_background${bgExt}`));
528
+ const bgExt = path4.extname(bgAd) || ".png";
529
+ fs4.copyFileSync(bgAd, path4.join(drawableDir, `ic_launcher_background${bgExt}`));
507
530
  }
508
- const anyDpi = path3.join(resDir, "mipmap-anydpi-v26");
509
- fs3.mkdirSync(anyDpi, { recursive: true });
531
+ const anyDpi = path4.join(resDir, "mipmap-anydpi-v26");
532
+ fs4.mkdirSync(anyDpi, { recursive: true });
510
533
  const adaptiveXml = `<?xml version="1.0" encoding="utf-8"?>
511
534
  <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
512
535
  <background android:drawable="@drawable/ic_launcher_background" />
513
536
  <foreground android:drawable="@drawable/ic_launcher_foreground" />
514
537
  </adaptive-icon>
515
538
  `;
516
- fs3.writeFileSync(path3.join(anyDpi, "ic_launcher.xml"), adaptiveXml);
517
- fs3.writeFileSync(path3.join(anyDpi, "ic_launcher_round.xml"), adaptiveXml);
539
+ fs4.writeFileSync(path4.join(anyDpi, "ic_launcher.xml"), adaptiveXml);
540
+ fs4.writeFileSync(path4.join(anyDpi, "ic_launcher_round.xml"), adaptiveXml);
518
541
  const legacySrc = iconPaths.source ?? fgAd;
519
- const legacyExt = path3.extname(legacySrc) || ".png";
542
+ const legacyExt = path4.extname(legacySrc) || ".png";
520
543
  const mipmapDensities = ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"];
521
544
  for (const d of mipmapDensities) {
522
- const dir = path3.join(resDir, `mipmap-${d}`);
523
- fs3.mkdirSync(dir, { recursive: true });
524
- fs3.copyFileSync(legacySrc, path3.join(dir, `ic_launcher${legacyExt}`));
545
+ const dir = path4.join(resDir, `mipmap-${d}`);
546
+ fs4.mkdirSync(dir, { recursive: true });
547
+ fs4.copyFileSync(legacySrc, path4.join(dir, `ic_launcher${legacyExt}`));
525
548
  }
526
549
  return true;
527
550
  }
528
551
  if (iconPaths.android) {
529
552
  const src = iconPaths.android;
530
- const entries = fs3.readdirSync(src, { withFileTypes: true });
553
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
531
554
  for (const e of entries) {
532
- const dest = path3.join(resDir, e.name);
555
+ const dest = path4.join(resDir, e.name);
533
556
  if (e.isDirectory()) {
534
- fs3.cpSync(path3.join(src, e.name), dest, { recursive: true });
557
+ fs4.cpSync(path4.join(src, e.name), dest, { recursive: true });
535
558
  } else {
536
- fs3.copyFileSync(path3.join(src, e.name), dest);
559
+ fs4.copyFileSync(path4.join(src, e.name), dest);
537
560
  }
538
561
  }
539
562
  return true;
@@ -541,9 +564,9 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
541
564
  if (iconPaths.source) {
542
565
  const mipmapDensities = ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"];
543
566
  for (const d of mipmapDensities) {
544
- const dir = path3.join(resDir, `mipmap-${d}`);
545
- fs3.mkdirSync(dir, { recursive: true });
546
- fs3.copyFileSync(iconPaths.source, path3.join(dir, "ic_launcher.png"));
567
+ const dir = path4.join(resDir, `mipmap-${d}`);
568
+ fs4.mkdirSync(dir, { recursive: true });
569
+ fs4.copyFileSync(iconPaths.source, path4.join(dir, "ic_launcher.png"));
547
570
  }
548
571
  return true;
549
572
  }
@@ -551,25 +574,25 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
551
574
  }
552
575
  function applyIosAppIconAssets(appIconDir, iconPaths) {
553
576
  if (!iconPaths) return false;
554
- fs3.mkdirSync(appIconDir, { recursive: true });
577
+ fs4.mkdirSync(appIconDir, { recursive: true });
555
578
  if (iconPaths.ios) {
556
- const entries = fs3.readdirSync(iconPaths.ios, { withFileTypes: true });
579
+ const entries = fs4.readdirSync(iconPaths.ios, { withFileTypes: true });
557
580
  for (const e of entries) {
558
- const dest = path3.join(appIconDir, e.name);
581
+ const dest = path4.join(appIconDir, e.name);
559
582
  if (e.isDirectory()) {
560
- fs3.cpSync(path3.join(iconPaths.ios, e.name), dest, { recursive: true });
583
+ fs4.cpSync(path4.join(iconPaths.ios, e.name), dest, { recursive: true });
561
584
  } else {
562
- fs3.copyFileSync(path3.join(iconPaths.ios, e.name), dest);
585
+ fs4.copyFileSync(path4.join(iconPaths.ios, e.name), dest);
563
586
  }
564
587
  }
565
588
  return true;
566
589
  }
567
590
  if (iconPaths.source) {
568
- const ext = path3.extname(iconPaths.source) || ".png";
591
+ const ext = path4.extname(iconPaths.source) || ".png";
569
592
  const icon1024 = `Icon-1024${ext}`;
570
- fs3.copyFileSync(iconPaths.source, path3.join(appIconDir, icon1024));
571
- fs3.writeFileSync(
572
- path3.join(appIconDir, "Contents.json"),
593
+ fs4.copyFileSync(iconPaths.source, path4.join(appIconDir, icon1024));
594
+ fs4.writeFileSync(
595
+ path4.join(appIconDir, "Contents.json"),
573
596
  JSON.stringify(
574
597
  {
575
598
  images: [{ filename: icon1024, idiom: "universal", platform: "ios", size: "1024x1024" }],
@@ -1121,7 +1144,7 @@ object DevServerPrefs {
1121
1144
 
1122
1145
  // src/android/create.ts
1123
1146
  function readAndSubstituteTemplate(templatePath, vars) {
1124
- const raw = fs4.readFileSync(templatePath, "utf-8");
1147
+ const raw = fs5.readFileSync(templatePath, "utf-8");
1125
1148
  return Object.entries(vars).reduce(
1126
1149
  (s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
1127
1150
  raw
@@ -1132,7 +1155,7 @@ var create = async (opts = {}) => {
1132
1155
  const origCwd = process.cwd();
1133
1156
  if (target === "dev-app") {
1134
1157
  const devAppDir = findDevAppPackage(origCwd) ?? findDevAppPackage(findRepoRoot(origCwd));
1135
- if (!devAppDir || !fs4.existsSync(path4.join(devAppDir, "tamer.config.json"))) {
1158
+ if (!devAppDir || !fs5.existsSync(path5.join(devAppDir, "tamer.config.json"))) {
1136
1159
  console.error("\u274C tamer-dev-app not found. Add @tamer4lynx/tamer-dev-app to dependencies.");
1137
1160
  process.exit(1);
1138
1161
  }
@@ -1162,30 +1185,30 @@ var create = async (opts = {}) => {
1162
1185
  const packagePath = packageName.replace(/\./g, "/");
1163
1186
  const gradleVersion = "8.14.2";
1164
1187
  const androidDir = config.paths?.androidDir ?? "android";
1165
- const rootDir = path4.join(process.cwd(), androidDir);
1166
- const appDir = path4.join(rootDir, "app");
1167
- const mainDir = path4.join(appDir, "src", "main");
1168
- const javaDir = path4.join(mainDir, "java", packagePath);
1169
- const kotlinDir = path4.join(mainDir, "kotlin", packagePath);
1170
- const kotlinGeneratedDir = path4.join(kotlinDir, "generated");
1171
- const assetsDir = path4.join(mainDir, "assets");
1172
- const themesDir = path4.join(mainDir, "res", "values");
1173
- const gradleDir = path4.join(rootDir, "gradle");
1188
+ const rootDir = path5.join(process.cwd(), androidDir);
1189
+ const appDir = path5.join(rootDir, "app");
1190
+ const mainDir = path5.join(appDir, "src", "main");
1191
+ const javaDir = path5.join(mainDir, "java", packagePath);
1192
+ const kotlinDir = path5.join(mainDir, "kotlin", packagePath);
1193
+ const kotlinGeneratedDir = path5.join(kotlinDir, "generated");
1194
+ const assetsDir = path5.join(mainDir, "assets");
1195
+ const themesDir = path5.join(mainDir, "res", "values");
1196
+ const gradleDir = path5.join(rootDir, "gradle");
1174
1197
  function writeFile2(filePath, content, options) {
1175
- fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
1176
- fs4.writeFileSync(
1198
+ fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
1199
+ fs5.writeFileSync(
1177
1200
  filePath,
1178
1201
  content.trimStart(),
1179
1202
  options?.encoding ?? "utf8"
1180
1203
  );
1181
1204
  }
1182
- if (fs4.existsSync(rootDir)) {
1205
+ if (fs5.existsSync(rootDir)) {
1183
1206
  console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
1184
- fs4.rmSync(rootDir, { recursive: true, force: true });
1207
+ fs5.rmSync(rootDir, { recursive: true, force: true });
1185
1208
  }
1186
1209
  console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
1187
1210
  writeFile2(
1188
- path4.join(gradleDir, "libs.versions.toml"),
1211
+ path5.join(gradleDir, "libs.versions.toml"),
1189
1212
  `
1190
1213
  [versions]
1191
1214
  agp = "8.9.1"
@@ -1246,7 +1269,7 @@ kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
1246
1269
  `
1247
1270
  );
1248
1271
  writeFile2(
1249
- path4.join(rootDir, "settings.gradle.kts"),
1272
+ path5.join(rootDir, "settings.gradle.kts"),
1250
1273
  `
1251
1274
  pluginManagement {
1252
1275
  repositories {
@@ -1280,7 +1303,7 @@ println("If you have native modules please run tamer android link")
1280
1303
  `
1281
1304
  );
1282
1305
  writeFile2(
1283
- path4.join(rootDir, "build.gradle.kts"),
1306
+ path5.join(rootDir, "build.gradle.kts"),
1284
1307
  `
1285
1308
  // Top-level build file where you can add configuration options common to all sub-projects/modules.
1286
1309
  plugins {
@@ -1292,7 +1315,7 @@ plugins {
1292
1315
  `
1293
1316
  );
1294
1317
  writeFile2(
1295
- path4.join(rootDir, "gradle.properties"),
1318
+ path5.join(rootDir, "gradle.properties"),
1296
1319
  `
1297
1320
  org.gradle.jvmargs=-Xmx2048m
1298
1321
  android.useAndroidX=true
@@ -1301,7 +1324,7 @@ android.enableJetifier=true
1301
1324
  `
1302
1325
  );
1303
1326
  writeFile2(
1304
- path4.join(appDir, "build.gradle.kts"),
1327
+ path5.join(appDir, "build.gradle.kts"),
1305
1328
  `
1306
1329
  plugins {
1307
1330
  alias(libs.plugins.android.application)
@@ -1393,7 +1416,7 @@ dependencies {
1393
1416
  `
1394
1417
  );
1395
1418
  writeFile2(
1396
- path4.join(themesDir, "themes.xml"),
1419
+ path5.join(themesDir, "themes.xml"),
1397
1420
  `
1398
1421
  <resources>
1399
1422
  <style name="Theme.MyApp" parent="Theme.AppCompat.Light.NoActionBar">
@@ -1427,7 +1450,7 @@ dependencies {
1427
1450
  const iconPaths = resolveIconPaths(process.cwd(), config);
1428
1451
  const manifestIconAttrs = iconPaths ? ' android:icon="@mipmap/ic_launcher"\n android:roundIcon="@mipmap/ic_launcher"\n' : "";
1429
1452
  writeFile2(
1430
- path4.join(mainDir, "AndroidManifest.xml"),
1453
+ path5.join(mainDir, "AndroidManifest.xml"),
1431
1454
  `
1432
1455
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
1433
1456
  ${manifestPermissions}
@@ -1441,7 +1464,7 @@ ${manifestIconAttrs} android:usesCleartextTraffic="true"
1441
1464
  `
1442
1465
  );
1443
1466
  writeFile2(
1444
- path4.join(kotlinGeneratedDir, "GeneratedLynxExtensions.kt"),
1467
+ path5.join(kotlinGeneratedDir, "GeneratedLynxExtensions.kt"),
1445
1468
  `
1446
1469
  package ${packageName}.generated
1447
1470
 
@@ -1469,14 +1492,14 @@ object GeneratedLynxExtensions {
1469
1492
  const hostPkg = findTamerHostPackage(process.cwd());
1470
1493
  const devClientPkg = findDevClientPackage(process.cwd());
1471
1494
  if (!hasDevLauncher && hostPkg) {
1472
- const templateDir = path4.join(hostPkg, "android", "templates");
1495
+ const templateDir = path5.join(hostPkg, "android", "templates");
1473
1496
  for (const [src, dst] of [
1474
- ["App.java", path4.join(javaDir, "App.java")],
1475
- ["TemplateProvider.java", path4.join(javaDir, "TemplateProvider.java")],
1476
- ["MainActivity.kt", path4.join(kotlinDir, "MainActivity.kt")]
1497
+ ["App.java", path5.join(javaDir, "App.java")],
1498
+ ["TemplateProvider.java", path5.join(javaDir, "TemplateProvider.java")],
1499
+ ["MainActivity.kt", path5.join(kotlinDir, "MainActivity.kt")]
1477
1500
  ]) {
1478
- const srcPath = path4.join(templateDir, src);
1479
- if (fs4.existsSync(srcPath)) {
1501
+ const srcPath = path5.join(templateDir, src);
1502
+ if (fs5.existsSync(srcPath)) {
1480
1503
  writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
1481
1504
  }
1482
1505
  }
@@ -1485,34 +1508,34 @@ object GeneratedLynxExtensions {
1485
1508
  fetchAndPatchApplication(vars),
1486
1509
  fetchAndPatchTemplateProvider(vars)
1487
1510
  ]);
1488
- writeFile2(path4.join(javaDir, "App.java"), applicationSource);
1489
- writeFile2(path4.join(javaDir, "TemplateProvider.java"), templateProviderSource);
1490
- writeFile2(path4.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
1511
+ writeFile2(path5.join(javaDir, "App.java"), applicationSource);
1512
+ writeFile2(path5.join(javaDir, "TemplateProvider.java"), templateProviderSource);
1513
+ writeFile2(path5.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
1491
1514
  if (hasDevLauncher) {
1492
1515
  if (devClientPkg) {
1493
- const templateDir = path4.join(devClientPkg, "android", "templates");
1516
+ const templateDir = path5.join(devClientPkg, "android", "templates");
1494
1517
  for (const [src, dst] of [
1495
- ["ProjectActivity.kt", path4.join(kotlinDir, "ProjectActivity.kt")],
1496
- ["DevClientManager.kt", path4.join(kotlinDir, "DevClientManager.kt")],
1497
- ["DevServerPrefs.kt", path4.join(kotlinDir, "DevServerPrefs.kt")]
1518
+ ["ProjectActivity.kt", path5.join(kotlinDir, "ProjectActivity.kt")],
1519
+ ["DevClientManager.kt", path5.join(kotlinDir, "DevClientManager.kt")],
1520
+ ["DevServerPrefs.kt", path5.join(kotlinDir, "DevServerPrefs.kt")]
1498
1521
  ]) {
1499
- const srcPath = path4.join(templateDir, src);
1500
- if (fs4.existsSync(srcPath)) {
1522
+ const srcPath = path5.join(templateDir, src);
1523
+ if (fs5.existsSync(srcPath)) {
1501
1524
  writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
1502
1525
  }
1503
1526
  }
1504
1527
  } else {
1505
- writeFile2(path4.join(kotlinDir, "ProjectActivity.kt"), getProjectActivity(vars));
1528
+ writeFile2(path5.join(kotlinDir, "ProjectActivity.kt"), getProjectActivity(vars));
1506
1529
  const devClientManagerSource = getDevClientManager(vars);
1507
1530
  if (devClientManagerSource) {
1508
- writeFile2(path4.join(kotlinDir, "DevClientManager.kt"), devClientManagerSource);
1509
- writeFile2(path4.join(kotlinDir, "DevServerPrefs.kt"), getDevServerPrefs(vars));
1531
+ writeFile2(path5.join(kotlinDir, "DevClientManager.kt"), devClientManagerSource);
1532
+ writeFile2(path5.join(kotlinDir, "DevServerPrefs.kt"), getDevServerPrefs(vars));
1510
1533
  }
1511
1534
  }
1512
1535
  }
1513
1536
  }
1514
1537
  if (iconPaths) {
1515
- const resDir = path4.join(mainDir, "res");
1538
+ const resDir = path5.join(mainDir, "res");
1516
1539
  if (applyAndroidLauncherIcons(resDir, iconPaths)) {
1517
1540
  if (iconPaths.androidAdaptiveForeground && (iconPaths.androidAdaptiveBackground || iconPaths.androidAdaptiveBackgroundColor)) {
1518
1541
  console.log("\u2705 Android adaptive launcher from tamer.config.json icon.androidAdaptive");
@@ -1523,23 +1546,23 @@ object GeneratedLynxExtensions {
1523
1546
  }
1524
1547
  }
1525
1548
  }
1526
- fs4.mkdirSync(assetsDir, { recursive: true });
1527
- fs4.writeFileSync(path4.join(assetsDir, ".gitkeep"), "");
1549
+ fs5.mkdirSync(assetsDir, { recursive: true });
1550
+ fs5.writeFileSync(path5.join(assetsDir, ".gitkeep"), "");
1528
1551
  console.log(`\u2705 Android Kotlin project created at ${rootDir}`);
1529
1552
  async function finalizeProjectSetup() {
1530
1553
  if (androidSdk) {
1531
1554
  try {
1532
1555
  const sdkDirContent = `sdk.dir=${androidSdk.replace(/\\/g, "/")}`;
1533
- writeFile2(path4.join(rootDir, "local.properties"), sdkDirContent);
1556
+ writeFile2(path5.join(rootDir, "local.properties"), sdkDirContent);
1534
1557
  console.log("\u{1F4E6} Created local.properties from tamer.config.json.");
1535
1558
  } catch (err) {
1536
1559
  console.error(`\u274C Failed to create local.properties: ${err.message}`);
1537
1560
  }
1538
1561
  } else {
1539
- const localPropsPath = path4.join(process.cwd(), "local.properties");
1540
- if (fs4.existsSync(localPropsPath)) {
1562
+ const localPropsPath = path5.join(process.cwd(), "local.properties");
1563
+ if (fs5.existsSync(localPropsPath)) {
1541
1564
  try {
1542
- fs4.copyFileSync(localPropsPath, path4.join(rootDir, "local.properties"));
1565
+ fs5.copyFileSync(localPropsPath, path5.join(rootDir, "local.properties"));
1543
1566
  console.log("\u{1F4E6} Copied existing local.properties to the android project.");
1544
1567
  } catch (err) {
1545
1568
  console.error("\u274C Failed to copy local.properties:", err);
@@ -1558,33 +1581,33 @@ object GeneratedLynxExtensions {
1558
1581
  var create_default = create;
1559
1582
 
1560
1583
  // src/android/autolink.ts
1561
- import fs7 from "fs";
1562
- import path7 from "path";
1584
+ import fs8 from "fs";
1585
+ import path8 from "path";
1563
1586
  import { execSync as execSync2 } from "child_process";
1564
1587
 
1565
1588
  // src/common/discoverModules.ts
1566
- import fs6 from "fs";
1567
- import path6 from "path";
1589
+ import fs7 from "fs";
1590
+ import path7 from "path";
1568
1591
 
1569
1592
  // src/common/config.ts
1570
- import fs5 from "fs";
1571
- import path5 from "path";
1593
+ import fs6 from "fs";
1594
+ import path6 from "path";
1572
1595
  var LYNX_EXT_JSON = "lynx.ext.json";
1573
1596
  var TAMER_JSON = "tamer.json";
1574
1597
  function loadLynxExtJson(packagePath) {
1575
- const p = path5.join(packagePath, LYNX_EXT_JSON);
1576
- if (!fs5.existsSync(p)) return null;
1598
+ const p = path6.join(packagePath, LYNX_EXT_JSON);
1599
+ if (!fs6.existsSync(p)) return null;
1577
1600
  try {
1578
- return JSON.parse(fs5.readFileSync(p, "utf8"));
1601
+ return JSON.parse(fs6.readFileSync(p, "utf8"));
1579
1602
  } catch {
1580
1603
  return null;
1581
1604
  }
1582
1605
  }
1583
1606
  function loadTamerJson(packagePath) {
1584
- const p = path5.join(packagePath, TAMER_JSON);
1585
- if (!fs5.existsSync(p)) return null;
1607
+ const p = path6.join(packagePath, TAMER_JSON);
1608
+ if (!fs6.existsSync(p)) return null;
1586
1609
  try {
1587
- return JSON.parse(fs5.readFileSync(p, "utf8"));
1610
+ return JSON.parse(fs6.readFileSync(p, "utf8"));
1588
1611
  } catch {
1589
1612
  return null;
1590
1613
  }
@@ -1637,7 +1660,7 @@ function loadExtensionConfig(packagePath) {
1637
1660
  return normalized;
1638
1661
  }
1639
1662
  function hasExtensionConfig(packagePath) {
1640
- return fs5.existsSync(path5.join(packagePath, LYNX_EXT_JSON)) || fs5.existsSync(path5.join(packagePath, TAMER_JSON));
1663
+ return fs6.existsSync(path6.join(packagePath, LYNX_EXT_JSON)) || fs6.existsSync(path6.join(packagePath, TAMER_JSON));
1641
1664
  }
1642
1665
  function getAndroidModuleClassNames(config) {
1643
1666
  if (!config) return [];
@@ -1655,15 +1678,15 @@ function getIosElements(config) {
1655
1678
  return config?.elements ?? {};
1656
1679
  }
1657
1680
  function getNodeModulesPath(projectRoot) {
1658
- let nodeModulesPath = path5.join(projectRoot, "node_modules");
1659
- const workspaceRoot = path5.join(projectRoot, "..", "..");
1660
- const rootNodeModules = path5.join(workspaceRoot, "node_modules");
1661
- if (fs5.existsSync(path5.join(workspaceRoot, "package.json")) && fs5.existsSync(rootNodeModules) && path5.basename(path5.dirname(projectRoot)) === "packages") {
1681
+ let nodeModulesPath = path6.join(projectRoot, "node_modules");
1682
+ const workspaceRoot = path6.join(projectRoot, "..", "..");
1683
+ const rootNodeModules = path6.join(workspaceRoot, "node_modules");
1684
+ if (fs6.existsSync(path6.join(workspaceRoot, "package.json")) && fs6.existsSync(rootNodeModules) && path6.basename(path6.dirname(projectRoot)) === "packages") {
1662
1685
  nodeModulesPath = rootNodeModules;
1663
- } else if (!fs5.existsSync(nodeModulesPath)) {
1664
- const altRoot = path5.join(projectRoot, "..", "..");
1665
- const altNodeModules = path5.join(altRoot, "node_modules");
1666
- if (fs5.existsSync(path5.join(altRoot, "package.json")) && fs5.existsSync(altNodeModules)) {
1686
+ } else if (!fs6.existsSync(nodeModulesPath)) {
1687
+ const altRoot = path6.join(projectRoot, "..", "..");
1688
+ const altNodeModules = path6.join(altRoot, "node_modules");
1689
+ if (fs6.existsSync(path6.join(altRoot, "package.json")) && fs6.existsSync(altNodeModules)) {
1667
1690
  nodeModulesPath = altNodeModules;
1668
1691
  }
1669
1692
  }
@@ -1672,8 +1695,8 @@ function getNodeModulesPath(projectRoot) {
1672
1695
  function discoverNativeExtensions(projectRoot) {
1673
1696
  const nodeModulesPath = getNodeModulesPath(projectRoot);
1674
1697
  const result = [];
1675
- if (!fs5.existsSync(nodeModulesPath)) return result;
1676
- const packageDirs = fs5.readdirSync(nodeModulesPath);
1698
+ if (!fs6.existsSync(nodeModulesPath)) return result;
1699
+ const packageDirs = fs6.readdirSync(nodeModulesPath);
1677
1700
  const check = (name, packagePath) => {
1678
1701
  if (!hasExtensionConfig(packagePath)) return;
1679
1702
  const config = loadExtensionConfig(packagePath);
@@ -1683,11 +1706,11 @@ function discoverNativeExtensions(projectRoot) {
1683
1706
  }
1684
1707
  };
1685
1708
  for (const dirName of packageDirs) {
1686
- const fullPath = path5.join(nodeModulesPath, dirName);
1709
+ const fullPath = path6.join(nodeModulesPath, dirName);
1687
1710
  if (dirName.startsWith("@")) {
1688
1711
  try {
1689
- for (const scopedDirName of fs5.readdirSync(fullPath)) {
1690
- check(`${dirName}/${scopedDirName}`, path5.join(fullPath, scopedDirName));
1712
+ for (const scopedDirName of fs6.readdirSync(fullPath)) {
1713
+ check(`${dirName}/${scopedDirName}`, path6.join(fullPath, scopedDirName));
1691
1714
  }
1692
1715
  } catch {
1693
1716
  }
@@ -1700,29 +1723,80 @@ function discoverNativeExtensions(projectRoot) {
1700
1723
 
1701
1724
  // src/common/discoverModules.ts
1702
1725
  function resolveNodeModulesPath(projectRoot) {
1703
- let nodeModulesPath = path6.join(projectRoot, "node_modules");
1704
- const workspaceRoot = path6.join(projectRoot, "..", "..");
1705
- const rootNodeModules = path6.join(workspaceRoot, "node_modules");
1706
- if (fs6.existsSync(path6.join(workspaceRoot, "package.json")) && fs6.existsSync(rootNodeModules) && path6.basename(path6.dirname(projectRoot)) === "packages") {
1726
+ let nodeModulesPath = path7.join(projectRoot, "node_modules");
1727
+ const workspaceRoot = path7.join(projectRoot, "..", "..");
1728
+ const rootNodeModules = path7.join(workspaceRoot, "node_modules");
1729
+ if (fs7.existsSync(path7.join(workspaceRoot, "package.json")) && fs7.existsSync(rootNodeModules) && path7.basename(path7.dirname(projectRoot)) === "packages") {
1707
1730
  nodeModulesPath = rootNodeModules;
1708
- } else if (!fs6.existsSync(nodeModulesPath)) {
1709
- const altRoot = path6.join(projectRoot, "..", "..");
1710
- const altNodeModules = path6.join(altRoot, "node_modules");
1711
- if (fs6.existsSync(path6.join(altRoot, "package.json")) && fs6.existsSync(altNodeModules)) {
1731
+ } else if (!fs7.existsSync(nodeModulesPath)) {
1732
+ const altRoot = path7.join(projectRoot, "..", "..");
1733
+ const altNodeModules = path7.join(altRoot, "node_modules");
1734
+ if (fs7.existsSync(path7.join(altRoot, "package.json")) && fs7.existsSync(altNodeModules)) {
1712
1735
  nodeModulesPath = altNodeModules;
1713
1736
  }
1714
1737
  }
1715
1738
  return nodeModulesPath;
1716
1739
  }
1740
+ function resolvePackageRoot(projectRoot, packageName) {
1741
+ const nodeModulesPath = resolveNodeModulesPath(projectRoot);
1742
+ const hasPkg = (dir) => fs7.existsSync(path7.join(dir, "package.json"));
1743
+ if (packageName.startsWith("@")) {
1744
+ const parts = packageName.split("/");
1745
+ if (parts.length !== 2 || !parts[0].startsWith("@")) return null;
1746
+ const direct2 = path7.join(nodeModulesPath, parts[0], parts[1]);
1747
+ if (hasPkg(direct2)) return direct2;
1748
+ const underDevClient = path7.join(
1749
+ nodeModulesPath,
1750
+ "@tamer4lynx",
1751
+ "tamer-dev-client",
1752
+ "node_modules",
1753
+ parts[0],
1754
+ parts[1]
1755
+ );
1756
+ if (hasPkg(underDevClient)) return underDevClient;
1757
+ return null;
1758
+ }
1759
+ const direct = path7.join(nodeModulesPath, packageName);
1760
+ return hasPkg(direct) ? direct : null;
1761
+ }
1762
+ function collectTransitiveTamer4LynxPackageNames(projectRoot) {
1763
+ const hostPjPath = path7.join(projectRoot, "package.json");
1764
+ if (!fs7.existsSync(hostPjPath)) return null;
1765
+ const hostPj = JSON.parse(fs7.readFileSync(hostPjPath, "utf8"));
1766
+ if (hostPj.name !== "@tamer4lynx/tamer-dev-app") return null;
1767
+ const visited = /* @__PURE__ */ new Set();
1768
+ const queue = [];
1769
+ const enqueue = (deps) => {
1770
+ if (!deps) return;
1771
+ for (const name of Object.keys(deps)) {
1772
+ if (name.startsWith("@tamer4lynx/")) queue.push(name);
1773
+ }
1774
+ };
1775
+ enqueue(hostPj.dependencies);
1776
+ enqueue(hostPj.optionalDependencies);
1777
+ while (queue.length) {
1778
+ const name = queue.shift();
1779
+ if (visited.has(name)) continue;
1780
+ visited.add(name);
1781
+ const pkgRoot = resolvePackageRoot(projectRoot, name);
1782
+ if (!pkgRoot || !fs7.existsSync(path7.join(pkgRoot, "package.json"))) continue;
1783
+ const sub = JSON.parse(fs7.readFileSync(path7.join(pkgRoot, "package.json"), "utf8"));
1784
+ const merged = { ...sub.dependencies, ...sub.optionalDependencies };
1785
+ for (const dep of Object.keys(merged)) {
1786
+ if (dep.startsWith("@tamer4lynx/") && !visited.has(dep)) queue.push(dep);
1787
+ }
1788
+ }
1789
+ return visited;
1790
+ }
1717
1791
  function discoverModules(projectRoot) {
1718
1792
  const nodeModulesPath = resolveNodeModulesPath(projectRoot);
1719
1793
  const packages = [];
1720
- if (!fs6.existsSync(nodeModulesPath)) {
1794
+ if (!fs7.existsSync(nodeModulesPath)) {
1721
1795
  return [];
1722
1796
  }
1723
- const packageDirs = fs6.readdirSync(nodeModulesPath);
1797
+ const packageDirs = fs7.readdirSync(nodeModulesPath);
1724
1798
  for (const dirName of packageDirs) {
1725
- const fullPath = path6.join(nodeModulesPath, dirName);
1799
+ const fullPath = path7.join(nodeModulesPath, dirName);
1726
1800
  const checkPackage = (name, packagePath) => {
1727
1801
  if (!hasExtensionConfig(packagePath)) return;
1728
1802
  const config = loadExtensionConfig(packagePath);
@@ -1731,9 +1805,9 @@ function discoverModules(projectRoot) {
1731
1805
  };
1732
1806
  if (dirName.startsWith("@")) {
1733
1807
  try {
1734
- const scopedDirs = fs6.readdirSync(fullPath);
1808
+ const scopedDirs = fs7.readdirSync(fullPath);
1735
1809
  for (const scopedDirName of scopedDirs) {
1736
- const scopedPackagePath = path6.join(fullPath, scopedDirName);
1810
+ const scopedPackagePath = path7.join(fullPath, scopedDirName);
1737
1811
  checkPackage(`${dirName}/${scopedDirName}`, scopedPackagePath);
1738
1812
  }
1739
1813
  } catch (e) {
@@ -1744,6 +1818,10 @@ function discoverModules(projectRoot) {
1744
1818
  checkPackage(dirName, fullPath);
1745
1819
  }
1746
1820
  }
1821
+ const allowed = collectTransitiveTamer4LynxPackageNames(projectRoot);
1822
+ if (allowed) {
1823
+ return packages.filter((p) => allowed.has(p.name));
1824
+ }
1747
1825
  return packages;
1748
1826
  }
1749
1827
 
@@ -1795,6 +1873,8 @@ ${allModuleClasses.map((c) => ` "${c.replace(/\\/g, "\\\\").replace(/
1795
1873
  fun onHostViewChanged(view: android.view.View?) {
1796
1874
  ${hostViewLines}
1797
1875
  }` : "\n fun onHostViewChanged(view: android.view.View?) {}\n";
1876
+ const devToolBootstrapPrefix = hasDevClient ? " com.nanofuxion.tamerdevclient.LynxDevToolBootstrap.configure(context)\n" : "";
1877
+ const devToolBootstrapSuffix = hasDevClient ? " com.nanofuxion.tamerdevclient.LynxDevToolBootstrap.enableLynxDebugFlags()\n" : "";
1798
1878
  return `package ${projectPackage}.generated
1799
1879
 
1800
1880
  import android.content.Context
@@ -1806,7 +1886,7 @@ ${elementImports}
1806
1886
 
1807
1887
  object GeneratedLynxExtensions {
1808
1888
  fun register(context: Context) {
1809
- ${allRegistrations}${devClientSupportedBlock}
1889
+ ${devToolBootstrapPrefix}${allRegistrations}${devClientSupportedBlock}${devToolBootstrapSuffix}
1810
1890
  }
1811
1891
 
1812
1892
  fun configureViewBuilder(viewBuilder: LynxViewBuilder) {
@@ -1939,11 +2019,11 @@ var autolink = (opts) => {
1939
2019
  const packageName = config.android.packageName;
1940
2020
  const projectRoot = resolved.projectRoot;
1941
2021
  function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
1942
- if (!fs7.existsSync(filePath)) {
2022
+ if (!fs8.existsSync(filePath)) {
1943
2023
  console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
1944
2024
  return;
1945
2025
  }
1946
- let fileContent = fs7.readFileSync(filePath, "utf8");
2026
+ let fileContent = fs8.readFileSync(filePath, "utf8");
1947
2027
  const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1948
2028
  const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1949
2029
  const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
@@ -1953,16 +2033,16 @@ ${endMarker}`;
1953
2033
  if (regex.test(fileContent)) {
1954
2034
  fileContent = fileContent.replace(regex, replacementBlock);
1955
2035
  } else {
1956
- console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path7.basename(filePath)}. Appending to the end of the file.`);
2036
+ console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path8.basename(filePath)}. Appending to the end of the file.`);
1957
2037
  fileContent += `
1958
2038
  ${replacementBlock}
1959
2039
  `;
1960
2040
  }
1961
- fs7.writeFileSync(filePath, fileContent);
1962
- console.log(`\u2705 Updated autolinked section in ${path7.basename(filePath)}`);
2041
+ fs8.writeFileSync(filePath, fileContent);
2042
+ console.log(`\u2705 Updated autolinked section in ${path8.basename(filePath)}`);
1963
2043
  }
1964
2044
  function updateSettingsGradle(packages) {
1965
- const settingsFilePath = path7.join(appAndroidPath, "settings.gradle.kts");
2045
+ const settingsFilePath = path8.join(appAndroidPath, "settings.gradle.kts");
1966
2046
  let scriptContent = `// This section is automatically generated by Tamer4Lynx.
1967
2047
  // Manual edits will be overwritten.`;
1968
2048
  const androidPackages = packages.filter((p) => p.config.android);
@@ -1970,7 +2050,7 @@ ${replacementBlock}
1970
2050
  androidPackages.forEach((pkg) => {
1971
2051
  const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
1972
2052
  const sourceDir = pkg.config.android?.sourceDir || "android";
1973
- const projectPath = path7.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
2053
+ const projectPath = path8.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
1974
2054
  scriptContent += `
1975
2055
  include(":${gradleProjectName}")`;
1976
2056
  scriptContent += `
@@ -1983,7 +2063,7 @@ println("No native modules found by Tamer4Lynx autolinker.")`;
1983
2063
  updateGeneratedSection(settingsFilePath, scriptContent.trim(), "// GENERATED AUTOLINK START", "// GENERATED AUTOLINK END");
1984
2064
  }
1985
2065
  function updateAppBuildGradle(packages) {
1986
- const appBuildGradlePath = path7.join(appAndroidPath, "app", "build.gradle.kts");
2066
+ const appBuildGradlePath = path8.join(appAndroidPath, "app", "build.gradle.kts");
1987
2067
  const androidPackages = packages.filter((p) => p.config.android);
1988
2068
  const implementationLines = androidPackages.map((p) => {
1989
2069
  const gradleProjectName = p.name.replace(/^@/, "").replace(/\//g, "_");
@@ -2001,35 +2081,35 @@ ${implementationLines || " // No native dependencies found to link."}`;
2001
2081
  }
2002
2082
  function generateKotlinExtensionsFile(packages, projectPackage) {
2003
2083
  const packagePath = projectPackage.replace(/\./g, "/");
2004
- const generatedDir = path7.join(appAndroidPath, "app", "src", "main", "kotlin", packagePath, "generated");
2005
- const kotlinExtensionsPath = path7.join(generatedDir, "GeneratedLynxExtensions.kt");
2084
+ const generatedDir = path8.join(appAndroidPath, "app", "src", "main", "kotlin", packagePath, "generated");
2085
+ const kotlinExtensionsPath = path8.join(generatedDir, "GeneratedLynxExtensions.kt");
2006
2086
  const content = `/**
2007
2087
  * This file is generated by the Tamer4Lynx autolinker.
2008
2088
  * Do not edit this file manually.
2009
2089
  */
2010
2090
  ${generateLynxExtensionsKotlin(packages, projectPackage)}`;
2011
- fs7.mkdirSync(generatedDir, { recursive: true });
2012
- fs7.writeFileSync(kotlinExtensionsPath, content.trimStart());
2091
+ fs8.mkdirSync(generatedDir, { recursive: true });
2092
+ fs8.writeFileSync(kotlinExtensionsPath, content.trimStart());
2013
2093
  console.log(`\u2705 Generated Kotlin extensions at ${kotlinExtensionsPath}`);
2014
2094
  }
2015
2095
  function generateActivityLifecycleFile(packages, projectPackage) {
2016
2096
  const packageKotlinPath = projectPackage.replace(/\./g, "/");
2017
- const generatedDir = path7.join(appAndroidPath, "app", "src", "main", "kotlin", packageKotlinPath, "generated");
2018
- const outputPath = path7.join(generatedDir, "GeneratedActivityLifecycle.kt");
2097
+ const generatedDir = path8.join(appAndroidPath, "app", "src", "main", "kotlin", packageKotlinPath, "generated");
2098
+ const outputPath = path8.join(generatedDir, "GeneratedActivityLifecycle.kt");
2019
2099
  const content = `/**
2020
2100
  * This file is generated by the Tamer4Lynx autolinker.
2021
2101
  * Do not edit this file manually.
2022
2102
  */
2023
2103
  ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2024
- fs7.mkdirSync(generatedDir, { recursive: true });
2025
- fs7.writeFileSync(outputPath, content);
2104
+ fs8.mkdirSync(generatedDir, { recursive: true });
2105
+ fs8.writeFileSync(outputPath, content);
2026
2106
  console.log(`\u2705 Generated activity lifecycle patches at ${outputPath}`);
2027
2107
  }
2028
2108
  function syncDeepLinkIntentFilters() {
2029
2109
  const deepLinks = config.android?.deepLinks;
2030
2110
  if (!deepLinks || deepLinks.length === 0) return;
2031
- const manifestPath = path7.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
2032
- if (!fs7.existsSync(manifestPath)) return;
2111
+ const manifestPath = path8.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
2112
+ if (!fs8.existsSync(manifestPath)) return;
2033
2113
  const intentFilters = deepLinks.map((link) => {
2034
2114
  const dataAttrs = [
2035
2115
  `android:scheme="${link.scheme}"`,
@@ -2054,9 +2134,9 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2054
2134
  console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
2055
2135
  let packages = discoverModules(projectRoot).filter((p) => p.config.android);
2056
2136
  const includeDevClient = opts?.includeDevClient === true;
2057
- const devClientScoped = path7.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client");
2058
- const devClientFlat = path7.join(projectRoot, "node_modules", "tamer-dev-client");
2059
- const devClientPath = fs7.existsSync(path7.join(devClientScoped, "android")) ? devClientScoped : fs7.existsSync(path7.join(devClientFlat, "android")) ? devClientFlat : null;
2137
+ const devClientScoped = path8.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client");
2138
+ const devClientFlat = path8.join(projectRoot, "node_modules", "tamer-dev-client");
2139
+ const devClientPath = fs8.existsSync(path8.join(devClientScoped, "android")) ? devClientScoped : fs8.existsSync(path8.join(devClientFlat, "android")) ? devClientFlat : null;
2060
2140
  const hasDevClient = packages.some((p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client");
2061
2141
  if (includeDevClient && devClientPath && !hasDevClient) {
2062
2142
  packages = [{
@@ -2083,8 +2163,8 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2083
2163
  console.log("\u2728 Autolinking complete.");
2084
2164
  }
2085
2165
  function runGradleSync() {
2086
- const gradlew = path7.join(appAndroidPath, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2087
- if (!fs7.existsSync(gradlew)) return;
2166
+ const gradlew = path8.join(appAndroidPath, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2167
+ if (!fs8.existsSync(gradlew)) return;
2088
2168
  try {
2089
2169
  console.log("\u2139\uFE0F Running Gradle sync in android directory...");
2090
2170
  execSync2(process.platform === "win32" ? "gradlew.bat projects" : "./gradlew projects", {
@@ -2098,14 +2178,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2098
2178
  }
2099
2179
  }
2100
2180
  function syncVersionCatalog(packages) {
2101
- const libsTomlPath = path7.join(appAndroidPath, "gradle", "libs.versions.toml");
2102
- if (!fs7.existsSync(libsTomlPath)) return;
2181
+ const libsTomlPath = path8.join(appAndroidPath, "gradle", "libs.versions.toml");
2182
+ if (!fs8.existsSync(libsTomlPath)) return;
2103
2183
  const requiredAliases = /* @__PURE__ */ new Set();
2104
2184
  const requiredPluginAliases = /* @__PURE__ */ new Set();
2105
2185
  for (const pkg of packages) {
2106
- const buildPath = path7.join(pkg.packagePath, pkg.config.android?.sourceDir || "android", "build.gradle.kts");
2107
- if (!fs7.existsSync(buildPath)) continue;
2108
- const content = fs7.readFileSync(buildPath, "utf8");
2186
+ const buildPath = path8.join(pkg.packagePath, pkg.config.android?.sourceDir || "android", "build.gradle.kts");
2187
+ if (!fs8.existsSync(buildPath)) continue;
2188
+ const content = fs8.readFileSync(buildPath, "utf8");
2109
2189
  for (const m of content.matchAll(/libs\.([\w.]+)/g)) {
2110
2190
  const alias = m[1];
2111
2191
  if (alias && alias in REQUIRED_CATALOG_ENTRIES) requiredAliases.add(alias);
@@ -2116,7 +2196,7 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2116
2196
  }
2117
2197
  }
2118
2198
  if (requiredAliases.size === 0 && requiredPluginAliases.size === 0) return;
2119
- let toml = fs7.readFileSync(libsTomlPath, "utf8");
2199
+ let toml = fs8.readFileSync(libsTomlPath, "utf8");
2120
2200
  let updated = false;
2121
2201
  for (const alias of requiredAliases) {
2122
2202
  const entry = REQUIRED_CATALOG_ENTRIES[alias];
@@ -2140,14 +2220,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
2140
2220
  updated = true;
2141
2221
  }
2142
2222
  if (updated) {
2143
- fs7.writeFileSync(libsTomlPath, toml);
2223
+ fs8.writeFileSync(libsTomlPath, toml);
2144
2224
  console.log("\u2705 Synced version catalog (libs.versions.toml) for linked modules.");
2145
2225
  }
2146
2226
  }
2147
2227
  function ensureXElementDeps() {
2148
- const libsTomlPath = path7.join(appAndroidPath, "gradle", "libs.versions.toml");
2149
- if (fs7.existsSync(libsTomlPath)) {
2150
- let toml = fs7.readFileSync(libsTomlPath, "utf8");
2228
+ const libsTomlPath = path8.join(appAndroidPath, "gradle", "libs.versions.toml");
2229
+ if (fs8.existsSync(libsTomlPath)) {
2230
+ let toml = fs8.readFileSync(libsTomlPath, "utf8");
2151
2231
  let updated = false;
2152
2232
  if (!toml.includes("lynx-xelement =")) {
2153
2233
  toml = toml.replace(
@@ -2159,13 +2239,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
2159
2239
  updated = true;
2160
2240
  }
2161
2241
  if (updated) {
2162
- fs7.writeFileSync(libsTomlPath, toml);
2242
+ fs8.writeFileSync(libsTomlPath, toml);
2163
2243
  console.log("\u2705 Added XElement entries to version catalog.");
2164
2244
  }
2165
2245
  }
2166
- const appBuildPath = path7.join(appAndroidPath, "app", "build.gradle.kts");
2167
- if (fs7.existsSync(appBuildPath)) {
2168
- let content = fs7.readFileSync(appBuildPath, "utf8");
2246
+ const appBuildPath = path8.join(appAndroidPath, "app", "build.gradle.kts");
2247
+ if (fs8.existsSync(appBuildPath)) {
2248
+ let content = fs8.readFileSync(appBuildPath, "utf8");
2169
2249
  if (!content.includes("lynx.xelement")) {
2170
2250
  content = content.replace(
2171
2251
  /(implementation\(libs\.lynx\.service\.http\))/,
@@ -2173,15 +2253,15 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
2173
2253
  implementation(libs.lynx.xelement)
2174
2254
  implementation(libs.lynx.xelement.input)`
2175
2255
  );
2176
- fs7.writeFileSync(appBuildPath, content);
2256
+ fs8.writeFileSync(appBuildPath, content);
2177
2257
  console.log("\u2705 Added XElement dependencies to app build.gradle.kts.");
2178
2258
  }
2179
2259
  }
2180
2260
  }
2181
2261
  function ensureReleaseSigning() {
2182
- const appBuildPath = path7.join(appAndroidPath, "app", "build.gradle.kts");
2183
- if (!fs7.existsSync(appBuildPath)) return;
2184
- let content = fs7.readFileSync(appBuildPath, "utf8");
2262
+ const appBuildPath = path8.join(appAndroidPath, "app", "build.gradle.kts");
2263
+ if (!fs8.existsSync(appBuildPath)) return;
2264
+ let content = fs8.readFileSync(appBuildPath, "utf8");
2185
2265
  if (content.includes('signingConfig = signingConfigs.getByName("debug")')) return;
2186
2266
  const releaseBlock = /(release\s*\{)([\s\S]*?)(\n \}\s*\n(\s*\}|\s*compileOptions))/;
2187
2267
  const match = content.match(releaseBlock);
@@ -2192,13 +2272,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
2192
2272
  signingConfig = signingConfigs.getByName("debug")
2193
2273
  $3`
2194
2274
  );
2195
- fs7.writeFileSync(appBuildPath, content);
2275
+ fs8.writeFileSync(appBuildPath, content);
2196
2276
  console.log("\u2705 Set release signing to debug so installRelease works without a keystore.");
2197
2277
  }
2198
2278
  }
2199
2279
  function syncManifestPermissions(packages) {
2200
- const manifestPath = path7.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
2201
- if (!fs7.existsSync(manifestPath)) return;
2280
+ const manifestPath = path8.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
2281
+ if (!fs8.existsSync(manifestPath)) return;
2202
2282
  const allPermissions = /* @__PURE__ */ new Set();
2203
2283
  for (const pkg of packages) {
2204
2284
  const perms = pkg.config.android?.permissions;
@@ -2210,7 +2290,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
2210
2290
  }
2211
2291
  }
2212
2292
  if (allPermissions.size === 0) return;
2213
- let manifest = fs7.readFileSync(manifestPath, "utf8");
2293
+ let manifest = fs8.readFileSync(manifestPath, "utf8");
2214
2294
  const existingMatch = [...manifest.matchAll(/<uses-permission android:name="(android\.permission\.\w+)"\s*\/>/g)];
2215
2295
  const existing = new Set(existingMatch.map((m) => m[1]));
2216
2296
  const toAdd = [...allPermissions].filter((p) => !existing.has(p));
@@ -2221,7 +2301,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
2221
2301
  `${newLines}
2222
2302
  $1$2`
2223
2303
  );
2224
- fs7.writeFileSync(manifestPath, manifest);
2304
+ fs8.writeFileSync(manifestPath, manifest);
2225
2305
  console.log(`\u2705 Synced manifest permissions: ${toAdd.map((p) => p.split(".").pop()).join(", ")}`);
2226
2306
  }
2227
2307
  run();
@@ -2229,26 +2309,26 @@ $1$2`
2229
2309
  var autolink_default = autolink;
2230
2310
 
2231
2311
  // src/android/bundle.ts
2232
- import fs11 from "fs";
2233
- import path11 from "path";
2312
+ import fs12 from "fs";
2313
+ import path12 from "path";
2234
2314
  import { execSync as execSync3 } from "child_process";
2235
2315
 
2236
2316
  // src/common/copyDistAssets.ts
2237
- import fs8 from "fs";
2238
- import path8 from "path";
2317
+ import fs9 from "fs";
2318
+ import path9 from "path";
2239
2319
  var SKIP = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
2240
2320
  function copyDistAssets(distDir, destDir, bundleFile) {
2241
- if (!fs8.existsSync(distDir)) return;
2242
- for (const entry of fs8.readdirSync(distDir)) {
2321
+ if (!fs9.existsSync(distDir)) return;
2322
+ for (const entry of fs9.readdirSync(distDir)) {
2243
2323
  if (SKIP.has(entry)) continue;
2244
- const src = path8.join(distDir, entry);
2245
- const dest = path8.join(destDir, entry);
2246
- const stat = fs8.statSync(src);
2324
+ const src = path9.join(distDir, entry);
2325
+ const dest = path9.join(destDir, entry);
2326
+ const stat = fs9.statSync(src);
2247
2327
  if (stat.isDirectory()) {
2248
- fs8.mkdirSync(dest, { recursive: true });
2328
+ fs9.mkdirSync(dest, { recursive: true });
2249
2329
  copyDistAssets(src, dest, bundleFile);
2250
2330
  } else {
2251
- fs8.copyFileSync(src, dest);
2331
+ fs9.copyFileSync(src, dest);
2252
2332
  if (entry !== bundleFile) {
2253
2333
  console.log(`\u2728 Copied asset: ${entry}`);
2254
2334
  }
@@ -2257,8 +2337,8 @@ function copyDistAssets(distDir, destDir, bundleFile) {
2257
2337
  }
2258
2338
 
2259
2339
  // src/common/tsconfigUtils.ts
2260
- import fs9 from "fs";
2261
- import path9 from "path";
2340
+ import fs10 from "fs";
2341
+ import path10 from "path";
2262
2342
  function stripJsonCommentsAndTrailingCommas(str) {
2263
2343
  return str.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,\s*([\]}])/g, "$1");
2264
2344
  }
@@ -2270,19 +2350,19 @@ function parseTsconfigJson(raw) {
2270
2350
  }
2271
2351
  }
2272
2352
  function readTsconfig(filePath) {
2273
- if (!fs9.existsSync(filePath)) return null;
2353
+ if (!fs10.existsSync(filePath)) return null;
2274
2354
  try {
2275
- return parseTsconfigJson(fs9.readFileSync(filePath, "utf-8"));
2355
+ return parseTsconfigJson(fs10.readFileSync(filePath, "utf-8"));
2276
2356
  } catch {
2277
2357
  return null;
2278
2358
  }
2279
2359
  }
2280
2360
  function resolveExtends(tsconfig, dir) {
2281
2361
  if (!tsconfig.extends) return tsconfig;
2282
- const basePath = path9.resolve(dir, tsconfig.extends);
2362
+ const basePath = path10.resolve(dir, tsconfig.extends);
2283
2363
  const base = readTsconfig(basePath);
2284
2364
  if (!base) return tsconfig;
2285
- const baseDir = path9.dirname(basePath);
2365
+ const baseDir = path10.dirname(basePath);
2286
2366
  const resolved = resolveExtends(base, baseDir);
2287
2367
  return {
2288
2368
  ...resolved,
@@ -2291,13 +2371,13 @@ function resolveExtends(tsconfig, dir) {
2291
2371
  };
2292
2372
  }
2293
2373
  function fixTsconfigReferencesForBuild(tsconfigPath) {
2294
- const dir = path9.dirname(tsconfigPath);
2374
+ const dir = path10.dirname(tsconfigPath);
2295
2375
  const tsconfig = readTsconfig(tsconfigPath);
2296
2376
  if (!tsconfig?.references?.length) return false;
2297
2377
  const refsWithNoEmit = [];
2298
2378
  for (const ref of tsconfig.references) {
2299
- const refPath = path9.resolve(dir, ref.path);
2300
- const refConfigPath = refPath.endsWith(".json") ? refPath : path9.join(refPath, "tsconfig.json");
2379
+ const refPath = path10.resolve(dir, ref.path);
2380
+ const refConfigPath = refPath.endsWith(".json") ? refPath : path10.join(refPath, "tsconfig.json");
2301
2381
  const refConfig = readTsconfig(refConfigPath);
2302
2382
  if (refConfig?.compilerOptions?.noEmit === true) {
2303
2383
  refsWithNoEmit.push(refConfigPath);
@@ -2312,16 +2392,16 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
2312
2392
  const includes = [];
2313
2393
  const compilerOpts = { ...tsconfig.compilerOptions };
2314
2394
  for (const ref of tsconfig.references) {
2315
- const refPath = path9.resolve(dir, ref.path);
2316
- const refConfigPath = refPath.endsWith(".json") ? refPath : path9.join(refPath, "tsconfig.json");
2395
+ const refPath = path10.resolve(dir, ref.path);
2396
+ const refConfigPath = refPath.endsWith(".json") ? refPath : path10.join(refPath, "tsconfig.json");
2317
2397
  const refConfig = readTsconfig(refConfigPath);
2318
2398
  if (!refConfig) continue;
2319
- const refDir = path9.dirname(refConfigPath);
2399
+ const refDir = path10.dirname(refConfigPath);
2320
2400
  const resolved = resolveExtends(refConfig, refDir);
2321
2401
  const inc = resolved.include;
2322
2402
  if (inc) {
2323
2403
  const arr = Array.isArray(inc) ? inc : [inc];
2324
- const baseDir = path9.relative(dir, refDir);
2404
+ const baseDir = path10.relative(dir, refDir);
2325
2405
  for (const p of arr) {
2326
2406
  const clean = typeof p === "string" && p.startsWith("./") ? p.slice(2) : p;
2327
2407
  includes.push(!baseDir || baseDir === "." ? clean : `${baseDir}/${clean}`);
@@ -2339,7 +2419,7 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
2339
2419
  if (includes.length > 0) merged.include = [...new Set(includes)];
2340
2420
  compilerOpts.noEmit = true;
2341
2421
  merged.compilerOptions = compilerOpts;
2342
- fs9.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
2422
+ fs10.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
2343
2423
  return true;
2344
2424
  }
2345
2425
  function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
@@ -2350,23 +2430,23 @@ function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
2350
2430
  if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) return false;
2351
2431
  arr.push(tamerTypesInclude);
2352
2432
  tsconfig.include = arr;
2353
- fs9.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
2433
+ fs10.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
2354
2434
  return true;
2355
2435
  }
2356
2436
 
2357
2437
  // src/android/syncDevClient.ts
2358
- import fs10 from "fs";
2359
- import path10 from "path";
2438
+ import fs11 from "fs";
2439
+ import path11 from "path";
2360
2440
  function readAndSubstituteTemplate2(templatePath, vars) {
2361
- const raw = fs10.readFileSync(templatePath, "utf-8");
2441
+ const raw = fs11.readFileSync(templatePath, "utf-8");
2362
2442
  return Object.entries(vars).reduce(
2363
2443
  (s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
2364
2444
  raw
2365
2445
  );
2366
2446
  }
2367
2447
  function patchAppLogService(appPath) {
2368
- if (!fs10.existsSync(appPath)) return;
2369
- const raw = fs10.readFileSync(appPath, "utf-8");
2448
+ if (!fs11.existsSync(appPath)) return;
2449
+ const raw = fs11.readFileSync(appPath, "utf-8");
2370
2450
  const patched = raw.replace(
2371
2451
  /private void initLynxService\(\)\s*\{[\s\S]*?\n\s*}\s*\n\s*private void initFresco\(\)/,
2372
2452
  `private void initLynxService() {
@@ -2385,7 +2465,7 @@ function patchAppLogService(appPath) {
2385
2465
  private void initFresco()`
2386
2466
  );
2387
2467
  if (patched !== raw) {
2388
- fs10.writeFileSync(appPath, patched);
2468
+ fs11.writeFileSync(appPath, patched);
2389
2469
  }
2390
2470
  }
2391
2471
  async function syncDevClient(opts) {
@@ -2400,9 +2480,9 @@ async function syncDevClient(opts) {
2400
2480
  const packageName = config.android?.packageName;
2401
2481
  const appName = config.android?.appName;
2402
2482
  const packagePath = packageName.replace(/\./g, "/");
2403
- const javaDir = path10.join(rootDir, "app", "src", "main", "java", packagePath);
2404
- const kotlinDir = path10.join(rootDir, "app", "src", "main", "kotlin", packagePath);
2405
- if (!fs10.existsSync(javaDir) || !fs10.existsSync(kotlinDir)) {
2483
+ const javaDir = path11.join(rootDir, "app", "src", "main", "java", packagePath);
2484
+ const kotlinDir = path11.join(rootDir, "app", "src", "main", "kotlin", packagePath);
2485
+ if (!fs11.existsSync(javaDir) || !fs11.existsSync(kotlinDir)) {
2406
2486
  console.error("\u274C Android project not found. Run `tamer android create` first.");
2407
2487
  process.exit(1);
2408
2488
  }
@@ -2418,14 +2498,14 @@ async function syncDevClient(opts) {
2418
2498
  const [templateProviderSource] = await Promise.all([
2419
2499
  fetchAndPatchTemplateProvider(vars)
2420
2500
  ]);
2421
- fs10.writeFileSync(path10.join(javaDir, "TemplateProvider.java"), templateProviderSource);
2422
- fs10.writeFileSync(path10.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
2423
- patchAppLogService(path10.join(javaDir, "App.java"));
2424
- const appDir = path10.join(rootDir, "app");
2425
- const mainDir = path10.join(appDir, "src", "main");
2426
- const manifestPath = path10.join(mainDir, "AndroidManifest.xml");
2501
+ fs11.writeFileSync(path11.join(javaDir, "TemplateProvider.java"), templateProviderSource);
2502
+ fs11.writeFileSync(path11.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
2503
+ patchAppLogService(path11.join(javaDir, "App.java"));
2504
+ const appDir = path11.join(rootDir, "app");
2505
+ const mainDir = path11.join(appDir, "src", "main");
2506
+ const manifestPath = path11.join(mainDir, "AndroidManifest.xml");
2427
2507
  if (hasDevClient) {
2428
- const templateDir = path10.join(devClientPkg, "android", "templates");
2508
+ const templateDir = path11.join(devClientPkg, "android", "templates");
2429
2509
  const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
2430
2510
  const devClientFiles = [
2431
2511
  "DevClientManager.kt",
@@ -2434,13 +2514,13 @@ async function syncDevClient(opts) {
2434
2514
  "PortraitCaptureActivity.kt"
2435
2515
  ];
2436
2516
  for (const f of devClientFiles) {
2437
- const src = path10.join(templateDir, f);
2438
- if (fs10.existsSync(src)) {
2517
+ const src = path11.join(templateDir, f);
2518
+ if (fs11.existsSync(src)) {
2439
2519
  const content = readAndSubstituteTemplate2(src, templateVars);
2440
- fs10.writeFileSync(path10.join(kotlinDir, f), content);
2520
+ fs11.writeFileSync(path11.join(kotlinDir, f), content);
2441
2521
  }
2442
2522
  }
2443
- let manifest = fs10.readFileSync(manifestPath, "utf-8");
2523
+ let manifest = fs11.readFileSync(manifestPath, "utf-8");
2444
2524
  const projectActivityEntry = ' <activity android:name=".ProjectActivity" android:exported="false" android:taskAffinity="" android:launchMode="singleTask" android:documentLaunchMode="always" android:windowSoftInputMode="adjustResize" />';
2445
2525
  const portraitCaptureEntry = ' <activity android:name=".PortraitCaptureActivity" android:screenOrientation="portrait" android:stateNotNeeded="true" android:theme="@style/zxing_CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden" />';
2446
2526
  if (!manifest.includes("ProjectActivity")) {
@@ -2462,16 +2542,16 @@ $1$2`);
2462
2542
  '$1 android:windowSoftInputMode="adjustResize"$2'
2463
2543
  );
2464
2544
  }
2465
- fs10.writeFileSync(manifestPath, manifest);
2545
+ fs11.writeFileSync(manifestPath, manifest);
2466
2546
  console.log("\u2705 Synced dev client (TemplateProvider, MainActivity, ProjectActivity, DevClientManager)");
2467
2547
  } else {
2468
2548
  for (const f of ["DevClientManager.kt", "DevServerPrefs.kt", "ProjectActivity.kt", "PortraitCaptureActivity.kt", "DevLauncherActivity.kt"]) {
2469
2549
  try {
2470
- fs10.rmSync(path10.join(kotlinDir, f));
2550
+ fs11.rmSync(path11.join(kotlinDir, f));
2471
2551
  } catch {
2472
2552
  }
2473
2553
  }
2474
- let manifest = fs10.readFileSync(manifestPath, "utf-8");
2554
+ let manifest = fs11.readFileSync(manifestPath, "utf-8");
2475
2555
  manifest = manifest.replace(/\s*<activity android:name="\.ProjectActivity"[^\/]*\/>\n?/g, "");
2476
2556
  manifest = manifest.replace(/\s*<activity android:name="\.PortraitCaptureActivity"[^\/]*\/>\n?/g, "");
2477
2557
  const mainActivityTag = manifest.match(/<activity[^>]*android:name="\.MainActivity"[^>]*>/);
@@ -2481,7 +2561,7 @@ $1$2`);
2481
2561
  '$1 android:windowSoftInputMode="adjustResize"$2'
2482
2562
  );
2483
2563
  }
2484
- fs10.writeFileSync(manifestPath, manifest);
2564
+ fs11.writeFileSync(manifestPath, manifest);
2485
2565
  console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
2486
2566
  }
2487
2567
  }
@@ -2505,15 +2585,15 @@ async function bundleAndDeploy(opts = {}) {
2505
2585
  await syncDevClient_default({ includeDevClient });
2506
2586
  const iconPaths = resolveIconPaths(projectRoot, resolved.config);
2507
2587
  if (iconPaths) {
2508
- const resDir = path11.join(resolved.androidAppDir, "src", "main", "res");
2588
+ const resDir = path12.join(resolved.androidAppDir, "src", "main", "res");
2509
2589
  if (applyAndroidLauncherIcons(resDir, iconPaths)) {
2510
2590
  console.log("\u2705 Synced Android launcher icon(s) from tamer.config.json");
2511
- ensureAndroidManifestLauncherIcon(path11.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
2591
+ ensureAndroidManifestLauncherIcon(path12.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
2512
2592
  }
2513
2593
  }
2514
2594
  try {
2515
- const lynxTsconfig = path11.join(lynxProjectDir, "tsconfig.json");
2516
- if (fs11.existsSync(lynxTsconfig)) {
2595
+ const lynxTsconfig = path12.join(lynxProjectDir, "tsconfig.json");
2596
+ if (fs12.existsSync(lynxTsconfig)) {
2517
2597
  fixTsconfigReferencesForBuild(lynxTsconfig);
2518
2598
  }
2519
2599
  console.log("\u{1F4E6} Building Lynx bundle...");
@@ -2523,8 +2603,8 @@ async function bundleAndDeploy(opts = {}) {
2523
2603
  console.error("\u274C Build process failed.");
2524
2604
  process.exit(1);
2525
2605
  }
2526
- if (includeDevClient && devClientBundlePath && !fs11.existsSync(devClientBundlePath)) {
2527
- const devClientDir = path11.dirname(path11.dirname(devClientBundlePath));
2606
+ if (includeDevClient && devClientBundlePath && !fs12.existsSync(devClientBundlePath)) {
2607
+ const devClientDir = path12.dirname(path12.dirname(devClientBundlePath));
2528
2608
  try {
2529
2609
  console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
2530
2610
  execSync3("npm run build", { stdio: "inherit", cwd: devClientDir });
@@ -2535,22 +2615,22 @@ async function bundleAndDeploy(opts = {}) {
2535
2615
  }
2536
2616
  }
2537
2617
  try {
2538
- fs11.mkdirSync(destinationDir, { recursive: true });
2618
+ fs12.mkdirSync(destinationDir, { recursive: true });
2539
2619
  if (release) {
2540
- const devClientAsset = path11.join(destinationDir, "dev-client.lynx.bundle");
2541
- if (fs11.existsSync(devClientAsset)) {
2542
- fs11.rmSync(devClientAsset);
2620
+ const devClientAsset = path12.join(destinationDir, "dev-client.lynx.bundle");
2621
+ if (fs12.existsSync(devClientAsset)) {
2622
+ fs12.rmSync(devClientAsset);
2543
2623
  console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
2544
2624
  }
2545
- } else if (includeDevClient && devClientBundlePath && fs11.existsSync(devClientBundlePath)) {
2546
- fs11.copyFileSync(devClientBundlePath, path11.join(destinationDir, "dev-client.lynx.bundle"));
2625
+ } else if (includeDevClient && devClientBundlePath && fs12.existsSync(devClientBundlePath)) {
2626
+ fs12.copyFileSync(devClientBundlePath, path12.join(destinationDir, "dev-client.lynx.bundle"));
2547
2627
  console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
2548
2628
  }
2549
- if (!fs11.existsSync(lynxBundlePath)) {
2629
+ if (!fs12.existsSync(lynxBundlePath)) {
2550
2630
  console.error(`\u274C Build output not found at: ${lynxBundlePath}`);
2551
2631
  process.exit(1);
2552
2632
  }
2553
- const distDir = path11.dirname(lynxBundlePath);
2633
+ const distDir = path12.dirname(lynxBundlePath);
2554
2634
  copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
2555
2635
  console.log(`\u2728 Copied ${resolved.lynxBundleFile} to assets`);
2556
2636
  } catch (error) {
@@ -2561,7 +2641,7 @@ async function bundleAndDeploy(opts = {}) {
2561
2641
  var bundle_default = bundleAndDeploy;
2562
2642
 
2563
2643
  // src/android/build.ts
2564
- import path12 from "path";
2644
+ import path13 from "path";
2565
2645
  import { execSync as execSync4 } from "child_process";
2566
2646
  async function buildApk(opts = {}) {
2567
2647
  let resolved;
@@ -2573,7 +2653,7 @@ async function buildApk(opts = {}) {
2573
2653
  const release = opts.release === true || opts.production === true;
2574
2654
  await bundle_default({ release, production: opts.production });
2575
2655
  const androidDir = resolved.androidDir;
2576
- const gradlew = path12.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2656
+ const gradlew = path13.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2577
2657
  const variant = release ? "Release" : "Debug";
2578
2658
  const task = opts.install ? `install${variant}` : `assemble${variant}`;
2579
2659
  console.log(`
@@ -2598,13 +2678,13 @@ async function buildApk(opts = {}) {
2598
2678
  var build_default = buildApk;
2599
2679
 
2600
2680
  // src/ios/create.ts
2601
- import fs13 from "fs";
2602
- import path14 from "path";
2681
+ import fs14 from "fs";
2682
+ import path15 from "path";
2603
2683
 
2604
2684
  // src/ios/getPod.ts
2605
2685
  import { execSync as execSync5 } from "child_process";
2606
- import fs12 from "fs";
2607
- import path13 from "path";
2686
+ import fs13 from "fs";
2687
+ import path14 from "path";
2608
2688
  function isCocoaPodsInstalled() {
2609
2689
  try {
2610
2690
  execSync5("command -v pod >/dev/null 2>&1");
@@ -2626,8 +2706,8 @@ async function setupCocoaPods(rootDir) {
2626
2706
  }
2627
2707
  try {
2628
2708
  console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
2629
- const podfilePath = path13.join(rootDir, "Podfile");
2630
- if (!fs12.existsSync(podfilePath)) {
2709
+ const podfilePath = path14.join(rootDir, "Podfile");
2710
+ if (!fs13.existsSync(podfilePath)) {
2631
2711
  throw new Error(`Podfile not found at ${podfilePath}`);
2632
2712
  }
2633
2713
  console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
@@ -2653,7 +2733,7 @@ async function setupCocoaPods(rootDir) {
2653
2733
  // src/ios/create.ts
2654
2734
  import { randomBytes } from "crypto";
2655
2735
  function readAndSubstituteTemplate3(templatePath, vars) {
2656
- const raw = fs13.readFileSync(templatePath, "utf-8");
2736
+ const raw = fs14.readFileSync(templatePath, "utf-8");
2657
2737
  return Object.entries(vars).reduce(
2658
2738
  (s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
2659
2739
  raw
@@ -2676,17 +2756,17 @@ var create2 = () => {
2676
2756
  process.exit(1);
2677
2757
  }
2678
2758
  const iosDir = config.paths?.iosDir ?? "ios";
2679
- const rootDir = path14.join(process.cwd(), iosDir);
2680
- const projectDir = path14.join(rootDir, appName);
2681
- const xcodeprojDir = path14.join(rootDir, `${appName}.xcodeproj`);
2759
+ const rootDir = path15.join(process.cwd(), iosDir);
2760
+ const projectDir = path15.join(rootDir, appName);
2761
+ const xcodeprojDir = path15.join(rootDir, `${appName}.xcodeproj`);
2682
2762
  const bridgingHeader = `${appName}-Bridging-Header.h`;
2683
2763
  function writeFile2(filePath, content) {
2684
- fs13.mkdirSync(path14.dirname(filePath), { recursive: true });
2685
- fs13.writeFileSync(filePath, content.trimStart(), "utf8");
2764
+ fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
2765
+ fs14.writeFileSync(filePath, content.trimStart(), "utf8");
2686
2766
  }
2687
- if (fs13.existsSync(rootDir)) {
2767
+ if (fs14.existsSync(rootDir)) {
2688
2768
  console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
2689
- fs13.rmSync(rootDir, { recursive: true, force: true });
2769
+ fs14.rmSync(rootDir, { recursive: true, force: true });
2690
2770
  }
2691
2771
  console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
2692
2772
  const ids = {
@@ -2722,9 +2802,11 @@ var create2 = () => {
2722
2802
  targetDebugConfig: generateId(),
2723
2803
  targetReleaseConfig: generateId()
2724
2804
  };
2725
- writeFile2(path14.join(rootDir, "Podfile"), `
2805
+ writeFile2(path15.join(rootDir, "Podfile"), `
2726
2806
  source 'https://cdn.cocoapods.org/'
2727
2807
 
2808
+ install! 'cocoapods', :incremental_installation => true, :generate_multiple_pod_projects => true
2809
+
2728
2810
  platform :ios, '13.0'
2729
2811
 
2730
2812
  target '${appName}' do
@@ -2807,15 +2889,15 @@ end
2807
2889
  const hostPkg = findTamerHostPackage(process.cwd());
2808
2890
  const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
2809
2891
  if (hostPkg) {
2810
- const templateDir = path14.join(hostPkg, "ios", "templates");
2892
+ const templateDir = path15.join(hostPkg, "ios", "templates");
2811
2893
  for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
2812
- const srcPath = path14.join(templateDir, f);
2813
- if (fs13.existsSync(srcPath)) {
2814
- writeFile2(path14.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
2894
+ const srcPath = path15.join(templateDir, f);
2895
+ if (fs14.existsSync(srcPath)) {
2896
+ writeFile2(path15.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
2815
2897
  }
2816
2898
  }
2817
2899
  } else {
2818
- writeFile2(path14.join(projectDir, "AppDelegate.swift"), `
2900
+ writeFile2(path15.join(projectDir, "AppDelegate.swift"), `
2819
2901
  import UIKit
2820
2902
 
2821
2903
  @UIApplicationMain
@@ -2830,7 +2912,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2830
2912
  }
2831
2913
  }
2832
2914
  `);
2833
- writeFile2(path14.join(projectDir, "SceneDelegate.swift"), `
2915
+ writeFile2(path15.join(projectDir, "SceneDelegate.swift"), `
2834
2916
  import UIKit
2835
2917
 
2836
2918
  class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@@ -2844,7 +2926,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
2844
2926
  }
2845
2927
  }
2846
2928
  `);
2847
- writeFile2(path14.join(projectDir, "ViewController.swift"), `
2929
+ writeFile2(path15.join(projectDir, "ViewController.swift"), `
2848
2930
  import UIKit
2849
2931
  import Lynx
2850
2932
  import tamerinsets
@@ -2917,7 +2999,7 @@ class ViewController: UIViewController {
2917
2999
  }
2918
3000
  }
2919
3001
  `);
2920
- writeFile2(path14.join(projectDir, "LynxProvider.swift"), `
3002
+ writeFile2(path15.join(projectDir, "LynxProvider.swift"), `
2921
3003
  import Foundation
2922
3004
 
2923
3005
  class LynxProvider: NSObject, LynxTemplateProvider {
@@ -2936,7 +3018,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
2936
3018
  }
2937
3019
  }
2938
3020
  `);
2939
- writeFile2(path14.join(projectDir, "LynxInitProcessor.swift"), `
3021
+ writeFile2(path15.join(projectDir, "LynxInitProcessor.swift"), `
2940
3022
  // Copyright 2024 The Lynx Authors. All rights reserved.
2941
3023
  // Licensed under the Apache License Version 2.0 that can be found in the
2942
3024
  // LICENSE file in the root directory of this source tree.
@@ -2976,7 +3058,7 @@ final class LynxInitProcessor {
2976
3058
  }
2977
3059
  `);
2978
3060
  }
2979
- writeFile2(path14.join(projectDir, bridgingHeader), `
3061
+ writeFile2(path15.join(projectDir, bridgingHeader), `
2980
3062
  #import <Lynx/LynxConfig.h>
2981
3063
  #import <Lynx/LynxEnv.h>
2982
3064
  #import <Lynx/LynxTemplateProvider.h>
@@ -2985,7 +3067,7 @@ final class LynxInitProcessor {
2985
3067
  #import <SDWebImage/SDWebImage.h>
2986
3068
  #import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
2987
3069
  `);
2988
- writeFile2(path14.join(projectDir, "Info.plist"), `
3070
+ writeFile2(path15.join(projectDir, "Info.plist"), `
2989
3071
  <?xml version="1.0" encoding="UTF-8"?>
2990
3072
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2991
3073
  <plist version="1.0">
@@ -3043,21 +3125,21 @@ final class LynxInitProcessor {
3043
3125
  </dict>
3044
3126
  </plist>
3045
3127
  `);
3046
- const appIconDir = path14.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
3047
- fs13.mkdirSync(appIconDir, { recursive: true });
3128
+ const appIconDir = path15.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
3129
+ fs14.mkdirSync(appIconDir, { recursive: true });
3048
3130
  const iconPaths = resolveIconPaths(process.cwd(), config);
3049
3131
  if (applyIosAppIconAssets(appIconDir, iconPaths)) {
3050
3132
  console.log(iconPaths?.ios ? "\u2705 Copied iOS icon from tamer.config.json icon.ios" : "\u2705 Copied app icon from tamer.config.json icon.source");
3051
3133
  } else {
3052
- writeFile2(path14.join(appIconDir, "Contents.json"), `
3134
+ writeFile2(path15.join(appIconDir, "Contents.json"), `
3053
3135
  {
3054
3136
  "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
3055
3137
  "info" : { "author" : "xcode", "version" : 1 }
3056
3138
  }
3057
3139
  `);
3058
3140
  }
3059
- fs13.mkdirSync(xcodeprojDir, { recursive: true });
3060
- writeFile2(path14.join(xcodeprojDir, "project.pbxproj"), `
3141
+ fs14.mkdirSync(xcodeprojDir, { recursive: true });
3142
+ writeFile2(path15.join(xcodeprojDir, "project.pbxproj"), `
3061
3143
  // !$*UTF8*$!
3062
3144
  {
3063
3145
  archiveVersion = 1;
@@ -3343,8 +3425,8 @@ final class LynxInitProcessor {
3343
3425
  var create_default2 = create2;
3344
3426
 
3345
3427
  // src/ios/autolink.ts
3346
- import fs15 from "fs";
3347
- import path16 from "path";
3428
+ import fs16 from "fs";
3429
+ import path17 from "path";
3348
3430
  import { execSync as execSync6 } from "child_process";
3349
3431
 
3350
3432
  // src/common/hostNativeModulesManifest.ts
@@ -3355,8 +3437,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
3355
3437
  }
3356
3438
 
3357
3439
  // src/ios/syncHost.ts
3358
- import fs14 from "fs";
3359
- import path15 from "path";
3440
+ import fs15 from "fs";
3441
+ import path16 from "path";
3360
3442
  import crypto from "crypto";
3361
3443
  function deterministicUUID(seed) {
3362
3444
  return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
@@ -3404,7 +3486,7 @@ function getLaunchScreenStoryboard() {
3404
3486
  `;
3405
3487
  }
3406
3488
  function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
3407
- let content = fs14.readFileSync(pbxprojPath, "utf8");
3489
+ let content = fs15.readFileSync(pbxprojPath, "utf8");
3408
3490
  if (content.includes("LaunchScreen.storyboard")) return;
3409
3491
  const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
3410
3492
  const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
@@ -3441,11 +3523,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
3441
3523
  );
3442
3524
  content = content.replace(groupPattern, `$1
3443
3525
  ${variantGroupUUID} /* LaunchScreen.storyboard */,`);
3444
- fs14.writeFileSync(pbxprojPath, content, "utf8");
3526
+ fs15.writeFileSync(pbxprojPath, content, "utf8");
3445
3527
  console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
3446
3528
  }
3447
3529
  function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
3448
- let content = fs14.readFileSync(pbxprojPath, "utf8");
3530
+ let content = fs15.readFileSync(pbxprojPath, "utf8");
3449
3531
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3450
3532
  if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
3451
3533
  const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
@@ -3470,11 +3552,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
3470
3552
  );
3471
3553
  content = content.replace(groupPattern, `$1
3472
3554
  ${fileRefUUID} /* ${filename} */,`);
3473
- fs14.writeFileSync(pbxprojPath, content, "utf8");
3555
+ fs15.writeFileSync(pbxprojPath, content, "utf8");
3474
3556
  console.log(`\u2705 Registered ${filename} in Xcode project sources`);
3475
3557
  }
3476
3558
  function addResourceToXcodeProject(pbxprojPath, appName, filename) {
3477
- let content = fs14.readFileSync(pbxprojPath, "utf8");
3559
+ let content = fs15.readFileSync(pbxprojPath, "utf8");
3478
3560
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3479
3561
  if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
3480
3562
  const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
@@ -3499,12 +3581,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
3499
3581
  );
3500
3582
  content = content.replace(groupPattern, `$1
3501
3583
  ${fileRefUUID} /* ${filename} */,`);
3502
- fs14.writeFileSync(pbxprojPath, content, "utf8");
3584
+ fs15.writeFileSync(pbxprojPath, content, "utf8");
3503
3585
  console.log(`\u2705 Registered ${filename} in Xcode project resources`);
3504
3586
  }
3505
3587
  function writeFile(filePath, content) {
3506
- fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
3507
- fs14.writeFileSync(filePath, content, "utf8");
3588
+ fs15.mkdirSync(path16.dirname(filePath), { recursive: true });
3589
+ fs15.writeFileSync(filePath, content, "utf8");
3508
3590
  }
3509
3591
  function getAppDelegateSwift() {
3510
3592
  return `import UIKit
@@ -3721,8 +3803,8 @@ class ViewController: UIViewController {
3721
3803
  `;
3722
3804
  }
3723
3805
  function patchInfoPlist(infoPlistPath) {
3724
- if (!fs14.existsSync(infoPlistPath)) return;
3725
- let content = fs14.readFileSync(infoPlistPath, "utf8");
3806
+ if (!fs15.existsSync(infoPlistPath)) return;
3807
+ let content = fs15.readFileSync(infoPlistPath, "utf8");
3726
3808
  content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
3727
3809
  if (!content.includes("UILaunchStoryboardName")) {
3728
3810
  content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
@@ -3754,7 +3836,7 @@ function patchInfoPlist(infoPlistPath) {
3754
3836
  </plist>`);
3755
3837
  console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
3756
3838
  }
3757
- fs14.writeFileSync(infoPlistPath, content, "utf8");
3839
+ fs15.writeFileSync(infoPlistPath, content, "utf8");
3758
3840
  }
3759
3841
  function getSimpleLynxProviderSwift() {
3760
3842
  return `import Foundation
@@ -3779,9 +3861,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
3779
3861
  }
3780
3862
  function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
3781
3863
  if (devClientPkg) {
3782
- const tplPath = path15.join(devClientPkg, "ios", "templates", templateName);
3783
- if (fs14.existsSync(tplPath)) {
3784
- let content = fs14.readFileSync(tplPath, "utf8");
3864
+ const tplPath = path16.join(devClientPkg, "ios", "templates", templateName);
3865
+ if (fs15.existsSync(tplPath)) {
3866
+ let content = fs15.readFileSync(tplPath, "utf8");
3785
3867
  for (const [k, v] of Object.entries(vars)) {
3786
3868
  content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
3787
3869
  }
@@ -3799,19 +3881,19 @@ function syncHostIos(opts) {
3799
3881
  if (!appName) {
3800
3882
  throw new Error('"ios.appName" must be defined in tamer.config.json');
3801
3883
  }
3802
- const projectDir = path15.join(resolved.iosDir, appName);
3803
- const infoPlistPath = path15.join(projectDir, "Info.plist");
3804
- if (!fs14.existsSync(projectDir)) {
3884
+ const projectDir = path16.join(resolved.iosDir, appName);
3885
+ const infoPlistPath = path16.join(projectDir, "Info.plist");
3886
+ if (!fs15.existsSync(projectDir)) {
3805
3887
  throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
3806
3888
  }
3807
- const pbxprojPath = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
3808
- const baseLprojDir = path15.join(projectDir, "Base.lproj");
3809
- const launchScreenPath = path15.join(baseLprojDir, "LaunchScreen.storyboard");
3889
+ const pbxprojPath = path16.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
3890
+ const baseLprojDir = path16.join(projectDir, "Base.lproj");
3891
+ const launchScreenPath = path16.join(baseLprojDir, "LaunchScreen.storyboard");
3810
3892
  patchInfoPlist(infoPlistPath);
3811
- writeFile(path15.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
3812
- writeFile(path15.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
3813
- if (!fs14.existsSync(launchScreenPath)) {
3814
- fs14.mkdirSync(baseLprojDir, { recursive: true });
3893
+ writeFile(path16.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
3894
+ writeFile(path16.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
3895
+ if (!fs15.existsSync(launchScreenPath)) {
3896
+ fs15.mkdirSync(baseLprojDir, { recursive: true });
3815
3897
  writeFile(launchScreenPath, getLaunchScreenStoryboard());
3816
3898
  addLaunchScreenToXcodeProject(pbxprojPath, appName);
3817
3899
  }
@@ -3820,33 +3902,33 @@ function syncHostIos(opts) {
3820
3902
  const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
3821
3903
  const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
3822
3904
  const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
3823
- writeFile(path15.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
3824
- writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
3905
+ writeFile(path16.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
3906
+ writeFile(path16.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
3825
3907
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
3826
3908
  const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
3827
3909
  if (devTPContent) {
3828
- writeFile(path15.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
3910
+ writeFile(path16.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
3829
3911
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
3830
3912
  }
3831
3913
  const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
3832
3914
  if (projectVCContent) {
3833
- writeFile(path15.join(projectDir, "ProjectViewController.swift"), projectVCContent);
3915
+ writeFile(path16.join(projectDir, "ProjectViewController.swift"), projectVCContent);
3834
3916
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
3835
3917
  }
3836
3918
  const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
3837
3919
  if (devCMContent) {
3838
- writeFile(path15.join(projectDir, "DevClientManager.swift"), devCMContent);
3920
+ writeFile(path16.join(projectDir, "DevClientManager.swift"), devCMContent);
3839
3921
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
3840
3922
  }
3841
3923
  const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
3842
3924
  if (qrContent) {
3843
- writeFile(path15.join(projectDir, "QRScannerViewController.swift"), qrContent);
3925
+ writeFile(path16.join(projectDir, "QRScannerViewController.swift"), qrContent);
3844
3926
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
3845
3927
  }
3846
3928
  console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
3847
3929
  } else {
3848
- writeFile(path15.join(projectDir, "ViewController.swift"), getViewControllerSwift());
3849
- writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
3930
+ writeFile(path16.join(projectDir, "ViewController.swift"), getViewControllerSwift());
3931
+ writeFile(path16.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
3850
3932
  addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
3851
3933
  console.log("\u2705 Synced iOS host app controller files");
3852
3934
  }
@@ -3854,7 +3936,7 @@ function syncHostIos(opts) {
3854
3936
  var syncHost_default = syncHostIos;
3855
3937
 
3856
3938
  // src/ios/autolink.ts
3857
- var autolink2 = () => {
3939
+ var autolink2 = (syncHostOpts) => {
3858
3940
  let resolved;
3859
3941
  try {
3860
3942
  resolved = resolveHostPaths();
@@ -3865,11 +3947,11 @@ var autolink2 = () => {
3865
3947
  const projectRoot = resolved.projectRoot;
3866
3948
  const iosProjectPath = resolved.iosDir;
3867
3949
  function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
3868
- if (!fs15.existsSync(filePath)) {
3950
+ if (!fs16.existsSync(filePath)) {
3869
3951
  console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
3870
3952
  return;
3871
3953
  }
3872
- let fileContent = fs15.readFileSync(filePath, "utf8");
3954
+ let fileContent = fs16.readFileSync(filePath, "utf8");
3873
3955
  const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3874
3956
  const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3875
3957
  const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
@@ -3889,33 +3971,33 @@ ${replacementBlock}
3889
3971
  `;
3890
3972
  }
3891
3973
  } else {
3892
- console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path16.basename(filePath)}. Appending to the end of the file.`);
3974
+ console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path17.basename(filePath)}. Appending to the end of the file.`);
3893
3975
  fileContent += `
3894
3976
  ${replacementBlock}
3895
3977
  `;
3896
3978
  }
3897
- fs15.writeFileSync(filePath, fileContent, "utf8");
3898
- console.log(`\u2705 Updated autolinked section in ${path16.basename(filePath)}`);
3979
+ fs16.writeFileSync(filePath, fileContent, "utf8");
3980
+ console.log(`\u2705 Updated autolinked section in ${path17.basename(filePath)}`);
3899
3981
  }
3900
3982
  function resolvePodDirectory(pkg) {
3901
- const configuredDir = path16.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
3902
- if (fs15.existsSync(configuredDir)) {
3983
+ const configuredDir = path17.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
3984
+ if (fs16.existsSync(configuredDir)) {
3903
3985
  return configuredDir;
3904
3986
  }
3905
- const iosDir = path16.join(pkg.packagePath, "ios");
3906
- if (fs15.existsSync(iosDir)) {
3987
+ const iosDir = path17.join(pkg.packagePath, "ios");
3988
+ if (fs16.existsSync(iosDir)) {
3907
3989
  const stack = [iosDir];
3908
3990
  while (stack.length > 0) {
3909
3991
  const current = stack.pop();
3910
3992
  try {
3911
- const entries = fs15.readdirSync(current, { withFileTypes: true });
3993
+ const entries = fs16.readdirSync(current, { withFileTypes: true });
3912
3994
  const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
3913
3995
  if (podspec) {
3914
3996
  return current;
3915
3997
  }
3916
3998
  for (const entry of entries) {
3917
3999
  if (entry.isDirectory()) {
3918
- stack.push(path16.join(current, entry.name));
4000
+ stack.push(path17.join(current, entry.name));
3919
4001
  }
3920
4002
  }
3921
4003
  } catch {
@@ -3926,9 +4008,9 @@ ${replacementBlock}
3926
4008
  }
3927
4009
  function resolvePodName(pkg) {
3928
4010
  const fullPodspecDir = resolvePodDirectory(pkg);
3929
- if (fs15.existsSync(fullPodspecDir)) {
4011
+ if (fs16.existsSync(fullPodspecDir)) {
3930
4012
  try {
3931
- const files = fs15.readdirSync(fullPodspecDir);
4013
+ const files = fs16.readdirSync(fullPodspecDir);
3932
4014
  const podspecFile = files.find((f) => f.endsWith(".podspec"));
3933
4015
  if (podspecFile) return podspecFile.replace(".podspec", "");
3934
4016
  } catch {
@@ -3937,13 +4019,13 @@ ${replacementBlock}
3937
4019
  return pkg.name.split("/").pop().replace(/-/g, "");
3938
4020
  }
3939
4021
  function updatePodfile(packages) {
3940
- const podfilePath = path16.join(iosProjectPath, "Podfile");
4022
+ const podfilePath = path17.join(iosProjectPath, "Podfile");
3941
4023
  let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
3942
4024
  # Manual edits will be overwritten.`;
3943
4025
  const iosPackages = packages.filter((p) => p.config.ios);
3944
4026
  if (iosPackages.length > 0) {
3945
4027
  iosPackages.forEach((pkg) => {
3946
- const relativePath = path16.relative(iosProjectPath, resolvePodDirectory(pkg));
4028
+ const relativePath = path17.relative(iosProjectPath, resolvePodDirectory(pkg));
3947
4029
  const podName = resolvePodName(pkg);
3948
4030
  scriptContent += `
3949
4031
  pod '${podName}', :path => '${relativePath}'`;
@@ -3955,9 +4037,9 @@ ${replacementBlock}
3955
4037
  updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
3956
4038
  }
3957
4039
  function ensureXElementPod() {
3958
- const podfilePath = path16.join(iosProjectPath, "Podfile");
3959
- if (!fs15.existsSync(podfilePath)) return;
3960
- let content = fs15.readFileSync(podfilePath, "utf8");
4040
+ const podfilePath = path17.join(iosProjectPath, "Podfile");
4041
+ if (!fs16.existsSync(podfilePath)) return;
4042
+ let content = fs16.readFileSync(podfilePath, "utf8");
3961
4043
  if (content.includes("pod 'XElement'")) return;
3962
4044
  const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
3963
4045
  const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
@@ -3982,13 +4064,51 @@ ${replacementBlock}
3982
4064
  `;
3983
4065
  }
3984
4066
  }
3985
- fs15.writeFileSync(podfilePath, content, "utf8");
4067
+ fs16.writeFileSync(podfilePath, content, "utf8");
3986
4068
  console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
3987
4069
  }
4070
+ function ensureLynxDevToolPods(packages) {
4071
+ const hasDevClient = packages.some(
4072
+ (p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client"
4073
+ );
4074
+ if (!hasDevClient) return;
4075
+ const podfilePath = path17.join(iosProjectPath, "Podfile");
4076
+ if (!fs16.existsSync(podfilePath)) return;
4077
+ let content = fs16.readFileSync(podfilePath, "utf8");
4078
+ if (content.includes("pod 'LynxDevtool'") || content.includes('pod "LynxDevtool"')) return;
4079
+ const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
4080
+ const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
4081
+ if (!content.includes("'Devtool'") && !content.includes('"Devtool"')) {
4082
+ content = content.replace(
4083
+ /(\s+'Http',\s*\n)(\s*\])/,
4084
+ "$1 'Devtool',\n$2"
4085
+ );
4086
+ }
4087
+ const devtoolLine = ` pod 'LynxDevtool', '${lynxVersion}'
4088
+
4089
+ `;
4090
+ if (content.includes("# GENERATED AUTOLINK DEPENDENCIES START")) {
4091
+ content = content.replace(/(# GENERATED AUTOLINK DEPENDENCIES START)/, `${devtoolLine}$1`);
4092
+ } else {
4093
+ const insertAfter = /pod\s+'LynxService'[^\n]*(?:\n\s*'[^']*',?\s*)*\]\s*/;
4094
+ const serviceMatch = content.match(insertAfter);
4095
+ if (serviceMatch) {
4096
+ const idx = serviceMatch.index + serviceMatch[0].length;
4097
+ content = content.slice(0, idx) + `
4098
+ pod 'LynxDevtool', '${lynxVersion}'` + content.slice(idx);
4099
+ } else {
4100
+ content += `
4101
+ pod 'LynxDevtool', '${lynxVersion}'
4102
+ `;
4103
+ }
4104
+ }
4105
+ fs16.writeFileSync(podfilePath, content, "utf8");
4106
+ console.log(`\u2705 Added Lynx DevTool pods (v${lynxVersion}) to Podfile`);
4107
+ }
3988
4108
  function ensureLynxPatchInPodfile() {
3989
- const podfilePath = path16.join(iosProjectPath, "Podfile");
3990
- if (!fs15.existsSync(podfilePath)) return;
3991
- let content = fs15.readFileSync(podfilePath, "utf8");
4109
+ const podfilePath = path17.join(iosProjectPath, "Podfile");
4110
+ if (!fs16.existsSync(podfilePath)) return;
4111
+ let content = fs16.readFileSync(podfilePath, "utf8");
3992
4112
  if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
3993
4113
  const patch = `
3994
4114
  Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
@@ -4000,13 +4120,13 @@ ${replacementBlock}
4000
4120
  end`;
4001
4121
  content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
4002
4122
  $2`);
4003
- fs15.writeFileSync(podfilePath, content, "utf8");
4123
+ fs16.writeFileSync(podfilePath, content, "utf8");
4004
4124
  console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
4005
4125
  }
4006
4126
  function ensurePodBuildSettings() {
4007
- const podfilePath = path16.join(iosProjectPath, "Podfile");
4008
- if (!fs15.existsSync(podfilePath)) return;
4009
- let content = fs15.readFileSync(podfilePath, "utf8");
4127
+ const podfilePath = path17.join(iosProjectPath, "Podfile");
4128
+ if (!fs16.existsSync(podfilePath)) return;
4129
+ let content = fs16.readFileSync(podfilePath, "utf8");
4010
4130
  let changed = false;
4011
4131
  if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
4012
4132
  content = content.replace(
@@ -4049,7 +4169,7 @@ $2`);
4049
4169
  changed = true;
4050
4170
  }
4051
4171
  if (changed) {
4052
- fs15.writeFileSync(podfilePath, content, "utf8");
4172
+ fs16.writeFileSync(podfilePath, content, "utf8");
4053
4173
  console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
4054
4174
  }
4055
4175
  }
@@ -4057,10 +4177,10 @@ $2`);
4057
4177
  const appNameFromConfig = resolved.config.ios?.appName;
4058
4178
  const candidatePaths = [];
4059
4179
  if (appNameFromConfig) {
4060
- candidatePaths.push(path16.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
4180
+ candidatePaths.push(path17.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
4061
4181
  }
4062
- candidatePaths.push(path16.join(iosProjectPath, "LynxInitProcessor.swift"));
4063
- const found = candidatePaths.find((p) => fs15.existsSync(p));
4182
+ candidatePaths.push(path17.join(iosProjectPath, "LynxInitProcessor.swift"));
4183
+ const found = candidatePaths.find((p) => fs16.existsSync(p));
4064
4184
  const lynxInitPath = found ?? candidatePaths[0];
4065
4185
  const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
4066
4186
  const seenModules = /* @__PURE__ */ new Set();
@@ -4099,7 +4219,7 @@ $2`);
4099
4219
  const podName = resolvePodName(pkg);
4100
4220
  return `import ${podName}`;
4101
4221
  }).join("\n");
4102
- const fileContent = fs15.readFileSync(filePath, "utf8");
4222
+ const fileContent = fs16.readFileSync(filePath, "utf8");
4103
4223
  if (fileContent.indexOf(startMarker) !== -1) {
4104
4224
  updateGeneratedSection(filePath, imports, startMarker, endMarker);
4105
4225
  return;
@@ -4136,8 +4256,8 @@ ${after}`;
4136
4256
  ${fileContent}`;
4137
4257
  }
4138
4258
  }
4139
- fs15.writeFileSync(filePath, newContent, "utf8");
4140
- console.log(`\u2705 Updated imports in ${path16.basename(filePath)}`);
4259
+ fs16.writeFileSync(filePath, newContent, "utf8");
4260
+ console.log(`\u2705 Updated imports in ${path17.basename(filePath)}`);
4141
4261
  }
4142
4262
  updateImportsSection(lynxInitPath, importPackages);
4143
4263
  if (importPackages.length === 0) {
@@ -4181,7 +4301,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
4181
4301
  } else {
4182
4302
  devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
4183
4303
  }
4184
- if (fs15.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
4304
+ if (fs16.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
4185
4305
  updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
4186
4306
  }
4187
4307
  }
@@ -4189,13 +4309,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
4189
4309
  const appNameFromConfig = resolved.config.ios?.appName;
4190
4310
  const candidates = [];
4191
4311
  if (appNameFromConfig) {
4192
- candidates.push(path16.join(iosProjectPath, appNameFromConfig, "Info.plist"));
4312
+ candidates.push(path17.join(iosProjectPath, appNameFromConfig, "Info.plist"));
4193
4313
  }
4194
- candidates.push(path16.join(iosProjectPath, "Info.plist"));
4195
- return candidates.find((p) => fs15.existsSync(p)) ?? null;
4314
+ candidates.push(path17.join(iosProjectPath, "Info.plist"));
4315
+ return candidates.find((p) => fs16.existsSync(p)) ?? null;
4196
4316
  }
4197
4317
  function readPlistXml(plistPath) {
4198
- return fs15.readFileSync(plistPath, "utf8");
4318
+ return fs16.readFileSync(plistPath, "utf8");
4199
4319
  }
4200
4320
  function syncInfoPlistPermissions(packages) {
4201
4321
  const plistPath = findInfoPlist();
@@ -4226,7 +4346,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
4226
4346
  added++;
4227
4347
  }
4228
4348
  if (added > 0) {
4229
- fs15.writeFileSync(plistPath, plist, "utf8");
4349
+ fs16.writeFileSync(plistPath, plist, "utf8");
4230
4350
  console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
4231
4351
  }
4232
4352
  }
@@ -4273,16 +4393,16 @@ ${schemesXml}
4273
4393
  $1`
4274
4394
  );
4275
4395
  }
4276
- fs15.writeFileSync(plistPath, plist, "utf8");
4396
+ fs16.writeFileSync(plistPath, plist, "utf8");
4277
4397
  console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
4278
4398
  }
4279
4399
  function runPodInstall(forcePath) {
4280
- const podfilePath = forcePath ?? path16.join(iosProjectPath, "Podfile");
4281
- if (!fs15.existsSync(podfilePath)) {
4400
+ const podfilePath = forcePath ?? path17.join(iosProjectPath, "Podfile");
4401
+ if (!fs16.existsSync(podfilePath)) {
4282
4402
  console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
4283
4403
  return;
4284
4404
  }
4285
- const cwd = path16.dirname(podfilePath);
4405
+ const cwd = path17.dirname(podfilePath);
4286
4406
  try {
4287
4407
  console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
4288
4408
  try {
@@ -4305,9 +4425,10 @@ $1`
4305
4425
  } else {
4306
4426
  console.log("\u2139\uFE0F No Tamer4Lynx native packages found.");
4307
4427
  }
4308
- syncHost_default();
4428
+ syncHost_default(syncHostOpts);
4309
4429
  updatePodfile(packages);
4310
4430
  ensureXElementPod();
4431
+ ensureLynxDevToolPods(discoverModules(projectRoot));
4311
4432
  ensureLynxPatchInPodfile();
4312
4433
  ensurePodBuildSettings();
4313
4434
  updateLynxInitProcessor(packages);
@@ -4316,8 +4437,8 @@ $1`
4316
4437
  syncInfoPlistUrlSchemes();
4317
4438
  const appNameFromConfig = resolved.config.ios?.appName;
4318
4439
  if (appNameFromConfig) {
4319
- const appPodfile = path16.join(iosProjectPath, appNameFromConfig, "Podfile");
4320
- if (fs15.existsSync(appPodfile)) {
4440
+ const appPodfile = path17.join(iosProjectPath, appNameFromConfig, "Podfile");
4441
+ if (fs16.existsSync(appPodfile)) {
4321
4442
  runPodInstall(appPodfile);
4322
4443
  console.log("\u2728 Autolinking complete for iOS.");
4323
4444
  return;
@@ -4332,13 +4453,13 @@ $1`
4332
4453
  const appFolder = resolved.config.ios?.appName;
4333
4454
  if (!hasDevClient || !appFolder) return;
4334
4455
  const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
4335
- const appDir = path16.join(iosProjectPath, appFolder);
4336
- fs15.mkdirSync(appDir, { recursive: true });
4337
- const manifestPath = path16.join(appDir, TAMER_HOST_NATIVE_MODULES_FILENAME);
4338
- fs15.writeFileSync(manifestPath, buildHostNativeModulesManifestJson(androidNames), "utf8");
4456
+ const appDir = path17.join(iosProjectPath, appFolder);
4457
+ fs16.mkdirSync(appDir, { recursive: true });
4458
+ const manifestPath = path17.join(appDir, TAMER_HOST_NATIVE_MODULES_FILENAME);
4459
+ fs16.writeFileSync(manifestPath, buildHostNativeModulesManifestJson(androidNames), "utf8");
4339
4460
  console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
4340
- const pbxprojPath = path16.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
4341
- if (fs15.existsSync(pbxprojPath)) {
4461
+ const pbxprojPath = path17.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
4462
+ if (fs16.existsSync(pbxprojPath)) {
4342
4463
  addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
4343
4464
  }
4344
4465
  }
@@ -4347,8 +4468,8 @@ $1`
4347
4468
  var autolink_default2 = autolink2;
4348
4469
 
4349
4470
  // src/ios/bundle.ts
4350
- import fs16 from "fs";
4351
- import path17 from "path";
4471
+ import fs17 from "fs";
4472
+ import path18 from "path";
4352
4473
  import { execSync as execSync7 } from "child_process";
4353
4474
  function bundleAndDeploy2(opts = {}) {
4354
4475
  const release = opts.release === true || opts.production === true;
@@ -4366,20 +4487,19 @@ function bundleAndDeploy2(opts = {}) {
4366
4487
  const includeDevClient = !release && !!devClientPkg;
4367
4488
  const appName = resolved.config.ios.appName;
4368
4489
  const sourceBundlePath = resolved.lynxBundlePath;
4369
- const destinationDir = path17.join(resolved.iosDir, appName);
4370
- const destinationBundlePath = path17.join(destinationDir, resolved.lynxBundleFile);
4371
- syncHost_default({ release, includeDevClient });
4372
- autolink_default2();
4490
+ const destinationDir = path18.join(resolved.iosDir, appName);
4491
+ const destinationBundlePath = path18.join(destinationDir, resolved.lynxBundleFile);
4492
+ autolink_default2({ release, includeDevClient });
4373
4493
  const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
4374
4494
  if (iconPaths) {
4375
- const appIconDir = path17.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
4495
+ const appIconDir = path18.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
4376
4496
  if (applyIosAppIconAssets(appIconDir, iconPaths)) {
4377
4497
  console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
4378
4498
  }
4379
4499
  }
4380
4500
  try {
4381
- const lynxTsconfig = path17.join(resolved.lynxProjectDir, "tsconfig.json");
4382
- if (fs16.existsSync(lynxTsconfig)) {
4501
+ const lynxTsconfig = path18.join(resolved.lynxProjectDir, "tsconfig.json");
4502
+ if (fs17.existsSync(lynxTsconfig)) {
4383
4503
  fixTsconfigReferencesForBuild(lynxTsconfig);
4384
4504
  }
4385
4505
  console.log("\u{1F4E6} Building Lynx bundle...");
@@ -4390,40 +4510,40 @@ function bundleAndDeploy2(opts = {}) {
4390
4510
  process.exit(1);
4391
4511
  }
4392
4512
  try {
4393
- if (!fs16.existsSync(sourceBundlePath)) {
4513
+ if (!fs17.existsSync(sourceBundlePath)) {
4394
4514
  console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
4395
4515
  process.exit(1);
4396
4516
  }
4397
- if (!fs16.existsSync(destinationDir)) {
4517
+ if (!fs17.existsSync(destinationDir)) {
4398
4518
  console.error(`Destination directory not found at: ${destinationDir}`);
4399
4519
  process.exit(1);
4400
4520
  }
4401
- const distDir = path17.dirname(sourceBundlePath);
4521
+ const distDir = path18.dirname(sourceBundlePath);
4402
4522
  console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
4403
4523
  copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
4404
4524
  console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
4405
- const pbxprojPath = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
4406
- if (fs16.existsSync(pbxprojPath)) {
4525
+ const pbxprojPath = path18.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
4526
+ if (fs17.existsSync(pbxprojPath)) {
4407
4527
  const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
4408
- for (const entry of fs16.readdirSync(distDir)) {
4409
- if (skip.has(entry) || fs16.statSync(path17.join(distDir, entry)).isDirectory()) continue;
4528
+ for (const entry of fs17.readdirSync(distDir)) {
4529
+ if (skip.has(entry) || fs17.statSync(path18.join(distDir, entry)).isDirectory()) continue;
4410
4530
  addResourceToXcodeProject(pbxprojPath, appName, entry);
4411
4531
  }
4412
4532
  }
4413
4533
  if (includeDevClient && devClientPkg) {
4414
- const devClientBundle = path17.join(destinationDir, "dev-client.lynx.bundle");
4534
+ const devClientBundle = path18.join(destinationDir, "dev-client.lynx.bundle");
4415
4535
  console.log("\u{1F4E6} Building dev-client bundle...");
4416
4536
  try {
4417
4537
  execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
4418
4538
  } catch {
4419
4539
  console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
4420
4540
  }
4421
- const builtBundle = path17.join(devClientPkg, "dist", "dev-client.lynx.bundle");
4422
- if (fs16.existsSync(builtBundle)) {
4423
- fs16.copyFileSync(builtBundle, devClientBundle);
4541
+ const builtBundle = path18.join(devClientPkg, "dist", "dev-client.lynx.bundle");
4542
+ if (fs17.existsSync(builtBundle)) {
4543
+ fs17.copyFileSync(builtBundle, devClientBundle);
4424
4544
  console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
4425
- const pbxprojPath2 = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
4426
- if (fs16.existsSync(pbxprojPath2)) {
4545
+ const pbxprojPath2 = path18.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
4546
+ if (fs17.existsSync(pbxprojPath2)) {
4427
4547
  addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
4428
4548
  }
4429
4549
  }
@@ -4437,9 +4557,10 @@ function bundleAndDeploy2(opts = {}) {
4437
4557
  var bundle_default2 = bundleAndDeploy2;
4438
4558
 
4439
4559
  // src/ios/build.ts
4440
- import fs17 from "fs";
4441
- import path18 from "path";
4560
+ import fs18 from "fs";
4561
+ import path19 from "path";
4442
4562
  import os3 from "os";
4563
+ import { randomBytes as randomBytes2 } from "crypto";
4443
4564
  import { execSync as execSync8 } from "child_process";
4444
4565
  function hostArch() {
4445
4566
  return os3.arch() === "arm64" ? "arm64" : "x86_64";
@@ -4457,6 +4578,31 @@ function findBootedSimulator() {
4457
4578
  }
4458
4579
  return null;
4459
4580
  }
4581
+ function findFirstConnectedIosDeviceUdid() {
4582
+ const jsonPath = path19.join(os3.tmpdir(), `t4l-devicectl-${randomBytes2(8).toString("hex")}.json`);
4583
+ try {
4584
+ execSync8(`xcrun devicectl list devices --json-output "${jsonPath}"`, {
4585
+ stdio: ["pipe", "pipe", "pipe"]
4586
+ });
4587
+ if (!fs18.existsSync(jsonPath)) return null;
4588
+ const raw = fs18.readFileSync(jsonPath, "utf8");
4589
+ fs18.unlinkSync(jsonPath);
4590
+ const data = JSON.parse(raw);
4591
+ const devices = data.result?.devices ?? [];
4592
+ for (const d of devices) {
4593
+ const udid = d.hardwareProperties?.udid ?? d.identifier;
4594
+ if (typeof udid === "string" && udid.length >= 20) {
4595
+ return udid;
4596
+ }
4597
+ }
4598
+ } catch {
4599
+ try {
4600
+ if (fs18.existsSync(jsonPath)) fs18.unlinkSync(jsonPath);
4601
+ } catch {
4602
+ }
4603
+ }
4604
+ return null;
4605
+ }
4460
4606
  async function buildIpa(opts = {}) {
4461
4607
  const resolved = resolveHostPaths();
4462
4608
  if (!resolved.config.ios?.appName) {
@@ -4469,17 +4615,19 @@ async function buildIpa(opts = {}) {
4469
4615
  const configuration = release ? "Release" : "Debug";
4470
4616
  bundle_default2({ release, production: opts.production });
4471
4617
  const scheme = appName;
4472
- const workspacePath = path18.join(iosDir, `${appName}.xcworkspace`);
4473
- const projectPath = path18.join(iosDir, `${appName}.xcodeproj`);
4474
- const xcproject = fs17.existsSync(workspacePath) ? workspacePath : projectPath;
4618
+ const workspacePath = path19.join(iosDir, `${appName}.xcworkspace`);
4619
+ const projectPath = path19.join(iosDir, `${appName}.xcodeproj`);
4620
+ const xcproject = fs18.existsSync(workspacePath) ? workspacePath : projectPath;
4475
4621
  const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
4476
- const derivedDataPath = path18.join(iosDir, "build");
4477
- const sdk = opts.install ? "iphonesimulator" : "iphoneos";
4478
- const signingArgs = opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
4479
- const archFlag = opts.install ? `-arch ${hostArch()} ` : "";
4622
+ const derivedDataPath = path19.join(iosDir, "build");
4623
+ const production = opts.production === true;
4624
+ const sdk = production ? "iphoneos" : opts.install ? "iphonesimulator" : "iphoneos";
4625
+ const signingArgs = production || opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
4626
+ const archFlag = opts.install && !production ? `-arch ${hostArch()} ` : "";
4480
4627
  const extraSettings = [
4481
4628
  "ONLY_ACTIVE_ARCH=YES",
4482
- "CLANG_ENABLE_EXPLICIT_MODULES=NO"
4629
+ "CLANG_ENABLE_EXPLICIT_MODULES=NO",
4630
+ ...configuration === "Debug" ? ["COMPILER_INDEX_STORE_ENABLE=NO"] : []
4483
4631
  ].join(" ");
4484
4632
  console.log(`
4485
4633
  \u{1F528} Building ${configuration} (${sdk})...`);
@@ -4489,38 +4637,77 @@ async function buildIpa(opts = {}) {
4489
4637
  );
4490
4638
  console.log(`\u2705 Build completed.`);
4491
4639
  if (opts.install) {
4492
- const appGlob = path18.join(
4493
- derivedDataPath,
4494
- "Build",
4495
- "Products",
4496
- `${configuration}-iphonesimulator`,
4497
- `${appName}.app`
4498
- );
4499
- if (!fs17.existsSync(appGlob)) {
4500
- console.error(`\u274C Built app not found at: ${appGlob}`);
4501
- process.exit(1);
4502
- }
4503
- const udid = findBootedSimulator();
4504
- if (!udid) {
4505
- console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
4506
- process.exit(1);
4507
- }
4508
- console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
4509
- execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
4510
- if (bundleId) {
4511
- console.log(`\u{1F680} Launching ${bundleId}...`);
4512
- execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
4513
- console.log("\u2705 App launched.");
4640
+ if (production) {
4641
+ const appPath = path19.join(
4642
+ derivedDataPath,
4643
+ "Build",
4644
+ "Products",
4645
+ `${configuration}-iphoneos`,
4646
+ `${appName}.app`
4647
+ );
4648
+ if (!fs18.existsSync(appPath)) {
4649
+ console.error(`\u274C Built app not found at: ${appPath}`);
4650
+ process.exit(1);
4651
+ }
4652
+ const udid = findFirstConnectedIosDeviceUdid();
4653
+ if (!udid) {
4654
+ console.error(
4655
+ "\u274C No connected iOS device found. Connect an iPhone/iPad with a trusted cable, unlock it, and ensure Developer Mode is on (iOS 16+). Requires Xcode 15+ (`xcrun devicectl`)."
4656
+ );
4657
+ process.exit(1);
4658
+ }
4659
+ console.log(`\u{1F4F2} Installing on device ${udid}...`);
4660
+ execSync8(`xcrun devicectl device install app --device "${udid}" "${appPath}"`, {
4661
+ stdio: "inherit"
4662
+ });
4663
+ if (bundleId) {
4664
+ console.log(`\u{1F680} Launching ${bundleId}...`);
4665
+ try {
4666
+ execSync8(
4667
+ `xcrun devicectl device process launch --device "${udid}" "${bundleId}"`,
4668
+ { stdio: "inherit" }
4669
+ );
4670
+ console.log("\u2705 App launched.");
4671
+ } catch {
4672
+ console.log("\u2705 Installed. Launch manually on the device if auto-launch failed.");
4673
+ }
4674
+ } else {
4675
+ console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
4676
+ }
4514
4677
  } else {
4515
- console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
4678
+ const appGlob = path19.join(
4679
+ derivedDataPath,
4680
+ "Build",
4681
+ "Products",
4682
+ `${configuration}-iphonesimulator`,
4683
+ `${appName}.app`
4684
+ );
4685
+ if (!fs18.existsSync(appGlob)) {
4686
+ console.error(`\u274C Built app not found at: ${appGlob}`);
4687
+ process.exit(1);
4688
+ }
4689
+ const udid = findBootedSimulator();
4690
+ if (!udid) {
4691
+ console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
4692
+ process.exit(1);
4693
+ }
4694
+ console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
4695
+ execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
4696
+ if (bundleId) {
4697
+ console.log(`\u{1F680} Launching ${bundleId}...`);
4698
+ execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
4699
+ console.log("\u2705 App launched.");
4700
+ } else {
4701
+ console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
4702
+ }
4516
4703
  }
4517
4704
  }
4518
4705
  }
4519
4706
  var build_default2 = buildIpa;
4520
4707
 
4521
4708
  // src/common/init.tsx
4522
- import fs18 from "fs";
4523
- import path19 from "path";
4709
+ import fs19 from "fs";
4710
+ import path20 from "path";
4524
4711
  import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
4525
4712
  import { render, Text as Text9, Box as Box8 } from "ink";
4526
4713
 
@@ -4678,7 +4865,9 @@ function Wizard({ step, total, title, children }) {
4678
4865
  import "react";
4679
4866
  import { Box as Box7, Text as Text8 } from "ink";
4680
4867
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4868
+ var LOG_DISPLAY_MAX = 120;
4681
4869
  function ServerDashboard({
4870
+ cliVersion,
4682
4871
  projectName,
4683
4872
  port,
4684
4873
  lanIp,
@@ -4691,7 +4880,6 @@ function ServerDashboard({
4691
4880
  buildError,
4692
4881
  wsConnections,
4693
4882
  logLines,
4694
- showLogs,
4695
4883
  qrLines,
4696
4884
  phase,
4697
4885
  startError
@@ -4710,6 +4898,10 @@ function ServerDashboard({
4710
4898
  projectName,
4711
4899
  ")"
4712
4900
  ] }),
4901
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
4902
+ "t4l v",
4903
+ cliVersion
4904
+ ] }),
4713
4905
  verbose ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Logs: verbose (native + JS)" }) : null,
4714
4906
  /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", columnGap: 3, alignItems: "flex-start", children: [
4715
4907
  qrLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
@@ -4746,7 +4938,7 @@ function ServerDashboard({
4746
4938
  bonjour ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "mDNS: _tamer._tcp" }) : null,
4747
4939
  /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4748
4940
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Build" }),
4749
- buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "Building\u2026" }) : buildPhase === "error" ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: buildError ?? "Build failed" }) : /* @__PURE__ */ jsx8(Text8, { color: "green", children: "Ready" })
4941
+ buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "building\u2026" }) : buildPhase === "error" ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: buildError ?? "Build failed" }) : buildPhase === "success" ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "Lynx bundle ready" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2014" })
4750
4942
  ] }),
4751
4943
  /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4752
4944
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Connections" }),
@@ -4759,15 +4951,15 @@ function ServerDashboard({
4759
4951
  }
4760
4952
  )
4761
4953
  ] }),
4762
- showLogs && logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
4763
- /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
4764
- "Build / output (last ",
4765
- logLines.length,
4766
- " lines)"
4767
- ] }),
4768
- logLines.slice(-12).map((line, i) => /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: line }, i))
4769
- ] }) : null,
4770
- /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 l toggle logs \xB7 q quit" }) })
4954
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 c clear output \xB7 Ctrl+C or q quit" }) }),
4955
+ logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4956
+ logLines.length > LOG_DISPLAY_MAX ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
4957
+ "\u2026 ",
4958
+ logLines.length - LOG_DISPLAY_MAX,
4959
+ " earlier lines omitted"
4960
+ ] }) : null,
4961
+ logLines.slice(-LOG_DISPLAY_MAX).map((line, i) => /* @__PURE__ */ jsx8(Text8, { children: line }, i))
4962
+ ] }) : null
4771
4963
  ] });
4772
4964
  }
4773
4965
 
@@ -4833,22 +5025,22 @@ function InitWizard() {
4833
5025
  paths: { androidDir: "android", iosDir: "ios" }
4834
5026
  };
4835
5027
  if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
4836
- const configPath = path19.join(process.cwd(), "tamer.config.json");
4837
- fs18.writeFileSync(configPath, JSON.stringify(config, null, 2));
5028
+ const configPath = path20.join(process.cwd(), "tamer.config.json");
5029
+ fs19.writeFileSync(configPath, JSON.stringify(config, null, 2));
4838
5030
  const lines = [`Generated tamer.config.json at ${configPath}`];
4839
5031
  const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
4840
5032
  const tsconfigCandidates = lynxProject.trim() ? [
4841
- path19.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
4842
- path19.join(process.cwd(), "tsconfig.json")
4843
- ] : [path19.join(process.cwd(), "tsconfig.json")];
5033
+ path20.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
5034
+ path20.join(process.cwd(), "tsconfig.json")
5035
+ ] : [path20.join(process.cwd(), "tsconfig.json")];
4844
5036
  for (const tsconfigPath of tsconfigCandidates) {
4845
- if (!fs18.existsSync(tsconfigPath)) continue;
5037
+ if (!fs19.existsSync(tsconfigPath)) continue;
4846
5038
  try {
4847
5039
  if (fixTsconfigReferencesForBuild(tsconfigPath)) {
4848
- lines.push(`Flattened ${path19.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
5040
+ lines.push(`Flattened ${path20.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
4849
5041
  }
4850
5042
  if (addTamerTypesInclude(tsconfigPath, tamerTypesInclude)) {
4851
- lines.push(`Updated ${path19.relative(process.cwd(), tsconfigPath)} for tamer types`);
5043
+ lines.push(`Updated ${path20.relative(process.cwd(), tsconfigPath)} for tamer types`);
4852
5044
  }
4853
5045
  break;
4854
5046
  } catch (e) {
@@ -5008,8 +5200,8 @@ async function init() {
5008
5200
  }
5009
5201
 
5010
5202
  // src/common/create.ts
5011
- import fs19 from "fs";
5012
- import path20 from "path";
5203
+ import fs20 from "fs";
5204
+ import path21 from "path";
5013
5205
  import readline from "readline";
5014
5206
  var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
5015
5207
  function ask(question) {
@@ -5071,13 +5263,13 @@ async function create3(opts) {
5071
5263
  const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
5072
5264
  const fullModuleClassName = `${packageName}.${simpleModuleName}`;
5073
5265
  const cwd = process.cwd();
5074
- const root = path20.join(cwd, extName);
5075
- if (fs19.existsSync(root)) {
5266
+ const root = path21.join(cwd, extName);
5267
+ if (fs20.existsSync(root)) {
5076
5268
  console.error(`\u274C Directory ${extName} already exists.`);
5077
5269
  rl.close();
5078
5270
  process.exit(1);
5079
5271
  }
5080
- fs19.mkdirSync(root, { recursive: true });
5272
+ fs20.mkdirSync(root, { recursive: true });
5081
5273
  const lynxExt = {
5082
5274
  platforms: {
5083
5275
  android: {
@@ -5092,7 +5284,7 @@ async function create3(opts) {
5092
5284
  web: {}
5093
5285
  }
5094
5286
  };
5095
- fs19.writeFileSync(path20.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
5287
+ fs20.writeFileSync(path21.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
5096
5288
  const pkg = {
5097
5289
  name: extName,
5098
5290
  version: "0.0.1",
@@ -5105,20 +5297,20 @@ async function create3(opts) {
5105
5297
  engines: { node: ">=18" }
5106
5298
  };
5107
5299
  if (includeModule) pkg.types = "src/index.d.ts";
5108
- fs19.writeFileSync(path20.join(root, "package.json"), JSON.stringify(pkg, null, 2));
5300
+ fs20.writeFileSync(path21.join(root, "package.json"), JSON.stringify(pkg, null, 2));
5109
5301
  const pkgPath = packageName.replace(/\./g, "/");
5110
5302
  const hasSrc = includeModule || includeElement || includeService;
5111
5303
  if (hasSrc) {
5112
- fs19.mkdirSync(path20.join(root, "src"), { recursive: true });
5304
+ fs20.mkdirSync(path21.join(root, "src"), { recursive: true });
5113
5305
  }
5114
5306
  if (includeModule) {
5115
- fs19.writeFileSync(path20.join(root, "src", "index.d.ts"), `/** @lynxmodule */
5307
+ fs20.writeFileSync(path21.join(root, "src", "index.d.ts"), `/** @lynxmodule */
5116
5308
  export declare class ${simpleModuleName} {
5117
5309
  // Add your module methods here
5118
5310
  }
5119
5311
  `);
5120
- fs19.mkdirSync(path20.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
5121
- fs19.writeFileSync(path20.join(root, "android", "build.gradle.kts"), `plugins {
5312
+ fs20.mkdirSync(path21.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
5313
+ fs20.writeFileSync(path21.join(root, "android", "build.gradle.kts"), `plugins {
5122
5314
  id("com.android.library")
5123
5315
  id("org.jetbrains.kotlin.android")
5124
5316
  }
@@ -5139,7 +5331,7 @@ dependencies {
5139
5331
  implementation(libs.lynx.jssdk)
5140
5332
  }
5141
5333
  `);
5142
- fs19.writeFileSync(path20.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
5334
+ fs20.writeFileSync(path21.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
5143
5335
  <manifest />
5144
5336
  `);
5145
5337
  const ktContent = `package ${packageName}
@@ -5156,8 +5348,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
5156
5348
  }
5157
5349
  }
5158
5350
  `;
5159
- fs19.writeFileSync(path20.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
5160
- fs19.mkdirSync(path20.join(root, "ios", extName, extName, "Classes"), { recursive: true });
5351
+ fs20.writeFileSync(path21.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
5352
+ fs20.mkdirSync(path21.join(root, "ios", extName, extName, "Classes"), { recursive: true });
5161
5353
  const podspec = `Pod::Spec.new do |s|
5162
5354
  s.name = '${extName}'
5163
5355
  s.version = '0.0.1'
@@ -5171,7 +5363,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
5171
5363
  s.dependency 'Lynx'
5172
5364
  end
5173
5365
  `;
5174
- fs19.writeFileSync(path20.join(root, "ios", extName, `${extName}.podspec`), podspec);
5366
+ fs20.writeFileSync(path21.join(root, "ios", extName, `${extName}.podspec`), podspec);
5175
5367
  const swiftContent = `import Foundation
5176
5368
 
5177
5369
  @objc public class ${simpleModuleName}: NSObject {
@@ -5180,18 +5372,18 @@ end
5180
5372
  }
5181
5373
  }
5182
5374
  `;
5183
- fs19.writeFileSync(path20.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
5375
+ fs20.writeFileSync(path21.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
5184
5376
  }
5185
5377
  if (includeElement && !includeModule) {
5186
5378
  const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
5187
- fs19.writeFileSync(path20.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
5379
+ fs20.writeFileSync(path21.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
5188
5380
 
5189
5381
  export const ${elementName}: FC = () => {
5190
5382
  return null;
5191
5383
  };
5192
5384
  `);
5193
5385
  }
5194
- fs19.writeFileSync(path20.join(root, "index.js"), `'use strict';
5386
+ fs20.writeFileSync(path21.join(root, "index.js"), `'use strict';
5195
5387
  module.exports = {};
5196
5388
  `);
5197
5389
  const tsconfigCompiler = {
@@ -5204,11 +5396,11 @@ module.exports = {};
5204
5396
  tsconfigCompiler.jsx = "preserve";
5205
5397
  tsconfigCompiler.jsxImportSource = "@lynx-js/react";
5206
5398
  }
5207
- fs19.writeFileSync(path20.join(root, "tsconfig.json"), JSON.stringify({
5399
+ fs20.writeFileSync(path21.join(root, "tsconfig.json"), JSON.stringify({
5208
5400
  compilerOptions: tsconfigCompiler,
5209
5401
  include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
5210
5402
  }, null, 2));
5211
- fs19.writeFileSync(path20.join(root, "README.md"), `# ${extName}
5403
+ fs20.writeFileSync(path21.join(root, "README.md"), `# ${extName}
5212
5404
 
5213
5405
  Lynx extension for ${extName}.
5214
5406
 
@@ -5233,8 +5425,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
5233
5425
  var create_default3 = create3;
5234
5426
 
5235
5427
  // src/common/codegen.ts
5236
- import fs20 from "fs";
5237
- import path21 from "path";
5428
+ import fs21 from "fs";
5429
+ import path22 from "path";
5238
5430
  function codegen() {
5239
5431
  const cwd = process.cwd();
5240
5432
  const config = loadExtensionConfig(cwd);
@@ -5242,9 +5434,9 @@ function codegen() {
5242
5434
  console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
5243
5435
  process.exit(1);
5244
5436
  }
5245
- const srcDir = path21.join(cwd, "src");
5246
- const generatedDir = path21.join(cwd, "generated");
5247
- fs20.mkdirSync(generatedDir, { recursive: true });
5437
+ const srcDir = path22.join(cwd, "src");
5438
+ const generatedDir = path22.join(cwd, "generated");
5439
+ fs21.mkdirSync(generatedDir, { recursive: true });
5248
5440
  const dtsFiles = findDtsFiles(srcDir);
5249
5441
  const modules = extractLynxModules(dtsFiles);
5250
5442
  if (modules.length === 0) {
@@ -5254,28 +5446,28 @@ function codegen() {
5254
5446
  for (const mod of modules) {
5255
5447
  const tsContent = `export type { ${mod} } from '../src/index.js';
5256
5448
  `;
5257
- const outPath = path21.join(generatedDir, `${mod}.ts`);
5258
- fs20.writeFileSync(outPath, tsContent);
5449
+ const outPath = path22.join(generatedDir, `${mod}.ts`);
5450
+ fs21.writeFileSync(outPath, tsContent);
5259
5451
  console.log(`\u2705 Generated ${outPath}`);
5260
5452
  }
5261
5453
  if (config.android) {
5262
- const androidGenerated = path21.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
5263
- fs20.mkdirSync(androidGenerated, { recursive: true });
5454
+ const androidGenerated = path22.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
5455
+ fs21.mkdirSync(androidGenerated, { recursive: true });
5264
5456
  console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
5265
5457
  }
5266
5458
  if (config.ios) {
5267
- const iosGenerated = path21.join(cwd, "ios", "generated");
5268
- fs20.mkdirSync(iosGenerated, { recursive: true });
5459
+ const iosGenerated = path22.join(cwd, "ios", "generated");
5460
+ fs21.mkdirSync(iosGenerated, { recursive: true });
5269
5461
  console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
5270
5462
  }
5271
5463
  console.log("\u2728 Codegen complete.");
5272
5464
  }
5273
5465
  function findDtsFiles(dir) {
5274
5466
  const result = [];
5275
- if (!fs20.existsSync(dir)) return result;
5276
- const entries = fs20.readdirSync(dir, { withFileTypes: true });
5467
+ if (!fs21.existsSync(dir)) return result;
5468
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
5277
5469
  for (const e of entries) {
5278
- const full = path21.join(dir, e.name);
5470
+ const full = path22.join(dir, e.name);
5279
5471
  if (e.isDirectory()) result.push(...findDtsFiles(full));
5280
5472
  else if (e.name.endsWith(".d.ts")) result.push(full);
5281
5473
  }
@@ -5285,7 +5477,7 @@ function extractLynxModules(files) {
5285
5477
  const modules = [];
5286
5478
  const seen = /* @__PURE__ */ new Set();
5287
5479
  for (const file of files) {
5288
- const content = fs20.readFileSync(file, "utf8");
5480
+ const content = fs21.readFileSync(file, "utf8");
5289
5481
  const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
5290
5482
  let m;
5291
5483
  while ((m = regex.exec(content)) !== null) {
@@ -5302,14 +5494,16 @@ var codegen_default = codegen;
5302
5494
  // src/common/devServer.tsx
5303
5495
  import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
5304
5496
  import { spawn } from "child_process";
5305
- import fs21 from "fs";
5497
+ import fs22 from "fs";
5306
5498
  import http from "http";
5307
5499
  import os4 from "os";
5308
- import path22 from "path";
5500
+ import path23 from "path";
5309
5501
  import { render as render2, useInput, useApp } from "ink";
5310
- import { WebSocketServer } from "ws";
5502
+ import { WebSocket, WebSocketServer } from "ws";
5311
5503
  import { jsx as jsx10 } from "react/jsx-runtime";
5312
5504
  var DEFAULT_PORT = 3e3;
5505
+ var TAMER_CLI_VERSION = getCliVersion();
5506
+ var MAX_LOG_LINES = 800;
5313
5507
  var STATIC_MIME = {
5314
5508
  ".png": "image/png",
5315
5509
  ".jpg": "image/jpeg",
@@ -5321,13 +5515,13 @@ var STATIC_MIME = {
5321
5515
  ".pdf": "application/pdf"
5322
5516
  };
5323
5517
  function sendFileFromDisk(res, absPath) {
5324
- fs21.readFile(absPath, (err, data) => {
5518
+ fs22.readFile(absPath, (err, data) => {
5325
5519
  if (err) {
5326
5520
  res.writeHead(404);
5327
5521
  res.end("Not found");
5328
5522
  return;
5329
5523
  }
5330
- const ext = path22.extname(absPath).toLowerCase();
5524
+ const ext = path23.extname(absPath).toLowerCase();
5331
5525
  res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
5332
5526
  res.setHeader("Access-Control-Allow-Origin", "*");
5333
5527
  res.end(data);
@@ -5364,9 +5558,9 @@ function getLanIp() {
5364
5558
  return "localhost";
5365
5559
  }
5366
5560
  function detectPackageManager(cwd) {
5367
- const dir = path22.resolve(cwd);
5368
- if (fs21.existsSync(path22.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
5369
- if (fs21.existsSync(path22.join(dir, "bun.lockb")) || fs21.existsSync(path22.join(dir, "bun.lock")))
5561
+ const dir = path23.resolve(cwd);
5562
+ if (fs22.existsSync(path23.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
5563
+ if (fs22.existsSync(path23.join(dir, "bun.lockb")) || fs22.existsSync(path23.join(dir, "bun.lock")))
5370
5564
  return { cmd: "bun", args: ["run", "build"] };
5371
5565
  return { cmd: "npm", args: ["run", "build"] };
5372
5566
  }
@@ -5383,7 +5577,6 @@ var initialUi = () => ({
5383
5577
  buildPhase: "idle",
5384
5578
  wsConnections: 0,
5385
5579
  logLines: [],
5386
- showLogs: false,
5387
5580
  qrLines: []
5388
5581
  });
5389
5582
  function DevServerApp({ verbose }) {
@@ -5396,13 +5589,20 @@ function DevServerApp({ verbose }) {
5396
5589
  const cleanupRef = useRef(null);
5397
5590
  const rebuildRef = useRef(() => Promise.resolve());
5398
5591
  const quitOnceRef = useRef(false);
5399
- const appendLog = useCallback4((chunk) => {
5400
- const lines = chunk.split(/\r?\n/).filter(Boolean);
5592
+ const appendLogLine = useCallback4((line) => {
5401
5593
  setUi((prev) => ({
5402
5594
  ...prev,
5403
- logLines: [...prev.logLines, ...lines].slice(-400)
5595
+ logLines: [...prev.logLines, line].slice(-MAX_LOG_LINES)
5404
5596
  }));
5405
5597
  }, []);
5598
+ const appendLog = useCallback4(
5599
+ (chunk) => {
5600
+ for (const line of chunk.split(/\r?\n/)) {
5601
+ appendLogLine(line);
5602
+ }
5603
+ },
5604
+ [appendLogLine]
5605
+ );
5406
5606
  const handleQuit = useCallback4(() => {
5407
5607
  if (quitOnceRef.current) return;
5408
5608
  quitOnceRef.current = true;
@@ -5410,7 +5610,7 @@ function DevServerApp({ verbose }) {
5410
5610
  exit();
5411
5611
  }, [exit]);
5412
5612
  useInput((input, key) => {
5413
- if (key.ctrl && key.name === "c") {
5613
+ if (key.ctrl && input === "c") {
5414
5614
  handleQuit();
5415
5615
  return;
5416
5616
  }
@@ -5422,8 +5622,9 @@ function DevServerApp({ verbose }) {
5422
5622
  void rebuildRef.current();
5423
5623
  return;
5424
5624
  }
5425
- if (input === "l") {
5426
- setUi((s) => ({ ...s, showLogs: !s.showLogs }));
5625
+ if (input === "c") {
5626
+ setUi((s) => ({ ...s, logLines: [] }));
5627
+ return;
5427
5628
  }
5428
5629
  });
5429
5630
  useEffect3(() => {
@@ -5446,8 +5647,8 @@ function DevServerApp({ verbose }) {
5446
5647
  try {
5447
5648
  const resolved = resolveHostPaths();
5448
5649
  const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
5449
- const distDir = path22.dirname(lynxBundlePath);
5450
- const projectName = path22.basename(lynxProjectDir);
5650
+ const distDir = path23.dirname(lynxBundlePath);
5651
+ const projectName = path23.basename(lynxProjectDir);
5451
5652
  const basePath = `/${projectName}`;
5452
5653
  setUi((s) => ({ ...s, projectName, lynxBundleFile }));
5453
5654
  const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
@@ -5457,18 +5658,18 @@ function DevServerApp({ verbose }) {
5457
5658
  }
5458
5659
  const iconPaths = resolveIconPaths(projectRoot, config);
5459
5660
  let iconFilePath = null;
5460
- if (iconPaths?.source && fs21.statSync(iconPaths.source).isFile()) {
5661
+ if (iconPaths?.source && fs22.statSync(iconPaths.source).isFile()) {
5461
5662
  iconFilePath = iconPaths.source;
5462
- } else if (iconPaths?.androidAdaptiveForeground && fs21.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
5663
+ } else if (iconPaths?.androidAdaptiveForeground && fs22.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
5463
5664
  iconFilePath = iconPaths.androidAdaptiveForeground;
5464
5665
  } else if (iconPaths?.android) {
5465
- const androidIcon = path22.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
5466
- if (fs21.existsSync(androidIcon)) iconFilePath = androidIcon;
5666
+ const androidIcon = path23.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
5667
+ if (fs22.existsSync(androidIcon)) iconFilePath = androidIcon;
5467
5668
  } else if (iconPaths?.ios) {
5468
- const iosIcon = path22.join(iconPaths.ios, "Icon-1024.png");
5469
- if (fs21.existsSync(iosIcon)) iconFilePath = iosIcon;
5669
+ const iosIcon = path23.join(iconPaths.ios, "Icon-1024.png");
5670
+ if (fs22.existsSync(iosIcon)) iconFilePath = iosIcon;
5470
5671
  }
5471
- const iconExt = iconFilePath ? path22.extname(iconFilePath) || ".png" : "";
5672
+ const iconExt = iconFilePath ? path23.extname(iconFilePath) || ".png" : "";
5472
5673
  const runBuild = () => {
5473
5674
  return new Promise((resolve, reject) => {
5474
5675
  const { cmd, args } = detectPackageManager(lynxProjectDir);
@@ -5477,19 +5678,15 @@ function DevServerApp({ verbose }) {
5477
5678
  stdio: "pipe",
5478
5679
  shell: process.platform === "win32"
5479
5680
  });
5480
- let stderr = "";
5481
- buildProcess.stdout?.on("data", (d) => {
5482
- appendLog(d.toString());
5483
- });
5681
+ let stderrRaw = "";
5682
+ buildProcess.stdout?.resume();
5484
5683
  buildProcess.stderr?.on("data", (d) => {
5485
- const t = d.toString();
5486
- stderr += t;
5487
- appendLog(t);
5684
+ stderrRaw += d.toString();
5488
5685
  });
5489
5686
  buildProcess.on("close", (code) => {
5490
5687
  buildProcess = null;
5491
5688
  if (code === 0) resolve();
5492
- else reject(new Error(stderr || `Build exited ${code}`));
5689
+ else reject(new Error(stderrRaw.trim() || `Build exited ${code}`));
5493
5690
  });
5494
5691
  });
5495
5692
  };
@@ -5554,7 +5751,7 @@ function DevServerApp({ verbose }) {
5554
5751
  return;
5555
5752
  }
5556
5753
  if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
5557
- fs21.readFile(iconFilePath, (err, data) => {
5754
+ fs22.readFile(iconFilePath, (err, data) => {
5558
5755
  if (err) {
5559
5756
  res.writeHead(404);
5560
5757
  res.end();
@@ -5580,20 +5777,20 @@ function DevServerApp({ verbose }) {
5580
5777
  res.end();
5581
5778
  return;
5582
5779
  }
5583
- const safe = path22.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
5584
- if (path22.isAbsolute(safe) || safe.startsWith("..")) {
5780
+ const safe = path23.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
5781
+ if (path23.isAbsolute(safe) || safe.startsWith("..")) {
5585
5782
  res.writeHead(403);
5586
5783
  res.end();
5587
5784
  return;
5588
5785
  }
5589
- const allowedRoot = path22.resolve(lynxProjectDir, rootSub);
5590
- const abs = path22.resolve(allowedRoot, safe);
5591
- if (!abs.startsWith(allowedRoot + path22.sep) && abs !== allowedRoot) {
5786
+ const allowedRoot = path23.resolve(lynxProjectDir, rootSub);
5787
+ const abs = path23.resolve(allowedRoot, safe);
5788
+ if (!abs.startsWith(allowedRoot + path23.sep) && abs !== allowedRoot) {
5592
5789
  res.writeHead(403);
5593
5790
  res.end();
5594
5791
  return;
5595
5792
  }
5596
- if (!fs21.existsSync(abs) || !fs21.statSync(abs).isFile()) {
5793
+ if (!fs22.existsSync(abs) || !fs22.statSync(abs).isFile()) {
5597
5794
  res.writeHead(404);
5598
5795
  res.end("Not found");
5599
5796
  return;
@@ -5607,14 +5804,14 @@ function DevServerApp({ verbose }) {
5607
5804
  reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
5608
5805
  }
5609
5806
  const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
5610
- const filePath = path22.resolve(distDir, relPath);
5611
- const distResolved = path22.resolve(distDir);
5612
- if (!filePath.startsWith(distResolved + path22.sep) && filePath !== distResolved) {
5807
+ const filePath = path23.resolve(distDir, relPath);
5808
+ const distResolved = path23.resolve(distDir);
5809
+ if (!filePath.startsWith(distResolved + path23.sep) && filePath !== distResolved) {
5613
5810
  res.writeHead(403);
5614
5811
  res.end();
5615
5812
  return;
5616
5813
  }
5617
- fs21.readFile(filePath, (err, data) => {
5814
+ fs22.readFile(filePath, (err, data) => {
5618
5815
  if (err) {
5619
5816
  res.writeHead(404);
5620
5817
  res.end("Not found");
@@ -5626,6 +5823,14 @@ function DevServerApp({ verbose }) {
5626
5823
  });
5627
5824
  });
5628
5825
  const wssInst = new WebSocketServer({ noServer: true });
5826
+ const syncWsClientCount = () => {
5827
+ if (!alive) return;
5828
+ let n = 0;
5829
+ wssInst.clients.forEach((c) => {
5830
+ if (c.readyState === WebSocket.OPEN) n++;
5831
+ });
5832
+ setUi((s) => ({ ...s, wsConnections: n }));
5833
+ };
5629
5834
  rebuildRef.current = async () => {
5630
5835
  try {
5631
5836
  await doBuild();
@@ -5647,12 +5852,15 @@ function DevServerApp({ verbose }) {
5647
5852
  });
5648
5853
  wssInst.on("connection", (ws, req) => {
5649
5854
  const clientIp = req.socket.remoteAddress ?? "unknown";
5650
- setUi((s) => ({ ...s, wsConnections: s.wsConnections + 1 }));
5651
5855
  appendLog(`[WS] connected: ${clientIp}`);
5652
5856
  ws.send(JSON.stringify({ type: "connected" }));
5857
+ syncWsClientCount();
5653
5858
  ws.on("close", () => {
5654
- setUi((s) => ({ ...s, wsConnections: Math.max(0, s.wsConnections - 1) }));
5655
5859
  appendLog(`[WS] disconnected: ${clientIp}`);
5860
+ queueMicrotask(() => syncWsClientCount());
5861
+ });
5862
+ ws.on("error", () => {
5863
+ queueMicrotask(() => syncWsClientCount());
5656
5864
  });
5657
5865
  ws.on("message", (data) => {
5658
5866
  try {
@@ -5675,10 +5883,10 @@ function DevServerApp({ verbose }) {
5675
5883
  }
5676
5884
  if (chokidar) {
5677
5885
  const watchPaths = [
5678
- path22.join(lynxProjectDir, "src"),
5679
- path22.join(lynxProjectDir, "lynx.config.ts"),
5680
- path22.join(lynxProjectDir, "lynx.config.js")
5681
- ].filter((p) => fs21.existsSync(p));
5886
+ path23.join(lynxProjectDir, "src"),
5887
+ path23.join(lynxProjectDir, "lynx.config.ts"),
5888
+ path23.join(lynxProjectDir, "lynx.config.js")
5889
+ ].filter((p) => fs22.existsSync(p));
5682
5890
  if (watchPaths.length > 0) {
5683
5891
  const w = chokidar.watch(watchPaths, { ignoreInitial: true });
5684
5892
  w.on("change", async () => {
@@ -5758,10 +5966,11 @@ function DevServerApp({ verbose }) {
5758
5966
  alive = false;
5759
5967
  void cleanupRef.current?.();
5760
5968
  };
5761
- }, [appendLog, verbose]);
5969
+ }, [appendLog, appendLogLine, verbose]);
5762
5970
  return /* @__PURE__ */ jsx10(
5763
5971
  ServerDashboard,
5764
5972
  {
5973
+ cliVersion: TAMER_CLI_VERSION,
5765
5974
  projectName: ui.projectName,
5766
5975
  port: ui.port,
5767
5976
  lanIp: ui.lanIp,
@@ -5774,7 +5983,6 @@ function DevServerApp({ verbose }) {
5774
5983
  buildError: ui.buildError,
5775
5984
  wsConnections: ui.wsConnections,
5776
5985
  logLines: ui.logLines,
5777
- showLogs: ui.showLogs,
5778
5986
  qrLines: ui.qrLines,
5779
5987
  phase: ui.phase,
5780
5988
  startError: ui.startError
@@ -5798,10 +6006,10 @@ async function start(opts) {
5798
6006
  var start_default = start;
5799
6007
 
5800
6008
  // src/common/injectHost.ts
5801
- import fs22 from "fs";
5802
- import path23 from "path";
6009
+ import fs23 from "fs";
6010
+ import path24 from "path";
5803
6011
  function readAndSubstitute(templatePath, vars) {
5804
- const raw = fs22.readFileSync(templatePath, "utf-8");
6012
+ const raw = fs23.readFileSync(templatePath, "utf-8");
5805
6013
  return Object.entries(vars).reduce(
5806
6014
  (s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
5807
6015
  raw
@@ -5822,32 +6030,32 @@ async function injectHostAndroid(opts) {
5822
6030
  process.exit(1);
5823
6031
  }
5824
6032
  const androidDir = config.paths?.androidDir ?? "android";
5825
- const rootDir = path23.join(projectRoot, androidDir);
6033
+ const rootDir = path24.join(projectRoot, androidDir);
5826
6034
  const packagePath = packageName.replace(/\./g, "/");
5827
- const javaDir = path23.join(rootDir, "app", "src", "main", "java", packagePath);
5828
- const kotlinDir = path23.join(rootDir, "app", "src", "main", "kotlin", packagePath);
5829
- if (!fs22.existsSync(javaDir) || !fs22.existsSync(kotlinDir)) {
6035
+ const javaDir = path24.join(rootDir, "app", "src", "main", "java", packagePath);
6036
+ const kotlinDir = path24.join(rootDir, "app", "src", "main", "kotlin", packagePath);
6037
+ if (!fs23.existsSync(javaDir) || !fs23.existsSync(kotlinDir)) {
5830
6038
  console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
5831
6039
  process.exit(1);
5832
6040
  }
5833
- const templateDir = path23.join(hostPkg, "android", "templates");
6041
+ const templateDir = path24.join(hostPkg, "android", "templates");
5834
6042
  const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
5835
6043
  const files = [
5836
- { src: "App.java", dst: path23.join(javaDir, "App.java") },
5837
- { src: "TemplateProvider.java", dst: path23.join(javaDir, "TemplateProvider.java") },
5838
- { src: "MainActivity.kt", dst: path23.join(kotlinDir, "MainActivity.kt") }
6044
+ { src: "App.java", dst: path24.join(javaDir, "App.java") },
6045
+ { src: "TemplateProvider.java", dst: path24.join(javaDir, "TemplateProvider.java") },
6046
+ { src: "MainActivity.kt", dst: path24.join(kotlinDir, "MainActivity.kt") }
5839
6047
  ];
5840
6048
  for (const { src, dst } of files) {
5841
- const srcPath = path23.join(templateDir, src);
5842
- if (!fs22.existsSync(srcPath)) continue;
5843
- if (fs22.existsSync(dst) && !opts?.force) {
5844
- console.log(`\u23ED\uFE0F Skipping ${path23.basename(dst)} (use --force to overwrite)`);
6049
+ const srcPath = path24.join(templateDir, src);
6050
+ if (!fs23.existsSync(srcPath)) continue;
6051
+ if (fs23.existsSync(dst) && !opts?.force) {
6052
+ console.log(`\u23ED\uFE0F Skipping ${path24.basename(dst)} (use --force to overwrite)`);
5845
6053
  continue;
5846
6054
  }
5847
6055
  const content = readAndSubstitute(srcPath, vars);
5848
- fs22.mkdirSync(path23.dirname(dst), { recursive: true });
5849
- fs22.writeFileSync(dst, content);
5850
- console.log(`\u2705 Injected ${path23.basename(dst)}`);
6056
+ fs23.mkdirSync(path24.dirname(dst), { recursive: true });
6057
+ fs23.writeFileSync(dst, content);
6058
+ console.log(`\u2705 Injected ${path24.basename(dst)}`);
5851
6059
  }
5852
6060
  }
5853
6061
  async function injectHostIos(opts) {
@@ -5865,13 +6073,13 @@ async function injectHostIos(opts) {
5865
6073
  process.exit(1);
5866
6074
  }
5867
6075
  const iosDir = config.paths?.iosDir ?? "ios";
5868
- const rootDir = path23.join(projectRoot, iosDir);
5869
- const projectDir = path23.join(rootDir, appName);
5870
- if (!fs22.existsSync(projectDir)) {
6076
+ const rootDir = path24.join(projectRoot, iosDir);
6077
+ const projectDir = path24.join(rootDir, appName);
6078
+ if (!fs23.existsSync(projectDir)) {
5871
6079
  console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
5872
6080
  process.exit(1);
5873
6081
  }
5874
- const templateDir = path23.join(hostPkg, "ios", "templates");
6082
+ const templateDir = path24.join(hostPkg, "ios", "templates");
5875
6083
  const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
5876
6084
  const files = [
5877
6085
  "AppDelegate.swift",
@@ -5881,22 +6089,22 @@ async function injectHostIos(opts) {
5881
6089
  "LynxInitProcessor.swift"
5882
6090
  ];
5883
6091
  for (const f of files) {
5884
- const srcPath = path23.join(templateDir, f);
5885
- const dstPath = path23.join(projectDir, f);
5886
- if (!fs22.existsSync(srcPath)) continue;
5887
- if (fs22.existsSync(dstPath) && !opts?.force) {
6092
+ const srcPath = path24.join(templateDir, f);
6093
+ const dstPath = path24.join(projectDir, f);
6094
+ if (!fs23.existsSync(srcPath)) continue;
6095
+ if (fs23.existsSync(dstPath) && !opts?.force) {
5888
6096
  console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
5889
6097
  continue;
5890
6098
  }
5891
6099
  const content = readAndSubstitute(srcPath, vars);
5892
- fs22.writeFileSync(dstPath, content);
6100
+ fs23.writeFileSync(dstPath, content);
5893
6101
  console.log(`\u2705 Injected ${f}`);
5894
6102
  }
5895
6103
  }
5896
6104
 
5897
6105
  // src/common/buildEmbeddable.ts
5898
- import fs23 from "fs";
5899
- import path24 from "path";
6106
+ import fs24 from "fs";
6107
+ import path25 from "path";
5900
6108
  import { execSync as execSync9 } from "child_process";
5901
6109
  var EMBEDDABLE_DIR = "embeddable";
5902
6110
  var LIB_PACKAGE = "com.tamer.embeddable";
@@ -5973,14 +6181,14 @@ object LynxEmbeddable {
5973
6181
  }
5974
6182
  `;
5975
6183
  function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
5976
- const libDir = path24.join(androidDir, "lib");
5977
- const libSrcMain = path24.join(libDir, "src", "main");
5978
- const assetsDir = path24.join(libSrcMain, "assets");
5979
- const kotlinDir = path24.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
5980
- const generatedDir = path24.join(kotlinDir, "generated");
5981
- fs23.mkdirSync(path24.join(androidDir, "gradle"), { recursive: true });
5982
- fs23.mkdirSync(generatedDir, { recursive: true });
5983
- fs23.mkdirSync(assetsDir, { recursive: true });
6184
+ const libDir = path25.join(androidDir, "lib");
6185
+ const libSrcMain = path25.join(libDir, "src", "main");
6186
+ const assetsDir = path25.join(libSrcMain, "assets");
6187
+ const kotlinDir = path25.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
6188
+ const generatedDir = path25.join(kotlinDir, "generated");
6189
+ fs24.mkdirSync(path25.join(androidDir, "gradle"), { recursive: true });
6190
+ fs24.mkdirSync(generatedDir, { recursive: true });
6191
+ fs24.mkdirSync(assetsDir, { recursive: true });
5984
6192
  const androidModules = modules.filter((m) => m.config.android);
5985
6193
  const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
5986
6194
  const settingsContent = `pluginManagement {
@@ -6000,7 +6208,7 @@ include(":lib")
6000
6208
  ${androidModules.map((p) => {
6001
6209
  const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
6002
6210
  const sourceDir = p.config.android?.sourceDir || "android";
6003
- const absPath = path24.join(p.packagePath, sourceDir).replace(/\\/g, "/");
6211
+ const absPath = path25.join(p.packagePath, sourceDir).replace(/\\/g, "/");
6004
6212
  return `include(":${gradleName}")
6005
6213
  project(":${gradleName}").projectDir = file("${absPath}")`;
6006
6214
  }).join("\n")}
@@ -6049,10 +6257,10 @@ dependencies {
6049
6257
  ${libDeps}
6050
6258
  }
6051
6259
  `;
6052
- fs23.writeFileSync(path24.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
6053
- fs23.writeFileSync(path24.join(androidDir, "settings.gradle.kts"), settingsContent);
6054
- fs23.writeFileSync(
6055
- path24.join(androidDir, "build.gradle.kts"),
6260
+ fs24.writeFileSync(path25.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
6261
+ fs24.writeFileSync(path25.join(androidDir, "settings.gradle.kts"), settingsContent);
6262
+ fs24.writeFileSync(
6263
+ path25.join(androidDir, "build.gradle.kts"),
6056
6264
  `plugins {
6057
6265
  alias(libs.plugins.android.library) apply false
6058
6266
  alias(libs.plugins.kotlin.android) apply false
@@ -6060,26 +6268,26 @@ ${libDeps}
6060
6268
  }
6061
6269
  `
6062
6270
  );
6063
- fs23.writeFileSync(
6064
- path24.join(androidDir, "gradle.properties"),
6271
+ fs24.writeFileSync(
6272
+ path25.join(androidDir, "gradle.properties"),
6065
6273
  `org.gradle.jvmargs=-Xmx2048m
6066
6274
  android.useAndroidX=true
6067
6275
  kotlin.code.style=official
6068
6276
  `
6069
6277
  );
6070
- fs23.writeFileSync(path24.join(libDir, "build.gradle.kts"), libBuildContent);
6071
- fs23.writeFileSync(
6072
- path24.join(libSrcMain, "AndroidManifest.xml"),
6278
+ fs24.writeFileSync(path25.join(libDir, "build.gradle.kts"), libBuildContent);
6279
+ fs24.writeFileSync(
6280
+ path25.join(libSrcMain, "AndroidManifest.xml"),
6073
6281
  '<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
6074
6282
  );
6075
- fs23.copyFileSync(lynxBundlePath, path24.join(assetsDir, lynxBundleFile));
6076
- fs23.writeFileSync(path24.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
6077
- fs23.writeFileSync(
6078
- path24.join(generatedDir, "GeneratedLynxExtensions.kt"),
6283
+ fs24.copyFileSync(lynxBundlePath, path25.join(assetsDir, lynxBundleFile));
6284
+ fs24.writeFileSync(path25.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
6285
+ fs24.writeFileSync(
6286
+ path25.join(generatedDir, "GeneratedLynxExtensions.kt"),
6079
6287
  generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
6080
6288
  );
6081
- fs23.writeFileSync(
6082
- path24.join(generatedDir, "GeneratedActivityLifecycle.kt"),
6289
+ fs24.writeFileSync(
6290
+ path25.join(generatedDir, "GeneratedActivityLifecycle.kt"),
6083
6291
  generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
6084
6292
  );
6085
6293
  }
@@ -6088,20 +6296,20 @@ async function buildEmbeddable(opts = {}) {
6088
6296
  const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
6089
6297
  console.log("\u{1F4E6} Building Lynx project (release)...");
6090
6298
  execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
6091
- if (!fs23.existsSync(lynxBundlePath)) {
6299
+ if (!fs24.existsSync(lynxBundlePath)) {
6092
6300
  console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
6093
6301
  process.exit(1);
6094
6302
  }
6095
- const outDir = path24.join(projectRoot, EMBEDDABLE_DIR);
6096
- fs23.mkdirSync(outDir, { recursive: true });
6097
- const distDir = path24.dirname(lynxBundlePath);
6303
+ const outDir = path25.join(projectRoot, EMBEDDABLE_DIR);
6304
+ fs24.mkdirSync(outDir, { recursive: true });
6305
+ const distDir = path25.dirname(lynxBundlePath);
6098
6306
  copyDistAssets(distDir, outDir, lynxBundleFile);
6099
6307
  const modules = discoverModules(projectRoot);
6100
6308
  const androidModules = modules.filter((m) => m.config.android);
6101
6309
  const abiFilters = resolveAbiFilters(config);
6102
- const androidDir = path24.join(outDir, "android");
6103
- if (fs23.existsSync(androidDir)) fs23.rmSync(androidDir, { recursive: true });
6104
- fs23.mkdirSync(androidDir, { recursive: true });
6310
+ const androidDir = path25.join(outDir, "android");
6311
+ if (fs24.existsSync(androidDir)) fs24.rmSync(androidDir, { recursive: true });
6312
+ fs24.mkdirSync(androidDir, { recursive: true });
6105
6313
  generateAndroidLibrary(
6106
6314
  outDir,
6107
6315
  androidDir,
@@ -6111,23 +6319,23 @@ async function buildEmbeddable(opts = {}) {
6111
6319
  modules,
6112
6320
  abiFilters
6113
6321
  );
6114
- const gradlewPath = path24.join(androidDir, "gradlew");
6322
+ const gradlewPath = path25.join(androidDir, "gradlew");
6115
6323
  const devAppDir = findDevAppPackage(projectRoot);
6116
6324
  const existingGradleDirs = [
6117
- path24.join(projectRoot, "android"),
6118
- devAppDir ? path24.join(devAppDir, "android") : null
6325
+ path25.join(projectRoot, "android"),
6326
+ devAppDir ? path25.join(devAppDir, "android") : null
6119
6327
  ].filter(Boolean);
6120
6328
  let hasWrapper = false;
6121
6329
  for (const d of existingGradleDirs) {
6122
- if (fs23.existsSync(path24.join(d, "gradlew"))) {
6330
+ if (fs24.existsSync(path25.join(d, "gradlew"))) {
6123
6331
  for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
6124
- const src = path24.join(d, name);
6125
- if (fs23.existsSync(src)) {
6126
- const dest = path24.join(androidDir, name);
6127
- if (fs23.statSync(src).isDirectory()) {
6128
- fs23.cpSync(src, dest, { recursive: true });
6332
+ const src = path25.join(d, name);
6333
+ if (fs24.existsSync(src)) {
6334
+ const dest = path25.join(androidDir, name);
6335
+ if (fs24.statSync(src).isDirectory()) {
6336
+ fs24.cpSync(src, dest, { recursive: true });
6129
6337
  } else {
6130
- fs23.copyFileSync(src, dest);
6338
+ fs24.copyFileSync(src, dest);
6131
6339
  }
6132
6340
  }
6133
6341
  }
@@ -6146,10 +6354,10 @@ async function buildEmbeddable(opts = {}) {
6146
6354
  console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
6147
6355
  throw e;
6148
6356
  }
6149
- const aarSrc = path24.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
6150
- const aarDest = path24.join(outDir, "tamer-embeddable.aar");
6151
- if (fs23.existsSync(aarSrc)) {
6152
- fs23.copyFileSync(aarSrc, aarDest);
6357
+ const aarSrc = path25.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
6358
+ const aarDest = path25.join(outDir, "tamer-embeddable.aar");
6359
+ if (fs24.existsSync(aarSrc)) {
6360
+ fs24.copyFileSync(aarSrc, aarDest);
6153
6361
  console.log(` - tamer-embeddable.aar`);
6154
6362
  }
6155
6363
  const snippetAndroid = `// Add to your app's build.gradle:
@@ -6160,7 +6368,7 @@ async function buildEmbeddable(opts = {}) {
6160
6368
  // LynxEmbeddable.init(applicationContext)
6161
6369
  // val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
6162
6370
  `;
6163
- fs23.writeFileSync(path24.join(outDir, "snippet-android.kt"), snippetAndroid);
6371
+ fs24.writeFileSync(path25.join(outDir, "snippet-android.kt"), snippetAndroid);
6164
6372
  generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
6165
6373
  const readme = `# Embeddable Lynx Bundle
6166
6374
 
@@ -6191,7 +6399,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
6191
6399
 
6192
6400
  - [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
6193
6401
  `;
6194
- fs23.writeFileSync(path24.join(outDir, "README.md"), readme);
6402
+ fs24.writeFileSync(path25.join(outDir, "README.md"), readme);
6195
6403
  console.log(`
6196
6404
  \u2705 Embeddable output at ${outDir}/`);
6197
6405
  console.log(" - main.lynx.bundle");
@@ -6203,20 +6411,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
6203
6411
  console.log(" - README.md");
6204
6412
  }
6205
6413
  function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
6206
- const iosDir = path24.join(outDir, "ios");
6207
- const podDir = path24.join(iosDir, "TamerEmbeddable");
6208
- const resourcesDir = path24.join(podDir, "Resources");
6209
- fs23.mkdirSync(resourcesDir, { recursive: true });
6210
- fs23.copyFileSync(lynxBundlePath, path24.join(resourcesDir, lynxBundleFile));
6414
+ const iosDir = path25.join(outDir, "ios");
6415
+ const podDir = path25.join(iosDir, "TamerEmbeddable");
6416
+ const resourcesDir = path25.join(podDir, "Resources");
6417
+ fs24.mkdirSync(resourcesDir, { recursive: true });
6418
+ fs24.copyFileSync(lynxBundlePath, path25.join(resourcesDir, lynxBundleFile));
6211
6419
  const iosModules = modules.filter((m) => m.config.ios);
6212
6420
  const podDeps = iosModules.map((p) => {
6213
6421
  const podspecPath = p.config.ios?.podspecPath || ".";
6214
- const podspecDir = path24.join(p.packagePath, podspecPath);
6215
- if (!fs23.existsSync(podspecDir)) return null;
6216
- const files = fs23.readdirSync(podspecDir);
6422
+ const podspecDir = path25.join(p.packagePath, podspecPath);
6423
+ if (!fs24.existsSync(podspecDir)) return null;
6424
+ const files = fs24.readdirSync(podspecDir);
6217
6425
  const podspecFile = files.find((f) => f.endsWith(".podspec"));
6218
6426
  const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
6219
- const absPath = path24.resolve(podspecDir);
6427
+ const absPath = path25.resolve(podspecDir);
6220
6428
  return { podName, absPath };
6221
6429
  }).filter(Boolean);
6222
6430
  const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
@@ -6256,9 +6464,9 @@ end
6256
6464
  });
6257
6465
  const swiftImports = iosModules.map((p) => {
6258
6466
  const podspecPath = p.config.ios?.podspecPath || ".";
6259
- const podspecDir = path24.join(p.packagePath, podspecPath);
6260
- if (!fs23.existsSync(podspecDir)) return null;
6261
- const files = fs23.readdirSync(podspecDir);
6467
+ const podspecDir = path25.join(p.packagePath, podspecPath);
6468
+ if (!fs24.existsSync(podspecDir)) return null;
6469
+ const files = fs24.readdirSync(podspecDir);
6262
6470
  const podspecFile = files.find((f) => f.endsWith(".podspec"));
6263
6471
  return podspecFile ? podspecFile.replace(".podspec", "") : null;
6264
6472
  }).filter(Boolean);
@@ -6277,17 +6485,17 @@ ${regBlock}
6277
6485
  }
6278
6486
  }
6279
6487
  `;
6280
- fs23.writeFileSync(path24.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
6281
- fs23.writeFileSync(path24.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
6282
- const absIosDir = path24.resolve(iosDir);
6488
+ fs24.writeFileSync(path25.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
6489
+ fs24.writeFileSync(path25.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
6490
+ const absIosDir = path25.resolve(iosDir);
6283
6491
  const podfileSnippet = `# Paste into your app target in Podfile:
6284
6492
 
6285
6493
  pod 'TamerEmbeddable', :path => '${absIosDir}'
6286
6494
  ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
6287
6495
  `;
6288
- fs23.writeFileSync(path24.join(iosDir, "Podfile.snippet"), podfileSnippet);
6289
- fs23.writeFileSync(
6290
- path24.join(outDir, "snippet-ios.swift"),
6496
+ fs24.writeFileSync(path25.join(iosDir, "Podfile.snippet"), podfileSnippet);
6497
+ fs24.writeFileSync(
6498
+ path25.join(outDir, "snippet-ios.swift"),
6291
6499
  `// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
6292
6500
  // Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
6293
6501
  `
@@ -6295,8 +6503,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
6295
6503
  }
6296
6504
 
6297
6505
  // src/common/add.ts
6298
- import fs24 from "fs";
6299
- import path25 from "path";
6506
+ import fs25 from "fs";
6507
+ import path26 from "path";
6300
6508
  import { execFile, execSync as execSync10 } from "child_process";
6301
6509
  import { promisify } from "util";
6302
6510
  import semver from "semver";
@@ -6311,21 +6519,15 @@ var CORE_PACKAGES = [
6311
6519
  "@tamer4lynx/tamer-icons"
6312
6520
  ];
6313
6521
  var DEV_STACK_PACKAGES = [
6314
- "@tamer4lynx/jiggle",
6315
- "@tamer4lynx/tamer-app-shell",
6316
- "@tamer4lynx/tamer-biometric",
6317
6522
  "@tamer4lynx/tamer-dev-app",
6318
6523
  "@tamer4lynx/tamer-dev-client",
6319
- "@tamer4lynx/tamer-display-browser",
6524
+ "@tamer4lynx/tamer-app-shell",
6320
6525
  "@tamer4lynx/tamer-icons",
6321
6526
  "@tamer4lynx/tamer-insets",
6322
- "@tamer4lynx/tamer-linking",
6323
6527
  "@tamer4lynx/tamer-plugin",
6324
6528
  "@tamer4lynx/tamer-router",
6325
6529
  "@tamer4lynx/tamer-screen",
6326
- "@tamer4lynx/tamer-secure-store",
6327
- "@tamer4lynx/tamer-system-ui",
6328
- "@tamer4lynx/tamer-transports"
6530
+ "@tamer4lynx/tamer-system-ui"
6329
6531
  ];
6330
6532
  var PACKAGE_ALIASES = {};
6331
6533
  async function getHighestPublishedVersion(fullName) {
@@ -6353,9 +6555,9 @@ async function normalizeTamerInstallSpec(pkg) {
6353
6555
  return `${pkg}@prerelease`;
6354
6556
  }
6355
6557
  function detectPackageManager2(cwd) {
6356
- const dir = path25.resolve(cwd);
6357
- if (fs24.existsSync(path25.join(dir, "pnpm-lock.yaml"))) return "pnpm";
6358
- if (fs24.existsSync(path25.join(dir, "bun.lockb"))) return "bun";
6558
+ const dir = path26.resolve(cwd);
6559
+ if (fs25.existsSync(path26.join(dir, "pnpm-lock.yaml"))) return "pnpm";
6560
+ if (fs25.existsSync(path26.join(dir, "bun.lockb"))) return "bun";
6359
6561
  return "npm";
6360
6562
  }
6361
6563
  function runInstall(cwd, packages, pm) {
@@ -6407,13 +6609,13 @@ async function add(packages = []) {
6407
6609
  // src/common/signing.tsx
6408
6610
  import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
6409
6611
  import { render as render3, Text as Text10, Box as Box9 } from "ink";
6410
- import fs27 from "fs";
6411
- import path28 from "path";
6612
+ import fs28 from "fs";
6613
+ import path29 from "path";
6412
6614
 
6413
6615
  // src/common/androidKeystore.ts
6414
6616
  import { execFileSync } from "child_process";
6415
- import fs25 from "fs";
6416
- import path26 from "path";
6617
+ import fs26 from "fs";
6618
+ import path27 from "path";
6417
6619
  function normalizeJavaHome(raw) {
6418
6620
  if (!raw) return void 0;
6419
6621
  const t = raw.trim().replace(/^["']|["']$/g, "");
@@ -6426,7 +6628,7 @@ function discoverJavaHomeMacOs() {
6426
6628
  encoding: "utf8",
6427
6629
  stdio: ["pipe", "pipe", "pipe"]
6428
6630
  }).trim().split("\n")[0]?.trim();
6429
- if (out && fs25.existsSync(path26.join(out, "bin", "keytool"))) return out;
6631
+ if (out && fs26.existsSync(path27.join(out, "bin", "keytool"))) return out;
6430
6632
  } catch {
6431
6633
  }
6432
6634
  return void 0;
@@ -6436,13 +6638,13 @@ function resolveKeytoolPath() {
6436
6638
  const win = process.platform === "win32";
6437
6639
  const keytoolName = win ? "keytool.exe" : "keytool";
6438
6640
  if (jh) {
6439
- const p = path26.join(jh, "bin", keytoolName);
6440
- if (fs25.existsSync(p)) return p;
6641
+ const p = path27.join(jh, "bin", keytoolName);
6642
+ if (fs26.existsSync(p)) return p;
6441
6643
  }
6442
6644
  const mac = discoverJavaHomeMacOs();
6443
6645
  if (mac) {
6444
- const p = path26.join(mac, "bin", keytoolName);
6445
- if (fs25.existsSync(p)) return p;
6646
+ const p = path27.join(mac, "bin", keytoolName);
6647
+ if (fs26.existsSync(p)) return p;
6446
6648
  }
6447
6649
  return "keytool";
6448
6650
  }
@@ -6457,16 +6659,16 @@ function keytoolAvailable() {
6457
6659
  };
6458
6660
  if (tryRun("keytool")) return true;
6459
6661
  const fromJavaHome = resolveKeytoolPath();
6460
- if (fromJavaHome !== "keytool" && fs25.existsSync(fromJavaHome)) {
6662
+ if (fromJavaHome !== "keytool" && fs26.existsSync(fromJavaHome)) {
6461
6663
  return tryRun(fromJavaHome);
6462
6664
  }
6463
6665
  return false;
6464
6666
  }
6465
6667
  function generateReleaseKeystore(opts) {
6466
6668
  const keytool = resolveKeytoolPath();
6467
- const dir = path26.dirname(opts.keystoreAbsPath);
6468
- fs25.mkdirSync(dir, { recursive: true });
6469
- if (fs25.existsSync(opts.keystoreAbsPath)) {
6669
+ const dir = path27.dirname(opts.keystoreAbsPath);
6670
+ fs26.mkdirSync(dir, { recursive: true });
6671
+ if (fs26.existsSync(opts.keystoreAbsPath)) {
6470
6672
  throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
6471
6673
  }
6472
6674
  if (!opts.storePassword || !opts.keyPassword) {
@@ -6504,13 +6706,13 @@ function generateReleaseKeystore(opts) {
6504
6706
  }
6505
6707
 
6506
6708
  // src/common/appendEnvFile.ts
6507
- import fs26 from "fs";
6508
- import path27 from "path";
6709
+ import fs27 from "fs";
6710
+ import path28 from "path";
6509
6711
  import { parse } from "dotenv";
6510
6712
  function keysDefinedInFile(filePath) {
6511
- if (!fs26.existsSync(filePath)) return /* @__PURE__ */ new Set();
6713
+ if (!fs27.existsSync(filePath)) return /* @__PURE__ */ new Set();
6512
6714
  try {
6513
- return new Set(Object.keys(parse(fs26.readFileSync(filePath, "utf8"))));
6715
+ return new Set(Object.keys(parse(fs27.readFileSync(filePath, "utf8"))));
6514
6716
  } catch {
6515
6717
  return /* @__PURE__ */ new Set();
6516
6718
  }
@@ -6525,11 +6727,11 @@ function formatEnvLine(key, value) {
6525
6727
  function appendEnvVarsIfMissing(projectRoot, vars) {
6526
6728
  const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
6527
6729
  if (entries.length === 0) return null;
6528
- const envLocal = path27.join(projectRoot, ".env.local");
6529
- const envDefault = path27.join(projectRoot, ".env");
6730
+ const envLocal = path28.join(projectRoot, ".env.local");
6731
+ const envDefault = path28.join(projectRoot, ".env");
6530
6732
  let target;
6531
- if (fs26.existsSync(envLocal)) target = envLocal;
6532
- else if (fs26.existsSync(envDefault)) target = envDefault;
6733
+ if (fs27.existsSync(envLocal)) target = envLocal;
6734
+ else if (fs27.existsSync(envDefault)) target = envDefault;
6533
6735
  else target = envLocal;
6534
6736
  const existing = keysDefinedInFile(target);
6535
6737
  const lines = [];
@@ -6541,20 +6743,20 @@ function appendEnvVarsIfMissing(projectRoot, vars) {
6541
6743
  }
6542
6744
  if (lines.length === 0) {
6543
6745
  return {
6544
- file: path27.basename(target),
6746
+ file: path28.basename(target),
6545
6747
  keys: [],
6546
6748
  skippedAll: entries.length > 0
6547
6749
  };
6548
6750
  }
6549
6751
  let prefix = "";
6550
- if (fs26.existsSync(target)) {
6551
- const cur = fs26.readFileSync(target, "utf8");
6752
+ if (fs27.existsSync(target)) {
6753
+ const cur = fs27.readFileSync(target, "utf8");
6552
6754
  prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
6553
6755
  `;
6554
6756
  }
6555
6757
  const block = lines.join("\n") + "\n";
6556
- fs26.writeFileSync(target, prefix + block, "utf8");
6557
- return { file: path27.basename(target), keys: appendedKeys };
6758
+ fs27.writeFileSync(target, prefix + block, "utf8");
6759
+ return { file: path28.basename(target), keys: appendedKeys };
6558
6760
  }
6559
6761
 
6560
6762
  // src/common/signing.tsx
@@ -6660,7 +6862,7 @@ function SigningWizard({ platform: initialPlatform }) {
6660
6862
  try {
6661
6863
  const resolved = resolveHostPaths();
6662
6864
  const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
6663
- abs = path28.isAbsolute(rel) ? rel : path28.join(resolved.projectRoot, rel);
6865
+ abs = path29.isAbsolute(rel) ? rel : path29.join(resolved.projectRoot, rel);
6664
6866
  const alias = state.android.keyAlias.trim() || "release";
6665
6867
  const pw = state.android.genPassword;
6666
6868
  const pkg = resolved.config.android?.packageName ?? "com.example.app";
@@ -6687,7 +6889,7 @@ function SigningWizard({ platform: initialPlatform }) {
6687
6889
  }));
6688
6890
  } catch (e) {
6689
6891
  const msg = e.message;
6690
- if (abs && fs27.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
6892
+ if (abs && fs28.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
6691
6893
  if (cancelled || runId !== generateRunId.current) return;
6692
6894
  const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
6693
6895
  const alias = state.android.keyAlias.trim() || "release";
@@ -6727,11 +6929,11 @@ function SigningWizard({ platform: initialPlatform }) {
6727
6929
  const saveConfig = async () => {
6728
6930
  try {
6729
6931
  const resolved = resolveHostPaths();
6730
- const configPath = path28.join(resolved.projectRoot, "tamer.config.json");
6932
+ const configPath = path29.join(resolved.projectRoot, "tamer.config.json");
6731
6933
  let config = {};
6732
6934
  let androidEnvAppend = null;
6733
- if (fs27.existsSync(configPath)) {
6734
- config = JSON.parse(fs27.readFileSync(configPath, "utf8"));
6935
+ if (fs28.existsSync(configPath)) {
6936
+ config = JSON.parse(fs28.readFileSync(configPath, "utf8"));
6735
6937
  }
6736
6938
  if (state.platform === "android" || state.platform === "both") {
6737
6939
  config.android = config.android || {};
@@ -6758,10 +6960,10 @@ function SigningWizard({ platform: initialPlatform }) {
6758
6960
  ...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
6759
6961
  };
6760
6962
  }
6761
- fs27.writeFileSync(configPath, JSON.stringify(config, null, 2));
6762
- const gitignorePath = path28.join(resolved.projectRoot, ".gitignore");
6763
- if (fs27.existsSync(gitignorePath)) {
6764
- let gitignore = fs27.readFileSync(gitignorePath, "utf8");
6963
+ fs28.writeFileSync(configPath, JSON.stringify(config, null, 2));
6964
+ const gitignorePath = path29.join(resolved.projectRoot, ".gitignore");
6965
+ if (fs28.existsSync(gitignorePath)) {
6966
+ let gitignore = fs28.readFileSync(gitignorePath, "utf8");
6765
6967
  const additions = [
6766
6968
  ".env.local",
6767
6969
  "*.jks",
@@ -6774,7 +6976,7 @@ ${addition}
6774
6976
  `;
6775
6977
  }
6776
6978
  }
6777
- fs27.writeFileSync(gitignorePath, gitignore);
6979
+ fs28.writeFileSync(gitignorePath, gitignore);
6778
6980
  }
6779
6981
  setState((s) => ({
6780
6982
  ...s,
@@ -7032,13 +7234,13 @@ async function signing(platform) {
7032
7234
  }
7033
7235
 
7034
7236
  // src/common/productionSigning.ts
7035
- import fs28 from "fs";
7036
- import path29 from "path";
7237
+ import fs29 from "fs";
7238
+ import path30 from "path";
7037
7239
  function isAndroidSigningConfigured(resolved) {
7038
7240
  const signing2 = resolved.config.android?.signing;
7039
7241
  const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
7040
- const signingProps = path29.join(resolved.androidDir, "signing.properties");
7041
- const hasProps = fs28.existsSync(signingProps);
7242
+ const signingProps = path30.join(resolved.androidDir, "signing.properties");
7243
+ const hasProps = fs29.existsSync(signingProps);
7042
7244
  return hasConfig || hasProps;
7043
7245
  }
7044
7246
  function isIosSigningConfigured(resolved) {
@@ -7068,14 +7270,7 @@ function assertProductionSigningReady(filter) {
7068
7270
  }
7069
7271
 
7070
7272
  // index.ts
7071
- function readCliVersion() {
7072
- const root = path30.dirname(fileURLToPath(import.meta.url));
7073
- const here = path30.join(root, "package.json");
7074
- const parent = path30.join(root, "..", "package.json");
7075
- const pkgPath = fs29.existsSync(here) ? here : parent;
7076
- return JSON.parse(fs29.readFileSync(pkgPath, "utf8")).version;
7077
- }
7078
- var version = readCliVersion();
7273
+ var version = getCliVersion();
7079
7274
  function validateBuildMode(debug, release, production) {
7080
7275
  const modes = [debug, release, production].filter(Boolean).length;
7081
7276
  if (modes > 1) {
@@ -7114,7 +7309,10 @@ program.command("create <target>").description("Create a project or extension. T
7114
7309
  console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
7115
7310
  process.exit(1);
7116
7311
  });
7117
- program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option("-i, --install", "Install after building").action(async (platform, opts) => {
7312
+ program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option(
7313
+ "-i, --install",
7314
+ "Install after build (iOS: simulator with -d; connected device with -p)"
7315
+ ).action(async (platform, opts) => {
7118
7316
  validateBuildMode(opts.debug, opts.release, opts.production);
7119
7317
  const release = opts.release === true || opts.production === true;
7120
7318
  const production = opts.production === true;
@@ -7291,10 +7489,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
7291
7489
  process.exit(1);
7292
7490
  });
7293
7491
  program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
7294
- const configPath = path30.join(process.cwd(), "tamer.config.json");
7492
+ const configPath = path31.join(process.cwd(), "tamer.config.json");
7295
7493
  let config = {};
7296
- if (fs29.existsSync(configPath)) {
7297
- config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
7494
+ if (fs30.existsSync(configPath)) {
7495
+ config = JSON.parse(fs30.readFileSync(configPath, "utf8"));
7298
7496
  }
7299
7497
  if (config.autolink) {
7300
7498
  delete config.autolink;
@@ -7303,7 +7501,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
7303
7501
  config.autolink = true;
7304
7502
  console.log("Autolink enabled in tamer.config.json");
7305
7503
  }
7306
- fs29.writeFileSync(configPath, JSON.stringify(config, null, 2));
7504
+ fs30.writeFileSync(configPath, JSON.stringify(config, null, 2));
7307
7505
  console.log(`Updated ${configPath}`);
7308
7506
  });
7309
7507
  if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {