@tamer4lynx/cli 0.0.12 → 0.0.14

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 +1722 -440
  2. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ process.on("warning", (w) => {
7
7
  });
8
8
 
9
9
  // index.ts
10
- import fs24 from "fs";
11
- import path25 from "path";
10
+ import fs28 from "fs";
11
+ import path29 from "path";
12
12
  import { fileURLToPath } from "url";
13
13
  import { program } from "commander";
14
14
 
@@ -2391,7 +2391,7 @@ var syncDevClient_default = syncDevClient;
2391
2391
 
2392
2392
  // src/android/bundle.ts
2393
2393
  async function bundleAndDeploy(opts = {}) {
2394
- const release = opts.release === true;
2394
+ const release = opts.release === true || opts.production === true;
2395
2395
  let resolved;
2396
2396
  try {
2397
2397
  resolved = resolveHostPaths();
@@ -2468,10 +2468,11 @@ async function buildApk(opts = {}) {
2468
2468
  } catch (error) {
2469
2469
  throw error;
2470
2470
  }
2471
- await bundle_default({ release: opts.release });
2471
+ const release = opts.release === true || opts.production === true;
2472
+ await bundle_default({ release, production: opts.production });
2472
2473
  const androidDir = resolved.androidDir;
2473
2474
  const gradlew = path11.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2474
- const variant = opts.release ? "Release" : "Debug";
2475
+ const variant = release ? "Release" : "Debug";
2475
2476
  const task = opts.install ? `install${variant}` : `assemble${variant}`;
2476
2477
  console.log(`
2477
2478
  \u{1F528} Building ${variant.toLowerCase()} APK${opts.install ? " and installing" : ""}...`);
@@ -4241,7 +4242,7 @@ import fs15 from "fs";
4241
4242
  import path16 from "path";
4242
4243
  import { execSync as execSync7 } from "child_process";
4243
4244
  function bundleAndDeploy2(opts = {}) {
4244
- const release = opts.release === true;
4245
+ const release = opts.release === true || opts.production === true;
4245
4246
  let resolved;
4246
4247
  try {
4247
4248
  resolved = resolveHostPaths();
@@ -4351,8 +4352,9 @@ async function buildIpa(opts = {}) {
4351
4352
  const appName = resolved.config.ios.appName;
4352
4353
  const bundleId = resolved.config.ios.bundleId;
4353
4354
  const iosDir = resolved.iosDir;
4354
- const configuration = opts.release ? "Release" : "Debug";
4355
- bundle_default2({ release: opts.release });
4355
+ const release = opts.release === true || opts.production === true;
4356
+ const configuration = release ? "Release" : "Debug";
4357
+ bundle_default2({ release, production: opts.production });
4356
4358
  const scheme = appName;
4357
4359
  const workspacePath = path17.join(iosDir, `${appName}.xcworkspace`);
4358
4360
  const projectPath = path17.join(iosDir, `${appName}.xcodeproj`);
@@ -4403,93 +4405,507 @@ async function buildIpa(opts = {}) {
4403
4405
  }
4404
4406
  var build_default2 = buildIpa;
4405
4407
 
4406
- // src/common/init.ts
4408
+ // src/common/init.tsx
4407
4409
  import fs17 from "fs";
4408
4410
  import path18 from "path";
4409
- import readline from "readline";
4410
- var rl = readline.createInterface({
4411
- input: process.stdin,
4412
- output: process.stdout,
4413
- terminal: false
4414
- });
4415
- function ask(question) {
4416
- return new Promise((resolve) => {
4417
- rl.question(question, (answer) => resolve(answer.trim()));
4418
- });
4419
- }
4420
- async function init() {
4421
- console.log("Tamer4Lynx Init: Let's set up your tamer.config.json\n");
4422
- const androidAppName = await ask("Android app name: ");
4423
- const androidPackageName = await ask("Android package name (e.g. com.example.app): ");
4424
- let androidSdk = await ask("Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME): ");
4411
+ import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
4412
+ import { render, Text as Text9, Box as Box8 } from "ink";
4413
+
4414
+ // src/common/tui/components/TextInput.tsx
4415
+ import { useState, useEffect } from "react";
4416
+ import { Box, Text } from "ink";
4417
+ import InkTextInput from "ink-text-input";
4418
+ import { jsx, jsxs } from "react/jsx-runtime";
4419
+ function TuiTextInput({
4420
+ label,
4421
+ value: valueProp,
4422
+ defaultValue = "",
4423
+ onChange: onChangeProp,
4424
+ onSubmitValue,
4425
+ onSubmit,
4426
+ hint,
4427
+ error
4428
+ }) {
4429
+ const controlled = valueProp !== void 0;
4430
+ const [internal, setInternal] = useState(defaultValue);
4431
+ useEffect(() => {
4432
+ if (!controlled) setInternal(defaultValue);
4433
+ }, [defaultValue, controlled]);
4434
+ const value = controlled ? valueProp : internal;
4435
+ const onChange = (v) => {
4436
+ if (!controlled) setInternal(v);
4437
+ onChangeProp?.(v);
4438
+ };
4439
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4440
+ label ? /* @__PURE__ */ jsx(Text, { children: label }) : null,
4441
+ /* @__PURE__ */ jsx(
4442
+ InkTextInput,
4443
+ {
4444
+ value,
4445
+ onChange,
4446
+ onSubmit: () => {
4447
+ const r = onSubmitValue?.(value);
4448
+ if (r === false) return;
4449
+ onSubmit();
4450
+ }
4451
+ }
4452
+ ),
4453
+ error ? /* @__PURE__ */ jsx(Text, { color: "red", children: error }) : hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
4454
+ ] });
4455
+ }
4456
+
4457
+ // src/common/tui/components/SelectInput.tsx
4458
+ import "react";
4459
+ import { Box as Box2, Text as Text2 } from "ink";
4460
+ import InkSelectInput from "ink-select-input";
4461
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4462
+ function TuiSelectInput({
4463
+ label,
4464
+ items,
4465
+ onSelect,
4466
+ hint
4467
+ }) {
4468
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
4469
+ label ? /* @__PURE__ */ jsx2(Text2, { children: label }) : null,
4470
+ /* @__PURE__ */ jsx2(
4471
+ InkSelectInput,
4472
+ {
4473
+ items,
4474
+ onSelect: (item) => onSelect(item.value)
4475
+ }
4476
+ ),
4477
+ hint ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: hint }) : null
4478
+ ] });
4479
+ }
4480
+
4481
+ // src/common/tui/components/PasswordInput.tsx
4482
+ import "react";
4483
+ import { Box as Box3, Text as Text3 } from "ink";
4484
+ import InkTextInput2 from "ink-text-input";
4485
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
4486
+
4487
+ // src/common/tui/components/ConfirmInput.tsx
4488
+ import "react";
4489
+ import { Box as Box4, Text as Text4 } from "ink";
4490
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
4491
+ function TuiConfirmInput({
4492
+ label,
4493
+ onConfirm,
4494
+ defaultYes = false,
4495
+ hint
4496
+ }) {
4497
+ const items = defaultYes ? [
4498
+ { label: "Yes (default)", value: "yes" },
4499
+ { label: "No", value: "no" }
4500
+ ] : [
4501
+ { label: "No (default)", value: "no" },
4502
+ { label: "Yes", value: "yes" }
4503
+ ];
4504
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
4505
+ label ? /* @__PURE__ */ jsx4(Text4, { children: label }) : null,
4506
+ /* @__PURE__ */ jsx4(
4507
+ TuiSelectInput,
4508
+ {
4509
+ items,
4510
+ onSelect: (v) => onConfirm(v === "yes"),
4511
+ hint
4512
+ }
4513
+ )
4514
+ ] });
4515
+ }
4516
+
4517
+ // src/common/tui/components/Spinner.tsx
4518
+ import "react";
4519
+ import { Text as Text5 } from "ink";
4520
+ import InkSpinner from "ink-spinner";
4521
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
4522
+ function TuiSpinner({ label, type = "dots" }) {
4523
+ return /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
4524
+ /* @__PURE__ */ jsx5(InkSpinner, { type }),
4525
+ label ? ` ${label}` : ""
4526
+ ] });
4527
+ }
4528
+
4529
+ // src/common/tui/components/StatusBox.tsx
4530
+ import "react";
4531
+ import { Box as Box5, Text as Text6 } from "ink";
4532
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
4533
+ var colors = {
4534
+ success: "green",
4535
+ error: "red",
4536
+ warning: "yellow",
4537
+ info: "cyan"
4538
+ };
4539
+ function StatusBox({ variant, children, title }) {
4540
+ const c = colors[variant];
4541
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", borderStyle: "round", borderColor: c, paddingX: 1, children: [
4542
+ title ? /* @__PURE__ */ jsx6(Text6, { bold: true, color: c, children: title }) : null,
4543
+ /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children })
4544
+ ] });
4545
+ }
4546
+
4547
+ // src/common/tui/components/Wizard.tsx
4548
+ import "react";
4549
+ import { Box as Box6, Text as Text7 } from "ink";
4550
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4551
+ function Wizard({ step, total, title, children }) {
4552
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
4553
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
4554
+ "Step ",
4555
+ step,
4556
+ "/",
4557
+ total,
4558
+ title ? ` \u2014 ${title}` : ""
4559
+ ] }),
4560
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children })
4561
+ ] });
4562
+ }
4563
+
4564
+ // src/common/tui/components/ServerDashboard.tsx
4565
+ import "react";
4566
+ import { Box as Box7, Text as Text8 } from "ink";
4567
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4568
+ function ServerDashboard({
4569
+ projectName,
4570
+ port,
4571
+ lanIp,
4572
+ devUrl,
4573
+ wsUrl,
4574
+ lynxBundleFile,
4575
+ bonjour,
4576
+ verbose,
4577
+ buildPhase,
4578
+ buildError,
4579
+ wsConnections,
4580
+ logLines,
4581
+ showLogs,
4582
+ qrLines,
4583
+ phase,
4584
+ startError
4585
+ }) {
4586
+ if (phase === "failed") {
4587
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
4588
+ /* @__PURE__ */ jsx8(Text8, { color: "red", bold: true, children: "Dev server failed to start" }),
4589
+ startError ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: startError }) : null,
4590
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press Ctrl+C or 'q' to quit" }) })
4591
+ ] });
4592
+ }
4593
+ const bundlePath = `${devUrl}/${lynxBundleFile}`;
4594
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
4595
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: "green", children: [
4596
+ "Tamer4Lynx dev server (",
4597
+ projectName,
4598
+ ")"
4599
+ ] }),
4600
+ verbose ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Logs: verbose (native + JS)" }) : null,
4601
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", columnGap: 3, alignItems: "flex-start", children: [
4602
+ qrLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
4603
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Scan" }),
4604
+ qrLines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { children: line }, i)),
4605
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4606
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Open" }),
4607
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", wrap: "truncate-end", children: devUrl })
4608
+ ] })
4609
+ ] }) : /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
4610
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Open" }),
4611
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", wrap: "truncate-end", children: devUrl })
4612
+ ] }),
4613
+ /* @__PURE__ */ jsxs8(
4614
+ Box7,
4615
+ {
4616
+ flexDirection: "column",
4617
+ flexGrow: 1,
4618
+ minWidth: 28,
4619
+ marginTop: qrLines.length > 0 ? 2 : 0,
4620
+ children: [
4621
+ /* @__PURE__ */ jsxs8(Text8, { children: [
4622
+ "Port: ",
4623
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: port }),
4624
+ " \xB7 LAN: ",
4625
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: lanIp })
4626
+ ] }),
4627
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "truncate-end", children: bundlePath }),
4628
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, wrap: "truncate-end", children: [
4629
+ devUrl,
4630
+ "/meta.json"
4631
+ ] }),
4632
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "truncate-end", children: wsUrl }),
4633
+ bonjour ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "mDNS: _tamer._tcp" }) : null,
4634
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4635
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Build" }),
4636
+ 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" })
4637
+ ] }),
4638
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
4639
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Connections" }),
4640
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
4641
+ "WebSocket clients: ",
4642
+ wsConnections
4643
+ ] })
4644
+ ] })
4645
+ ]
4646
+ }
4647
+ )
4648
+ ] }),
4649
+ showLogs && logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
4650
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
4651
+ "Build / output (last ",
4652
+ logLines.length,
4653
+ " lines)"
4654
+ ] }),
4655
+ logLines.slice(-12).map((line, i) => /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: line }, i))
4656
+ ] }) : null,
4657
+ /* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 l toggle logs \xB7 q quit" }) })
4658
+ ] });
4659
+ }
4660
+
4661
+ // src/common/tui/hooks/useInputState.ts
4662
+ import { useState as useState2, useCallback } from "react";
4663
+
4664
+ // src/common/tui/hooks/useValidation.ts
4665
+ function isValidAndroidPackage(name) {
4666
+ const s = name.trim();
4667
+ if (!s) return false;
4668
+ return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(s);
4669
+ }
4670
+ function isValidIosBundleId(id) {
4671
+ const s = id.trim();
4672
+ if (!s) return false;
4673
+ return /^[a-zA-Z][a-zA-Z0-9_-]*(\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(s);
4674
+ }
4675
+
4676
+ // src/common/tui/hooks/useServerStatus.ts
4677
+ import { useState as useState3, useCallback as useCallback2 } from "react";
4678
+
4679
+ // src/common/init.tsx
4680
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4681
+ function resolveSdkInput(raw) {
4682
+ let androidSdk = raw.trim();
4425
4683
  if (androidSdk.startsWith("$") && /^[A-Z0-9_]+$/.test(androidSdk.slice(1))) {
4426
4684
  const envVar = androidSdk.slice(1);
4427
4685
  const envValue = process.env[envVar];
4428
4686
  if (envValue) {
4429
4687
  androidSdk = envValue;
4430
- console.log(`Resolved ${androidSdk} from $${envVar}`);
4431
- } else {
4432
- console.warn(`Environment variable $${envVar} not found. SDK path will be left as-is.`);
4688
+ return { resolved: androidSdk, message: `Using ${androidSdk} from $${envVar}` };
4433
4689
  }
4690
+ return {
4691
+ resolved: androidSdk,
4692
+ message: `Environment variable $${envVar} not found \u2014 path saved as typed.`
4693
+ };
4434
4694
  }
4435
- const useSame = await ask("Use same name and bundle ID for iOS as Android? (y/N): ");
4436
- let iosAppName;
4437
- let iosBundleId;
4438
- if (/^y(es)?$/i.test(useSame)) {
4439
- iosAppName = androidAppName;
4440
- iosBundleId = androidPackageName;
4441
- } else {
4442
- iosAppName = await ask("iOS app name: ");
4443
- iosBundleId = await ask("iOS bundle ID (e.g. com.example.app): ");
4444
- }
4445
- const lynxProject = await ask("Lynx project path (relative to project root, e.g. packages/example) [optional]: ");
4446
- const config = {
4447
- android: {
4448
- appName: androidAppName || void 0,
4449
- packageName: androidPackageName || void 0,
4450
- sdk: androidSdk || void 0
4451
- },
4452
- ios: {
4453
- appName: iosAppName || void 0,
4454
- bundleId: iosBundleId || void 0
4455
- },
4456
- paths: { androidDir: "android", iosDir: "ios" }
4457
- };
4458
- if (lynxProject) config.lynxProject = lynxProject;
4459
- const configPath = path18.join(process.cwd(), "tamer.config.json");
4460
- fs17.writeFileSync(configPath, JSON.stringify(config, null, 2));
4461
- console.log(`
4462
- \u2705 Generated tamer.config.json at ${configPath}`);
4463
- const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
4464
- const tsconfigCandidates = lynxProject ? [path18.join(process.cwd(), lynxProject, "tsconfig.json"), path18.join(process.cwd(), "tsconfig.json")] : [path18.join(process.cwd(), "tsconfig.json")];
4465
- for (const tsconfigPath of tsconfigCandidates) {
4466
- if (!fs17.existsSync(tsconfigPath)) continue;
4467
- try {
4468
- const raw = fs17.readFileSync(tsconfigPath, "utf-8");
4469
- const tsconfig = JSON.parse(raw);
4470
- const include = tsconfig.include ?? [];
4471
- const arr = Array.isArray(include) ? include : [include];
4472
- if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) continue;
4473
- arr.push(tamerTypesInclude);
4474
- tsconfig.include = arr;
4475
- fs17.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
4476
- console.log(`\u2705 Updated ${path18.relative(process.cwd(), tsconfigPath)} to include tamer type declarations`);
4477
- break;
4478
- } catch (e) {
4479
- console.warn(`\u26A0 Could not update ${tsconfigPath}:`, e.message);
4695
+ return { resolved: androidSdk };
4696
+ }
4697
+ function InitWizard() {
4698
+ const [step, setStep] = useState4("welcome");
4699
+ const [androidAppName, setAndroidAppName] = useState4("");
4700
+ const [androidPackageName, setAndroidPackageName] = useState4("");
4701
+ const [androidSdk, setAndroidSdk] = useState4("");
4702
+ const [sdkHint, setSdkHint] = useState4();
4703
+ const [iosAppName, setIosAppName] = useState4("");
4704
+ const [iosBundleId, setIosBundleId] = useState4("");
4705
+ const [lynxProject, setLynxProject] = useState4("");
4706
+ const [pkgError, setPkgError] = useState4();
4707
+ const [bundleError, setBundleError] = useState4();
4708
+ const [doneMessage, setDoneMessage] = useState4([]);
4709
+ const writeConfigAndTsconfig = useCallback3(() => {
4710
+ const config = {
4711
+ android: {
4712
+ appName: androidAppName || void 0,
4713
+ packageName: androidPackageName || void 0,
4714
+ sdk: androidSdk || void 0
4715
+ },
4716
+ ios: {
4717
+ appName: iosAppName || void 0,
4718
+ bundleId: iosBundleId || void 0
4719
+ },
4720
+ paths: { androidDir: "android", iosDir: "ios" }
4721
+ };
4722
+ if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
4723
+ const configPath = path18.join(process.cwd(), "tamer.config.json");
4724
+ fs17.writeFileSync(configPath, JSON.stringify(config, null, 2));
4725
+ const lines = [`Generated tamer.config.json at ${configPath}`];
4726
+ const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
4727
+ const tsconfigCandidates = lynxProject.trim() ? [
4728
+ path18.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
4729
+ path18.join(process.cwd(), "tsconfig.json")
4730
+ ] : [path18.join(process.cwd(), "tsconfig.json")];
4731
+ function parseTsconfigJson(raw) {
4732
+ try {
4733
+ return JSON.parse(raw);
4734
+ } catch {
4735
+ const noTrailingCommas = raw.replace(/,\s*([\]}])/g, "$1");
4736
+ return JSON.parse(noTrailingCommas);
4737
+ }
4480
4738
  }
4739
+ for (const tsconfigPath of tsconfigCandidates) {
4740
+ if (!fs17.existsSync(tsconfigPath)) continue;
4741
+ try {
4742
+ const raw = fs17.readFileSync(tsconfigPath, "utf-8");
4743
+ const tsconfig = parseTsconfigJson(raw);
4744
+ const include = tsconfig.include ?? [];
4745
+ const arr = Array.isArray(include) ? include : [include];
4746
+ if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) continue;
4747
+ arr.push(tamerTypesInclude);
4748
+ tsconfig.include = arr;
4749
+ fs17.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
4750
+ lines.push(`Updated ${path18.relative(process.cwd(), tsconfigPath)} for tamer types`);
4751
+ break;
4752
+ } catch (e) {
4753
+ lines.push(`Could not update ${tsconfigPath}: ${e.message}`);
4754
+ }
4755
+ }
4756
+ setDoneMessage(lines);
4757
+ setStep("done");
4758
+ setTimeout(() => process.exit(0), 2e3);
4759
+ }, [androidAppName, androidPackageName, androidSdk, iosAppName, iosBundleId, lynxProject]);
4760
+ useEffect2(() => {
4761
+ if (step !== "saving") return;
4762
+ writeConfigAndTsconfig();
4763
+ }, [step, writeConfigAndTsconfig]);
4764
+ if (step === "welcome") {
4765
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
4766
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "Tamer4Lynx init" }),
4767
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Set up tamer.config.json for your project." }),
4768
+ /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(
4769
+ TuiSelectInput,
4770
+ {
4771
+ label: "Continue?",
4772
+ items: [{ label: "Start", value: "start" }],
4773
+ onSelect: () => setStep("android-app")
4774
+ }
4775
+ ) })
4776
+ ] });
4777
+ }
4778
+ if (step === "android-app") {
4779
+ return /* @__PURE__ */ jsx9(Wizard, { step: 1, total: 6, title: "Android app name", children: /* @__PURE__ */ jsx9(
4780
+ TuiTextInput,
4781
+ {
4782
+ label: "Android app name:",
4783
+ defaultValue: androidAppName,
4784
+ onSubmitValue: (v) => setAndroidAppName(v),
4785
+ onSubmit: () => setStep("android-pkg")
4786
+ }
4787
+ ) });
4788
+ }
4789
+ if (step === "android-pkg") {
4790
+ return /* @__PURE__ */ jsx9(Wizard, { step: 2, total: 6, title: "Android package name", children: /* @__PURE__ */ jsx9(
4791
+ TuiTextInput,
4792
+ {
4793
+ label: "Android package name (e.g. com.example.app):",
4794
+ defaultValue: androidPackageName,
4795
+ error: pkgError,
4796
+ onChange: () => setPkgError(void 0),
4797
+ onSubmitValue: (v) => {
4798
+ const t = v.trim();
4799
+ if (t && !isValidAndroidPackage(t)) {
4800
+ setPkgError("Use reverse-DNS form: com.mycompany.app");
4801
+ return false;
4802
+ }
4803
+ setAndroidPackageName(t);
4804
+ setPkgError(void 0);
4805
+ },
4806
+ onSubmit: () => setStep("android-sdk")
4807
+ }
4808
+ ) });
4809
+ }
4810
+ if (step === "android-sdk") {
4811
+ return /* @__PURE__ */ jsx9(Wizard, { step: 3, total: 6, title: "Android SDK", children: /* @__PURE__ */ jsx9(
4812
+ TuiTextInput,
4813
+ {
4814
+ label: "Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME):",
4815
+ defaultValue: androidSdk,
4816
+ onSubmitValue: (v) => {
4817
+ const { resolved, message } = resolveSdkInput(v);
4818
+ setAndroidSdk(resolved);
4819
+ setSdkHint(message);
4820
+ },
4821
+ onSubmit: () => setStep("ios-reuse"),
4822
+ hint: sdkHint
4823
+ }
4824
+ ) });
4825
+ }
4826
+ if (step === "ios-reuse") {
4827
+ return /* @__PURE__ */ jsx9(Wizard, { step: 4, total: 6, title: "iOS", children: /* @__PURE__ */ jsx9(
4828
+ TuiConfirmInput,
4829
+ {
4830
+ label: "Use the same app name and bundle ID for iOS as Android?",
4831
+ defaultYes: false,
4832
+ onConfirm: (yes) => {
4833
+ if (yes) {
4834
+ setIosAppName(androidAppName);
4835
+ setIosBundleId(androidPackageName);
4836
+ setStep("lynx-path");
4837
+ } else {
4838
+ setStep("ios-app");
4839
+ }
4840
+ },
4841
+ hint: "No = enter iOS-specific values next"
4842
+ }
4843
+ ) });
4844
+ }
4845
+ if (step === "ios-app") {
4846
+ return /* @__PURE__ */ jsx9(Wizard, { step: 4, total: 6, title: "iOS app name", children: /* @__PURE__ */ jsx9(
4847
+ TuiTextInput,
4848
+ {
4849
+ label: "iOS app name:",
4850
+ defaultValue: iosAppName,
4851
+ onSubmitValue: (v) => setIosAppName(v),
4852
+ onSubmit: () => setStep("ios-bundle")
4853
+ }
4854
+ ) });
4855
+ }
4856
+ if (step === "ios-bundle") {
4857
+ return /* @__PURE__ */ jsx9(Wizard, { step: 5, total: 6, title: "iOS bundle ID", children: /* @__PURE__ */ jsx9(
4858
+ TuiTextInput,
4859
+ {
4860
+ label: "iOS bundle ID (e.g. com.example.app):",
4861
+ defaultValue: iosBundleId,
4862
+ error: bundleError,
4863
+ onChange: () => setBundleError(void 0),
4864
+ onSubmitValue: (v) => {
4865
+ const t = v.trim();
4866
+ if (t && !isValidIosBundleId(t)) {
4867
+ setBundleError("Use reverse-DNS form: com.mycompany.App");
4868
+ return false;
4869
+ }
4870
+ setIosBundleId(t);
4871
+ setBundleError(void 0);
4872
+ },
4873
+ onSubmit: () => setStep("lynx-path")
4874
+ }
4875
+ ) });
4876
+ }
4877
+ if (step === "lynx-path") {
4878
+ return /* @__PURE__ */ jsx9(Wizard, { step: 6, total: 6, title: "Lynx project", children: /* @__PURE__ */ jsx9(
4879
+ TuiTextInput,
4880
+ {
4881
+ label: "Lynx project path relative to project root (optional, e.g. packages/example):",
4882
+ defaultValue: lynxProject,
4883
+ onSubmitValue: (v) => setLynxProject(v),
4884
+ onSubmit: () => setStep("saving"),
4885
+ hint: "Press Enter with empty to skip"
4886
+ }
4887
+ ) });
4481
4888
  }
