@tamer4lynx/cli 0.0.13 → 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.
- package/dist/index.js +1675 -437
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ process.on("warning", (w) => {
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// index.ts
|
|
10
|
-
import
|
|
11
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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
|
|
4355
|
-
|
|
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,101 +4405,507 @@ async function buildIpa(opts = {}) {
|
|
|
4403
4405
|
}
|
|
4404
4406
|
var build_default2 = buildIpa;
|
|
4405
4407
|
|
|
4406
|
-
// src/common/init.
|
|
4408
|
+
// src/common/init.tsx
|
|
4407
4409
|
import fs17 from "fs";
|
|
4408
4410
|
import path18 from "path";
|
|
4409
|
-
import
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
const lynxProject =
|
|
4446
|
-
const
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
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
|
+
}
|
|
4471
4738
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
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
|
+
}
|
|
4488
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
|
+
) });
|
|
4489
4888
|
}
|
|
4490
|
-
|
|
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();
|
|
4491
4900
|
}
|
|
4492
|
-
var init_default = init;
|
|
4493
4901
|
|
|
4494
4902
|
// src/common/create.ts
|
|
4495
4903
|
import fs18 from "fs";
|
|
4496
4904
|
import path19 from "path";
|
|
4497
|
-
import
|
|
4498
|
-
var
|
|
4499
|
-
function
|
|
4500
|
-
return new Promise((resolve) =>
|
|
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())));
|
|
4501
4909
|
}
|
|
4502
4910
|
async function create3(opts) {
|
|
4503
4911
|
console.log("Tamer4Lynx: Create Lynx Extension\n");
|
|
@@ -4536,29 +4944,29 @@ async function create3(opts) {
|
|
|
4536
4944
|
console.log(" [ ] Native Module");
|
|
4537
4945
|
console.log(" [ ] Element");
|
|
4538
4946
|
console.log(" [ ] Service\n");
|
|
4539
|
-
includeModule = /^y(es)?$/i.test(await
|
|
4540
|
-
includeElement = /^y(es)?$/i.test(await
|
|
4541
|
-
includeService = /^y(es)?$/i.test(await
|
|
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");
|
|
4542
4950
|
}
|
|
4543
4951
|
if (!includeModule && !includeElement && !includeService) {
|
|
4544
4952
|
console.error("\u274C At least one extension type is required.");
|
|
4545
|
-
|
|
4953
|
+
rl.close();
|
|
4546
4954
|
process.exit(1);
|
|
4547
4955
|
}
|
|
4548
|
-
const extName = await
|
|
4956
|
+
const extName = await ask("Extension package name (e.g. my-lynx-module): ");
|
|
4549
4957
|
if (!extName || !/^[a-z0-9-_]+$/.test(extName)) {
|
|
4550
4958
|
console.error("\u274C Invalid package name. Use lowercase letters, numbers, hyphens, underscores.");
|
|
4551
|
-
|
|
4959
|
+
rl.close();
|
|
4552
4960
|
process.exit(1);
|
|
4553
4961
|
}
|
|
4554
|
-
const packageName = await
|
|
4962
|
+
const packageName = await ask("Android package name (e.g. com.example.mymodule): ") || `com.example.${extName.replace(/-/g, "")}`;
|
|
4555
4963
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
4556
4964
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
4557
4965
|
const cwd = process.cwd();
|
|
4558
4966
|
const root = path19.join(cwd, extName);
|
|
4559
4967
|
if (fs18.existsSync(root)) {
|
|
4560
4968
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
4561
|
-
|
|
4969
|
+
rl.close();
|
|
4562
4970
|
process.exit(1);
|
|
4563
4971
|
}
|
|
4564
4972
|
fs18.mkdirSync(root, { recursive: true });
|
|
@@ -4712,7 +5120,7 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
4712
5120
|
console.log(` cd ${extName}`);
|
|
4713
5121
|
console.log(" npm install");
|
|
4714
5122
|
if (includeModule) console.log(" npm run codegen");
|
|
4715
|
-
|
|
5123
|
+
rl.close();
|
|
4716
5124
|
}
|
|
4717
5125
|
var create_default3 = create3;
|
|
4718
5126
|
|
|
@@ -4783,14 +5191,16 @@ function extractLynxModules(files) {
|
|
|
4783
5191
|
}
|
|
4784
5192
|
var codegen_default = codegen;
|
|
4785
5193
|
|
|
4786
|
-
// src/common/devServer.
|
|
5194
|
+
// src/common/devServer.tsx
|
|
5195
|
+
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
4787
5196
|
import { spawn } from "child_process";
|
|
4788
5197
|
import fs20 from "fs";
|
|
4789
5198
|
import http from "http";
|
|
4790
5199
|
import os4 from "os";
|
|
4791
5200
|
import path21 from "path";
|
|
4792
|
-
import
|
|
5201
|
+
import { render as render2, useInput, useApp } from "ink";
|
|
4793
5202
|
import { WebSocketServer } from "ws";
|
|
5203
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
4794
5204
|
var DEFAULT_PORT = 3e3;
|
|
4795
5205
|
var STATIC_MIME = {
|
|
4796
5206
|
".png": "image/png",
|
|
@@ -4845,319 +5255,431 @@ function getLanIp() {
|
|
|
4845
5255
|
}
|
|
4846
5256
|
return "localhost";
|
|
4847
5257
|
}
|
|
4848
|
-
|
|
4849
|
-
const
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
const
|
|
4880
|
-
const
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
if (fs20.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
4898
|
-
}
|
|
4899
|
-
const iconExt = iconFilePath ? path21.extname(iconFilePath) || ".png" : "";
|
|
4900
|
-
const httpServer = http.createServer((req, res) => {
|
|
4901
|
-
let reqPath = (req.url || "/").split("?")[0];
|
|
4902
|
-
if (reqPath === `${basePath}/status`) {
|
|
4903
|
-
res.setHeader("Content-Type", "text/plain");
|
|
4904
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4905
|
-
res.end("packager-status:running");
|
|
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();
|
|
4906
5307
|
return;
|
|
4907
5308
|
}
|
|
4908
|
-
if (
|
|
4909
|
-
|
|
4910
|
-
const nativeModules = discoverNativeExtensions(projectRoot);
|
|
4911
|
-
const androidPackageName = config.android?.packageName?.trim();
|
|
4912
|
-
const iosBundleId = config.ios?.bundleId?.trim();
|
|
4913
|
-
const idParts = [androidPackageName?.toLowerCase(), iosBundleId?.toLowerCase()].filter(
|
|
4914
|
-
(x) => Boolean(x)
|
|
4915
|
-
);
|
|
4916
|
-
const meta = {
|
|
4917
|
-
name: projectName,
|
|
4918
|
-
slug: projectName,
|
|
4919
|
-
bundleUrl: `http://${lanIp}:${port}${basePath}/${lynxBundleFile}`,
|
|
4920
|
-
bundleFile: lynxBundleFile,
|
|
4921
|
-
hostUri: `http://${lanIp}:${port}${basePath}`,
|
|
4922
|
-
debuggerHost: `${lanIp}:${port}`,
|
|
4923
|
-
developer: { tool: "tamer4lynx" },
|
|
4924
|
-
packagerStatus: "running",
|
|
4925
|
-
nativeModules: nativeModules.map((m) => ({ packageName: m.packageName, moduleClassName: m.moduleClassName }))
|
|
4926
|
-
};
|
|
4927
|
-
if (androidPackageName) meta.androidPackageName = androidPackageName;
|
|
4928
|
-
if (iosBundleId) meta.iosBundleId = iosBundleId;
|
|
4929
|
-
if (idParts.length > 0) meta.tamerAppKey = idParts.join("|");
|
|
4930
|
-
const rawIcon = config.icon;
|
|
4931
|
-
if (rawIcon && typeof rawIcon === "object" && "source" in rawIcon && typeof rawIcon.source === "string") {
|
|
4932
|
-
meta.iconSource = rawIcon.source;
|
|
4933
|
-
} else if (typeof rawIcon === "string") {
|
|
4934
|
-
meta.iconSource = rawIcon;
|
|
4935
|
-
}
|
|
4936
|
-
if (iconFilePath) {
|
|
4937
|
-
meta.icon = `http://${lanIp}:${port}${basePath}/icon${iconExt}`;
|
|
4938
|
-
}
|
|
4939
|
-
res.setHeader("Content-Type", "application/json");
|
|
4940
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4941
|
-
res.end(JSON.stringify(meta, null, 2));
|
|
5309
|
+
if (input === "q") {
|
|
5310
|
+
handleQuit();
|
|
4942
5311
|
return;
|
|
4943
5312
|
}
|
|
4944
|
-
if (
|
|
4945
|
-
|
|
4946
|
-
if (err) {
|
|
4947
|
-
res.writeHead(404);
|
|
4948
|
-
res.end();
|
|
4949
|
-
return;
|
|
4950
|
-
}
|
|
4951
|
-
res.setHeader("Content-Type", STATIC_MIME[iconExt] ?? "image/png");
|
|
4952
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4953
|
-
res.end(data);
|
|
4954
|
-
});
|
|
5313
|
+
if (input === "r") {
|
|
5314
|
+
void rebuildRef.current();
|
|
4955
5315
|
return;
|
|
4956
5316
|
}
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
{ prefix: `${basePath}/assets/`, rootSub: "assets" }
|
|
4960
|
-
];
|
|
4961
|
-
for (const { prefix, rootSub } of lynxStaticMounts) {
|
|
4962
|
-
if (!reqPath.startsWith(prefix)) continue;
|
|
4963
|
-
let rel = reqPath.slice(prefix.length);
|
|
4964
|
-
try {
|
|
4965
|
-
rel = decodeURIComponent(rel);
|
|
4966
|
-
} catch {
|
|
4967
|
-
res.writeHead(400);
|
|
4968
|
-
res.end();
|
|
4969
|
-
return;
|
|
4970
|
-
}
|
|
4971
|
-
const safe = path21.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
4972
|
-
if (path21.isAbsolute(safe) || safe.startsWith("..")) {
|
|
4973
|
-
res.writeHead(403);
|
|
4974
|
-
res.end();
|
|
4975
|
-
return;
|
|
4976
|
-
}
|
|
4977
|
-
const allowedRoot = path21.resolve(lynxProjectDir, rootSub);
|
|
4978
|
-
const abs = path21.resolve(allowedRoot, safe);
|
|
4979
|
-
if (!abs.startsWith(allowedRoot + path21.sep) && abs !== allowedRoot) {
|
|
4980
|
-
res.writeHead(403);
|
|
4981
|
-
res.end();
|
|
4982
|
-
return;
|
|
4983
|
-
}
|
|
4984
|
-
if (!fs20.existsSync(abs) || !fs20.statSync(abs).isFile()) {
|
|
4985
|
-
res.writeHead(404);
|
|
4986
|
-
res.end("Not found");
|
|
4987
|
-
return;
|
|
4988
|
-
}
|
|
4989
|
-
sendFileFromDisk(res, abs);
|
|
4990
|
-
return;
|
|
4991
|
-
}
|
|
4992
|
-
if (reqPath === "/" || reqPath === basePath || reqPath === `${basePath}/`) {
|
|
4993
|
-
reqPath = `${basePath}/${lynxBundleFile}`;
|
|
4994
|
-
} else if (!reqPath.startsWith(basePath)) {
|
|
4995
|
-
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
4996
|
-
}
|
|
4997
|
-
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
4998
|
-
const filePath = path21.resolve(distDir, relPath);
|
|
4999
|
-
const distResolved = path21.resolve(distDir);
|
|
5000
|
-
if (!filePath.startsWith(distResolved + path21.sep) && filePath !== distResolved) {
|
|
5001
|
-
res.writeHead(403);
|
|
5002
|
-
res.end();
|
|
5003
|
-
return;
|
|
5317
|
+
if (input === "l") {
|
|
5318
|
+
setUi((s) => ({ ...s, showLogs: !s.showLogs }));
|
|
5004
5319
|
}
|
|
5005
|
-
fs20.readFile(filePath, (err, data) => {
|
|
5006
|
-
if (err) {
|
|
5007
|
-
res.writeHead(404);
|
|
5008
|
-
res.end("Not found");
|
|
5009
|
-
return;
|
|
5010
|
-
}
|
|
5011
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5012
|
-
res.setHeader("Content-Type", reqPath.endsWith(".bundle") ? "application/octet-stream" : "application/javascript");
|
|
5013
|
-
res.end(data);
|
|
5014
|
-
});
|
|
5015
5320
|
});
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
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 () => {
|
|
5033
5338
|
try {
|
|
5034
|
-
const
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
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}`);
|
|
5042
5349
|
}
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
} catch {
|
|
5056
|
-
}
|
|
5057
|
-
if (chokidar) {
|
|
5058
|
-
const watchPaths = [
|
|
5059
|
-
path21.join(lynxProjectDir, "src"),
|
|
5060
|
-
path21.join(lynxProjectDir, "lynx.config.ts"),
|
|
5061
|
-
path21.join(lynxProjectDir, "lynx.config.js")
|
|
5062
|
-
].filter((p) => fs20.existsSync(p));
|
|
5063
|
-
if (watchPaths.length > 0) {
|
|
5064
|
-
const watcher = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5065
|
-
watcher.on("change", async () => {
|
|
5066
|
-
try {
|
|
5067
|
-
await runBuild();
|
|
5068
|
-
broadcastReload();
|
|
5069
|
-
console.log("\u{1F504} Rebuilt, clients notified");
|
|
5070
|
-
} catch (e) {
|
|
5071
|
-
console.error("Build failed:", e.message);
|
|
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;
|
|
5072
5362
|
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
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 {
|
|
5126
5567
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
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
|
+
}
|
|
5142
5588
|
}
|
|
5143
|
-
|
|
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
|
|
5144
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
|
|
5145
5681
|
});
|
|
5146
|
-
|
|
5147
|
-
buildProcess?.kill();
|
|
5148
|
-
await stopBonjour?.();
|
|
5149
|
-
httpServer.close();
|
|
5150
|
-
wss.close();
|
|
5151
|
-
process.exit(0);
|
|
5152
|
-
};
|
|
5153
|
-
process.on("SIGINT", () => {
|
|
5154
|
-
void cleanup();
|
|
5155
|
-
});
|
|
5156
|
-
process.on("SIGTERM", () => {
|
|
5157
|
-
void cleanup();
|
|
5158
|
-
});
|
|
5159
|
-
await new Promise(() => {
|
|
5160
|
-
});
|
|
5682
|
+
await waitUntilExit();
|
|
5161
5683
|
}
|
|
5162
5684
|
var devServer_default = startDevServer;
|
|
5163
5685
|
|
|
@@ -5680,6 +6202,23 @@ var CORE_PACKAGES = [
|
|
|
5680
6202
|
"@tamer4lynx/tamer-system-ui",
|
|
5681
6203
|
"@tamer4lynx/tamer-icons"
|
|
5682
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
|
+
];
|
|
5683
6222
|
var PACKAGE_ALIASES = {};
|
|
5684
6223
|
async function getHighestPublishedVersion(fullName) {
|
|
5685
6224
|
try {
|
|
@@ -5705,7 +6244,7 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
5705
6244
|
console.warn(`\u26A0\uFE0F Could not resolve published versions for ${pkg}; using @prerelease`);
|
|
5706
6245
|
return `${pkg}@prerelease`;
|
|
5707
6246
|
}
|
|
5708
|
-
function
|
|
6247
|
+
function detectPackageManager2(cwd) {
|
|
5709
6248
|
const dir = path24.resolve(cwd);
|
|
5710
6249
|
if (fs23.existsSync(path24.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
5711
6250
|
if (fs23.existsSync(path24.join(dir, "bun.lockb"))) return "bun";
|
|
@@ -5718,13 +6257,22 @@ function runInstall(cwd, packages, pm) {
|
|
|
5718
6257
|
}
|
|
5719
6258
|
async function addCore() {
|
|
5720
6259
|
const { lynxProjectDir } = resolveHostPaths();
|
|
5721
|
-
const pm =
|
|
6260
|
+
const pm = detectPackageManager2(lynxProjectDir);
|
|
5722
6261
|
console.log(`Resolving latest published versions (npm)\u2026`);
|
|
5723
6262
|
const resolved = await Promise.all(CORE_PACKAGES.map(normalizeTamerInstallSpec));
|
|
5724
6263
|
console.log(`Adding core packages to ${lynxProjectDir} (using ${pm})\u2026`);
|
|
5725
6264
|
runInstall(lynxProjectDir, resolved, pm);
|
|
5726
6265
|
console.log("\u2705 Core packages installed. Run `t4l link` to link native modules.");
|
|
5727
6266
|
}
|
|
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
|
+
}
|
|
5728
6276
|
async function add(packages = []) {
|
|
5729
6277
|
const list = Array.isArray(packages) ? packages : [];
|
|
5730
6278
|
if (list.length === 0) {
|
|
@@ -5735,7 +6283,7 @@ async function add(packages = []) {
|
|
|
5735
6283
|
return;
|
|
5736
6284
|
}
|
|
5737
6285
|
const { lynxProjectDir } = resolveHostPaths();
|
|
5738
|
-
const pm =
|
|
6286
|
+
const pm = detectPackageManager2(lynxProjectDir);
|
|
5739
6287
|
console.log(`Resolving latest published versions (npm)\u2026`);
|
|
5740
6288
|
const normalized = await Promise.all(
|
|
5741
6289
|
list.map(async (p) => {
|
|
@@ -5748,18 +6296,682 @@ async function add(packages = []) {
|
|
|
5748
6296
|
console.log("\u2705 Packages installed. Run `t4l link` to link native modules.");
|
|
5749
6297
|
}
|
|
5750
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
|
+
|
|
5751
6962
|
// index.ts
|
|
5752
6963
|
function readCliVersion() {
|
|
5753
|
-
const root =
|
|
5754
|
-
const here =
|
|
5755
|
-
const parent =
|
|
5756
|
-
const pkgPath =
|
|
5757
|
-
return JSON.parse(
|
|
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;
|
|
5758
6969
|
}
|
|
5759
6970
|
var version = readCliVersion();
|
|
5760
|
-
function
|
|
5761
|
-
|
|
5762
|
-
|
|
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.");
|
|
5763
6975
|
process.exit(1);
|
|
5764
6976
|
}
|
|
5765
6977
|
}
|
|
@@ -5771,7 +6983,7 @@ function parsePlatform(value) {
|
|
|
5771
6983
|
}
|
|
5772
6984
|
program.version(version).description("Tamer4Lynx CLI - A tool for managing Lynx projects");
|
|
5773
6985
|
program.command("init").description("Initialize tamer.config.json interactively").action(() => {
|
|
5774
|
-
|
|
6986
|
+
init();
|
|
5775
6987
|
});
|
|
5776
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) => {
|
|
5777
6989
|
const t = target.toLowerCase();
|
|
@@ -5794,22 +7006,26 @@ program.command("create <target>").description("Create a project or extension. T
|
|
|
5794
7006
|
console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
|
|
5795
7007
|
process.exit(1);
|
|
5796
7008
|
});
|
|
5797
|
-
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) => {
|
|
5798
|
-
|
|
5799
|
-
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;
|
|
5800
7013
|
if (opts.embeddable) {
|
|
5801
7014
|
await buildEmbeddable({ release: true });
|
|
5802
7015
|
return;
|
|
5803
7016
|
}
|
|
5804
7017
|
const p = parsePlatform(platform ?? "all") ?? "all";
|
|
7018
|
+
if (production) {
|
|
7019
|
+
assertProductionSigningReady(p);
|
|
7020
|
+
}
|
|
5805
7021
|
if (p === "android" || p === "all") {
|
|
5806
|
-
await build_default({ install: opts.install, release });
|
|
7022
|
+
await build_default({ install: opts.install, release, production });
|
|
5807
7023
|
}
|
|
5808
7024
|
if (p === "ios" || p === "all") {
|
|
5809
|
-
await build_default2({ install: opts.install, release });
|
|
7025
|
+
await build_default2({ install: opts.install, release, production });
|
|
5810
7026
|
}
|
|
5811
7027
|
});
|
|
5812
|
-
program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run
|
|
7028
|
+
program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run without output").action((platform, opts) => {
|
|
5813
7029
|
if (opts.silent) {
|
|
5814
7030
|
console.log = () => {
|
|
5815
7031
|
};
|
|
@@ -5830,14 +7046,15 @@ program.command("link [platform]").description("Link native modules. Platform: i
|
|
|
5830
7046
|
autolink_default2();
|
|
5831
7047
|
autolink_default();
|
|
5832
7048
|
});
|
|
5833
|
-
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) => {
|
|
5834
|
-
|
|
5835
|
-
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;
|
|
5836
7053
|
const p = parsePlatform(platform ?? "both") ?? "both";
|
|
5837
|
-
if (p === "android" || p === "all") await bundle_default({ release });
|
|
5838
|
-
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 });
|
|
5839
7056
|
});
|
|
5840
|
-
program.command("inject <platform>").description("Inject
|
|
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) => {
|
|
5841
7058
|
const p = platform?.toLowerCase();
|
|
5842
7059
|
if (p === "ios") {
|
|
5843
7060
|
await injectHostIos({ force: opts.force });
|
|
@@ -5850,7 +7067,7 @@ program.command("inject <platform>").description("Inject tamer-host templates in
|
|
|
5850
7067
|
console.error(`Invalid inject platform: ${platform}. Use ios | android`);
|
|
5851
7068
|
process.exit(1);
|
|
5852
7069
|
});
|
|
5853
|
-
program.command("sync [platform]").description("Sync dev client
|
|
7070
|
+
program.command("sync [platform]").description("Sync dev client. Platform: android (default)").action(async (platform) => {
|
|
5854
7071
|
const p = (platform ?? "android").toLowerCase();
|
|
5855
7072
|
if (p !== "android") {
|
|
5856
7073
|
console.error("sync only supports android.");
|
|
@@ -5871,16 +7088,27 @@ program.command("build-dev-app").option("-p, --platform <platform>", "Platform:
|
|
|
5871
7088
|
await build_default2({ install: opts.install, release: false });
|
|
5872
7089
|
}
|
|
5873
7090
|
});
|
|
5874
|
-
program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project
|
|
7091
|
+
program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project").action(async (packages) => {
|
|
5875
7092
|
await add(packages);
|
|
5876
7093
|
});
|
|
5877
|
-
program.command("add-core").description("Add core packages
|
|
7094
|
+
program.command("add-core").description("Add core packages").action(async () => {
|
|
5878
7095
|
await addCore();
|
|
5879
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
|
+
});
|
|
5880
7108
|
program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
|
|
5881
7109
|
codegen_default();
|
|
5882
7110
|
});
|
|
5883
|
-
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) => {
|
|
5884
7112
|
const sub = subcommand?.toLowerCase();
|
|
5885
7113
|
if (sub === "create") {
|
|
5886
7114
|
if (opts.debug && opts.release) {
|
|
@@ -5895,14 +7123,19 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
5895
7123
|
return;
|
|
5896
7124
|
}
|
|
5897
7125
|
if (sub === "bundle") {
|
|
5898
|
-
|
|
5899
|
-
|
|
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 });
|
|
5900
7129
|
return;
|
|
5901
7130
|
}
|
|
5902
7131
|
if (sub === "build") {
|
|
5903
|
-
|
|
7132
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7133
|
+
const release = opts.release === true || opts.production === true;
|
|
5904
7134
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
5905
|
-
else
|
|
7135
|
+
else {
|
|
7136
|
+
if (opts.production === true) assertProductionSigningReady("android");
|
|
7137
|
+
await build_default({ install: opts.install, release, production: opts.production === true });
|
|
7138
|
+
}
|
|
5906
7139
|
return;
|
|
5907
7140
|
}
|
|
5908
7141
|
if (sub === "sync") {
|
|
@@ -5916,7 +7149,7 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
5916
7149
|
console.error(`Unknown android subcommand: ${subcommand}. Use: create | link | bundle | build | sync | inject`);
|
|
5917
7150
|
process.exit(1);
|
|
5918
7151
|
});
|
|
5919
|
-
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) => {
|
|
5920
7153
|
const sub = subcommand?.toLowerCase();
|
|
5921
7154
|
if (sub === "create") {
|
|
5922
7155
|
create_default2();
|
|
@@ -5927,14 +7160,19 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
5927
7160
|
return;
|
|
5928
7161
|
}
|
|
5929
7162
|
if (sub === "bundle") {
|
|
5930
|
-
|
|
5931
|
-
|
|
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 });
|
|
5932
7166
|
return;
|
|
5933
7167
|
}
|
|
5934
7168
|
if (sub === "build") {
|
|
5935
|
-
|
|
7169
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7170
|
+
const release = opts.release === true || opts.production === true;
|
|
5936
7171
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
5937
|
-
else
|
|
7172
|
+
else {
|
|
7173
|
+
if (opts.production === true) assertProductionSigningReady("ios");
|
|
7174
|
+
await build_default2({ install: opts.install, release, production: opts.production === true });
|
|
7175
|
+
}
|
|
5938
7176
|
return;
|
|
5939
7177
|
}
|
|
5940
7178
|
if (sub === "inject") {
|
|
@@ -5945,10 +7183,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
5945
7183
|
process.exit(1);
|
|
5946
7184
|
});
|
|
5947
7185
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
5948
|
-
const configPath =
|
|
7186
|
+
const configPath = path29.join(process.cwd(), "tamer.config.json");
|
|
5949
7187
|
let config = {};
|
|
5950
|
-
if (
|
|
5951
|
-
config = JSON.parse(
|
|
7188
|
+
if (fs28.existsSync(configPath)) {
|
|
7189
|
+
config = JSON.parse(fs28.readFileSync(configPath, "utf8"));
|
|
5952
7190
|
}
|
|
5953
7191
|
if (config.autolink) {
|
|
5954
7192
|
delete config.autolink;
|
|
@@ -5957,11 +7195,11 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
5957
7195
|
config.autolink = true;
|
|
5958
7196
|
console.log("Autolink enabled in tamer.config.json");
|
|
5959
7197
|
}
|
|
5960
|
-
|
|
7198
|
+
fs28.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
5961
7199
|
console.log(`Updated ${configPath}`);
|
|
5962
7200
|
});
|
|
5963
7201
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|
|
5964
|
-
Promise.resolve(
|
|
7202
|
+
Promise.resolve(init()).then(() => process.exit(0));
|
|
5965
7203
|
} else {
|
|
5966
7204
|
program.parseAsync().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
5967
7205
|
}
|