4482
- rl.close();
4889
+ if (step === "saving") {
4890
+ return /* @__PURE__ */ jsx9(Box8, { children: /* @__PURE__ */ jsx9(TuiSpinner, { label: "Writing tamer.config.json and updating tsconfig\u2026" }) });
4891
+ }
4892
+ if (step === "done") {
4893
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx9(StatusBox, { variant: "success", title: "Done", children: doneMessage.map((line, i) => /* @__PURE__ */ jsx9(Text9, { color: "green", children: line }, i)) }) });
4894
+ }
4895
+ return null;
4896
+ }
4897
+ async function init() {
4898
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(InitWizard, {}));
4899
+ await waitUntilExit();
4483
4900
  }
4484
- var init_default = init;
4485
4901
 
4486
4902
  // src/common/create.ts
4487
4903
  import fs18 from "fs";
4488
4904
  import path19 from "path";
4489
- import readline2 from "readline";
4490
- var rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
4491
- function ask2(question) {
4492
- return new Promise((resolve) => rl2.question(question, (answer) => resolve(answer.trim())));
4905
+ import readline from "readline";
4906
+ var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
4907
+ function ask(question) {
4908
+ return new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
4493
4909
  }
4494
4910
  async function create3(opts) {
4495
4911
  console.log("Tamer4Lynx: Create Lynx Extension\n");
@@ -4528,29 +4944,29 @@ async function create3(opts) {
4528
4944
  console.log(" [ ] Native Module");
4529
4945
  console.log(" [ ] Element");
4530
4946
  console.log(" [ ] Service\n");
4531
- includeModule = /^y(es)?$/i.test(await ask2("Include Native Module? (Y/n): ") || "y");
4532
- includeElement = /^y(es)?$/i.test(await ask2("Include Element? (y/N): ") || "n");
4533
- includeService = /^y(es)?$/i.test(await ask2("Include Service? (y/N): ") || "n");
4947
+ includeModule = /^y(es)?$/i.test(await ask("Include Native Module? (Y/n): ") || "y");
4948
+ includeElement = /^y(es)?$/i.test(await ask("Include Element? (y/N): ") || "n");
4949
+ includeService = /^y(es)?$/i.test(await ask("Include Service? (y/N): ") || "n");
4534
4950
  }
4535
4951
  if (!includeModule && !includeElement && !includeService) {
4536
4952
  console.error("\u274C At least one extension type is required.");
4537
- rl2.close();
4953
+ rl.close();
4538
4954
  process.exit(1);
4539
4955
  }
4540
- const extName = await ask2("Extension package name (e.g. my-lynx-module): ");
4956
+ const extName = await ask("Extension package name (e.g. my-lynx-module): ");
4541
4957
  if (!extName || !/^[a-z0-9-_]+$/.test(extName)) {
4542
4958
  console.error("\u274C Invalid package name. Use lowercase letters, numbers, hyphens, underscores.");
4543
- rl2.close();
4959
+ rl.close();
4544
4960
  process.exit(1);
4545
4961
  }
4546
- const packageName = await ask2("Android package name (e.g. com.example.mymodule): ") || `com.example.${extName.replace(/-/g, "")}`;
4962
+ const packageName = await ask("Android package name (e.g. com.example.mymodule): ") || `com.example.${extName.replace(/-/g, "")}`;
4547
4963
  const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
4548
4964
  const fullModuleClassName = `${packageName}.${simpleModuleName}`;
4549
4965
  const cwd = process.cwd();
4550
4966
  const root = path19.join(cwd, extName);
4551
4967
  if (fs18.existsSync(root)) {
4552
4968
  console.error(`\u274C Directory ${extName} already exists.`);
4553
- rl2.close();
4969
+ rl.close();
4554
4970
  process.exit(1);
4555
4971
  }
4556
4972
  fs18.mkdirSync(root, { recursive: true });
@@ -4704,7 +5120,7 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
4704
5120
  console.log(` cd ${extName}`);
4705
5121
  console.log(" npm install");
4706
5122
  if (includeModule) console.log(" npm run codegen");
4707
- rl2.close();
5123
+ rl.close();
4708
5124
  }
4709
5125
  var create_default3 = create3;
4710
5126
 
@@ -4775,14 +5191,16 @@ function extractLynxModules(files) {
4775
5191
  }
4776
5192
  var codegen_default = codegen;
4777
5193
 
4778
- // src/common/devServer.ts
5194
+ // src/common/devServer.tsx
5195
+ import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
4779
5196
  import { spawn } from "child_process";
4780
5197
  import fs20 from "fs";
4781
5198
  import http from "http";
4782
5199
  import os4 from "os";
4783
5200
  import path21 from "path";
4784
- import readline3 from "readline";
5201
+ import { render as render2, useInput, useApp } from "ink";
4785
5202
  import { WebSocketServer } from "ws";
5203
+ import { jsx as jsx10 } from "react/jsx-runtime";
4786
5204
  var DEFAULT_PORT = 3e3;
4787
5205
  var STATIC_MIME = {
4788
5206
  ".png": "image/png",
@@ -4837,319 +5255,431 @@ function getLanIp() {
4837
5255
  }
4838
5256
  return "localhost";
4839
5257
  }
4840
- async function startDevServer(opts) {
4841
- const verbose = opts?.verbose ?? false;
4842
- const resolved = resolveHostPaths();
4843
- const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
4844
- const distDir = path21.dirname(lynxBundlePath);
4845
- let buildProcess = null;
4846
- function detectPackageManager2(cwd) {
4847
- const dir = path21.resolve(cwd);
4848
- if (fs20.existsSync(path21.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
4849
- if (fs20.existsSync(path21.join(dir, "bun.lockb")) || fs20.existsSync(path21.join(dir, "bun.lock"))) return { cmd: "bun", args: ["run", "build"] };
4850
- return { cmd: "npm", args: ["run", "build"] };
4851
- }
4852
- function runBuild() {
4853
- return new Promise((resolve, reject) => {
4854
- const { cmd, args } = detectPackageManager2(lynxProjectDir);
4855
- buildProcess = spawn(cmd, args, {
4856
- cwd: lynxProjectDir,
4857
- stdio: "pipe",
4858
- shell: process.platform === "win32"
4859
- });
4860
- let stderr = "";
4861
- buildProcess.stderr?.on("data", (d) => {
4862
- stderr += d.toString();
4863
- });
4864
- buildProcess.on("close", (code) => {
4865
- buildProcess = null;
4866
- if (code === 0) resolve();
4867
- else reject(new Error(stderr || `Build exited ${code}`));
4868
- });
4869
- });
4870
- }
4871
- const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
4872
- const port = await findAvailablePort(preferredPort);
4873
- if (port !== preferredPort) {
4874
- console.log(`\x1B[33m\u26A0 Port ${preferredPort} in use, using ${port}\x1B[0m`);
4875
- }
4876
- const projectName = path21.basename(lynxProjectDir);
4877
- const basePath = `/${projectName}`;
4878
- const iconPaths = resolveIconPaths(projectRoot, config);
4879
- let iconFilePath = null;
4880
- if (iconPaths?.source && fs20.statSync(iconPaths.source).isFile()) {
4881
- iconFilePath = iconPaths.source;
4882
- } else if (iconPaths?.androidAdaptiveForeground && fs20.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
4883
- iconFilePath = iconPaths.androidAdaptiveForeground;
4884
- } else if (iconPaths?.android) {
4885
- const androidIcon = path21.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
4886
- if (fs20.existsSync(androidIcon)) iconFilePath = androidIcon;
4887
- } else if (iconPaths?.ios) {
4888
- const iosIcon = path21.join(iconPaths.ios, "Icon-1024.png");
4889
- if (fs20.existsSync(iosIcon)) iconFilePath = iosIcon;
4890
- }
4891
- const iconExt = iconFilePath ? path21.extname(iconFilePath) || ".png" : "";
4892
- const httpServer = http.createServer((req, res) => {
4893
- let reqPath = (req.url || "/").split("?")[0];
4894
- if (reqPath === `${basePath}/status`) {
4895
- res.setHeader("Content-Type", "text/plain");
4896
- res.setHeader("Access-Control-Allow-Origin", "*");
4897
- res.end("packager-status:running");
4898
- return;
4899
- }
4900
- if (reqPath === `${basePath}/meta.json`) {
4901
- const lanIp = getLanIp();
4902
- const nativeModules = discoverNativeExtensions(projectRoot);
4903
- const androidPackageName = config.android?.packageName?.trim();
4904
- const iosBundleId = config.ios?.bundleId?.trim();
4905
- const idParts = [androidPackageName?.toLowerCase(), iosBundleId?.toLowerCase()].filter(
4906
- (x) => Boolean(x)
4907
- );
4908
- const meta = {
4909
- name: projectName,
4910
- slug: projectName,
4911
- bundleUrl: `http://${lanIp}:${port}${basePath}/${lynxBundleFile}`,
4912
- bundleFile: lynxBundleFile,
4913
- hostUri: `http://${lanIp}:${port}${basePath}`,
4914
- debuggerHost: `${lanIp}:${port}`,
4915
- developer: { tool: "tamer4lynx" },
4916
- packagerStatus: "running",
4917
- nativeModules: nativeModules.map((m) => ({ packageName: m.packageName, moduleClassName: m.moduleClassName }))
4918
- };
4919
- if (androidPackageName) meta.androidPackageName = androidPackageName;
4920
- if (iosBundleId) meta.iosBundleId = iosBundleId;
4921
- if (idParts.length > 0) meta.tamerAppKey = idParts.join("|");
4922
- const rawIcon = config.icon;
4923
- if (rawIcon && typeof rawIcon === "object" && "source" in rawIcon && typeof rawIcon.source === "string") {
4924
- meta.iconSource = rawIcon.source;
4925
- } else if (typeof rawIcon === "string") {
4926
- meta.iconSource = rawIcon;
4927
- }
4928
- if (iconFilePath) {
4929
- meta.icon = `http://${lanIp}:${port}${basePath}/icon${iconExt}`;
4930
- }
4931
- res.setHeader("Content-Type", "application/json");
4932
- res.setHeader("Access-Control-Allow-Origin", "*");
4933
- res.end(JSON.stringify(meta, null, 2));
4934
- return;
4935
- }
4936
- if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
4937
- fs20.readFile(iconFilePath, (err, data) => {
4938
- if (err) {
4939
- res.writeHead(404);
4940
- res.end();
4941
- return;
4942
- }
4943
- res.setHeader("Content-Type", STATIC_MIME[iconExt] ?? "image/png");
4944
- res.setHeader("Access-Control-Allow-Origin", "*");
4945
- res.end(data);
4946
- });
5258
+ function detectPackageManager(cwd) {
5259
+ const dir = path21.resolve(cwd);
5260
+ if (fs20.existsSync(path21.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
5261
+ if (fs20.existsSync(path21.join(dir, "bun.lockb")) || fs20.existsSync(path21.join(dir, "bun.lock")))
5262
+ return { cmd: "bun", args: ["run", "build"] };
5263
+ return { cmd: "npm", args: ["run", "build"] };
5264
+ }
5265
+ var initialUi = () => ({
5266
+ phase: "starting",
5267
+ projectName: "",
5268
+ port: 0,
5269
+ lanIp: "localhost",
5270
+ devUrl: "",
5271
+ wsUrl: "",
5272
+ lynxBundleFile: "main.lynx.bundle",
5273
+ bonjour: false,
5274
+ verbose: false,
5275
+ buildPhase: "idle",
5276
+ wsConnections: 0,
5277
+ logLines: [],
5278
+ showLogs: false,
5279
+ qrLines: []
5280
+ });
5281
+ function DevServerApp({ verbose }) {
5282
+ const { exit } = useApp();
5283
+ const [ui, setUi] = useState5(() => {
5284
+ const s = initialUi();
5285
+ s.verbose = verbose;
5286
+ return s;
5287
+ });
5288
+ const cleanupRef = useRef(null);
5289
+ const rebuildRef = useRef(() => Promise.resolve());
5290
+ const quitOnceRef = useRef(false);
5291
+ const appendLog = useCallback4((chunk) => {
5292
+ const lines = chunk.split(/\r?\n/).filter(Boolean);
5293
+ setUi((prev) => ({
5294
+ ...prev,
5295
+ logLines: [...prev.logLines, ...lines].slice(-400)
5296
+ }));
5297
+ }, []);
5298
+ const handleQuit = useCallback4(() => {
5299
+ if (quitOnceRef.current) return;
5300
+ quitOnceRef.current = true;
5301
+ void cleanupRef.current?.();
5302
+ exit();
5303
+ }, [exit]);
5304
+ useInput((input, key) => {
5305
+ if (key.ctrl && key.name === "c") {
5306
+ handleQuit();
4947
5307
  return;
4948
5308
  }
4949
- const lynxStaticMounts = [
4950
- { prefix: `${basePath}/src/assets/`, rootSub: "src/assets" },
4951
- { prefix: `${basePath}/assets/`, rootSub: "assets" }
4952
- ];
4953
- for (const { prefix, rootSub } of lynxStaticMounts) {
4954
- if (!reqPath.startsWith(prefix)) continue;
4955
- let rel = reqPath.slice(prefix.length);
4956
- try {
4957
- rel = decodeURIComponent(rel);
4958
- } catch {
4959
- res.writeHead(400);
4960
- res.end();
4961
- return;
4962
- }
4963
- const safe = path21.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
4964
- if (path21.isAbsolute(safe) || safe.startsWith("..")) {
4965
- res.writeHead(403);
4966
- res.end();
4967
- return;
4968
- }
4969
- const allowedRoot = path21.resolve(lynxProjectDir, rootSub);
4970
- const abs = path21.resolve(allowedRoot, safe);
4971
- if (!abs.startsWith(allowedRoot + path21.sep) && abs !== allowedRoot) {
4972
- res.writeHead(403);
4973
- res.end();
4974
- return;
4975
- }
4976
- if (!fs20.existsSync(abs) || !fs20.statSync(abs).isFile()) {
4977
- res.writeHead(404);
4978
- res.end("Not found");
4979
- return;
4980
- }
4981
- sendFileFromDisk(res, abs);
5309
+ if (input === "q") {
5310
+ handleQuit();
4982
5311
  return;
4983
5312
  }
4984
- if (reqPath === "/" || reqPath === basePath || reqPath === `${basePath}/`) {
4985
- reqPath = `${basePath}/${lynxBundleFile}`;
4986
- } else if (!reqPath.startsWith(basePath)) {
4987
- reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
4988
- }
4989
- const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
4990
- const filePath = path21.resolve(distDir, relPath);
4991
- const distResolved = path21.resolve(distDir);
4992
- if (!filePath.startsWith(distResolved + path21.sep) && filePath !== distResolved) {
4993
- res.writeHead(403);
4994
- res.end();
5313
+ if (input === "r") {
5314
+ void rebuildRef.current();
4995
5315
  return;
4996
5316
  }
4997
- fs20.readFile(filePath, (err, data) => {
4998
- if (err) {
4999
- res.writeHead(404);
5000
- res.end("Not found");
5001
- return;
5002
- }
5003
- res.setHeader("Access-Control-Allow-Origin", "*");
5004
- res.setHeader("Content-Type", reqPath.endsWith(".bundle") ? "application/octet-stream" : "application/javascript");
5005
- res.end(data);
5006
- });
5007
- });
5008
- const wss = new WebSocketServer({ noServer: true });
5009
- httpServer.on("upgrade", (request, socket, head) => {
5010
- const reqPath = (request.url || "").split("?")[0];
5011
- if (reqPath === `${basePath}/__hmr` || reqPath === "/__hmr" || reqPath.endsWith("/__hmr")) {
5012
- wss.handleUpgrade(request, socket, head, (ws) => wss.emit("connection", ws, request));
5013
- } else {
5014
- socket.destroy();
5317
+ if (input === "l") {
5318
+ setUi((s) => ({ ...s, showLogs: !s.showLogs }));
5015
5319
  }
5016
5320
  });
5017
- wss.on("connection", (ws, req) => {
5018
- const clientIp = req.socket.remoteAddress ?? "unknown";
5019
- console.log(`\x1B[90m[WS] client connected: ${clientIp}\x1B[0m`);
5020
- ws.send(JSON.stringify({ type: "connected" }));
5021
- ws.on("close", () => {
5022
- console.log(`\x1B[90m[WS] client disconnected: ${clientIp}\x1B[0m`);
5023
- });
5024
- ws.on("message", (data) => {
5321
+ useEffect3(() => {
5322
+ const onSig = () => {
5323
+ handleQuit();
5324
+ };
5325
+ process.on("SIGINT", onSig);
5326
+ process.on("SIGTERM", onSig);
5327
+ return () => {
5328
+ process.off("SIGINT", onSig);
5329
+ process.off("SIGTERM", onSig);
5330
+ };
5331
+ }, [handleQuit]);
5332
+ useEffect3(() => {
5333
+ let alive = true;
5334
+ let buildProcess = null;
5335
+ let watcher = null;
5336
+ let stopBonjour;
5337
+ const run = async () => {
5025
5338
  try {
5026
- const msg = JSON.parse(data.toString());
5027
- if (msg?.type === "console_log" && Array.isArray(msg.message)) {
5028
- const skip = msg.message.includes("[rspeedy-dev-server]") || msg.message.includes("[HMR]");
5029
- if (skip) return;
5030
- const isJs = msg.tag === "lynx-console" || msg.tag == null;
5031
- if (!verbose && !isJs) return;
5032
- const prefix = isJs ? "\x1B[36m[APP]:\x1B[0m" : "\x1B[33m[NATIVE]:\x1B[0m";
5033
- console.log(prefix, ...msg.message);
5339
+ const resolved = resolveHostPaths();
5340
+ const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
5341
+ const distDir = path21.dirname(lynxBundlePath);
5342
+ const projectName = path21.basename(lynxProjectDir);
5343
+ const basePath = `/${projectName}`;
5344
+ setUi((s) => ({ ...s, projectName, lynxBundleFile }));
5345
+ const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
5346
+ const port = await findAvailablePort(preferredPort);
5347
+ if (port !== preferredPort) {
5348
+ appendLog(`Port ${preferredPort} in use, using ${port}`);
5034
5349
  }
5035
- } catch {
5036
- }
5037
- });
5038
- });
5039
- function broadcastReload() {
5040
- wss.clients.forEach((client) => {
5041
- if (client.readyState === 1) client.send(JSON.stringify({ type: "reload" }));
5042
- });
5043
- }
5044
- let chokidar = null;
5045
- try {
5046
- chokidar = await import("chokidar");
5047
- } catch {
5048
- }
5049
- if (chokidar) {
5050
- const watchPaths = [
5051
- path21.join(lynxProjectDir, "src"),
5052
- path21.join(lynxProjectDir, "lynx.config.ts"),
5053
- path21.join(lynxProjectDir, "lynx.config.js")
5054
- ].filter((p) => fs20.existsSync(p));
5055
- if (watchPaths.length > 0) {
5056
- const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });
5057
- watcher.on("change", async () => {
5058
- try {
5059
- await runBuild();
5060
- broadcastReload();
5061
- console.log("\u{1F504} Rebuilt, clients notified");
5062
- } catch (e) {
5063
- console.error("Build failed:", e.message);
5064
- }
5065
- });
5066
- }
5067
- }
5068
- try {
5069
- await runBuild();
5070
- } catch (e) {
5071
- console.error("\u274C Initial build failed:", e.message);
5072
- process.exit(1);
5073
- }
5074
- let stopBonjour;
5075
- httpServer.listen(port, "0.0.0.0", () => {
5076
- void import("dnssd-advertise").then(({ advertise }) => {
5077
- stopBonjour = advertise({
5078
- name: projectName,
5079
- type: "tamer",
5080
- protocol: "tcp",
5081
- port,
5082
- txt: {
5083
- name: projectName.slice(0, 255),
5084
- path: basePath.slice(0, 255)
5350
+ const iconPaths = resolveIconPaths(projectRoot, config);
5351
+ let iconFilePath = null;
5352
+ if (iconPaths?.source && fs20.statSync(iconPaths.source).isFile()) {
5353
+ iconFilePath = iconPaths.source;
5354
+ } else if (iconPaths?.androidAdaptiveForeground && fs20.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
5355
+ iconFilePath = iconPaths.androidAdaptiveForeground;
5356
+ } else if (iconPaths?.android) {
5357
+ const androidIcon = path21.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
5358
+ if (fs20.existsSync(androidIcon)) iconFilePath = androidIcon;
5359
+ } else if (iconPaths?.ios) {
5360
+ const iosIcon = path21.join(iconPaths.ios, "Icon-1024.png");
5361
+ if (fs20.existsSync(iosIcon)) iconFilePath = iosIcon;
5085
5362
  }
5086
- });
5087
- }).catch(() => {
5088
- });
5089
- const lanIp = getLanIp();
5090
- const devUrl = `http://${lanIp}:${port}${basePath}`;
5091
- const wsUrl = `ws://${lanIp}:${port}${basePath}/__hmr`;
5092
- console.log(`
5093
- \u{1F680} Tamer4Lynx dev server (${projectName})`);
5094
- if (verbose) console.log(` Logs: \x1B[33mverbose\x1B[0m (native + JS)`);
5095
- console.log(` Bundle: ${devUrl}/${lynxBundleFile}`);
5096
- console.log(` Meta: ${devUrl}/meta.json`);
5097
- console.log(` HMR WS: ${wsUrl}`);
5098
- if (stopBonjour) console.log(` mDNS: _tamer._tcp (discoverable on LAN)`);
5099
- console.log(`
5100
- Scan QR or enter in app: ${devUrl}
5101
- `);
5102
- void import("qrcode-terminal").then((mod) => {
5103
- const qrcode = mod.default ?? mod;
5104
- qrcode.generate(devUrl, { small: true });
5105
- }).catch(() => {
5106
- });
5107
- if (process.stdin.isTTY) {
5108
- readline3.emitKeypressEvents(process.stdin);
5109
- process.stdin.setRawMode(true);
5110
- process.stdin.resume();
5111
- process.stdin.setEncoding("utf8");
5112
- const help = "\x1B[90m r: refresh c/Ctrl+L: clear Ctrl+C: exit\x1B[0m";
5113
- console.log(help);
5114
- process.stdin.on("keypress", (str, key) => {
5115
- if (key.ctrl && key.name === "c") {
5116
- void cleanup();
5117
- return;
5363
+ const iconExt = iconFilePath ? path21.extname(iconFilePath) || ".png" : "";
5364
+ const runBuild = () => {
5365
+ return new Promise((resolve, reject) => {
5366
+ const { cmd, args } = detectPackageManager(lynxProjectDir);
5367
+ buildProcess = spawn(cmd, args, {
5368
+ cwd: lynxProjectDir,
5369
+ stdio: "pipe",
5370
+ shell: process.platform === "win32"
5371
+ });
5372
+ let stderr = "";
5373
+ buildProcess.stdout?.on("data", (d) => {
5374
+ appendLog(d.toString());
5375
+ });
5376
+ buildProcess.stderr?.on("data", (d) => {
5377
+ const t = d.toString();
5378
+ stderr += t;
5379
+ appendLog(t);
5380
+ });
5381
+ buildProcess.on("close", (code) => {
5382
+ buildProcess = null;
5383
+ if (code === 0) resolve();
5384
+ else reject(new Error(stderr || `Build exited ${code}`));
5385
+ });
5386
+ });
5387
+ };
5388
+ const doBuild = async () => {
5389
+ setUi((s) => ({ ...s, buildPhase: "building", buildError: void 0 }));
5390
+ try {
5391
+ await runBuild();
5392
+ if (!alive) return;
5393
+ setUi((s) => ({ ...s, buildPhase: "success" }));
5394
+ } catch (e) {
5395
+ if (!alive) return;
5396
+ const msg = e.message;
5397
+ setUi((s) => ({ ...s, buildPhase: "error", buildError: msg }));
5398
+ throw e;
5399
+ }
5400
+ };
5401
+ const httpSrv = http.createServer((req, res) => {
5402
+ let reqPath = (req.url || "/").split("?")[0];
5403
+ if (reqPath === `${basePath}/status`) {
5404
+ res.setHeader("Content-Type", "text/plain");
5405
+ res.setHeader("Access-Control-Allow-Origin", "*");
5406
+ res.end("packager-status:running");
5407
+ return;
5408
+ }
5409
+ if (reqPath === `${basePath}/meta.json`) {
5410
+ const lanIp2 = getLanIp();
5411
+ const nativeModules = discoverNativeExtensions(projectRoot);
5412
+ const androidPackageName = config.android?.packageName?.trim();
5413
+ const iosBundleId = config.ios?.bundleId?.trim();
5414
+ const idParts = [androidPackageName?.toLowerCase(), iosBundleId?.toLowerCase()].filter(
5415
+ (x) => Boolean(x)
5416
+ );
5417
+ const meta = {
5418
+ name: projectName,
5419
+ slug: projectName,
5420
+ bundleUrl: `http://${lanIp2}:${port}${basePath}/${lynxBundleFile}`,
5421
+ bundleFile: lynxBundleFile,
5422
+ hostUri: `http://${lanIp2}:${port}${basePath}`,
5423
+ debuggerHost: `${lanIp2}:${port}`,
5424
+ developer: { tool: "tamer4lynx" },
5425
+ packagerStatus: "running",
5426
+ nativeModules: nativeModules.map((m) => ({
5427
+ packageName: m.packageName,
5428
+ moduleClassName: m.moduleClassName
5429
+ }))
5430
+ };
5431
+ if (androidPackageName) meta.androidPackageName = androidPackageName;
5432
+ if (iosBundleId) meta.iosBundleId = iosBundleId;
5433
+ if (idParts.length > 0) meta.tamerAppKey = idParts.join("|");
5434
+ const rawIcon = config.icon;
5435
+ if (rawIcon && typeof rawIcon === "object" && "source" in rawIcon && typeof rawIcon.source === "string") {
5436
+ meta.iconSource = rawIcon.source;
5437
+ } else if (typeof rawIcon === "string") {
5438
+ meta.iconSource = rawIcon;
5439
+ }
5440
+ if (iconFilePath) {
5441
+ meta.icon = `http://${lanIp2}:${port}${basePath}/icon${iconExt}`;
5442
+ }
5443
+ res.setHeader("Content-Type", "application/json");
5444
+ res.setHeader("Access-Control-Allow-Origin", "*");
5445
+ res.end(JSON.stringify(meta, null, 2));
5446
+ return;
5447
+ }
5448
+ if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
5449
+ fs20.readFile(iconFilePath, (err, data) => {
5450
+ if (err) {
5451
+ res.writeHead(404);
5452
+ res.end();
5453
+ return;
5454
+ }
5455
+ res.setHeader("Content-Type", STATIC_MIME[iconExt] ?? "image/png");
5456
+ res.setHeader("Access-Control-Allow-Origin", "*");
5457
+ res.end(data);
5458
+ });
5459
+ return;
5460
+ }
5461
+ const lynxStaticMounts = [
5462
+ { prefix: `${basePath}/src/assets/`, rootSub: "src/assets" },
5463
+ { prefix: `${basePath}/assets/`, rootSub: "assets" }
5464
+ ];
5465
+ for (const { prefix, rootSub } of lynxStaticMounts) {
5466
+ if (!reqPath.startsWith(prefix)) continue;
5467
+ let rel = reqPath.slice(prefix.length);
5468
+ try {
5469
+ rel = decodeURIComponent(rel);
5470
+ } catch {
5471
+ res.writeHead(400);
5472
+ res.end();
5473
+ return;
5474
+ }
5475
+ const safe = path21.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
5476
+ if (path21.isAbsolute(safe) || safe.startsWith("..")) {
5477
+ res.writeHead(403);
5478
+ res.end();
5479
+ return;
5480
+ }
5481
+ const allowedRoot = path21.resolve(lynxProjectDir, rootSub);
5482
+ const abs = path21.resolve(allowedRoot, safe);
5483
+ if (!abs.startsWith(allowedRoot + path21.sep) && abs !== allowedRoot) {
5484
+ res.writeHead(403);
5485
+ res.end();
5486
+ return;
5487
+ }
5488
+ if (!fs20.existsSync(abs) || !fs20.statSync(abs).isFile()) {
5489
+ res.writeHead(404);
5490
+ res.end("Not found");
5491
+ return;
5492
+ }
5493
+ sendFileFromDisk(res, abs);
5494
+ return;
5495
+ }
5496
+ if (reqPath === "/" || reqPath === basePath || reqPath === `${basePath}/`) {
5497
+ reqPath = `${basePath}/${lynxBundleFile}`;
5498
+ } else if (!reqPath.startsWith(basePath)) {
5499
+ reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
5500
+ }
5501
+ const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
5502
+ const filePath = path21.resolve(distDir, relPath);
5503
+ const distResolved = path21.resolve(distDir);
5504
+ if (!filePath.startsWith(distResolved + path21.sep) && filePath !== distResolved) {
5505
+ res.writeHead(403);
5506
+ res.end();
5507
+ return;
5508
+ }
5509
+ fs20.readFile(filePath, (err, data) => {
5510
+ if (err) {
5511
+ res.writeHead(404);
5512
+ res.end("Not found");
5513
+ return;
5514
+ }
5515
+ res.setHeader("Access-Control-Allow-Origin", "*");
5516
+ res.setHeader("Content-Type", reqPath.endsWith(".bundle") ? "application/octet-stream" : "application/javascript");
5517
+ res.end(data);
5518
+ });
5519
+ });
5520
+ const wssInst = new WebSocketServer({ noServer: true });
5521
+ rebuildRef.current = async () => {
5522
+ try {
5523
+ await doBuild();
5524
+ if (!alive) return;
5525
+ wssInst.clients.forEach((client) => {
5526
+ if (client.readyState === 1) client.send(JSON.stringify({ type: "reload" }));
5527
+ });
5528
+ appendLog("Rebuilt, clients notified");
5529
+ } catch {
5530
+ }
5531
+ };
5532
+ httpSrv.on("upgrade", (request, socket, head) => {
5533
+ const p = (request.url || "").split("?")[0];
5534
+ if (p === `${basePath}/__hmr` || p === "/__hmr" || p.endsWith("/__hmr")) {
5535
+ wssInst.handleUpgrade(request, socket, head, (ws) => wssInst.emit("connection", ws, request));
5536
+ } else {
5537
+ socket.destroy();
5538
+ }
5539
+ });
5540
+ wssInst.on("connection", (ws, req) => {
5541
+ const clientIp = req.socket.remoteAddress ?? "unknown";
5542
+ setUi((s) => ({ ...s, wsConnections: s.wsConnections + 1 }));
5543
+ appendLog(`[WS] connected: ${clientIp}`);
5544
+ ws.send(JSON.stringify({ type: "connected" }));
5545
+ ws.on("close", () => {
5546
+ setUi((s) => ({ ...s, wsConnections: Math.max(0, s.wsConnections - 1) }));
5547
+ appendLog(`[WS] disconnected: ${clientIp}`);
5548
+ });
5549
+ ws.on("message", (data) => {
5550
+ try {
5551
+ const msg = JSON.parse(data.toString());
5552
+ if (msg?.type === "console_log" && Array.isArray(msg.message)) {
5553
+ const skip = msg.message.includes("[rspeedy-dev-server]") || msg.message.includes("[HMR]");
5554
+ if (skip) return;
5555
+ const isJs = msg.tag === "lynx-console" || msg.tag == null;
5556
+ if (!verbose && !isJs) return;
5557
+ appendLog(`${isJs ? "[APP]" : "[NATIVE]"} ${msg.message.join(" ")}`);
5558
+ }
5559
+ } catch {
5560
+ }
5561
+ });
5562
+ });
5563
+ let chokidar = null;
5564
+ try {
5565
+ chokidar = await import("chokidar");
5566
+ } catch {
5118
5567
  }
5119
- switch (key.name) {
5120
- case "r":
5121
- runBuild().then(() => {
5122
- broadcastReload();
5123
- console.log("\u{1F504} Refreshed, clients notified");
5124
- }).catch((e) => console.error("Build failed:", e.message));
5125
- break;
5126
- case "c":
5127
- process.stdout.write("\x1B[2J\x1B[H");
5128
- break;
5129
- case "l":
5130
- if (key.ctrl) process.stdout.write("\x1B[2J\x1B[H");
5131
- break;
5132
- default:
5133
- break;
5568
+ if (chokidar) {
5569
+ const watchPaths = [
5570
+ path21.join(lynxProjectDir, "src"),
5571
+ path21.join(lynxProjectDir, "lynx.config.ts"),
5572
+ path21.join(lynxProjectDir, "lynx.config.js")
5573
+ ].filter((p) => fs20.existsSync(p));
5574
+ if (watchPaths.length > 0) {
5575
+ const w = chokidar.watch(watchPaths, { ignoreInitial: true });
5576
+ w.on("change", async () => {
5577
+ try {
5578
+ await rebuildRef.current();
5579
+ } catch {
5580
+ }
5581
+ });
5582
+ watcher = {
5583
+ close: async () => {
5584
+ await w.close();
5585
+ }
5586
+ };
5587
+ }
5134
5588
  }
5135
- });
5589
+ await doBuild();
5590
+ if (!alive) return;
5591
+ await new Promise((listenResolve, listenReject) => {
5592
+ httpSrv.listen(port, "0.0.0.0", () => {
5593
+ listenResolve();
5594
+ });
5595
+ httpSrv.once("error", (err) => listenReject(err));
5596
+ });
5597
+ if (!alive) return;
5598
+ void import("dnssd-advertise").then(({ advertise }) => {
5599
+ stopBonjour = advertise({
5600
+ name: projectName,
5601
+ type: "tamer",
5602
+ protocol: "tcp",
5603
+ port,
5604
+ txt: {
5605
+ name: projectName.slice(0, 255),
5606
+ path: basePath.slice(0, 255)
5607
+ }
5608
+ });
5609
+ setUi((s) => ({ ...s, bonjour: true }));
5610
+ }).catch(() => {
5611
+ });
5612
+ const lanIp = getLanIp();
5613
+ const devUrl = `http://${lanIp}:${port}${basePath}`;
5614
+ const wsUrl = `ws://${lanIp}:${port}${basePath}/__hmr`;
5615
+ setUi((s) => ({
5616
+ ...s,
5617
+ phase: "running",
5618
+ port,
5619
+ lanIp,
5620
+ devUrl,
5621
+ wsUrl
5622
+ }));
5623
+ void import("qrcode-terminal").then((mod) => {
5624
+ const qrcode = mod.default ?? mod;
5625
+ qrcode.generate(devUrl, { small: true }, (qr) => {
5626
+ if (!alive) return;
5627
+ setUi((s) => ({ ...s, qrLines: qr.split("\n").filter(Boolean) }));
5628
+ });
5629
+ }).catch(() => {
5630
+ });
5631
+ cleanupRef.current = async () => {
5632
+ buildProcess?.kill();
5633
+ await watcher?.close().catch(() => {
5634
+ });
5635
+ await stopBonjour?.();
5636
+ httpSrv.close();
5637
+ wssInst.close();
5638
+ };
5639
+ } catch (e) {
5640
+ if (!alive) return;
5641
+ setUi((s) => ({
5642
+ ...s,
5643
+ phase: "failed",
5644
+ startError: e.message
5645
+ }));
5646
+ }
5647
+ };
5648
+ void run();
5649
+ return () => {
5650
+ alive = false;
5651
+ void cleanupRef.current?.();
5652
+ };
5653
+ }, [appendLog, verbose]);
5654
+ return /* @__PURE__ */ jsx10(
5655
+ ServerDashboard,
5656
+ {
5657
+ projectName: ui.projectName,
5658
+ port: ui.port,
5659
+ lanIp: ui.lanIp,
5660
+ devUrl: ui.devUrl,
5661
+ wsUrl: ui.wsUrl,
5662
+ lynxBundleFile: ui.lynxBundleFile,
5663
+ bonjour: ui.bonjour,
5664
+ verbose: ui.verbose,
5665
+ buildPhase: ui.buildPhase,
5666
+ buildError: ui.buildError,
5667
+ wsConnections: ui.wsConnections,
5668
+ logLines: ui.logLines,
5669
+ showLogs: ui.showLogs,
5670
+ qrLines: ui.qrLines,
5671
+ phase: ui.phase,
5672
+ startError: ui.startError
5136
5673
  }
5674
+ );
5675
+ }
5676
+ async function startDevServer(opts) {
5677
+ const verbose = opts?.verbose ?? false;
5678
+ const { waitUntilExit } = render2(/* @__PURE__ */ jsx10(DevServerApp, { verbose }), {
5679
+ exitOnCtrlC: false,
5680
+ patchConsole: false
5137
5681
  });
5138
- const cleanup = async () => {
5139
- buildProcess?.kill();
5140
- await stopBonjour?.();
5141
- httpServer.close();
5142
- wss.close();
5143
- process.exit(0);
5144
- };
5145
- process.on("SIGINT", () => {
5146
- void cleanup();
5147
- });
5148
- process.on("SIGTERM", () => {
5149
- void cleanup();
5150
- });
5151
- await new Promise(() => {
5152
- });
5682
+ await waitUntilExit();
5153
5683
  }
5154
5684
  var devServer_default = startDevServer;
5155
5685
 
@@ -5659,7 +6189,10 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
5659
6189
  // src/common/add.ts
5660
6190
  import fs23 from "fs";
5661
6191
  import path24 from "path";
5662
- import { execSync as execSync10 } from "child_process";
6192
+ import { execFile, execSync as execSync10 } from "child_process";
6193
+ import { promisify } from "util";
6194
+ import semver from "semver";
6195
+ var execFileAsync = promisify(execFile);
5663
6196
  var CORE_PACKAGES = [
5664
6197
  "@tamer4lynx/tamer-app-shell",
5665
6198
  "@tamer4lynx/tamer-screen",
@@ -5669,8 +6202,49 @@ var CORE_PACKAGES = [
5669
6202
  "@tamer4lynx/tamer-system-ui",
5670
6203
  "@tamer4lynx/tamer-icons"
5671
6204
  ];
6205
+ var DEV_STACK_PACKAGES = [
6206
+ "@tamer4lynx/jiggle",
6207
+ "@tamer4lynx/tamer-app-shell",
6208
+ "@tamer4lynx/tamer-biometric",
6209
+ "@tamer4lynx/tamer-dev-app",
6210
+ "@tamer4lynx/tamer-dev-client",
6211
+ "@tamer4lynx/tamer-display-browser",
6212
+ "@tamer4lynx/tamer-icons",
6213
+ "@tamer4lynx/tamer-insets",
6214
+ "@tamer4lynx/tamer-linking",
6215
+ "@tamer4lynx/tamer-plugin",
6216
+ "@tamer4lynx/tamer-router",
6217
+ "@tamer4lynx/tamer-screen",
6218
+ "@tamer4lynx/tamer-secure-store",
6219
+ "@tamer4lynx/tamer-system-ui",
6220
+ "@tamer4lynx/tamer-transports"
6221
+ ];
5672
6222
  var PACKAGE_ALIASES = {};
5673
- function detectPackageManager(cwd) {
6223
+ async function getHighestPublishedVersion(fullName) {
6224
+ try {
6225
+ const { stdout } = await execFileAsync("npm", ["view", fullName, "versions", "--json"], {
6226
+ maxBuffer: 10 * 1024 * 1024
6227
+ });
6228
+ const parsed = JSON.parse(stdout.trim());
6229
+ const list = Array.isArray(parsed) ? parsed : [parsed];
6230
+ const valid = list.filter((v) => typeof v === "string" && !!semver.valid(v));
6231
+ if (valid.length === 0) return null;
6232
+ return semver.rsort(valid)[0] ?? null;
6233
+ } catch {
6234
+ return null;
6235
+ }
6236
+ }
6237
+ async function normalizeTamerInstallSpec(pkg) {
6238
+ if (!pkg.startsWith("@tamer4lynx/")) return pkg;
6239
+ if (/^@[^/]+\/[^@]+@/.test(pkg)) return pkg;
6240
+ const highest = await getHighestPublishedVersion(pkg);
6241
+ if (highest) {
6242
+ return `${pkg}@${highest}`;
6243
+ }
6244
+ console.warn(`\u26A0\uFE0F Could not resolve published versions for ${pkg}; using @prerelease`);
6245
+ return `${pkg}@prerelease`;
6246
+ }
6247
+ function detectPackageManager2(cwd) {
5674
6248
  const dir = path24.resolve(cwd);
5675
6249
  if (fs23.existsSync(path24.join(dir, "pnpm-lock.yaml"))) return "pnpm";
5676
6250
  if (fs23.existsSync(path24.join(dir, "bun.lockb"))) return "bun";
@@ -5681,14 +6255,25 @@ function runInstall(cwd, packages, pm) {
5681
6255
  const cmd = pm === "npm" ? "npm" : pm === "pnpm" ? "pnpm" : "bun";
5682
6256
  execSync10(`${cmd} ${args.join(" ")}`, { stdio: "inherit", cwd });
5683
6257
  }
5684
- function addCore() {
6258
+ async function addCore() {
5685
6259
  const { lynxProjectDir } = resolveHostPaths();
5686
- const pm = detectPackageManager(lynxProjectDir);
5687
- console.log(`Adding core packages to ${lynxProjectDir} (using ${pm})...`);
5688
- runInstall(lynxProjectDir, CORE_PACKAGES, pm);
6260
+ const pm = detectPackageManager2(lynxProjectDir);
6261
+ console.log(`Resolving latest published versions (npm)\u2026`);
6262
+ const resolved = await Promise.all(CORE_PACKAGES.map(normalizeTamerInstallSpec));
6263
+ console.log(`Adding core packages to ${lynxProjectDir} (using ${pm})\u2026`);
6264
+ runInstall(lynxProjectDir, resolved, pm);
5689
6265
  console.log("\u2705 Core packages installed. Run `t4l link` to link native modules.");
5690
6266
  }
5691
- function add(packages = []) {
6267
+ async function addDev() {
6268
+ const { lynxProjectDir } = resolveHostPaths();
6269
+ const pm = detectPackageManager2(lynxProjectDir);
6270
+ console.log(`Resolving latest published versions (npm)\u2026`);
6271
+ const resolved = await Promise.all([...DEV_STACK_PACKAGES].map(normalizeTamerInstallSpec));
6272
+ console.log(`Adding dev stack (${DEV_STACK_PACKAGES.length} @tamer4lynx packages) to ${lynxProjectDir} (using ${pm})\u2026`);
6273
+ runInstall(lynxProjectDir, resolved, pm);
6274
+ console.log("\u2705 Dev stack installed. Run `t4l link` to link native modules.");
6275
+ }
6276
+ async function add(packages = []) {
5692
6277
  const list = Array.isArray(packages) ? packages : [];
5693
6278
  if (list.length === 0) {
5694
6279
  console.log("Usage: t4l add <package> [package...]");
@@ -5698,28 +6283,695 @@ function add(packages = []) {
5698
6283
  return;
5699
6284
  }
5700
6285
  const { lynxProjectDir } = resolveHostPaths();
5701
- const pm = detectPackageManager(lynxProjectDir);
5702
- const normalized = list.map((p) => {
5703
- if (p.startsWith("@")) return p;
5704
- return PACKAGE_ALIASES[p] ?? `@tamer4lynx/${p}`;
5705
- });
5706
- console.log(`Adding ${normalized.join(", ")} to ${lynxProjectDir} (using ${pm})...`);
6286
+ const pm = detectPackageManager2(lynxProjectDir);
6287
+ console.log(`Resolving latest published versions (npm)\u2026`);
6288
+ const normalized = await Promise.all(
6289
+ list.map(async (p) => {
6290
+ const spec = p.startsWith("@") ? p : PACKAGE_ALIASES[p] ?? `@tamer4lynx/${p}`;
6291
+ return normalizeTamerInstallSpec(spec);
6292
+ })
6293
+ );
6294
+ console.log(`Adding ${normalized.join(", ")} to ${lynxProjectDir} (using ${pm})\u2026`);
5707
6295
  runInstall(lynxProjectDir, normalized, pm);
5708
6296
  console.log("\u2705 Packages installed. Run `t4l link` to link native modules.");
5709
6297
  }
5710
6298
 
6299
+ // src/common/signing.tsx
6300
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
6301
+ import { render as render3, Text as Text10, Box as Box9 } from "ink";
6302
+ import fs26 from "fs";
6303
+ import path27 from "path";
6304
+
6305
+ // src/common/androidKeystore.ts
6306
+ import { execFileSync } from "child_process";
6307
+ import fs24 from "fs";
6308
+ import path25 from "path";
6309
+ function normalizeJavaHome(raw) {
6310
+ if (!raw) return void 0;
6311
+ const t = raw.trim().replace(/^["']|["']$/g, "");
6312
+ return t || void 0;
6313
+ }
6314
+ function discoverJavaHomeMacOs() {
6315
+ if (process.platform !== "darwin") return void 0;
6316
+ try {
6317
+ const out = execFileSync("/usr/libexec/java_home", [], {
6318
+ encoding: "utf8",
6319
+ stdio: ["pipe", "pipe", "pipe"]
6320
+ }).trim().split("\n")[0]?.trim();
6321
+ if (out && fs24.existsSync(path25.join(out, "bin", "keytool"))) return out;
6322
+ } catch {
6323
+ }
6324
+ return void 0;
6325
+ }
6326
+ function resolveKeytoolPath() {
6327
+ const jh = normalizeJavaHome(process.env.JAVA_HOME);
6328
+ const win = process.platform === "win32";
6329
+ const keytoolName = win ? "keytool.exe" : "keytool";
6330
+ if (jh) {
6331
+ const p = path25.join(jh, "bin", keytoolName);
6332
+ if (fs24.existsSync(p)) return p;
6333
+ }
6334
+ const mac = discoverJavaHomeMacOs();
6335
+ if (mac) {
6336
+ const p = path25.join(mac, "bin", keytoolName);
6337
+ if (fs24.existsSync(p)) return p;
6338
+ }
6339
+ return "keytool";
6340
+ }
6341
+ function keytoolAvailable() {
6342
+ const tryRun = (cmd) => {
6343
+ try {
6344
+ execFileSync(cmd, ["-help"], { stdio: "pipe" });
6345
+ return true;
6346
+ } catch {
6347
+ return false;
6348
+ }
6349
+ };
6350
+ if (tryRun("keytool")) return true;
6351
+ const fromJavaHome = resolveKeytoolPath();
6352
+ if (fromJavaHome !== "keytool" && fs24.existsSync(fromJavaHome)) {
6353
+ return tryRun(fromJavaHome);
6354
+ }
6355
+ return false;
6356
+ }
6357
+ function generateReleaseKeystore(opts) {
6358
+ const keytool = resolveKeytoolPath();
6359
+ const dir = path25.dirname(opts.keystoreAbsPath);
6360
+ fs24.mkdirSync(dir, { recursive: true });
6361
+ if (fs24.existsSync(opts.keystoreAbsPath)) {
6362
+ throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
6363
+ }
6364
+ if (!opts.storePassword || !opts.keyPassword) {
6365
+ throw new Error(
6366
+ "JDK keytool requires a keystore and key password of at least 6 characters for -genkeypair. Enter a password or use an existing keystore."
6367
+ );
6368
+ }
6369
+ const args = [
6370
+ "-genkeypair",
6371
+ "-v",
6372
+ "-keystore",
6373
+ opts.keystoreAbsPath,
6374
+ "-alias",
6375
+ opts.alias,
6376
+ "-keyalg",
6377
+ "RSA",
6378
+ "-keysize",
6379
+ "2048",
6380
+ "-validity",
6381
+ "10000",
6382
+ "-storepass",
6383
+ opts.storePassword,
6384
+ "-keypass",
6385
+ opts.keyPassword,
6386
+ "-dname",
6387
+ opts.dname
6388
+ ];
6389
+ try {
6390
+ execFileSync(keytool, args, { stdio: ["pipe", "pipe", "pipe"] });
6391
+ } catch (e) {
6392
+ const err = e;
6393
+ const fromKeytool = [err.stdout, err.stderr].filter(Boolean).map((b) => Buffer.from(b).toString("utf8")).join("\n").trim();
6394
+ throw new Error(fromKeytool || err.message || "keytool failed");
6395
+ }
6396
+ }
6397
+
6398
+ // src/common/appendEnvFile.ts
6399
+ import fs25 from "fs";
6400
+ import path26 from "path";
6401
+ import { parse } from "dotenv";
6402
+ function keysDefinedInFile(filePath) {
6403
+ if (!fs25.existsSync(filePath)) return /* @__PURE__ */ new Set();
6404
+ try {
6405
+ return new Set(Object.keys(parse(fs25.readFileSync(filePath, "utf8"))));
6406
+ } catch {
6407
+ return /* @__PURE__ */ new Set();
6408
+ }
6409
+ }
6410
+ function formatEnvLine(key, value) {
6411
+ if (/[\r\n]/.test(value) || /^\s|\s$/.test(value) || /[#"'\\=]/.test(value)) {
6412
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
6413
+ return `${key}="${escaped}"`;
6414
+ }
6415
+ return `${key}=${value}`;
6416
+ }
6417
+ function appendEnvVarsIfMissing(projectRoot, vars) {
6418
+ const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
6419
+ if (entries.length === 0) return null;
6420
+ const envLocal = path26.join(projectRoot, ".env.local");
6421
+ const envDefault = path26.join(projectRoot, ".env");
6422
+ let target;
6423
+ if (fs25.existsSync(envLocal)) target = envLocal;
6424
+ else if (fs25.existsSync(envDefault)) target = envDefault;
6425
+ else target = envLocal;
6426
+ const existing = keysDefinedInFile(target);
6427
+ const lines = [];
6428
+ const appendedKeys = [];
6429
+ for (const [k, v] of entries) {
6430
+ if (existing.has(k)) continue;
6431
+ lines.push(formatEnvLine(k, v));
6432
+ appendedKeys.push(k);
6433
+ }
6434
+ if (lines.length === 0) {
6435
+ return {
6436
+ file: path26.basename(target),
6437
+ keys: [],
6438
+ skippedAll: entries.length > 0
6439
+ };
6440
+ }
6441
+ let prefix = "";
6442
+ if (fs25.existsSync(target)) {
6443
+ const cur = fs25.readFileSync(target, "utf8");
6444
+ prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
6445
+ `;
6446
+ }
6447
+ const block = lines.join("\n") + "\n";
6448
+ fs25.writeFileSync(target, prefix + block, "utf8");
6449
+ return { file: path26.basename(target), keys: appendedKeys };
6450
+ }
6451
+
6452
+ // src/common/signing.tsx
6453
+ import { Fragment, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
6454
+ function AndroidKeystoreModeSelect({
6455
+ onSelect
6456
+ }) {
6457
+ const canGen = keytoolAvailable();
6458
+ const items = canGen ? [
6459
+ { label: "Generate a new release keystore (JDK keytool)", value: "generate" },
6460
+ { label: "Use an existing keystore file", value: "existing" }
6461
+ ] : [
6462
+ {
6463
+ label: "Use an existing keystore file (install a JDK for keytool to generate)",
6464
+ value: "existing"
6465
+ }
6466
+ ];
6467
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
6468
+ /* @__PURE__ */ jsx11(
6469
+ TuiSelectInput,
6470
+ {
6471
+ label: "Android release keystore:",
6472
+ items,
6473
+ onSelect
6474
+ }
6475
+ ),
6476
+ !canGen && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "keytool not found on PATH / JAVA_HOME. Install a JDK or set JAVA_HOME, then run signing again to generate." })
6477
+ ] });
6478
+ }
6479
+ function firstStepForPlatform(p) {
6480
+ if (p === "ios") return "ios-team";
6481
+ if (p === "android" || p === "both") return "android-keystore-mode";
6482
+ return "platform";
6483
+ }
6484
+ function SigningWizard({ platform: initialPlatform }) {
6485
+ const [state, setState] = useState6({
6486
+ platform: initialPlatform || null,
6487
+ android: {
6488
+ keystoreFile: "",
6489
+ keyAlias: "release",
6490
+ storePasswordEnv: "ANDROID_KEYSTORE_PASSWORD",
6491
+ keyPasswordEnv: "ANDROID_KEY_PASSWORD",
6492
+ keystoreMode: null,
6493
+ genKeystorePath: "android/release.keystore",
6494
+ genPassword: ""
6495
+ },
6496
+ ios: {
6497
+ developmentTeam: "",
6498
+ codeSignIdentity: "",
6499
+ provisioningProfileSpecifier: ""
6500
+ },
6501
+ step: initialPlatform ? firstStepForPlatform(initialPlatform) : "platform",
6502
+ generateError: null,
6503
+ androidEnvAppend: null
6504
+ });
6505
+ const nextStep = () => {
6506
+ setState((s) => {
6507
+ if (s.step === "android-gen-path") {
6508
+ return { ...s, step: "android-gen-alias" };
6509
+ }
6510
+ if (s.step === "android-gen-alias") {
6511
+ return { ...s, step: "android-gen-password" };
6512
+ }
6513
+ if (s.step === "android-keystore") {
6514
+ return { ...s, step: "android-alias" };
6515
+ }
6516
+ if (s.step === "android-alias") {
6517
+ return { ...s, step: "android-password-env" };
6518
+ }
6519
+ if (s.step === "android-password-env") {
6520
+ return { ...s, step: "android-key-password-env" };
6521
+ }
6522
+ if (s.step === "android-key-password-env") {
6523
+ if (s.platform === "both") {
6524
+ return { ...s, step: "ios-team" };
6525
+ }
6526
+ return { ...s, step: "saving" };
6527
+ }
6528
+ if (s.step === "ios-team") {
6529
+ return { ...s, step: "ios-identity" };
6530
+ }
6531
+ if (s.step === "ios-identity") {
6532
+ return { ...s, step: "ios-profile" };
6533
+ }
6534
+ if (s.step === "ios-profile") {
6535
+ return { ...s, step: "saving" };
6536
+ }
6537
+ return s;
6538
+ });
6539
+ };
6540
+ useEffect4(() => {
6541
+ if (state.step === "saving") {
6542
+ saveConfig();
6543
+ }
6544
+ }, [state.step]);
6545
+ const generateRunId = useRef2(0);
6546
+ useEffect4(() => {
6547
+ if (state.step !== "android-generating") return;
6548
+ const runId = ++generateRunId.current;
6549
+ let cancelled = false;
6550
+ const run = () => {
6551
+ let abs = "";
6552
+ try {
6553
+ const resolved = resolveHostPaths();
6554
+ const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
6555
+ abs = path27.isAbsolute(rel) ? rel : path27.join(resolved.projectRoot, rel);
6556
+ const alias = state.android.keyAlias.trim() || "release";
6557
+ const pw = state.android.genPassword;
6558
+ const pkg = resolved.config.android?.packageName ?? "com.example.app";
6559
+ const safeOU = pkg.replace(/[,=+]/g, "_");
6560
+ const dname = `CN=Android Release, OU=${safeOU}, O=Android, C=US`;
6561
+ generateReleaseKeystore({
6562
+ keystoreAbsPath: abs,
6563
+ alias,
6564
+ storePassword: pw,
6565
+ keyPassword: pw,
6566
+ dname
6567
+ });
6568
+ if (cancelled || runId !== generateRunId.current) return;
6569
+ setState((s) => ({
6570
+ ...s,
6571
+ android: {
6572
+ ...s.android,
6573
+ keystoreFile: rel,
6574
+ keyAlias: alias,
6575
+ keystoreMode: "generate"
6576
+ },
6577
+ step: "android-password-env",
6578
+ generateError: null
6579
+ }));
6580
+ } catch (e) {
6581
+ const msg = e.message;
6582
+ if (abs && fs26.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
6583
+ if (cancelled || runId !== generateRunId.current) return;
6584
+ const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
6585
+ const alias = state.android.keyAlias.trim() || "release";
6586
+ setState((s) => ({
6587
+ ...s,
6588
+ android: {
6589
+ ...s.android,
6590
+ keystoreFile: rel,
6591
+ keyAlias: alias,
6592
+ keystoreMode: "generate"
6593
+ },
6594
+ step: "android-password-env",
6595
+ generateError: null
6596
+ }));
6597
+ return;
6598
+ }
6599
+ if (cancelled || runId !== generateRunId.current) return;
6600
+ setState((s) => ({
6601
+ ...s,
6602
+ step: "android-gen-password",
6603
+ generateError: msg
6604
+ }));
6605
+ }
6606
+ };
6607
+ run();
6608
+ return () => {
6609
+ cancelled = true;
6610
+ };
6611
+ }, [state.step, state.android.genKeystorePath, state.android.keyAlias, state.android.genPassword]);
6612
+ useEffect4(() => {
6613
+ if (state.step === "done") {
6614
+ setTimeout(() => {
6615
+ process.exit(0);
6616
+ }, 3e3);
6617
+ }
6618
+ }, [state.step]);
6619
+ const saveConfig = async () => {
6620
+ try {
6621
+ const resolved = resolveHostPaths();
6622
+ const configPath = path27.join(resolved.projectRoot, "tamer.config.json");
6623
+ let config = {};
6624
+ let androidEnvAppend = null;
6625
+ if (fs26.existsSync(configPath)) {
6626
+ config = JSON.parse(fs26.readFileSync(configPath, "utf8"));
6627
+ }
6628
+ if (state.platform === "android" || state.platform === "both") {
6629
+ config.android = config.android || {};
6630
+ config.android.signing = {
6631
+ keystoreFile: state.android.keystoreFile,
6632
+ keyAlias: state.android.keyAlias,
6633
+ storePasswordEnv: state.android.storePasswordEnv,
6634
+ keyPasswordEnv: state.android.keyPasswordEnv
6635
+ };
6636
+ if (state.android.keystoreMode === "generate" && state.android.genPassword) {
6637
+ const storeEnv = state.android.storePasswordEnv.trim() || "ANDROID_KEYSTORE_PASSWORD";
6638
+ const keyEnv = state.android.keyPasswordEnv.trim() || "ANDROID_KEY_PASSWORD";
6639
+ androidEnvAppend = appendEnvVarsIfMissing(resolved.projectRoot, {
6640
+ [storeEnv]: state.android.genPassword,
6641
+ [keyEnv]: state.android.genPassword
6642
+ });
6643
+ }
6644
+ }
6645
+ if (state.platform === "ios" || state.platform === "both") {
6646
+ config.ios = config.ios || {};
6647
+ config.ios.signing = {
6648
+ developmentTeam: state.ios.developmentTeam,
6649
+ ...state.ios.codeSignIdentity && { codeSignIdentity: state.ios.codeSignIdentity },
6650
+ ...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
6651
+ };
6652
+ }
6653
+ fs26.writeFileSync(configPath, JSON.stringify(config, null, 2));
6654
+ const gitignorePath = path27.join(resolved.projectRoot, ".gitignore");
6655
+ if (fs26.existsSync(gitignorePath)) {
6656
+ let gitignore = fs26.readFileSync(gitignorePath, "utf8");
6657
+ const additions = [
6658
+ ".env.local",
6659
+ "*.jks",
6660
+ "*.keystore"
6661
+ ];
6662
+ for (const addition of additions) {
6663
+ if (!gitignore.includes(addition)) {
6664
+ gitignore += `
6665
+ ${addition}
6666
+ `;
6667
+ }
6668
+ }
6669
+ fs26.writeFileSync(gitignorePath, gitignore);
6670
+ }
6671
+ setState((s) => ({
6672
+ ...s,
6673
+ step: "done",
6674
+ androidEnvAppend: state.platform === "android" || state.platform === "both" ? androidEnvAppend : null
6675
+ }));
6676
+ } catch (error) {
6677
+ console.error("Error saving config:", error);
6678
+ process.exit(1);
6679
+ }
6680
+ };
6681
+ if (state.step === "done") {
6682
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
6683
+ /* @__PURE__ */ jsx11(Text10, { color: "green", children: "\u2705 Signing configuration saved to tamer.config.json" }),
6684
+ (state.platform === "android" || state.platform === "both") && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
6685
+ /* @__PURE__ */ jsx11(Text10, { children: "Android signing configured:" }),
6686
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6687
+ " Keystore: ",
6688
+ state.android.keystoreFile
6689
+ ] }),
6690
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6691
+ " Alias: ",
6692
+ state.android.keyAlias
6693
+ ] }),
6694
+ state.androidEnvAppend?.keys.length ? /* @__PURE__ */ jsxs10(Text10, { children: [
6695
+ "Appended ",
6696
+ state.androidEnvAppend.keys.join(", "),
6697
+ " to ",
6698
+ state.androidEnvAppend.file,
6699
+ " (existing keys left unchanged)."
6700
+ ] }) : state.androidEnvAppend?.skippedAll ? /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6701
+ state.androidEnvAppend.file,
6702
+ " already defines the signing env vars; left unchanged."
6703
+ ] }) : /* @__PURE__ */ jsxs10(Fragment, { children: [
6704
+ /* @__PURE__ */ jsx11(Text10, { children: "Set environment variables (or add them to .env / .env.local):" }),
6705
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6706
+ " export ",
6707
+ state.android.storePasswordEnv,
6708
+ '="your-keystore-password"'
6709
+ ] }),
6710
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6711
+ " export ",
6712
+ state.android.keyPasswordEnv,
6713
+ '="your-key-password"'
6714
+ ] })
6715
+ ] })
6716
+ ] }),
6717
+ (state.platform === "ios" || state.platform === "both") && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
6718
+ /* @__PURE__ */ jsx11(Text10, { children: "iOS signing configured:" }),
6719
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6720
+ " Team ID: ",
6721
+ state.ios.developmentTeam
6722
+ ] }),
6723
+ state.ios.codeSignIdentity && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
6724
+ " Identity: ",
6725
+ state.ios.codeSignIdentity
6726
+ ] })
6727
+ ] }),
6728
+ /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
6729
+ state.platform === "android" && /* @__PURE__ */ jsxs10(Fragment, { children: [
6730
+ /* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build android -p` to build this platform with signing." }),
6731
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "`t4l build -p` (no platform) builds both Android and iOS." })
6732
+ ] }),
6733
+ state.platform === "ios" && /* @__PURE__ */ jsxs10(Fragment, { children: [
6734
+ /* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build ios -p` to build this platform with signing." }),
6735
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "`t4l build -p` (no platform) builds both Android and iOS." })
6736
+ ] }),
6737
+ state.platform === "both" && /* @__PURE__ */ jsxs10(Fragment, { children: [
6738
+ /* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build -p` to build both platforms with signing." }),
6739
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "Or: `t4l build android -p` / `t4l build ios -p` for one platform." })
6740
+ ] })
6741
+ ] })
6742
+ ] });
6743
+ }
6744
+ if (state.step === "saving") {
6745
+ return /* @__PURE__ */ jsx11(Box9, { children: /* @__PURE__ */ jsx11(TuiSpinner, { label: "Saving configuration..." }) });
6746
+ }
6747
+ if (state.step === "android-generating") {
6748
+ return /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx11(TuiSpinner, { label: "Running keytool to create release keystore..." }) });
6749
+ }
6750
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
6751
+ state.step === "platform" && /* @__PURE__ */ jsx11(
6752
+ TuiSelectInput,
6753
+ {
6754
+ label: "Select platform(s) to configure signing:",
6755
+ items: [
6756
+ { label: "Android", value: "android" },
6757
+ { label: "iOS", value: "ios" },
6758
+ { label: "Both", value: "both" }
6759
+ ],
6760
+ onSelect: (platform) => {
6761
+ setState((s) => ({ ...s, platform, step: firstStepForPlatform(platform) }));
6762
+ }
6763
+ }
6764
+ ),
6765
+ state.step === "android-keystore-mode" && /* @__PURE__ */ jsx11(
6766
+ AndroidKeystoreModeSelect,
6767
+ {
6768
+ onSelect: (mode) => {
6769
+ setState((s) => ({
6770
+ ...s,
6771
+ android: { ...s.android, keystoreMode: mode },
6772
+ step: mode === "generate" ? "android-gen-path" : "android-keystore",
6773
+ generateError: null
6774
+ }));
6775
+ }
6776
+ }
6777
+ ),
6778
+ state.step === "android-gen-path" && /* @__PURE__ */ jsx11(
6779
+ TuiTextInput,
6780
+ {
6781
+ label: "Keystore output path (relative to project root):",
6782
+ defaultValue: state.android.genKeystorePath,
6783
+ onSubmitValue: (v) => {
6784
+ const p = v.trim() || "android/release.keystore";
6785
+ setState((s) => ({ ...s, android: { ...s.android, genKeystorePath: p } }));
6786
+ },
6787
+ onSubmit: nextStep,
6788
+ hint: "Default: android/release.keystore (gitignored pattern *.keystore)"
6789
+ }
6790
+ ),
6791
+ state.step === "android-gen-alias" && /* @__PURE__ */ jsx11(
6792
+ TuiTextInput,
6793
+ {
6794
+ label: "Android key alias:",
6795
+ defaultValue: state.android.keyAlias,
6796
+ onSubmitValue: (v) => {
6797
+ setState((s) => ({ ...s, android: { ...s.android, keyAlias: v } }));
6798
+ },
6799
+ onSubmit: nextStep
6800
+ }
6801
+ ),
6802
+ state.step === "android-gen-password" && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
6803
+ state.generateError ? /* @__PURE__ */ jsx11(Text10, { color: "red", children: state.generateError }) : null,
6804
+ /* @__PURE__ */ jsx11(
6805
+ TuiTextInput,
6806
+ {
6807
+ label: "Keystore and key password (same for both; shown as you type):",
6808
+ value: state.android.genPassword,
6809
+ onChange: (v) => setState((s) => ({ ...s, android: { ...s.android, genPassword: v } })),
6810
+ onSubmitValue: (pw) => {
6811
+ setState((s) => ({
6812
+ ...s,
6813
+ android: { ...s.android, genPassword: pw.trim() },
6814
+ step: "android-generating",
6815
+ generateError: null
6816
+ }));
6817
+ },
6818
+ onSubmit: () => {
6819
+ },
6820
+ hint: "At least 6 characters (JDK keytool). Same value used for -storepass and -keypass."
6821
+ }
6822
+ )
6823
+ ] }),
6824
+ state.step === "android-keystore" && /* @__PURE__ */ jsx11(
6825
+ TuiTextInput,
6826
+ {
6827
+ label: "Android keystore file path (relative to project root or android/):",
6828
+ defaultValue: state.android.keystoreFile,
6829
+ onSubmitValue: (v) => {
6830
+ setState((s) => ({ ...s, android: { ...s.android, keystoreFile: v } }));
6831
+ },
6832
+ onSubmit: nextStep,
6833
+ hint: "Example: android/app/my-release-key.keystore or ./my-release-key.keystore"
6834
+ }
6835
+ ),
6836
+ state.step === "android-alias" && /* @__PURE__ */ jsx11(
6837
+ TuiTextInput,
6838
+ {
6839
+ label: "Android key alias:",
6840
+ defaultValue: state.android.keyAlias,
6841
+ onSubmitValue: (v) => {
6842
+ setState((s) => ({ ...s, android: { ...s.android, keyAlias: v } }));
6843
+ },
6844
+ onSubmit: nextStep
6845
+ }
6846
+ ),
6847
+ state.step === "android-password-env" && /* @__PURE__ */ jsx11(
6848
+ TuiTextInput,
6849
+ {
6850
+ label: "Keystore password environment variable name:",
6851
+ defaultValue: state.android.storePasswordEnv || "ANDROID_KEYSTORE_PASSWORD",
6852
+ onSubmitValue: (v) => {
6853
+ setState((s) => ({ ...s, android: { ...s.android, storePasswordEnv: v } }));
6854
+ },
6855
+ onSubmit: () => {
6856
+ setState((s) => ({ ...s, step: "android-key-password-env" }));
6857
+ },
6858
+ hint: "Default: ANDROID_KEYSTORE_PASSWORD (will be written to .env / .env.local)"
6859
+ }
6860
+ ),
6861
+ state.step === "android-key-password-env" && /* @__PURE__ */ jsx11(
6862
+ TuiTextInput,
6863
+ {
6864
+ label: "Key password environment variable name:",
6865
+ defaultValue: state.android.keyPasswordEnv || "ANDROID_KEY_PASSWORD",
6866
+ onSubmitValue: (v) => {
6867
+ setState((s) => ({ ...s, android: { ...s.android, keyPasswordEnv: v } }));
6868
+ },
6869
+ onSubmit: () => {
6870
+ if (state.platform === "both") {
6871
+ setState((s) => ({ ...s, step: "ios-team" }));
6872
+ } else {
6873
+ setState((s) => ({ ...s, step: "saving" }));
6874
+ }
6875
+ },
6876
+ hint: "Default: ANDROID_KEY_PASSWORD (will be written to .env / .env.local)"
6877
+ }
6878
+ ),
6879
+ state.step === "ios-team" && /* @__PURE__ */ jsx11(
6880
+ TuiTextInput,
6881
+ {
6882
+ label: "iOS Development Team ID:",
6883
+ defaultValue: state.ios.developmentTeam,
6884
+ onSubmitValue: (v) => {
6885
+ setState((s) => ({ ...s, ios: { ...s.ios, developmentTeam: v } }));
6886
+ },
6887
+ onSubmit: nextStep,
6888
+ hint: "Example: ABC123DEF4 (found in Apple Developer account)"
6889
+ }
6890
+ ),
6891
+ state.step === "ios-identity" && /* @__PURE__ */ jsx11(
6892
+ TuiTextInput,
6893
+ {
6894
+ label: "iOS Code Sign Identity (optional, press Enter to skip):",
6895
+ defaultValue: state.ios.codeSignIdentity,
6896
+ onSubmitValue: (v) => {
6897
+ setState((s) => ({ ...s, ios: { ...s.ios, codeSignIdentity: v } }));
6898
+ },
6899
+ onSubmit: () => {
6900
+ setState((s) => ({ ...s, step: "ios-profile" }));
6901
+ },
6902
+ hint: 'Example: "iPhone Developer" or "Apple Development"'
6903
+ }
6904
+ ),
6905
+ state.step === "ios-profile" && /* @__PURE__ */ jsx11(
6906
+ TuiTextInput,
6907
+ {
6908
+ label: "iOS Provisioning Profile Specifier (optional, press Enter to skip):",
6909
+ defaultValue: state.ios.provisioningProfileSpecifier,
6910
+ onSubmitValue: (v) => {
6911
+ setState((s) => ({ ...s, ios: { ...s.ios, provisioningProfileSpecifier: v } }));
6912
+ },
6913
+ onSubmit: () => {
6914
+ setState((s) => ({ ...s, step: "saving" }));
6915
+ },
6916
+ hint: "UUID of the provisioning profile"
6917
+ }
6918
+ )
6919
+ ] });
6920
+ }
6921
+ async function signing(platform) {
6922
+ const { waitUntilExit } = render3(/* @__PURE__ */ jsx11(SigningWizard, { platform }));
6923
+ await waitUntilExit();
6924
+ }
6925
+
6926
+ // src/common/productionSigning.ts
6927
+ import fs27 from "fs";
6928
+ import path28 from "path";
6929
+ function isAndroidSigningConfigured(resolved) {
6930
+ const signing2 = resolved.config.android?.signing;
6931
+ const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
6932
+ const signingProps = path28.join(resolved.androidDir, "signing.properties");
6933
+ const hasProps = fs27.existsSync(signingProps);
6934
+ return hasConfig || hasProps;
6935
+ }
6936
+ function isIosSigningConfigured(resolved) {
6937
+ const team = resolved.config.ios?.signing?.developmentTeam?.trim();
6938
+ return Boolean(team);
6939
+ }
6940
+ function assertProductionSigningReady(filter) {
6941
+ const resolved = resolveHostPaths();
6942
+ const needAndroid = filter === "android" || filter === "all";
6943
+ const needIos = filter === "ios" || filter === "all";
6944
+ const missing = [];
6945
+ if (needAndroid && !isAndroidSigningConfigured(resolved)) {
6946
+ missing.push("Android: run `t4l signing android`, then `t4l build android -p`.");
6947
+ }
6948
+ if (needIos && !isIosSigningConfigured(resolved)) {
6949
+ missing.push("iOS: run `t4l signing ios`, then `t4l build ios -p`.");
6950
+ }
6951
+ if (missing.length === 0) return;
6952
+ console.error("\n\u274C Production build (`-p`) needs signing configured for the platform(s) you are building.");
6953
+ for (const line of missing) {
6954
+ console.error(` ${line}`);
6955
+ }
6956
+ console.error(
6957
+ "\n `t4l build -p` (no platform) builds both Android and iOS; use `t4l build android -p` or `t4l build ios -p` for one platform only.\n"
6958
+ );
6959
+ process.exit(1);
6960
+ }
6961
+
5711
6962
  // index.ts
5712
6963
  function readCliVersion() {
5713
- const root = path25.dirname(fileURLToPath(import.meta.url));
5714
- const here = path25.join(root, "package.json");
5715
- const parent = path25.join(root, "..", "package.json");
5716
- const pkgPath = fs24.existsSync(here) ? here : parent;
5717
- return JSON.parse(fs24.readFileSync(pkgPath, "utf8")).version;
6964
+ const root = path29.dirname(fileURLToPath(import.meta.url));
6965
+ const here = path29.join(root, "package.json");
6966
+ const parent = path29.join(root, "..", "package.json");
6967
+ const pkgPath = fs28.existsSync(here) ? here : parent;
6968
+ return JSON.parse(fs28.readFileSync(pkgPath, "utf8")).version;
5718
6969
  }
5719
6970
  var version = readCliVersion();
5720
- function validateDebugRelease(debug, release) {
5721
- if (debug && release) {
5722
- console.error("Cannot use --debug and --release together.");
6971
+ function validateBuildMode(debug, release, production) {
6972
+ const modes = [debug, release, production].filter(Boolean).length;
6973
+ if (modes > 1) {
6974
+ console.error("Cannot use --debug, --release, and --production together. Use only one.");
5723
6975
  process.exit(1);
5724
6976
  }
5725
6977
  }
@@ -5731,7 +6983,7 @@ function parsePlatform(value) {
5731
6983
  }
5732
6984
  program.version(version).description("Tamer4Lynx CLI - A tool for managing Lynx projects");
5733
6985
  program.command("init").description("Initialize tamer.config.json interactively").action(() => {
5734
- init_default();
6986
+ init();
5735
6987
  });
5736
6988
  program.command("create <target>").description("Create a project or extension. Target: ios | android | module | element | service | combo").option("-d, --debug", "For android: create host project (default)").option("-r, --release", "For android: create dev-app project").action(async (target, opts) => {
5737
6989
  const t = target.toLowerCase();
@@ -5754,22 +7006,26 @@ program.command("create <target>").description("Create a project or extension. T
5754
7006
  console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
5755
7007
  process.exit(1);
5756
7008
  });
5757
- 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").option("-i, --install", "Install after building").action(async (platform, opts) => {
5758
- validateDebugRelease(opts.debug, opts.release);
5759
- const release = opts.release === true;
7009
+ 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) => {
7010
+ validateBuildMode(opts.debug, opts.release, opts.production);
7011
+ const release = opts.release === true || opts.production === true;
7012
+ const production = opts.production === true;
5760
7013
  if (opts.embeddable) {
5761
7014
  await buildEmbeddable({ release: true });
5762
7015
  return;
5763
7016
  }
5764
7017
  const p = parsePlatform(platform ?? "all") ?? "all";
7018
+ if (production) {
7019
+ assertProductionSigningReady(p);
7020
+ }
5765
7021
  if (p === "android" || p === "all") {
5766
- await build_default({ install: opts.install, release });
7022
+ await build_default({ install: opts.install, release, production });
5767
7023
  }
5768
7024
  if (p === "ios" || p === "all") {
5769
- await build_default2({ install: opts.install, release });
7025
+ await build_default2({ install: opts.install, release, production });
5770
7026
  }
5771
7027
  });
5772
- program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run in silent mode (e.g. for postinstall)").action((platform, opts) => {
7028
+ program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run without output").action((platform, opts) => {
5773
7029
  if (opts.silent) {
5774
7030
  console.log = () => {
5775
7031
  };
@@ -5790,14 +7046,15 @@ program.command("link [platform]").description("Link native modules. Platform: i
5790
7046
  autolink_default2();
5791
7047
  autolink_default();
5792
7048
  });
5793
- program.command("bundle [platform]").description("Build Lynx bundle and copy to native project. Platform: ios | android (default: both)").option("-d, --debug", "Debug bundle with dev client embedded (default)").option("-r, --release", "Release bundle without dev client").action(async (platform, opts) => {
5794
- validateDebugRelease(opts.debug, opts.release);
5795
- const release = opts.release === true;
7049
+ program.command("bundle [platform]").description("Build Lynx bundle and copy to native project. Platform: ios | android (default: both)").option("-d, --debug", "Debug bundle with dev client embedded (default)").option("-r, --release", "Release bundle without dev client (unsigned)").option("-p, --production", "Production bundle for app store (signed)").action(async (platform, opts) => {
7050
+ validateBuildMode(opts.debug, opts.release, opts.production);
7051
+ const release = opts.release === true || opts.production === true;
7052
+ const production = opts.production === true;
5796
7053
  const p = parsePlatform(platform ?? "both") ?? "both";
5797
- if (p === "android" || p === "all") await bundle_default({ release });
5798
- if (p === "ios" || p === "all") bundle_default2({ release });
7054
+ if (p === "android" || p === "all") await bundle_default({ release, production });
7055
+ if (p === "ios" || p === "all") bundle_default2({ release, production });
5799
7056
  });
5800
- program.command("inject <platform>").description("Inject tamer-host templates into an existing project. Platform: ios | android").option("-f, --force", "Overwrite existing files").action(async (platform, opts) => {
7057
+ program.command("inject <platform>").description("Inject host templates into an existing project. Platform: ios | android").option("-f, --force", "Overwrite existing files").action(async (platform, opts) => {
5801
7058
  const p = platform?.toLowerCase();
5802
7059
  if (p === "ios") {
5803
7060
  await injectHostIos({ force: opts.force });
@@ -5810,7 +7067,7 @@ program.command("inject <platform>").description("Inject tamer-host templates in
5810
7067
  console.error(`Invalid inject platform: ${platform}. Use ios | android`);
5811
7068
  process.exit(1);
5812
7069
  });
5813
- program.command("sync [platform]").description("Sync dev client files from tamer.config.json. Platform: android (default)").action(async (platform) => {
7070
+ program.command("sync [platform]").description("Sync dev client. Platform: android (default)").action(async (platform) => {
5814
7071
  const p = (platform ?? "android").toLowerCase();
5815
7072
  if (p !== "android") {
5816
7073
  console.error("sync only supports android.");
@@ -5831,12 +7088,27 @@ program.command("build-dev-app").option("-p, --platform <platform>", "Platform:
5831
7088
  await build_default2({ install: opts.install, release: false });
5832
7089
  }
5833
7090
  });
5834
- program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project. Future: will track versions for compatibility (Expo-style).").action((packages) => add(packages));
5835
- program.command("add-core").description("Add core packages (app-shell, screen, router, insets, transports, system-ui, icons)").action(() => addCore());
7091
+ program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project").action(async (packages) => {
7092
+ await add(packages);
7093
+ });
7094
+ program.command("add-core").description("Add core packages").action(async () => {
7095
+ await addCore();
7096
+ });
7097
+ program.command("add-dev").description("Add dev-app, dev-client, and their dependencies").action(async () => {
7098
+ await addDev();
7099
+ });
7100
+ program.command("signing [platform]").description("Configure Android and iOS signing interactively").action(async (platform) => {
7101
+ const p = platform?.toLowerCase();
7102
+ if (p === "android" || p === "ios") {
7103
+ await signing(p);
7104
+ } else {
7105
+ await signing();
7106
+ }
7107
+ });
5836
7108
  program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
5837
7109
  codegen_default();
5838
7110
  });
5839
- program.command("android <subcommand>").description("(Legacy) Use: t4l <command> android. e.g. t4l create android").option("-d, --debug", "Create: host project. Bundle/build: debug with dev client.").option("-r, --release", "Create: dev-app project. Bundle/build: release without dev client.").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
7111
+ program.command("android <subcommand>").description("(Legacy) Use: t4l <command> android. e.g. t4l create android").option("-d, --debug", "Create: host project. Bundle/build: debug with dev client.").option("-r, --release", "Create: dev-app project. Bundle/build: release without dev client.").option("-p, --production", "Bundle/build: production for app store (signed)").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
5840
7112
  const sub = subcommand?.toLowerCase();
5841
7113
  if (sub === "create") {
5842
7114
  if (opts.debug && opts.release) {
@@ -5851,14 +7123,19 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
5851
7123
  return;
5852
7124
  }
5853
7125
  if (sub === "bundle") {
5854
- validateDebugRelease(opts.debug, opts.release);
5855
- await bundle_default({ release: opts.release === true });
7126
+ validateBuildMode(opts.debug, opts.release, opts.production);
7127
+ const release = opts.release === true || opts.production === true;
7128
+ await bundle_default({ release, production: opts.production === true });
5856
7129
  return;
5857
7130
  }
5858
7131
  if (sub === "build") {
5859
- validateDebugRelease(opts.debug, opts.release);
7132
+ validateBuildMode(opts.debug, opts.release, opts.production);
7133
+ const release = opts.release === true || opts.production === true;
5860
7134
  if (opts.embeddable) await buildEmbeddable({ release: true });
5861
- else await build_default({ install: opts.install, release: opts.release === true });
7135
+ else {
7136
+ if (opts.production === true) assertProductionSigningReady("android");
7137
+ await build_default({ install: opts.install, release, production: opts.production === true });
7138
+ }
5862
7139
  return;
5863
7140
  }
5864
7141
  if (sub === "sync") {
@@ -5872,7 +7149,7 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
5872
7149
  console.error(`Unknown android subcommand: ${subcommand}. Use: create | link | bundle | build | sync | inject`);
5873
7150
  process.exit(1);
5874
7151
  });
5875
- program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios. e.g. t4l create ios").option("-d, --debug", "Debug (bundle/build)").option("-r, --release", "Release (bundle/build)").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
7152
+ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios. e.g. t4l create ios").option("-d, --debug", "Debug (bundle/build)").option("-r, --release", "Release (bundle/build)").option("-p, --production", "Production for app store (signed)").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
5876
7153
  const sub = subcommand?.toLowerCase();
5877
7154
  if (sub === "create") {
5878
7155
  create_default2();
@@ -5883,14 +7160,19 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
5883
7160
  return;
5884
7161
  }
5885
7162
  if (sub === "bundle") {
5886
- validateDebugRelease(opts.debug, opts.release);
5887
- bundle_default2({ release: opts.release === true });
7163
+ validateBuildMode(opts.debug, opts.release, opts.production);
7164
+ const release = opts.release === true || opts.production === true;
7165
+ bundle_default2({ release, production: opts.production === true });
5888
7166
  return;
5889
7167
  }
5890
7168
  if (sub === "build") {
5891
- validateDebugRelease(opts.debug, opts.release);
7169
+ validateBuildMode(opts.debug, opts.release, opts.production);
7170
+ const release = opts.release === true || opts.production === true;
5892
7171
  if (opts.embeddable) await buildEmbeddable({ release: true });
5893
- else await build_default2({ install: opts.install, release: opts.release === true });
7172
+ else {
7173
+ if (opts.production === true) assertProductionSigningReady("ios");
7174
+ await build_default2({ install: opts.install, release, production: opts.production === true });
7175
+ }
5894
7176
  return;
5895
7177
  }
5896
7178
  if (sub === "inject") {
@@ -5901,10 +7183,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
5901
7183
  process.exit(1);
5902
7184
  });
5903
7185
  program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
5904
- const configPath = path25.join(process.cwd(), "tamer.config.json");
7186
+ const configPath = path29.join(process.cwd(), "tamer.config.json");
5905
7187
  let config = {};
5906
- if (fs24.existsSync(configPath)) {
5907
- config = JSON.parse(fs24.readFileSync(configPath, "utf8"));
7188
+ if (fs28.existsSync(configPath)) {
7189
+ config = JSON.parse(fs28.readFileSync(configPath, "utf8"));
5908
7190
  }
5909
7191
  if (config.autolink) {
5910
7192
  delete config.autolink;
@@ -5913,11 +7195,11 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
5913
7195
  config.autolink = true;
5914
7196
  console.log("Autolink enabled in tamer.config.json");
5915
7197
  }
5916
- fs24.writeFileSync(configPath, JSON.stringify(config, null, 2));
7198
+ fs28.writeFileSync(configPath, JSON.stringify(config, null, 2));
5917
7199
  console.log(`Updated ${configPath}`);
5918
7200
  });
5919
7201
  if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
5920
- Promise.resolve(init_default()).then(() => process.exit(0));
7202
+ Promise.resolve(init()).then(() => process.exit(0));
5921
7203
  } else {
5922
7204
  program.parseAsync().then(() => process.exit(0)).catch(() => process.exit(1));
5923
7205
  }