@tamer4lynx/cli 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/index.js +2110 -764
- package/package.json +10 -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 fs29 from "fs";
|
|
11
|
+
import path30 from "path";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
import { program } from "commander";
|
|
14
14
|
|
|
@@ -2229,8 +2229,8 @@ $1$2`
|
|
|
2229
2229
|
var autolink_default = autolink;
|
|
2230
2230
|
|
|
2231
2231
|
// src/android/bundle.ts
|
|
2232
|
-
import
|
|
2233
|
-
import
|
|
2232
|
+
import fs11 from "fs";
|
|
2233
|
+
import path11 from "path";
|
|
2234
2234
|
import { execSync as execSync3 } from "child_process";
|
|
2235
2235
|
|
|
2236
2236
|
// src/common/copyDistAssets.ts
|
|
@@ -2256,19 +2256,117 @@ function copyDistAssets(distDir, destDir, bundleFile) {
|
|
|
2256
2256
|
}
|
|
2257
2257
|
}
|
|
2258
2258
|
|
|
2259
|
-
// src/
|
|
2259
|
+
// src/common/tsconfigUtils.ts
|
|
2260
2260
|
import fs9 from "fs";
|
|
2261
2261
|
import path9 from "path";
|
|
2262
|
+
function stripJsonCommentsAndTrailingCommas(str) {
|
|
2263
|
+
return str.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,\s*([\]}])/g, "$1");
|
|
2264
|
+
}
|
|
2265
|
+
function parseTsconfigJson(raw) {
|
|
2266
|
+
try {
|
|
2267
|
+
return JSON.parse(raw);
|
|
2268
|
+
} catch {
|
|
2269
|
+
return JSON.parse(stripJsonCommentsAndTrailingCommas(raw));
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function readTsconfig(filePath) {
|
|
2273
|
+
if (!fs9.existsSync(filePath)) return null;
|
|
2274
|
+
try {
|
|
2275
|
+
return parseTsconfigJson(fs9.readFileSync(filePath, "utf-8"));
|
|
2276
|
+
} catch {
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function resolveExtends(tsconfig, dir) {
|
|
2281
|
+
if (!tsconfig.extends) return tsconfig;
|
|
2282
|
+
const basePath = path9.resolve(dir, tsconfig.extends);
|
|
2283
|
+
const base = readTsconfig(basePath);
|
|
2284
|
+
if (!base) return tsconfig;
|
|
2285
|
+
const baseDir = path9.dirname(basePath);
|
|
2286
|
+
const resolved = resolveExtends(base, baseDir);
|
|
2287
|
+
return {
|
|
2288
|
+
...resolved,
|
|
2289
|
+
...tsconfig,
|
|
2290
|
+
compilerOptions: { ...resolved.compilerOptions, ...tsconfig.compilerOptions }
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
2294
|
+
const dir = path9.dirname(tsconfigPath);
|
|
2295
|
+
const tsconfig = readTsconfig(tsconfigPath);
|
|
2296
|
+
if (!tsconfig?.references?.length) return false;
|
|
2297
|
+
const refsWithNoEmit = [];
|
|
2298
|
+
for (const ref of tsconfig.references) {
|
|
2299
|
+
const refPath = path9.resolve(dir, ref.path);
|
|
2300
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path9.join(refPath, "tsconfig.json");
|
|
2301
|
+
const refConfig = readTsconfig(refConfigPath);
|
|
2302
|
+
if (refConfig?.compilerOptions?.noEmit === true) {
|
|
2303
|
+
refsWithNoEmit.push(refConfigPath);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
if (refsWithNoEmit.length === 0) return false;
|
|
2307
|
+
const merged = {
|
|
2308
|
+
...tsconfig,
|
|
2309
|
+
references: void 0,
|
|
2310
|
+
files: void 0
|
|
2311
|
+
};
|
|
2312
|
+
const includes = [];
|
|
2313
|
+
const compilerOpts = { ...tsconfig.compilerOptions };
|
|
2314
|
+
for (const ref of tsconfig.references) {
|
|
2315
|
+
const refPath = path9.resolve(dir, ref.path);
|
|
2316
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path9.join(refPath, "tsconfig.json");
|
|
2317
|
+
const refConfig = readTsconfig(refConfigPath);
|
|
2318
|
+
if (!refConfig) continue;
|
|
2319
|
+
const refDir = path9.dirname(refConfigPath);
|
|
2320
|
+
const resolved = resolveExtends(refConfig, refDir);
|
|
2321
|
+
const inc = resolved.include;
|
|
2322
|
+
if (inc) {
|
|
2323
|
+
const arr = Array.isArray(inc) ? inc : [inc];
|
|
2324
|
+
const baseDir = path9.relative(dir, refDir);
|
|
2325
|
+
for (const p of arr) {
|
|
2326
|
+
const clean = typeof p === "string" && p.startsWith("./") ? p.slice(2) : p;
|
|
2327
|
+
includes.push(!baseDir || baseDir === "." ? clean : `${baseDir}/${clean}`);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
const opts = resolved.compilerOptions;
|
|
2331
|
+
if (opts) {
|
|
2332
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
2333
|
+
if (k !== "composite" && k !== "noEmit" && compilerOpts[k] === void 0) {
|
|
2334
|
+
compilerOpts[k] = v;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (includes.length > 0) merged.include = [...new Set(includes)];
|
|
2340
|
+
compilerOpts.noEmit = true;
|
|
2341
|
+
merged.compilerOptions = compilerOpts;
|
|
2342
|
+
fs9.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
|
|
2343
|
+
return true;
|
|
2344
|
+
}
|
|
2345
|
+
function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
2346
|
+
const tsconfig = readTsconfig(tsconfigPath);
|
|
2347
|
+
if (!tsconfig) return false;
|
|
2348
|
+
const include = tsconfig.include ?? [];
|
|
2349
|
+
const arr = Array.isArray(include) ? include : [include];
|
|
2350
|
+
if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) return false;
|
|
2351
|
+
arr.push(tamerTypesInclude);
|
|
2352
|
+
tsconfig.include = arr;
|
|
2353
|
+
fs9.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
2354
|
+
return true;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// src/android/syncDevClient.ts
|
|
2358
|
+
import fs10 from "fs";
|
|
2359
|
+
import path10 from "path";
|
|
2262
2360
|
function readAndSubstituteTemplate2(templatePath, vars) {
|
|
2263
|
-
const raw =
|
|
2361
|
+
const raw = fs10.readFileSync(templatePath, "utf-8");
|
|
2264
2362
|
return Object.entries(vars).reduce(
|
|
2265
2363
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2266
2364
|
raw
|
|
2267
2365
|
);
|
|
2268
2366
|
}
|
|
2269
2367
|
function patchAppLogService(appPath) {
|
|
2270
|
-
if (!
|
|
2271
|
-
const raw =
|
|
2368
|
+
if (!fs10.existsSync(appPath)) return;
|
|
2369
|
+
const raw = fs10.readFileSync(appPath, "utf-8");
|
|
2272
2370
|
const patched = raw.replace(
|
|
2273
2371
|
/private void initLynxService\(\)\s*\{[\s\S]*?\n\s*}\s*\n\s*private void initFresco\(\)/,
|
|
2274
2372
|
`private void initLynxService() {
|
|
@@ -2287,7 +2385,7 @@ function patchAppLogService(appPath) {
|
|
|
2287
2385
|
private void initFresco()`
|
|
2288
2386
|
);
|
|
2289
2387
|
if (patched !== raw) {
|
|
2290
|
-
|
|
2388
|
+
fs10.writeFileSync(appPath, patched);
|
|
2291
2389
|
}
|
|
2292
2390
|
}
|
|
2293
2391
|
async function syncDevClient(opts) {
|
|
@@ -2302,9 +2400,9 @@ async function syncDevClient(opts) {
|
|
|
2302
2400
|
const packageName = config.android?.packageName;
|
|
2303
2401
|
const appName = config.android?.appName;
|
|
2304
2402
|
const packagePath = packageName.replace(/\./g, "/");
|
|
2305
|
-
const javaDir =
|
|
2306
|
-
const kotlinDir =
|
|
2307
|
-
if (!
|
|
2403
|
+
const javaDir = path10.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
2404
|
+
const kotlinDir = path10.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
2405
|
+
if (!fs10.existsSync(javaDir) || !fs10.existsSync(kotlinDir)) {
|
|
2308
2406
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2309
2407
|
process.exit(1);
|
|
2310
2408
|
}
|
|
@@ -2320,14 +2418,14 @@ async function syncDevClient(opts) {
|
|
|
2320
2418
|
const [templateProviderSource] = await Promise.all([
|
|
2321
2419
|
fetchAndPatchTemplateProvider(vars)
|
|
2322
2420
|
]);
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
patchAppLogService(
|
|
2326
|
-
const appDir =
|
|
2327
|
-
const mainDir =
|
|
2328
|
-
const manifestPath =
|
|
2421
|
+
fs10.writeFileSync(path10.join(javaDir, "TemplateProvider.java"), templateProviderSource);
|
|
2422
|
+
fs10.writeFileSync(path10.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
|
|
2423
|
+
patchAppLogService(path10.join(javaDir, "App.java"));
|
|
2424
|
+
const appDir = path10.join(rootDir, "app");
|
|
2425
|
+
const mainDir = path10.join(appDir, "src", "main");
|
|
2426
|
+
const manifestPath = path10.join(mainDir, "AndroidManifest.xml");
|
|
2329
2427
|
if (hasDevClient) {
|
|
2330
|
-
const templateDir =
|
|
2428
|
+
const templateDir = path10.join(devClientPkg, "android", "templates");
|
|
2331
2429
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
2332
2430
|
const devClientFiles = [
|
|
2333
2431
|
"DevClientManager.kt",
|
|
@@ -2336,13 +2434,13 @@ async function syncDevClient(opts) {
|
|
|
2336
2434
|
"PortraitCaptureActivity.kt"
|
|
2337
2435
|
];
|
|
2338
2436
|
for (const f of devClientFiles) {
|
|
2339
|
-
const src =
|
|
2340
|
-
if (
|
|
2437
|
+
const src = path10.join(templateDir, f);
|
|
2438
|
+
if (fs10.existsSync(src)) {
|
|
2341
2439
|
const content = readAndSubstituteTemplate2(src, templateVars);
|
|
2342
|
-
|
|
2440
|
+
fs10.writeFileSync(path10.join(kotlinDir, f), content);
|
|
2343
2441
|
}
|
|
2344
2442
|
}
|
|
2345
|
-
let manifest =
|
|
2443
|
+
let manifest = fs10.readFileSync(manifestPath, "utf-8");
|
|
2346
2444
|
const projectActivityEntry = ' <activity android:name=".ProjectActivity" android:exported="false" android:taskAffinity="" android:launchMode="singleTask" android:documentLaunchMode="always" android:windowSoftInputMode="adjustResize" />';
|
|
2347
2445
|
const portraitCaptureEntry = ' <activity android:name=".PortraitCaptureActivity" android:screenOrientation="portrait" android:stateNotNeeded="true" android:theme="@style/zxing_CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden" />';
|
|
2348
2446
|
if (!manifest.includes("ProjectActivity")) {
|
|
@@ -2364,16 +2462,16 @@ $1$2`);
|
|
|
2364
2462
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2365
2463
|
);
|
|
2366
2464
|
}
|
|
2367
|
-
|
|
2465
|
+
fs10.writeFileSync(manifestPath, manifest);
|
|
2368
2466
|
console.log("\u2705 Synced dev client (TemplateProvider, MainActivity, ProjectActivity, DevClientManager)");
|
|
2369
2467
|
} else {
|
|
2370
2468
|
for (const f of ["DevClientManager.kt", "DevServerPrefs.kt", "ProjectActivity.kt", "PortraitCaptureActivity.kt", "DevLauncherActivity.kt"]) {
|
|
2371
2469
|
try {
|
|
2372
|
-
|
|
2470
|
+
fs10.rmSync(path10.join(kotlinDir, f));
|
|
2373
2471
|
} catch {
|
|
2374
2472
|
}
|
|
2375
2473
|
}
|
|
2376
|
-
let manifest =
|
|
2474
|
+
let manifest = fs10.readFileSync(manifestPath, "utf-8");
|
|
2377
2475
|
manifest = manifest.replace(/\s*<activity android:name="\.ProjectActivity"[^\/]*\/>\n?/g, "");
|
|
2378
2476
|
manifest = manifest.replace(/\s*<activity android:name="\.PortraitCaptureActivity"[^\/]*\/>\n?/g, "");
|
|
2379
2477
|
const mainActivityTag = manifest.match(/<activity[^>]*android:name="\.MainActivity"[^>]*>/);
|
|
@@ -2383,7 +2481,7 @@ $1$2`);
|
|
|
2383
2481
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2384
2482
|
);
|
|
2385
2483
|
}
|
|
2386
|
-
|
|
2484
|
+
fs10.writeFileSync(manifestPath, manifest);
|
|
2387
2485
|
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2388
2486
|
}
|
|
2389
2487
|
}
|
|
@@ -2391,7 +2489,7 @@ var syncDevClient_default = syncDevClient;
|
|
|
2391
2489
|
|
|
2392
2490
|
// src/android/bundle.ts
|
|
2393
2491
|
async function bundleAndDeploy(opts = {}) {
|
|
2394
|
-
const release = opts.release === true;
|
|
2492
|
+
const release = opts.release === true || opts.production === true;
|
|
2395
2493
|
let resolved;
|
|
2396
2494
|
try {
|
|
2397
2495
|
resolved = resolveHostPaths();
|
|
@@ -2407,13 +2505,17 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2407
2505
|
await syncDevClient_default({ includeDevClient });
|
|
2408
2506
|
const iconPaths = resolveIconPaths(projectRoot, resolved.config);
|
|
2409
2507
|
if (iconPaths) {
|
|
2410
|
-
const resDir =
|
|
2508
|
+
const resDir = path11.join(resolved.androidAppDir, "src", "main", "res");
|
|
2411
2509
|
if (applyAndroidLauncherIcons(resDir, iconPaths)) {
|
|
2412
2510
|
console.log("\u2705 Synced Android launcher icon(s) from tamer.config.json");
|
|
2413
|
-
ensureAndroidManifestLauncherIcon(
|
|
2511
|
+
ensureAndroidManifestLauncherIcon(path11.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
|
|
2414
2512
|
}
|
|
2415
2513
|
}
|
|
2416
2514
|
try {
|
|
2515
|
+
const lynxTsconfig = path11.join(lynxProjectDir, "tsconfig.json");
|
|
2516
|
+
if (fs11.existsSync(lynxTsconfig)) {
|
|
2517
|
+
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
2518
|
+
}
|
|
2417
2519
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
2418
2520
|
execSync3("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
2419
2521
|
console.log("\u2705 Build completed successfully.");
|
|
@@ -2421,8 +2523,8 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2421
2523
|
console.error("\u274C Build process failed.");
|
|
2422
2524
|
process.exit(1);
|
|
2423
2525
|
}
|
|
2424
|
-
if (includeDevClient && devClientBundlePath && !
|
|
2425
|
-
const devClientDir =
|
|
2526
|
+
if (includeDevClient && devClientBundlePath && !fs11.existsSync(devClientBundlePath)) {
|
|
2527
|
+
const devClientDir = path11.dirname(path11.dirname(devClientBundlePath));
|
|
2426
2528
|
try {
|
|
2427
2529
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
2428
2530
|
execSync3("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
@@ -2433,22 +2535,22 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2433
2535
|
}
|
|
2434
2536
|
}
|
|
2435
2537
|
try {
|
|
2436
|
-
|
|
2538
|
+
fs11.mkdirSync(destinationDir, { recursive: true });
|
|
2437
2539
|
if (release) {
|
|
2438
|
-
const devClientAsset =
|
|
2439
|
-
if (
|
|
2440
|
-
|
|
2540
|
+
const devClientAsset = path11.join(destinationDir, "dev-client.lynx.bundle");
|
|
2541
|
+
if (fs11.existsSync(devClientAsset)) {
|
|
2542
|
+
fs11.rmSync(devClientAsset);
|
|
2441
2543
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2442
2544
|
}
|
|
2443
|
-
} else if (includeDevClient && devClientBundlePath &&
|
|
2444
|
-
|
|
2545
|
+
} else if (includeDevClient && devClientBundlePath && fs11.existsSync(devClientBundlePath)) {
|
|
2546
|
+
fs11.copyFileSync(devClientBundlePath, path11.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2445
2547
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2446
2548
|
}
|
|
2447
|
-
if (!
|
|
2549
|
+
if (!fs11.existsSync(lynxBundlePath)) {
|
|
2448
2550
|
console.error(`\u274C Build output not found at: ${lynxBundlePath}`);
|
|
2449
2551
|
process.exit(1);
|
|
2450
2552
|
}
|
|
2451
|
-
const distDir =
|
|
2553
|
+
const distDir = path11.dirname(lynxBundlePath);
|
|
2452
2554
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
2453
2555
|
console.log(`\u2728 Copied ${resolved.lynxBundleFile} to assets`);
|
|
2454
2556
|
} catch (error) {
|
|
@@ -2459,7 +2561,7 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2459
2561
|
var bundle_default = bundleAndDeploy;
|
|
2460
2562
|
|
|
2461
2563
|
// src/android/build.ts
|
|
2462
|
-
import
|
|
2564
|
+
import path12 from "path";
|
|
2463
2565
|
import { execSync as execSync4 } from "child_process";
|
|
2464
2566
|
async function buildApk(opts = {}) {
|
|
2465
2567
|
let resolved;
|
|
@@ -2468,10 +2570,11 @@ async function buildApk(opts = {}) {
|
|
|
2468
2570
|
} catch (error) {
|
|
2469
2571
|
throw error;
|
|
2470
2572
|
}
|
|
2471
|
-
|
|
2573
|
+
const release = opts.release === true || opts.production === true;
|
|
2574
|
+
await bundle_default({ release, production: opts.production });
|
|
2472
2575
|
const androidDir = resolved.androidDir;
|
|
2473
|
-
const gradlew =
|
|
2474
|
-
const variant =
|
|
2576
|
+
const gradlew = path12.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2577
|
+
const variant = release ? "Release" : "Debug";
|
|
2475
2578
|
const task = opts.install ? `install${variant}` : `assemble${variant}`;
|
|
2476
2579
|
console.log(`
|
|
2477
2580
|
\u{1F528} Building ${variant.toLowerCase()} APK${opts.install ? " and installing" : ""}...`);
|
|
@@ -2495,13 +2598,13 @@ async function buildApk(opts = {}) {
|
|
|
2495
2598
|
var build_default = buildApk;
|
|
2496
2599
|
|
|
2497
2600
|
// src/ios/create.ts
|
|
2498
|
-
import
|
|
2499
|
-
import
|
|
2601
|
+
import fs13 from "fs";
|
|
2602
|
+
import path14 from "path";
|
|
2500
2603
|
|
|
2501
2604
|
// src/ios/getPod.ts
|
|
2502
2605
|
import { execSync as execSync5 } from "child_process";
|
|
2503
|
-
import
|
|
2504
|
-
import
|
|
2606
|
+
import fs12 from "fs";
|
|
2607
|
+
import path13 from "path";
|
|
2505
2608
|
function isCocoaPodsInstalled() {
|
|
2506
2609
|
try {
|
|
2507
2610
|
execSync5("command -v pod >/dev/null 2>&1");
|
|
@@ -2523,8 +2626,8 @@ async function setupCocoaPods(rootDir) {
|
|
|
2523
2626
|
}
|
|
2524
2627
|
try {
|
|
2525
2628
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2526
|
-
const podfilePath =
|
|
2527
|
-
if (!
|
|
2629
|
+
const podfilePath = path13.join(rootDir, "Podfile");
|
|
2630
|
+
if (!fs12.existsSync(podfilePath)) {
|
|
2528
2631
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2529
2632
|
}
|
|
2530
2633
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2550,7 +2653,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2550
2653
|
// src/ios/create.ts
|
|
2551
2654
|
import { randomBytes } from "crypto";
|
|
2552
2655
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2553
|
-
const raw =
|
|
2656
|
+
const raw = fs13.readFileSync(templatePath, "utf-8");
|
|
2554
2657
|
return Object.entries(vars).reduce(
|
|
2555
2658
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2556
2659
|
raw
|
|
@@ -2573,17 +2676,17 @@ var create2 = () => {
|
|
|
2573
2676
|
process.exit(1);
|
|
2574
2677
|
}
|
|
2575
2678
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
2576
|
-
const rootDir =
|
|
2577
|
-
const projectDir =
|
|
2578
|
-
const xcodeprojDir =
|
|
2679
|
+
const rootDir = path14.join(process.cwd(), iosDir);
|
|
2680
|
+
const projectDir = path14.join(rootDir, appName);
|
|
2681
|
+
const xcodeprojDir = path14.join(rootDir, `${appName}.xcodeproj`);
|
|
2579
2682
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2580
2683
|
function writeFile2(filePath, content) {
|
|
2581
|
-
|
|
2582
|
-
|
|
2684
|
+
fs13.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
2685
|
+
fs13.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2583
2686
|
}
|
|
2584
|
-
if (
|
|
2687
|
+
if (fs13.existsSync(rootDir)) {
|
|
2585
2688
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2586
|
-
|
|
2689
|
+
fs13.rmSync(rootDir, { recursive: true, force: true });
|
|
2587
2690
|
}
|
|
2588
2691
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2589
2692
|
const ids = {
|
|
@@ -2619,7 +2722,7 @@ var create2 = () => {
|
|
|
2619
2722
|
targetDebugConfig: generateId(),
|
|
2620
2723
|
targetReleaseConfig: generateId()
|
|
2621
2724
|
};
|
|
2622
|
-
writeFile2(
|
|
2725
|
+
writeFile2(path14.join(rootDir, "Podfile"), `
|
|
2623
2726
|
source 'https://cdn.cocoapods.org/'
|
|
2624
2727
|
|
|
2625
2728
|
platform :ios, '13.0'
|
|
@@ -2704,15 +2807,15 @@ end
|
|
|
2704
2807
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
2705
2808
|
const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
2706
2809
|
if (hostPkg) {
|
|
2707
|
-
const templateDir =
|
|
2810
|
+
const templateDir = path14.join(hostPkg, "ios", "templates");
|
|
2708
2811
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2709
|
-
const srcPath =
|
|
2710
|
-
if (
|
|
2711
|
-
writeFile2(
|
|
2812
|
+
const srcPath = path14.join(templateDir, f);
|
|
2813
|
+
if (fs13.existsSync(srcPath)) {
|
|
2814
|
+
writeFile2(path14.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2712
2815
|
}
|
|
2713
2816
|
}
|
|
2714
2817
|
} else {
|
|
2715
|
-
writeFile2(
|
|
2818
|
+
writeFile2(path14.join(projectDir, "AppDelegate.swift"), `
|
|
2716
2819
|
import UIKit
|
|
2717
2820
|
|
|
2718
2821
|
@UIApplicationMain
|
|
@@ -2727,7 +2830,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2727
2830
|
}
|
|
2728
2831
|
}
|
|
2729
2832
|
`);
|
|
2730
|
-
writeFile2(
|
|
2833
|
+
writeFile2(path14.join(projectDir, "SceneDelegate.swift"), `
|
|
2731
2834
|
import UIKit
|
|
2732
2835
|
|
|
2733
2836
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2741,7 +2844,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2741
2844
|
}
|
|
2742
2845
|
}
|
|
2743
2846
|
`);
|
|
2744
|
-
writeFile2(
|
|
2847
|
+
writeFile2(path14.join(projectDir, "ViewController.swift"), `
|
|
2745
2848
|
import UIKit
|
|
2746
2849
|
import Lynx
|
|
2747
2850
|
import tamerinsets
|
|
@@ -2757,7 +2860,9 @@ class ViewController: UIViewController {
|
|
|
2757
2860
|
additionalSafeAreaInsets = .zero
|
|
2758
2861
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
2759
2862
|
view.preservesSuperviewLayoutMargins = false
|
|
2760
|
-
|
|
2863
|
+
if #available(iOS 15.0, *) {
|
|
2864
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
2865
|
+
}
|
|
2761
2866
|
}
|
|
2762
2867
|
|
|
2763
2868
|
override func viewDidLayoutSubviews() {
|
|
@@ -2812,7 +2917,7 @@ class ViewController: UIViewController {
|
|
|
2812
2917
|
}
|
|
2813
2918
|
}
|
|
2814
2919
|
`);
|
|
2815
|
-
writeFile2(
|
|
2920
|
+
writeFile2(path14.join(projectDir, "LynxProvider.swift"), `
|
|
2816
2921
|
import Foundation
|
|
2817
2922
|
|
|
2818
2923
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2831,7 +2936,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2831
2936
|
}
|
|
2832
2937
|
}
|
|
2833
2938
|
`);
|
|
2834
|
-
writeFile2(
|
|
2939
|
+
writeFile2(path14.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2835
2940
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2836
2941
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2837
2942
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2871,7 +2976,7 @@ final class LynxInitProcessor {
|
|
|
2871
2976
|
}
|
|
2872
2977
|
`);
|
|
2873
2978
|
}
|
|
2874
|
-
writeFile2(
|
|
2979
|
+
writeFile2(path14.join(projectDir, bridgingHeader), `
|
|
2875
2980
|
#import <Lynx/LynxConfig.h>
|
|
2876
2981
|
#import <Lynx/LynxEnv.h>
|
|
2877
2982
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2880,7 +2985,7 @@ final class LynxInitProcessor {
|
|
|
2880
2985
|
#import <SDWebImage/SDWebImage.h>
|
|
2881
2986
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2882
2987
|
`);
|
|
2883
|
-
writeFile2(
|
|
2988
|
+
writeFile2(path14.join(projectDir, "Info.plist"), `
|
|
2884
2989
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2885
2990
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2886
2991
|
<plist version="1.0">
|
|
@@ -2938,21 +3043,21 @@ final class LynxInitProcessor {
|
|
|
2938
3043
|
</dict>
|
|
2939
3044
|
</plist>
|
|
2940
3045
|
`);
|
|
2941
|
-
const appIconDir =
|
|
2942
|
-
|
|
3046
|
+
const appIconDir = path14.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
3047
|
+
fs13.mkdirSync(appIconDir, { recursive: true });
|
|
2943
3048
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
2944
3049
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
2945
3050
|
console.log(iconPaths?.ios ? "\u2705 Copied iOS icon from tamer.config.json icon.ios" : "\u2705 Copied app icon from tamer.config.json icon.source");
|
|
2946
3051
|
} else {
|
|
2947
|
-
writeFile2(
|
|
3052
|
+
writeFile2(path14.join(appIconDir, "Contents.json"), `
|
|
2948
3053
|
{
|
|
2949
3054
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
2950
3055
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
2951
3056
|
}
|
|
2952
3057
|
`);
|
|
2953
3058
|
}
|
|
2954
|
-
|
|
2955
|
-
writeFile2(
|
|
3059
|
+
fs13.mkdirSync(xcodeprojDir, { recursive: true });
|
|
3060
|
+
writeFile2(path14.join(xcodeprojDir, "project.pbxproj"), `
|
|
2956
3061
|
// !$*UTF8*$!
|
|
2957
3062
|
{
|
|
2958
3063
|
archiveVersion = 1;
|
|
@@ -3238,8 +3343,8 @@ final class LynxInitProcessor {
|
|
|
3238
3343
|
var create_default2 = create2;
|
|
3239
3344
|
|
|
3240
3345
|
// src/ios/autolink.ts
|
|
3241
|
-
import
|
|
3242
|
-
import
|
|
3346
|
+
import fs15 from "fs";
|
|
3347
|
+
import path16 from "path";
|
|
3243
3348
|
import { execSync as execSync6 } from "child_process";
|
|
3244
3349
|
|
|
3245
3350
|
// src/common/hostNativeModulesManifest.ts
|
|
@@ -3250,8 +3355,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
|
|
|
3250
3355
|
}
|
|
3251
3356
|
|
|
3252
3357
|
// src/ios/syncHost.ts
|
|
3253
|
-
import
|
|
3254
|
-
import
|
|
3358
|
+
import fs14 from "fs";
|
|
3359
|
+
import path15 from "path";
|
|
3255
3360
|
import crypto from "crypto";
|
|
3256
3361
|
function deterministicUUID(seed) {
|
|
3257
3362
|
return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
|
|
@@ -3299,7 +3404,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3299
3404
|
`;
|
|
3300
3405
|
}
|
|
3301
3406
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3302
|
-
let content =
|
|
3407
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3303
3408
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3304
3409
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3305
3410
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3336,11 +3441,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3336
3441
|
);
|
|
3337
3442
|
content = content.replace(groupPattern, `$1
|
|
3338
3443
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3339
|
-
|
|
3444
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3340
3445
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3341
3446
|
}
|
|
3342
3447
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3343
|
-
let content =
|
|
3448
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3344
3449
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3345
3450
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3346
3451
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3365,11 +3470,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3365
3470
|
);
|
|
3366
3471
|
content = content.replace(groupPattern, `$1
|
|
3367
3472
|
${fileRefUUID} /* ${filename} */,`);
|
|
3368
|
-
|
|
3473
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3369
3474
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3370
3475
|
}
|
|
3371
3476
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3372
|
-
let content =
|
|
3477
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3373
3478
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3374
3479
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3375
3480
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3394,12 +3499,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3394
3499
|
);
|
|
3395
3500
|
content = content.replace(groupPattern, `$1
|
|
3396
3501
|
${fileRefUUID} /* ${filename} */,`);
|
|
3397
|
-
|
|
3502
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3398
3503
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3399
3504
|
}
|
|
3400
3505
|
function writeFile(filePath, content) {
|
|
3401
|
-
|
|
3402
|
-
|
|
3506
|
+
fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
|
|
3507
|
+
fs14.writeFileSync(filePath, content, "utf8");
|
|
3403
3508
|
}
|
|
3404
3509
|
function getAppDelegateSwift() {
|
|
3405
3510
|
return `import UIKit
|
|
@@ -3459,7 +3564,9 @@ class ViewController: UIViewController {
|
|
|
3459
3564
|
additionalSafeAreaInsets = .zero
|
|
3460
3565
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
3461
3566
|
view.preservesSuperviewLayoutMargins = false
|
|
3462
|
-
|
|
3567
|
+
if #available(iOS 15.0, *) {
|
|
3568
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
3569
|
+
}
|
|
3463
3570
|
}
|
|
3464
3571
|
|
|
3465
3572
|
override func viewDidLayoutSubviews() {
|
|
@@ -3520,6 +3627,7 @@ function getDevViewControllerSwift() {
|
|
|
3520
3627
|
import Lynx
|
|
3521
3628
|
import tamerdevclient
|
|
3522
3629
|
import tamerinsets
|
|
3630
|
+
import tamersystemui
|
|
3523
3631
|
|
|
3524
3632
|
class ViewController: UIViewController {
|
|
3525
3633
|
private var lynxView: LynxView?
|
|
@@ -3532,7 +3640,9 @@ class ViewController: UIViewController {
|
|
|
3532
3640
|
additionalSafeAreaInsets = .zero
|
|
3533
3641
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
3534
3642
|
view.preservesSuperviewLayoutMargins = false
|
|
3535
|
-
|
|
3643
|
+
if #available(iOS 15.0, *) {
|
|
3644
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
3645
|
+
}
|
|
3536
3646
|
setupLynxView()
|
|
3537
3647
|
setupDevClientModule()
|
|
3538
3648
|
}
|
|
@@ -3549,7 +3659,7 @@ class ViewController: UIViewController {
|
|
|
3549
3659
|
TamerInsetsModule.reRequestInsets()
|
|
3550
3660
|
}
|
|
3551
3661
|
|
|
3552
|
-
override var preferredStatusBarStyle: UIStatusBarStyle {
|
|
3662
|
+
override var preferredStatusBarStyle: UIStatusBarStyle { SystemUIModule.statusBarStyleForHost }
|
|
3553
3663
|
|
|
3554
3664
|
private func setupLynxView() {
|
|
3555
3665
|
let size = fullscreenBounds().size
|
|
@@ -3611,8 +3721,8 @@ class ViewController: UIViewController {
|
|
|
3611
3721
|
`;
|
|
3612
3722
|
}
|
|
3613
3723
|
function patchInfoPlist(infoPlistPath) {
|
|
3614
|
-
if (!
|
|
3615
|
-
let content =
|
|
3724
|
+
if (!fs14.existsSync(infoPlistPath)) return;
|
|
3725
|
+
let content = fs14.readFileSync(infoPlistPath, "utf8");
|
|
3616
3726
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3617
3727
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3618
3728
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3644,7 +3754,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3644
3754
|
</plist>`);
|
|
3645
3755
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3646
3756
|
}
|
|
3647
|
-
|
|
3757
|
+
fs14.writeFileSync(infoPlistPath, content, "utf8");
|
|
3648
3758
|
}
|
|
3649
3759
|
function getSimpleLynxProviderSwift() {
|
|
3650
3760
|
return `import Foundation
|
|
@@ -3669,9 +3779,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3669
3779
|
}
|
|
3670
3780
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3671
3781
|
if (devClientPkg) {
|
|
3672
|
-
const tplPath =
|
|
3673
|
-
if (
|
|
3674
|
-
let content =
|
|
3782
|
+
const tplPath = path15.join(devClientPkg, "ios", "templates", templateName);
|
|
3783
|
+
if (fs14.existsSync(tplPath)) {
|
|
3784
|
+
let content = fs14.readFileSync(tplPath, "utf8");
|
|
3675
3785
|
for (const [k, v] of Object.entries(vars)) {
|
|
3676
3786
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3677
3787
|
}
|
|
@@ -3689,19 +3799,19 @@ function syncHostIos(opts) {
|
|
|
3689
3799
|
if (!appName) {
|
|
3690
3800
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3691
3801
|
}
|
|
3692
|
-
const projectDir =
|
|
3693
|
-
const infoPlistPath =
|
|
3694
|
-
if (!
|
|
3802
|
+
const projectDir = path15.join(resolved.iosDir, appName);
|
|
3803
|
+
const infoPlistPath = path15.join(projectDir, "Info.plist");
|
|
3804
|
+
if (!fs14.existsSync(projectDir)) {
|
|
3695
3805
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3696
3806
|
}
|
|
3697
|
-
const pbxprojPath =
|
|
3698
|
-
const baseLprojDir =
|
|
3699
|
-
const launchScreenPath =
|
|
3807
|
+
const pbxprojPath = path15.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3808
|
+
const baseLprojDir = path15.join(projectDir, "Base.lproj");
|
|
3809
|
+
const launchScreenPath = path15.join(baseLprojDir, "LaunchScreen.storyboard");
|
|
3700
3810
|
patchInfoPlist(infoPlistPath);
|
|
3701
|
-
writeFile(
|
|
3702
|
-
writeFile(
|
|
3703
|
-
if (!
|
|
3704
|
-
|
|
3811
|
+
writeFile(path15.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
|
|
3812
|
+
writeFile(path15.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
|
|
3813
|
+
if (!fs14.existsSync(launchScreenPath)) {
|
|
3814
|
+
fs14.mkdirSync(baseLprojDir, { recursive: true });
|
|
3705
3815
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3706
3816
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3707
3817
|
}
|
|
@@ -3710,33 +3820,33 @@ function syncHostIos(opts) {
|
|
|
3710
3820
|
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3711
3821
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3712
3822
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3713
|
-
writeFile(
|
|
3714
|
-
writeFile(
|
|
3823
|
+
writeFile(path15.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3824
|
+
writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3715
3825
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3716
3826
|
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3717
3827
|
if (devTPContent) {
|
|
3718
|
-
writeFile(
|
|
3828
|
+
writeFile(path15.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3719
3829
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3720
3830
|
}
|
|
3721
3831
|
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3722
3832
|
if (projectVCContent) {
|
|
3723
|
-
writeFile(
|
|
3833
|
+
writeFile(path15.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3724
3834
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3725
3835
|
}
|
|
3726
3836
|
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3727
3837
|
if (devCMContent) {
|
|
3728
|
-
writeFile(
|
|
3838
|
+
writeFile(path15.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3729
3839
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3730
3840
|
}
|
|
3731
3841
|
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3732
3842
|
if (qrContent) {
|
|
3733
|
-
writeFile(
|
|
3843
|
+
writeFile(path15.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3734
3844
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
3735
3845
|
}
|
|
3736
3846
|
console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
|
|
3737
3847
|
} else {
|
|
3738
|
-
writeFile(
|
|
3739
|
-
writeFile(
|
|
3848
|
+
writeFile(path15.join(projectDir, "ViewController.swift"), getViewControllerSwift());
|
|
3849
|
+
writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3740
3850
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3741
3851
|
console.log("\u2705 Synced iOS host app controller files");
|
|
3742
3852
|
}
|
|
@@ -3755,11 +3865,11 @@ var autolink2 = () => {
|
|
|
3755
3865
|
const projectRoot = resolved.projectRoot;
|
|
3756
3866
|
const iosProjectPath = resolved.iosDir;
|
|
3757
3867
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
3758
|
-
if (!
|
|
3868
|
+
if (!fs15.existsSync(filePath)) {
|
|
3759
3869
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
3760
3870
|
return;
|
|
3761
3871
|
}
|
|
3762
|
-
let fileContent =
|
|
3872
|
+
let fileContent = fs15.readFileSync(filePath, "utf8");
|
|
3763
3873
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3764
3874
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3765
3875
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3779,33 +3889,33 @@ ${replacementBlock}
|
|
|
3779
3889
|
`;
|
|
3780
3890
|
}
|
|
3781
3891
|
} else {
|
|
3782
|
-
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${
|
|
3892
|
+
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path16.basename(filePath)}. Appending to the end of the file.`);
|
|
3783
3893
|
fileContent += `
|
|
3784
3894
|
${replacementBlock}
|
|
3785
3895
|
`;
|
|
3786
3896
|
}
|
|
3787
|
-
|
|
3788
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
3897
|
+
fs15.writeFileSync(filePath, fileContent, "utf8");
|
|
3898
|
+
console.log(`\u2705 Updated autolinked section in ${path16.basename(filePath)}`);
|
|
3789
3899
|
}
|
|
3790
3900
|
function resolvePodDirectory(pkg) {
|
|
3791
|
-
const configuredDir =
|
|
3792
|
-
if (
|
|
3901
|
+
const configuredDir = path16.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
|
|
3902
|
+
if (fs15.existsSync(configuredDir)) {
|
|
3793
3903
|
return configuredDir;
|
|
3794
3904
|
}
|
|
3795
|
-
const iosDir =
|
|
3796
|
-
if (
|
|
3905
|
+
const iosDir = path16.join(pkg.packagePath, "ios");
|
|
3906
|
+
if (fs15.existsSync(iosDir)) {
|
|
3797
3907
|
const stack = [iosDir];
|
|
3798
3908
|
while (stack.length > 0) {
|
|
3799
3909
|
const current = stack.pop();
|
|
3800
3910
|
try {
|
|
3801
|
-
const entries =
|
|
3911
|
+
const entries = fs15.readdirSync(current, { withFileTypes: true });
|
|
3802
3912
|
const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
|
|
3803
3913
|
if (podspec) {
|
|
3804
3914
|
return current;
|
|
3805
3915
|
}
|
|
3806
3916
|
for (const entry of entries) {
|
|
3807
3917
|
if (entry.isDirectory()) {
|
|
3808
|
-
stack.push(
|
|
3918
|
+
stack.push(path16.join(current, entry.name));
|
|
3809
3919
|
}
|
|
3810
3920
|
}
|
|
3811
3921
|
} catch {
|
|
@@ -3816,9 +3926,9 @@ ${replacementBlock}
|
|
|
3816
3926
|
}
|
|
3817
3927
|
function resolvePodName(pkg) {
|
|
3818
3928
|
const fullPodspecDir = resolvePodDirectory(pkg);
|
|
3819
|
-
if (
|
|
3929
|
+
if (fs15.existsSync(fullPodspecDir)) {
|
|
3820
3930
|
try {
|
|
3821
|
-
const files =
|
|
3931
|
+
const files = fs15.readdirSync(fullPodspecDir);
|
|
3822
3932
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
3823
3933
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
3824
3934
|
} catch {
|
|
@@ -3827,13 +3937,13 @@ ${replacementBlock}
|
|
|
3827
3937
|
return pkg.name.split("/").pop().replace(/-/g, "");
|
|
3828
3938
|
}
|
|
3829
3939
|
function updatePodfile(packages) {
|
|
3830
|
-
const podfilePath =
|
|
3940
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3831
3941
|
let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
|
|
3832
3942
|
# Manual edits will be overwritten.`;
|
|
3833
3943
|
const iosPackages = packages.filter((p) => p.config.ios);
|
|
3834
3944
|
if (iosPackages.length > 0) {
|
|
3835
3945
|
iosPackages.forEach((pkg) => {
|
|
3836
|
-
const relativePath =
|
|
3946
|
+
const relativePath = path16.relative(iosProjectPath, resolvePodDirectory(pkg));
|
|
3837
3947
|
const podName = resolvePodName(pkg);
|
|
3838
3948
|
scriptContent += `
|
|
3839
3949
|
pod '${podName}', :path => '${relativePath}'`;
|
|
@@ -3845,9 +3955,9 @@ ${replacementBlock}
|
|
|
3845
3955
|
updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
|
|
3846
3956
|
}
|
|
3847
3957
|
function ensureXElementPod() {
|
|
3848
|
-
const podfilePath =
|
|
3849
|
-
if (!
|
|
3850
|
-
let content =
|
|
3958
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3959
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
3960
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3851
3961
|
if (content.includes("pod 'XElement'")) return;
|
|
3852
3962
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
3853
3963
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -3872,13 +3982,13 @@ ${replacementBlock}
|
|
|
3872
3982
|
`;
|
|
3873
3983
|
}
|
|
3874
3984
|
}
|
|
3875
|
-
|
|
3985
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3876
3986
|
console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
|
|
3877
3987
|
}
|
|
3878
3988
|
function ensureLynxPatchInPodfile() {
|
|
3879
|
-
const podfilePath =
|
|
3880
|
-
if (!
|
|
3881
|
-
let content =
|
|
3989
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3990
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
3991
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3882
3992
|
if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
|
|
3883
3993
|
const patch = `
|
|
3884
3994
|
Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
|
|
@@ -3890,13 +4000,13 @@ ${replacementBlock}
|
|
|
3890
4000
|
end`;
|
|
3891
4001
|
content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
|
|
3892
4002
|
$2`);
|
|
3893
|
-
|
|
4003
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3894
4004
|
console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
|
|
3895
4005
|
}
|
|
3896
4006
|
function ensurePodBuildSettings() {
|
|
3897
|
-
const podfilePath =
|
|
3898
|
-
if (!
|
|
3899
|
-
let content =
|
|
4007
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
4008
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
4009
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3900
4010
|
let changed = false;
|
|
3901
4011
|
if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
|
|
3902
4012
|
content = content.replace(
|
|
@@ -3939,7 +4049,7 @@ $2`);
|
|
|
3939
4049
|
changed = true;
|
|
3940
4050
|
}
|
|
3941
4051
|
if (changed) {
|
|
3942
|
-
|
|
4052
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3943
4053
|
console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
|
|
3944
4054
|
}
|
|
3945
4055
|
}
|
|
@@ -3947,10 +4057,10 @@ $2`);
|
|
|
3947
4057
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
3948
4058
|
const candidatePaths = [];
|
|
3949
4059
|
if (appNameFromConfig) {
|
|
3950
|
-
candidatePaths.push(
|
|
4060
|
+
candidatePaths.push(path16.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
3951
4061
|
}
|
|
3952
|
-
candidatePaths.push(
|
|
3953
|
-
const found = candidatePaths.find((p) =>
|
|
4062
|
+
candidatePaths.push(path16.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
4063
|
+
const found = candidatePaths.find((p) => fs15.existsSync(p));
|
|
3954
4064
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
3955
4065
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
3956
4066
|
const seenModules = /* @__PURE__ */ new Set();
|
|
@@ -3989,7 +4099,7 @@ $2`);
|
|
|
3989
4099
|
const podName = resolvePodName(pkg);
|
|
3990
4100
|
return `import ${podName}`;
|
|
3991
4101
|
}).join("\n");
|
|
3992
|
-
const fileContent =
|
|
4102
|
+
const fileContent = fs15.readFileSync(filePath, "utf8");
|
|
3993
4103
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
3994
4104
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
3995
4105
|
return;
|
|
@@ -4026,8 +4136,8 @@ ${after}`;
|
|
|
4026
4136
|
${fileContent}`;
|
|
4027
4137
|
}
|
|
4028
4138
|
}
|
|
4029
|
-
|
|
4030
|
-
console.log(`\u2705 Updated imports in ${
|
|
4139
|
+
fs15.writeFileSync(filePath, newContent, "utf8");
|
|
4140
|
+
console.log(`\u2705 Updated imports in ${path16.basename(filePath)}`);
|
|
4031
4141
|
}
|
|
4032
4142
|
updateImportsSection(lynxInitPath, importPackages);
|
|
4033
4143
|
if (importPackages.length === 0) {
|
|
@@ -4071,7 +4181,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4071
4181
|
} else {
|
|
4072
4182
|
devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
|
|
4073
4183
|
}
|
|
4074
|
-
if (
|
|
4184
|
+
if (fs15.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
|
|
4075
4185
|
updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
|
|
4076
4186
|
}
|
|
4077
4187
|
}
|
|
@@ -4079,13 +4189,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4079
4189
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4080
4190
|
const candidates = [];
|
|
4081
4191
|
if (appNameFromConfig) {
|
|
4082
|
-
candidates.push(
|
|
4192
|
+
candidates.push(path16.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
4083
4193
|
}
|
|
4084
|
-
candidates.push(
|
|
4085
|
-
return candidates.find((p) =>
|
|
4194
|
+
candidates.push(path16.join(iosProjectPath, "Info.plist"));
|
|
4195
|
+
return candidates.find((p) => fs15.existsSync(p)) ?? null;
|
|
4086
4196
|
}
|
|
4087
4197
|
function readPlistXml(plistPath) {
|
|
4088
|
-
return
|
|
4198
|
+
return fs15.readFileSync(plistPath, "utf8");
|
|
4089
4199
|
}
|
|
4090
4200
|
function syncInfoPlistPermissions(packages) {
|
|
4091
4201
|
const plistPath = findInfoPlist();
|
|
@@ -4116,7 +4226,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4116
4226
|
added++;
|
|
4117
4227
|
}
|
|
4118
4228
|
if (added > 0) {
|
|
4119
|
-
|
|
4229
|
+
fs15.writeFileSync(plistPath, plist, "utf8");
|
|
4120
4230
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
4121
4231
|
}
|
|
4122
4232
|
}
|
|
@@ -4163,16 +4273,16 @@ ${schemesXml}
|
|
|
4163
4273
|
$1`
|
|
4164
4274
|
);
|
|
4165
4275
|
}
|
|
4166
|
-
|
|
4276
|
+
fs15.writeFileSync(plistPath, plist, "utf8");
|
|
4167
4277
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
4168
4278
|
}
|
|
4169
4279
|
function runPodInstall(forcePath) {
|
|
4170
|
-
const podfilePath = forcePath ??
|
|
4171
|
-
if (!
|
|
4280
|
+
const podfilePath = forcePath ?? path16.join(iosProjectPath, "Podfile");
|
|
4281
|
+
if (!fs15.existsSync(podfilePath)) {
|
|
4172
4282
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
4173
4283
|
return;
|
|
4174
4284
|
}
|
|
4175
|
-
const cwd =
|
|
4285
|
+
const cwd = path16.dirname(podfilePath);
|
|
4176
4286
|
try {
|
|
4177
4287
|
console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
|
|
4178
4288
|
try {
|
|
@@ -4206,8 +4316,8 @@ $1`
|
|
|
4206
4316
|
syncInfoPlistUrlSchemes();
|
|
4207
4317
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4208
4318
|
if (appNameFromConfig) {
|
|
4209
|
-
const appPodfile =
|
|
4210
|
-
if (
|
|
4319
|
+
const appPodfile = path16.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
4320
|
+
if (fs15.existsSync(appPodfile)) {
|
|
4211
4321
|
runPodInstall(appPodfile);
|
|
4212
4322
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
4213
4323
|
return;
|
|
@@ -4222,13 +4332,13 @@ $1`
|
|
|
4222
4332
|
const appFolder = resolved.config.ios?.appName;
|
|
4223
4333
|
if (!hasDevClient || !appFolder) return;
|
|
4224
4334
|
const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
|
|
4225
|
-
const appDir =
|
|
4226
|
-
|
|
4227
|
-
const manifestPath =
|
|
4228
|
-
|
|
4335
|
+
const appDir = path16.join(iosProjectPath, appFolder);
|
|
4336
|
+
fs15.mkdirSync(appDir, { recursive: true });
|
|
4337
|
+
const manifestPath = path16.join(appDir, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4338
|
+
fs15.writeFileSync(manifestPath, buildHostNativeModulesManifestJson(androidNames), "utf8");
|
|
4229
4339
|
console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
|
|
4230
|
-
const pbxprojPath =
|
|
4231
|
-
if (
|
|
4340
|
+
const pbxprojPath = path16.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
|
|
4341
|
+
if (fs15.existsSync(pbxprojPath)) {
|
|
4232
4342
|
addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4233
4343
|
}
|
|
4234
4344
|
}
|
|
@@ -4237,11 +4347,11 @@ $1`
|
|
|
4237
4347
|
var autolink_default2 = autolink2;
|
|
4238
4348
|
|
|
4239
4349
|
// src/ios/bundle.ts
|
|
4240
|
-
import
|
|
4241
|
-
import
|
|
4350
|
+
import fs16 from "fs";
|
|
4351
|
+
import path17 from "path";
|
|
4242
4352
|
import { execSync as execSync7 } from "child_process";
|
|
4243
4353
|
function bundleAndDeploy2(opts = {}) {
|
|
4244
|
-
const release = opts.release === true;
|
|
4354
|
+
const release = opts.release === true || opts.production === true;
|
|
4245
4355
|
let resolved;
|
|
4246
4356
|
try {
|
|
4247
4357
|
resolved = resolveHostPaths();
|
|
@@ -4256,18 +4366,22 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4256
4366
|
const includeDevClient = !release && !!devClientPkg;
|
|
4257
4367
|
const appName = resolved.config.ios.appName;
|
|
4258
4368
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
4259
|
-
const destinationDir =
|
|
4260
|
-
const destinationBundlePath =
|
|
4369
|
+
const destinationDir = path17.join(resolved.iosDir, appName);
|
|
4370
|
+
const destinationBundlePath = path17.join(destinationDir, resolved.lynxBundleFile);
|
|
4261
4371
|
syncHost_default({ release, includeDevClient });
|
|
4262
4372
|
autolink_default2();
|
|
4263
4373
|
const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
|
|
4264
4374
|
if (iconPaths) {
|
|
4265
|
-
const appIconDir =
|
|
4375
|
+
const appIconDir = path17.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
4266
4376
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
4267
4377
|
console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
|
|
4268
4378
|
}
|
|
4269
4379
|
}
|
|
4270
4380
|
try {
|
|
4381
|
+
const lynxTsconfig = path17.join(resolved.lynxProjectDir, "tsconfig.json");
|
|
4382
|
+
if (fs16.existsSync(lynxTsconfig)) {
|
|
4383
|
+
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
4384
|
+
}
|
|
4271
4385
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
4272
4386
|
execSync7("npm run build", { stdio: "inherit", cwd: resolved.lynxProjectDir });
|
|
4273
4387
|
console.log("\u2705 Build completed successfully.");
|
|
@@ -4276,40 +4390,40 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4276
4390
|
process.exit(1);
|
|
4277
4391
|
}
|
|
4278
4392
|
try {
|
|
4279
|
-
if (!
|
|
4393
|
+
if (!fs16.existsSync(sourceBundlePath)) {
|
|
4280
4394
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
4281
4395
|
process.exit(1);
|
|
4282
4396
|
}
|
|
4283
|
-
if (!
|
|
4397
|
+
if (!fs16.existsSync(destinationDir)) {
|
|
4284
4398
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
4285
4399
|
process.exit(1);
|
|
4286
4400
|
}
|
|
4287
|
-
const distDir =
|
|
4401
|
+
const distDir = path17.dirname(sourceBundlePath);
|
|
4288
4402
|
console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
|
|
4289
4403
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
4290
4404
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
4291
|
-
const pbxprojPath =
|
|
4292
|
-
if (
|
|
4405
|
+
const pbxprojPath = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4406
|
+
if (fs16.existsSync(pbxprojPath)) {
|
|
4293
4407
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
4294
|
-
for (const entry of
|
|
4295
|
-
if (skip.has(entry) ||
|
|
4408
|
+
for (const entry of fs16.readdirSync(distDir)) {
|
|
4409
|
+
if (skip.has(entry) || fs16.statSync(path17.join(distDir, entry)).isDirectory()) continue;
|
|
4296
4410
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
4297
4411
|
}
|
|
4298
4412
|
}
|
|
4299
4413
|
if (includeDevClient && devClientPkg) {
|
|
4300
|
-
const devClientBundle =
|
|
4414
|
+
const devClientBundle = path17.join(destinationDir, "dev-client.lynx.bundle");
|
|
4301
4415
|
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
4302
4416
|
try {
|
|
4303
4417
|
execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
4304
4418
|
} catch {
|
|
4305
4419
|
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
4306
4420
|
}
|
|
4307
|
-
const builtBundle =
|
|
4308
|
-
if (
|
|
4309
|
-
|
|
4421
|
+
const builtBundle = path17.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
4422
|
+
if (fs16.existsSync(builtBundle)) {
|
|
4423
|
+
fs16.copyFileSync(builtBundle, devClientBundle);
|
|
4310
4424
|
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
4311
|
-
const pbxprojPath2 =
|
|
4312
|
-
if (
|
|
4425
|
+
const pbxprojPath2 = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4426
|
+
if (fs16.existsSync(pbxprojPath2)) {
|
|
4313
4427
|
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
4314
4428
|
}
|
|
4315
4429
|
}
|
|
@@ -4323,8 +4437,8 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4323
4437
|
var bundle_default2 = bundleAndDeploy2;
|
|
4324
4438
|
|
|
4325
4439
|
// src/ios/build.ts
|
|
4326
|
-
import
|
|
4327
|
-
import
|
|
4440
|
+
import fs17 from "fs";
|
|
4441
|
+
import path18 from "path";
|
|
4328
4442
|
import os3 from "os";
|
|
4329
4443
|
import { execSync as execSync8 } from "child_process";
|
|
4330
4444
|
function hostArch() {
|
|
@@ -4351,14 +4465,15 @@ async function buildIpa(opts = {}) {
|
|
|
4351
4465
|
const appName = resolved.config.ios.appName;
|
|
4352
4466
|
const bundleId = resolved.config.ios.bundleId;
|
|
4353
4467
|
const iosDir = resolved.iosDir;
|
|
4354
|
-
const
|
|
4355
|
-
|
|
4468
|
+
const release = opts.release === true || opts.production === true;
|
|
4469
|
+
const configuration = release ? "Release" : "Debug";
|
|
4470
|
+
bundle_default2({ release, production: opts.production });
|
|
4356
4471
|
const scheme = appName;
|
|
4357
|
-
const workspacePath =
|
|
4358
|
-
const projectPath =
|
|
4359
|
-
const xcproject =
|
|
4472
|
+
const workspacePath = path18.join(iosDir, `${appName}.xcworkspace`);
|
|
4473
|
+
const projectPath = path18.join(iosDir, `${appName}.xcodeproj`);
|
|
4474
|
+
const xcproject = fs17.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
4360
4475
|
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
4361
|
-
const derivedDataPath =
|
|
4476
|
+
const derivedDataPath = path18.join(iosDir, "build");
|
|
4362
4477
|
const sdk = opts.install ? "iphonesimulator" : "iphoneos";
|
|
4363
4478
|
const signingArgs = opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
|
|
4364
4479
|
const archFlag = opts.install ? `-arch ${hostArch()} ` : "";
|
|
@@ -4374,14 +4489,14 @@ async function buildIpa(opts = {}) {
|
|
|
4374
4489
|
);
|
|
4375
4490
|
console.log(`\u2705 Build completed.`);
|
|
4376
4491
|
if (opts.install) {
|
|
4377
|
-
const appGlob =
|
|
4492
|
+
const appGlob = path18.join(
|
|
4378
4493
|
derivedDataPath,
|
|
4379
4494
|
"Build",
|
|
4380
4495
|
"Products",
|
|
4381
4496
|
`${configuration}-iphonesimulator`,
|
|
4382
4497
|
`${appName}.app`
|
|
4383
4498
|
);
|
|
4384
|
-
if (!
|
|
4499
|
+
if (!fs17.existsSync(appGlob)) {
|
|
4385
4500
|
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
4386
4501
|
process.exit(1);
|
|
4387
4502
|
}
|
|
@@ -4403,101 +4518,502 @@ async function buildIpa(opts = {}) {
|
|
|
4403
4518
|
}
|
|
4404
4519
|
var build_default2 = buildIpa;
|
|
4405
4520
|
|
|
4406
|
-
// src/common/init.
|
|
4407
|
-
import
|
|
4408
|
-
import
|
|
4409
|
-
import
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4521
|
+
// src/common/init.tsx
|
|
4522
|
+
import fs18 from "fs";
|
|
4523
|
+
import path19 from "path";
|
|
4524
|
+
import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
4525
|
+
import { render, Text as Text9, Box as Box8 } from "ink";
|
|
4526
|
+
|
|
4527
|
+
// src/common/tui/components/TextInput.tsx
|
|
4528
|
+
import { useState, useEffect } from "react";
|
|
4529
|
+
import { Box, Text } from "ink";
|
|
4530
|
+
import InkTextInput from "ink-text-input";
|
|
4531
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4532
|
+
function TuiTextInput({
|
|
4533
|
+
label,
|
|
4534
|
+
value: valueProp,
|
|
4535
|
+
defaultValue = "",
|
|
4536
|
+
onChange: onChangeProp,
|
|
4537
|
+
onSubmitValue,
|
|
4538
|
+
onSubmit,
|
|
4539
|
+
hint,
|
|
4540
|
+
error
|
|
4541
|
+
}) {
|
|
4542
|
+
const controlled = valueProp !== void 0;
|
|
4543
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
4544
|
+
useEffect(() => {
|
|
4545
|
+
if (!controlled) setInternal(defaultValue);
|
|
4546
|
+
}, [defaultValue, controlled]);
|
|
4547
|
+
const value = controlled ? valueProp : internal;
|
|
4548
|
+
const onChange = (v) => {
|
|
4549
|
+
if (!controlled) setInternal(v);
|
|
4550
|
+
onChangeProp?.(v);
|
|
4551
|
+
};
|
|
4552
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
4553
|
+
label ? /* @__PURE__ */ jsx(Text, { children: label }) : null,
|
|
4554
|
+
/* @__PURE__ */ jsx(
|
|
4555
|
+
InkTextInput,
|
|
4556
|
+
{
|
|
4557
|
+
value,
|
|
4558
|
+
onChange,
|
|
4559
|
+
onSubmit: () => {
|
|
4560
|
+
const r = onSubmitValue?.(value);
|
|
4561
|
+
if (r === false) return;
|
|
4562
|
+
onSubmit();
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
),
|
|
4566
|
+
error ? /* @__PURE__ */ jsx(Text, { color: "red", children: error }) : hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
4567
|
+
] });
|
|
4568
|
+
}
|
|
4569
|
+
|
|
4570
|
+
// src/common/tui/components/SelectInput.tsx
|
|
4571
|
+
import "react";
|
|
4572
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
4573
|
+
import InkSelectInput from "ink-select-input";
|
|
4574
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4575
|
+
function TuiSelectInput({
|
|
4576
|
+
label,
|
|
4577
|
+
items,
|
|
4578
|
+
onSelect,
|
|
4579
|
+
hint
|
|
4580
|
+
}) {
|
|
4581
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
4582
|
+
label ? /* @__PURE__ */ jsx2(Text2, { children: label }) : null,
|
|
4583
|
+
/* @__PURE__ */ jsx2(
|
|
4584
|
+
InkSelectInput,
|
|
4585
|
+
{
|
|
4586
|
+
items,
|
|
4587
|
+
onSelect: (item) => onSelect(item.value)
|
|
4588
|
+
}
|
|
4589
|
+
),
|
|
4590
|
+
hint ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: hint }) : null
|
|
4591
|
+
] });
|
|
4592
|
+
}
|
|
4593
|
+
|
|
4594
|
+
// src/common/tui/components/PasswordInput.tsx
|
|
4595
|
+
import "react";
|
|
4596
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
4597
|
+
import InkTextInput2 from "ink-text-input";
|
|
4598
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
4599
|
+
|
|
4600
|
+
// src/common/tui/components/ConfirmInput.tsx
|
|
4601
|
+
import "react";
|
|
4602
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
4603
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
4604
|
+
function TuiConfirmInput({
|
|
4605
|
+
label,
|
|
4606
|
+
onConfirm,
|
|
4607
|
+
defaultYes = false,
|
|
4608
|
+
hint
|
|
4609
|
+
}) {
|
|
4610
|
+
const items = defaultYes ? [
|
|
4611
|
+
{ label: "Yes (default)", value: "yes" },
|
|
4612
|
+
{ label: "No", value: "no" }
|
|
4613
|
+
] : [
|
|
4614
|
+
{ label: "No (default)", value: "no" },
|
|
4615
|
+
{ label: "Yes", value: "yes" }
|
|
4616
|
+
];
|
|
4617
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
4618
|
+
label ? /* @__PURE__ */ jsx4(Text4, { children: label }) : null,
|
|
4619
|
+
/* @__PURE__ */ jsx4(
|
|
4620
|
+
TuiSelectInput,
|
|
4621
|
+
{
|
|
4622
|
+
items,
|
|
4623
|
+
onSelect: (v) => onConfirm(v === "yes"),
|
|
4624
|
+
hint
|
|
4625
|
+
}
|
|
4626
|
+
)
|
|
4627
|
+
] });
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
// src/common/tui/components/Spinner.tsx
|
|
4631
|
+
import "react";
|
|
4632
|
+
import { Text as Text5 } from "ink";
|
|
4633
|
+
import InkSpinner from "ink-spinner";
|
|
4634
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
4635
|
+
function TuiSpinner({ label, type = "dots" }) {
|
|
4636
|
+
return /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
|
|
4637
|
+
/* @__PURE__ */ jsx5(InkSpinner, { type }),
|
|
4638
|
+
label ? ` ${label}` : ""
|
|
4639
|
+
] });
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
// src/common/tui/components/StatusBox.tsx
|
|
4643
|
+
import "react";
|
|
4644
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
4645
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4646
|
+
var colors = {
|
|
4647
|
+
success: "green",
|
|
4648
|
+
error: "red",
|
|
4649
|
+
warning: "yellow",
|
|
4650
|
+
info: "cyan"
|
|
4651
|
+
};
|
|
4652
|
+
function StatusBox({ variant, children, title }) {
|
|
4653
|
+
const c = colors[variant];
|
|
4654
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", borderStyle: "round", borderColor: c, paddingX: 1, children: [
|
|
4655
|
+
title ? /* @__PURE__ */ jsx6(Text6, { bold: true, color: c, children: title }) : null,
|
|
4656
|
+
/* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children })
|
|
4657
|
+
] });
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
// src/common/tui/components/Wizard.tsx
|
|
4661
|
+
import "react";
|
|
4662
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
4663
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4664
|
+
function Wizard({ step, total, title, children }) {
|
|
4665
|
+
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
|
|
4666
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4667
|
+
"Step ",
|
|
4668
|
+
step,
|
|
4669
|
+
"/",
|
|
4670
|
+
total,
|
|
4671
|
+
title ? ` \u2014 ${title}` : ""
|
|
4672
|
+
] }),
|
|
4673
|
+
/* @__PURE__ */ jsx7(Box6, { marginTop: 1, flexDirection: "column", children })
|
|
4674
|
+
] });
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
// src/common/tui/components/ServerDashboard.tsx
|
|
4678
|
+
import "react";
|
|
4679
|
+
import { Box as Box7, Text as Text8 } from "ink";
|
|
4680
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4681
|
+
function ServerDashboard({
|
|
4682
|
+
projectName,
|
|
4683
|
+
port,
|
|
4684
|
+
lanIp,
|
|
4685
|
+
devUrl,
|
|
4686
|
+
wsUrl,
|
|
4687
|
+
lynxBundleFile,
|
|
4688
|
+
bonjour,
|
|
4689
|
+
verbose,
|
|
4690
|
+
buildPhase,
|
|
4691
|
+
buildError,
|
|
4692
|
+
wsConnections,
|
|
4693
|
+
logLines,
|
|
4694
|
+
showLogs,
|
|
4695
|
+
qrLines,
|
|
4696
|
+
phase,
|
|
4697
|
+
startError
|
|
4698
|
+
}) {
|
|
4699
|
+
if (phase === "failed") {
|
|
4700
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
4701
|
+
/* @__PURE__ */ jsx8(Text8, { color: "red", bold: true, children: "Dev server failed to start" }),
|
|
4702
|
+
startError ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: startError }) : null,
|
|
4703
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press Ctrl+C or 'q' to quit" }) })
|
|
4704
|
+
] });
|
|
4705
|
+
}
|
|
4706
|
+
const bundlePath = `${devUrl}/${lynxBundleFile}`;
|
|
4707
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
4708
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: "green", children: [
|
|
4709
|
+
"Tamer4Lynx dev server (",
|
|
4710
|
+
projectName,
|
|
4711
|
+
")"
|
|
4712
|
+
] }),
|
|
4713
|
+
verbose ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Logs: verbose (native + JS)" }) : null,
|
|
4714
|
+
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", columnGap: 3, alignItems: "flex-start", children: [
|
|
4715
|
+
qrLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
|
|
4716
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Scan" }),
|
|
4717
|
+
qrLines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { children: line }, i)),
|
|
4718
|
+
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4719
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Open" }),
|
|
4720
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", wrap: "truncate-end", children: devUrl })
|
|
4721
|
+
] })
|
|
4722
|
+
] }) : /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
|
|
4723
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Open" }),
|
|
4724
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", wrap: "truncate-end", children: devUrl })
|
|
4725
|
+
] }),
|
|
4726
|
+
/* @__PURE__ */ jsxs8(
|
|
4727
|
+
Box7,
|
|
4728
|
+
{
|
|
4729
|
+
flexDirection: "column",
|
|
4730
|
+
flexGrow: 1,
|
|
4731
|
+
minWidth: 28,
|
|
4732
|
+
marginTop: qrLines.length > 0 ? 2 : 0,
|
|
4733
|
+
children: [
|
|
4734
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
4735
|
+
"Port: ",
|
|
4736
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", children: port }),
|
|
4737
|
+
" \xB7 LAN: ",
|
|
4738
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", children: lanIp })
|
|
4739
|
+
] }),
|
|
4740
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "truncate-end", children: bundlePath }),
|
|
4741
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, wrap: "truncate-end", children: [
|
|
4742
|
+
devUrl,
|
|
4743
|
+
"/meta.json"
|
|
4744
|
+
] }),
|
|
4745
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, wrap: "truncate-end", children: wsUrl }),
|
|
4746
|
+
bonjour ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "mDNS: _tamer._tcp" }) : null,
|
|
4747
|
+
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4748
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Build" }),
|
|
4749
|
+
buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "Building\u2026" }) : buildPhase === "error" ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: buildError ?? "Build failed" }) : /* @__PURE__ */ jsx8(Text8, { color: "green", children: "Ready" })
|
|
4750
|
+
] }),
|
|
4751
|
+
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4752
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Connections" }),
|
|
4753
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
4754
|
+
"WebSocket clients: ",
|
|
4755
|
+
wsConnections
|
|
4756
|
+
] })
|
|
4757
|
+
] })
|
|
4758
|
+
]
|
|
4759
|
+
}
|
|
4760
|
+
)
|
|
4761
|
+
] }),
|
|
4762
|
+
showLogs && logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
|
|
4763
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
4764
|
+
"Build / output (last ",
|
|
4765
|
+
logLines.length,
|
|
4766
|
+
" lines)"
|
|
4767
|
+
] }),
|
|
4768
|
+
logLines.slice(-12).map((line, i) => /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: line }, i))
|
|
4769
|
+
] }) : null,
|
|
4770
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 l toggle logs \xB7 q quit" }) })
|
|
4771
|
+
] });
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
// src/common/tui/hooks/useInputState.ts
|
|
4775
|
+
import { useState as useState2, useCallback } from "react";
|
|
4776
|
+
|
|
4777
|
+
// src/common/tui/hooks/useValidation.ts
|
|
4778
|
+
function isValidAndroidPackage(name) {
|
|
4779
|
+
const s = name.trim();
|
|
4780
|
+
if (!s) return false;
|
|
4781
|
+
return /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.test(s);
|
|
4782
|
+
}
|
|
4783
|
+
function isValidIosBundleId(id) {
|
|
4784
|
+
const s = id.trim();
|
|
4785
|
+
if (!s) return false;
|
|
4786
|
+
return /^[a-zA-Z][a-zA-Z0-9_-]*(\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(s);
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
// src/common/tui/hooks/useServerStatus.ts
|
|
4790
|
+
import { useState as useState3, useCallback as useCallback2 } from "react";
|
|
4791
|
+
|
|
4792
|
+
// src/common/init.tsx
|
|
4793
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4794
|
+
function resolveSdkInput(raw) {
|
|
4795
|
+
let androidSdk = raw.trim();
|
|
4425
4796
|
if (androidSdk.startsWith("$") && /^[A-Z0-9_]+$/.test(androidSdk.slice(1))) {
|
|
4426
4797
|
const envVar = androidSdk.slice(1);
|
|
4427
4798
|
const envValue = process.env[envVar];
|
|
4428
4799
|
if (envValue) {
|
|
4429
4800
|
androidSdk = envValue;
|
|
4430
|
-
|
|
4431
|
-
} else {
|
|
4432
|
-
console.warn(`Environment variable $${envVar} not found. SDK path will be left as-is.`);
|
|
4801
|
+
return { resolved: androidSdk, message: `Using ${androidSdk} from $${envVar}` };
|
|
4433
4802
|
}
|
|
4803
|
+
return {
|
|
4804
|
+
resolved: androidSdk,
|
|
4805
|
+
message: `Environment variable $${envVar} not found \u2014 path saved as typed.`
|
|
4806
|
+
};
|
|
4434
4807
|
}
|
|
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
|
-
|
|
4808
|
+
return { resolved: androidSdk };
|
|
4809
|
+
}
|
|
4810
|
+
function InitWizard() {
|
|
4811
|
+
const [step, setStep] = useState4("welcome");
|
|
4812
|
+
const [androidAppName, setAndroidAppName] = useState4("");
|
|
4813
|
+
const [androidPackageName, setAndroidPackageName] = useState4("");
|
|
4814
|
+
const [androidSdk, setAndroidSdk] = useState4("");
|
|
4815
|
+
const [sdkHint, setSdkHint] = useState4();
|
|
4816
|
+
const [iosAppName, setIosAppName] = useState4("");
|
|
4817
|
+
const [iosBundleId, setIosBundleId] = useState4("");
|
|
4818
|
+
const [lynxProject, setLynxProject] = useState4("");
|
|
4819
|
+
const [pkgError, setPkgError] = useState4();
|
|
4820
|
+
const [bundleError, setBundleError] = useState4();
|
|
4821
|
+
const [doneMessage, setDoneMessage] = useState4([]);
|
|
4822
|
+
const writeConfigAndTsconfig = useCallback3(() => {
|
|
4823
|
+
const config = {
|
|
4824
|
+
android: {
|
|
4825
|
+
appName: androidAppName || void 0,
|
|
4826
|
+
packageName: androidPackageName || void 0,
|
|
4827
|
+
sdk: androidSdk || void 0
|
|
4828
|
+
},
|
|
4829
|
+
ios: {
|
|
4830
|
+
appName: iosAppName || void 0,
|
|
4831
|
+
bundleId: iosBundleId || void 0
|
|
4832
|
+
},
|
|
4833
|
+
paths: { androidDir: "android", iosDir: "ios" }
|
|
4834
|
+
};
|
|
4835
|
+
if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
|
|
4836
|
+
const configPath = path19.join(process.cwd(), "tamer.config.json");
|
|
4837
|
+
fs18.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4838
|
+
const lines = [`Generated tamer.config.json at ${configPath}`];
|
|
4839
|
+
const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
|
|
4840
|
+
const tsconfigCandidates = lynxProject.trim() ? [
|
|
4841
|
+
path19.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
|
|
4842
|
+
path19.join(process.cwd(), "tsconfig.json")
|
|
4843
|
+
] : [path19.join(process.cwd(), "tsconfig.json")];
|
|
4844
|
+
for (const tsconfigPath of tsconfigCandidates) {
|
|
4845
|
+
if (!fs18.existsSync(tsconfigPath)) continue;
|
|
4846
|
+
try {
|
|
4847
|
+
if (fixTsconfigReferencesForBuild(tsconfigPath)) {
|
|
4848
|
+
lines.push(`Flattened ${path19.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
|
|
4849
|
+
}
|
|
4850
|
+
if (addTamerTypesInclude(tsconfigPath, tamerTypesInclude)) {
|
|
4851
|
+
lines.push(`Updated ${path19.relative(process.cwd(), tsconfigPath)} for tamer types`);
|
|
4852
|
+
}
|
|
4853
|
+
break;
|
|
4854
|
+
} catch (e) {
|
|
4855
|
+
lines.push(`Could not update ${tsconfigPath}: ${e.message}`);
|
|
4856
|
+
}
|
|
4471
4857
|
}
|
|
4858
|
+
setDoneMessage(lines);
|
|
4859
|
+
setStep("done");
|
|
4860
|
+
setTimeout(() => process.exit(0), 2e3);
|
|
4861
|
+
}, [androidAppName, androidPackageName, androidSdk, iosAppName, iosBundleId, lynxProject]);
|
|
4862
|
+
useEffect2(() => {
|
|
4863
|
+
if (step !== "saving") return;
|
|
4864
|
+
writeConfigAndTsconfig();
|
|
4865
|
+
}, [step, writeConfigAndTsconfig]);
|
|
4866
|
+
if (step === "welcome") {
|
|
4867
|
+
return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
|
|
4868
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Tamer4Lynx init" }),
|
|
4869
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Set up tamer.config.json for your project." }),
|
|
4870
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(
|
|
4871
|
+
TuiSelectInput,
|
|
4872
|
+
{
|
|
4873
|
+
label: "Continue?",
|
|
4874
|
+
items: [{ label: "Start", value: "start" }],
|
|
4875
|
+
onSelect: () => setStep("android-app")
|
|
4876
|
+
}
|
|
4877
|
+
) })
|
|
4878
|
+
] });
|
|
4879
|
+
}
|
|
4880
|
+
if (step === "android-app") {
|
|
4881
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 1, total: 6, title: "Android app name", children: /* @__PURE__ */ jsx9(
|
|
4882
|
+
TuiTextInput,
|
|
4883
|
+
{
|
|
4884
|
+
label: "Android app name:",
|
|
4885
|
+
defaultValue: androidAppName,
|
|
4886
|
+
onSubmitValue: (v) => setAndroidAppName(v),
|
|
4887
|
+
onSubmit: () => setStep("android-pkg")
|
|
4888
|
+
},
|
|
4889
|
+
step
|
|
4890
|
+
) });
|
|
4891
|
+
}
|
|
4892
|
+
if (step === "android-pkg") {
|
|
4893
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 2, total: 6, title: "Android package name", children: /* @__PURE__ */ jsx9(
|
|
4894
|
+
TuiTextInput,
|
|
4895
|
+
{
|
|
4896
|
+
label: "Android package name (e.g. com.example.app):",
|
|
4897
|
+
defaultValue: androidPackageName,
|
|
4898
|
+
error: pkgError,
|
|
4899
|
+
onChange: () => setPkgError(void 0),
|
|
4900
|
+
onSubmitValue: (v) => {
|
|
4901
|
+
const t = v.trim();
|
|
4902
|
+
if (t && !isValidAndroidPackage(t)) {
|
|
4903
|
+
setPkgError("Use reverse-DNS form: com.mycompany.app");
|
|
4904
|
+
return false;
|
|
4905
|
+
}
|
|
4906
|
+
setAndroidPackageName(t);
|
|
4907
|
+
setPkgError(void 0);
|
|
4908
|
+
},
|
|
4909
|
+
onSubmit: () => setStep("android-sdk")
|
|
4910
|
+
},
|
|
4911
|
+
step
|
|
4912
|
+
) });
|
|
4913
|
+
}
|
|
4914
|
+
if (step === "android-sdk") {
|
|
4915
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 3, total: 6, title: "Android SDK", children: /* @__PURE__ */ jsx9(
|
|
4916
|
+
TuiTextInput,
|
|
4917
|
+
{
|
|
4918
|
+
label: "Android SDK path (e.g. ~/Library/Android/sdk or $ANDROID_HOME):",
|
|
4919
|
+
defaultValue: androidSdk,
|
|
4920
|
+
onSubmitValue: (v) => {
|
|
4921
|
+
const { resolved, message } = resolveSdkInput(v);
|
|
4922
|
+
setAndroidSdk(resolved);
|
|
4923
|
+
setSdkHint(message);
|
|
4924
|
+
},
|
|
4925
|
+
onSubmit: () => setStep("ios-reuse"),
|
|
4926
|
+
hint: sdkHint
|
|
4927
|
+
},
|
|
4928
|
+
step
|
|
4929
|
+
) });
|
|
4930
|
+
}
|
|
4931
|
+
if (step === "ios-reuse") {
|
|
4932
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 4, total: 6, title: "iOS", children: /* @__PURE__ */ jsx9(
|
|
4933
|
+
TuiConfirmInput,
|
|
4934
|
+
{
|
|
4935
|
+
label: "Use the same app name and bundle ID for iOS as Android?",
|
|
4936
|
+
defaultYes: false,
|
|
4937
|
+
onConfirm: (yes) => {
|
|
4938
|
+
if (yes) {
|
|
4939
|
+
setIosAppName(androidAppName);
|
|
4940
|
+
setIosBundleId(androidPackageName);
|
|
4941
|
+
setStep("lynx-path");
|
|
4942
|
+
} else {
|
|
4943
|
+
setStep("ios-app");
|
|
4944
|
+
}
|
|
4945
|
+
},
|
|
4946
|
+
hint: "No = enter iOS-specific values next"
|
|
4947
|
+
}
|
|
4948
|
+
) });
|
|
4949
|
+
}
|
|
4950
|
+
if (step === "ios-app") {
|
|
4951
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 4, total: 6, title: "iOS app name", children: /* @__PURE__ */ jsx9(
|
|
4952
|
+
TuiTextInput,
|
|
4953
|
+
{
|
|
4954
|
+
label: "iOS app name:",
|
|
4955
|
+
defaultValue: iosAppName,
|
|
4956
|
+
onSubmitValue: (v) => setIosAppName(v),
|
|
4957
|
+
onSubmit: () => setStep("ios-bundle")
|
|
4958
|
+
},
|
|
4959
|
+
step
|
|
4960
|
+
) });
|
|
4961
|
+
}
|
|
4962
|
+
if (step === "ios-bundle") {
|
|
4963
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 5, total: 6, title: "iOS bundle ID", children: /* @__PURE__ */ jsx9(
|
|
4964
|
+
TuiTextInput,
|
|
4965
|
+
{
|
|
4966
|
+
label: "iOS bundle ID (e.g. com.example.app):",
|
|
4967
|
+
defaultValue: iosBundleId,
|
|
4968
|
+
error: bundleError,
|
|
4969
|
+
onChange: () => setBundleError(void 0),
|
|
4970
|
+
onSubmitValue: (v) => {
|
|
4971
|
+
const t = v.trim();
|
|
4972
|
+
if (t && !isValidIosBundleId(t)) {
|
|
4973
|
+
setBundleError("Use reverse-DNS form: com.mycompany.App");
|
|
4974
|
+
return false;
|
|
4975
|
+
}
|
|
4976
|
+
setIosBundleId(t);
|
|
4977
|
+
setBundleError(void 0);
|
|
4978
|
+
},
|
|
4979
|
+
onSubmit: () => setStep("lynx-path")
|
|
4980
|
+
},
|
|
4981
|
+
step
|
|
4982
|
+
) });
|
|
4983
|
+
}
|
|
4984
|
+
if (step === "lynx-path") {
|
|
4985
|
+
return /* @__PURE__ */ jsx9(Wizard, { step: 6, total: 6, title: "Lynx project", children: /* @__PURE__ */ jsx9(
|
|
4986
|
+
TuiTextInput,
|
|
4987
|
+
{
|
|
4988
|
+
label: "Lynx project path relative to project root (optional, e.g. packages/example):",
|
|
4989
|
+
defaultValue: lynxProject,
|
|
4990
|
+
onSubmitValue: (v) => setLynxProject(v),
|
|
4991
|
+
onSubmit: () => setStep("saving"),
|
|
4992
|
+
hint: "Press Enter with empty to skip"
|
|
4993
|
+
},
|
|
4994
|
+
step
|
|
4995
|
+
) });
|
|
4472
4996
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
try {
|
|
4476
|
-
const raw = fs17.readFileSync(tsconfigPath, "utf-8");
|
|
4477
|
-
const tsconfig = parseTsconfigJson(raw);
|
|
4478
|
-
const include = tsconfig.include ?? [];
|
|
4479
|
-
const arr = Array.isArray(include) ? include : [include];
|
|
4480
|
-
if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) continue;
|
|
4481
|
-
arr.push(tamerTypesInclude);
|
|
4482
|
-
tsconfig.include = arr;
|
|
4483
|
-
fs17.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
4484
|
-
console.log(`\u2705 Updated ${path18.relative(process.cwd(), tsconfigPath)} to include tamer type declarations`);
|
|
4485
|
-
break;
|
|
4486
|
-
} catch (e) {
|
|
4487
|
-
console.warn(`\u26A0 Could not update ${tsconfigPath}:`, e.message);
|
|
4488
|
-
}
|
|
4997
|
+
if (step === "saving") {
|
|
4998
|
+
return /* @__PURE__ */ jsx9(Box8, { children: /* @__PURE__ */ jsx9(TuiSpinner, { label: "Writing tamer.config.json and updating tsconfig\u2026" }) });
|
|
4489
4999
|
}
|
|
4490
|
-
|
|
5000
|
+
if (step === "done") {
|
|
5001
|
+
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)) }) });
|
|
5002
|
+
}
|
|
5003
|
+
return null;
|
|
5004
|
+
}
|
|
5005
|
+
async function init() {
|
|
5006
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx9(InitWizard, {}));
|
|
5007
|
+
await waitUntilExit();
|
|
4491
5008
|
}
|
|
4492
|
-
var init_default = init;
|
|
4493
5009
|
|
|
4494
5010
|
// src/common/create.ts
|
|
4495
|
-
import
|
|
4496
|
-
import
|
|
4497
|
-
import
|
|
4498
|
-
var
|
|
4499
|
-
function
|
|
4500
|
-
return new Promise((resolve) =>
|
|
5011
|
+
import fs19 from "fs";
|
|
5012
|
+
import path20 from "path";
|
|
5013
|
+
import readline from "readline";
|
|
5014
|
+
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5015
|
+
function ask(question) {
|
|
5016
|
+
return new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
|
|
4501
5017
|
}
|
|
4502
5018
|
async function create3(opts) {
|
|
4503
5019
|
console.log("Tamer4Lynx: Create Lynx Extension\n");
|
|
@@ -4536,32 +5052,32 @@ async function create3(opts) {
|
|
|
4536
5052
|
console.log(" [ ] Native Module");
|
|
4537
5053
|
console.log(" [ ] Element");
|
|
4538
5054
|
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
|
|
5055
|
+
includeModule = /^y(es)?$/i.test(await ask("Include Native Module? (Y/n): ") || "y");
|
|
5056
|
+
includeElement = /^y(es)?$/i.test(await ask("Include Element? (y/N): ") || "n");
|
|
5057
|
+
includeService = /^y(es)?$/i.test(await ask("Include Service? (y/N): ") || "n");
|
|
4542
5058
|
}
|
|
4543
5059
|
if (!includeModule && !includeElement && !includeService) {
|
|
4544
5060
|
console.error("\u274C At least one extension type is required.");
|
|
4545
|
-
|
|
5061
|
+
rl.close();
|
|
4546
5062
|
process.exit(1);
|
|
4547
5063
|
}
|
|
4548
|
-
const extName = await
|
|
5064
|
+
const extName = await ask("Extension package name (e.g. my-lynx-module): ");
|
|
4549
5065
|
if (!extName || !/^[a-z0-9-_]+$/.test(extName)) {
|
|
4550
5066
|
console.error("\u274C Invalid package name. Use lowercase letters, numbers, hyphens, underscores.");
|
|
4551
|
-
|
|
5067
|
+
rl.close();
|
|
4552
5068
|
process.exit(1);
|
|
4553
5069
|
}
|
|
4554
|
-
const packageName = await
|
|
5070
|
+
const packageName = await ask("Android package name (e.g. com.example.mymodule): ") || `com.example.${extName.replace(/-/g, "")}`;
|
|
4555
5071
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
4556
5072
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
4557
5073
|
const cwd = process.cwd();
|
|
4558
|
-
const root =
|
|
4559
|
-
if (
|
|
5074
|
+
const root = path20.join(cwd, extName);
|
|
5075
|
+
if (fs19.existsSync(root)) {
|
|
4560
5076
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
4561
|
-
|
|
5077
|
+
rl.close();
|
|
4562
5078
|
process.exit(1);
|
|
4563
5079
|
}
|
|
4564
|
-
|
|
5080
|
+
fs19.mkdirSync(root, { recursive: true });
|
|
4565
5081
|
const lynxExt = {
|
|
4566
5082
|
platforms: {
|
|
4567
5083
|
android: {
|
|
@@ -4576,7 +5092,7 @@ async function create3(opts) {
|
|
|
4576
5092
|
web: {}
|
|
4577
5093
|
}
|
|
4578
5094
|
};
|
|
4579
|
-
|
|
5095
|
+
fs19.writeFileSync(path20.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
4580
5096
|
const pkg = {
|
|
4581
5097
|
name: extName,
|
|
4582
5098
|
version: "0.0.1",
|
|
@@ -4589,20 +5105,20 @@ async function create3(opts) {
|
|
|
4589
5105
|
engines: { node: ">=18" }
|
|
4590
5106
|
};
|
|
4591
5107
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
4592
|
-
|
|
5108
|
+
fs19.writeFileSync(path20.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
4593
5109
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
4594
5110
|
const hasSrc = includeModule || includeElement || includeService;
|
|
4595
5111
|
if (hasSrc) {
|
|
4596
|
-
|
|
5112
|
+
fs19.mkdirSync(path20.join(root, "src"), { recursive: true });
|
|
4597
5113
|
}
|
|
4598
5114
|
if (includeModule) {
|
|
4599
|
-
|
|
5115
|
+
fs19.writeFileSync(path20.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
4600
5116
|
export declare class ${simpleModuleName} {
|
|
4601
5117
|
// Add your module methods here
|
|
4602
5118
|
}
|
|
4603
5119
|
`);
|
|
4604
|
-
|
|
4605
|
-
|
|
5120
|
+
fs19.mkdirSync(path20.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
5121
|
+
fs19.writeFileSync(path20.join(root, "android", "build.gradle.kts"), `plugins {
|
|
4606
5122
|
id("com.android.library")
|
|
4607
5123
|
id("org.jetbrains.kotlin.android")
|
|
4608
5124
|
}
|
|
@@ -4623,7 +5139,7 @@ dependencies {
|
|
|
4623
5139
|
implementation(libs.lynx.jssdk)
|
|
4624
5140
|
}
|
|
4625
5141
|
`);
|
|
4626
|
-
|
|
5142
|
+
fs19.writeFileSync(path20.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
4627
5143
|
<manifest />
|
|
4628
5144
|
`);
|
|
4629
5145
|
const ktContent = `package ${packageName}
|
|
@@ -4640,8 +5156,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
4640
5156
|
}
|
|
4641
5157
|
}
|
|
4642
5158
|
`;
|
|
4643
|
-
|
|
4644
|
-
|
|
5159
|
+
fs19.writeFileSync(path20.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
|
|
5160
|
+
fs19.mkdirSync(path20.join(root, "ios", extName, extName, "Classes"), { recursive: true });
|
|
4645
5161
|
const podspec = `Pod::Spec.new do |s|
|
|
4646
5162
|
s.name = '${extName}'
|
|
4647
5163
|
s.version = '0.0.1'
|
|
@@ -4655,7 +5171,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
4655
5171
|
s.dependency 'Lynx'
|
|
4656
5172
|
end
|
|
4657
5173
|
`;
|
|
4658
|
-
|
|
5174
|
+
fs19.writeFileSync(path20.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
4659
5175
|
const swiftContent = `import Foundation
|
|
4660
5176
|
|
|
4661
5177
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -4664,18 +5180,18 @@ end
|
|
|
4664
5180
|
}
|
|
4665
5181
|
}
|
|
4666
5182
|
`;
|
|
4667
|
-
|
|
5183
|
+
fs19.writeFileSync(path20.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
4668
5184
|
}
|
|
4669
5185
|
if (includeElement && !includeModule) {
|
|
4670
5186
|
const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
4671
|
-
|
|
5187
|
+
fs19.writeFileSync(path20.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
|
|
4672
5188
|
|
|
4673
5189
|
export const ${elementName}: FC = () => {
|
|
4674
5190
|
return null;
|
|
4675
5191
|
};
|
|
4676
5192
|
`);
|
|
4677
5193
|
}
|
|
4678
|
-
|
|
5194
|
+
fs19.writeFileSync(path20.join(root, "index.js"), `'use strict';
|
|
4679
5195
|
module.exports = {};
|
|
4680
5196
|
`);
|
|
4681
5197
|
const tsconfigCompiler = {
|
|
@@ -4688,11 +5204,11 @@ module.exports = {};
|
|
|
4688
5204
|
tsconfigCompiler.jsx = "preserve";
|
|
4689
5205
|
tsconfigCompiler.jsxImportSource = "@lynx-js/react";
|
|
4690
5206
|
}
|
|
4691
|
-
|
|
5207
|
+
fs19.writeFileSync(path20.join(root, "tsconfig.json"), JSON.stringify({
|
|
4692
5208
|
compilerOptions: tsconfigCompiler,
|
|
4693
5209
|
include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
|
|
4694
5210
|
}, null, 2));
|
|
4695
|
-
|
|
5211
|
+
fs19.writeFileSync(path20.join(root, "README.md"), `# ${extName}
|
|
4696
5212
|
|
|
4697
5213
|
Lynx extension for ${extName}.
|
|
4698
5214
|
|
|
@@ -4712,13 +5228,13 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
4712
5228
|
console.log(` cd ${extName}`);
|
|
4713
5229
|
console.log(" npm install");
|
|
4714
5230
|
if (includeModule) console.log(" npm run codegen");
|
|
4715
|
-
|
|
5231
|
+
rl.close();
|
|
4716
5232
|
}
|
|
4717
5233
|
var create_default3 = create3;
|
|
4718
5234
|
|
|
4719
5235
|
// src/common/codegen.ts
|
|
4720
|
-
import
|
|
4721
|
-
import
|
|
5236
|
+
import fs20 from "fs";
|
|
5237
|
+
import path21 from "path";
|
|
4722
5238
|
function codegen() {
|
|
4723
5239
|
const cwd = process.cwd();
|
|
4724
5240
|
const config = loadExtensionConfig(cwd);
|
|
@@ -4726,9 +5242,9 @@ function codegen() {
|
|
|
4726
5242
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
4727
5243
|
process.exit(1);
|
|
4728
5244
|
}
|
|
4729
|
-
const srcDir =
|
|
4730
|
-
const generatedDir =
|
|
4731
|
-
|
|
5245
|
+
const srcDir = path21.join(cwd, "src");
|
|
5246
|
+
const generatedDir = path21.join(cwd, "generated");
|
|
5247
|
+
fs20.mkdirSync(generatedDir, { recursive: true });
|
|
4732
5248
|
const dtsFiles = findDtsFiles(srcDir);
|
|
4733
5249
|
const modules = extractLynxModules(dtsFiles);
|
|
4734
5250
|
if (modules.length === 0) {
|
|
@@ -4738,28 +5254,28 @@ function codegen() {
|
|
|
4738
5254
|
for (const mod of modules) {
|
|
4739
5255
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
4740
5256
|
`;
|
|
4741
|
-
const outPath =
|
|
4742
|
-
|
|
5257
|
+
const outPath = path21.join(generatedDir, `${mod}.ts`);
|
|
5258
|
+
fs20.writeFileSync(outPath, tsContent);
|
|
4743
5259
|
console.log(`\u2705 Generated ${outPath}`);
|
|
4744
5260
|
}
|
|
4745
5261
|
if (config.android) {
|
|
4746
|
-
const androidGenerated =
|
|
4747
|
-
|
|
5262
|
+
const androidGenerated = path21.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
5263
|
+
fs20.mkdirSync(androidGenerated, { recursive: true });
|
|
4748
5264
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
4749
5265
|
}
|
|
4750
5266
|
if (config.ios) {
|
|
4751
|
-
const iosGenerated =
|
|
4752
|
-
|
|
5267
|
+
const iosGenerated = path21.join(cwd, "ios", "generated");
|
|
5268
|
+
fs20.mkdirSync(iosGenerated, { recursive: true });
|
|
4753
5269
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
4754
5270
|
}
|
|
4755
5271
|
console.log("\u2728 Codegen complete.");
|
|
4756
5272
|
}
|
|
4757
5273
|
function findDtsFiles(dir) {
|
|
4758
5274
|
const result = [];
|
|
4759
|
-
if (!
|
|
4760
|
-
const entries =
|
|
5275
|
+
if (!fs20.existsSync(dir)) return result;
|
|
5276
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
4761
5277
|
for (const e of entries) {
|
|
4762
|
-
const full =
|
|
5278
|
+
const full = path21.join(dir, e.name);
|
|
4763
5279
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
4764
5280
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
4765
5281
|
}
|
|
@@ -4769,7 +5285,7 @@ function extractLynxModules(files) {
|
|
|
4769
5285
|
const modules = [];
|
|
4770
5286
|
const seen = /* @__PURE__ */ new Set();
|
|
4771
5287
|
for (const file of files) {
|
|
4772
|
-
const content =
|
|
5288
|
+
const content = fs20.readFileSync(file, "utf8");
|
|
4773
5289
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
4774
5290
|
let m;
|
|
4775
5291
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -4783,14 +5299,16 @@ function extractLynxModules(files) {
|
|
|
4783
5299
|
}
|
|
4784
5300
|
var codegen_default = codegen;
|
|
4785
5301
|
|
|
4786
|
-
// src/common/devServer.
|
|
5302
|
+
// src/common/devServer.tsx
|
|
5303
|
+
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
4787
5304
|
import { spawn } from "child_process";
|
|
4788
|
-
import
|
|
5305
|
+
import fs21 from "fs";
|
|
4789
5306
|
import http from "http";
|
|
4790
5307
|
import os4 from "os";
|
|
4791
|
-
import
|
|
4792
|
-
import
|
|
5308
|
+
import path22 from "path";
|
|
5309
|
+
import { render as render2, useInput, useApp } from "ink";
|
|
4793
5310
|
import { WebSocketServer } from "ws";
|
|
5311
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
4794
5312
|
var DEFAULT_PORT = 3e3;
|
|
4795
5313
|
var STATIC_MIME = {
|
|
4796
5314
|
".png": "image/png",
|
|
@@ -4803,13 +5321,13 @@ var STATIC_MIME = {
|
|
|
4803
5321
|
".pdf": "application/pdf"
|
|
4804
5322
|
};
|
|
4805
5323
|
function sendFileFromDisk(res, absPath) {
|
|
4806
|
-
|
|
5324
|
+
fs21.readFile(absPath, (err, data) => {
|
|
4807
5325
|
if (err) {
|
|
4808
5326
|
res.writeHead(404);
|
|
4809
5327
|
res.end("Not found");
|
|
4810
5328
|
return;
|
|
4811
5329
|
}
|
|
4812
|
-
const ext =
|
|
5330
|
+
const ext = path22.extname(absPath).toLowerCase();
|
|
4813
5331
|
res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
|
|
4814
5332
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4815
5333
|
res.end(data);
|
|
@@ -4845,319 +5363,431 @@ function getLanIp() {
|
|
|
4845
5363
|
}
|
|
4846
5364
|
return "localhost";
|
|
4847
5365
|
}
|
|
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");
|
|
5366
|
+
function detectPackageManager(cwd) {
|
|
5367
|
+
const dir = path22.resolve(cwd);
|
|
5368
|
+
if (fs21.existsSync(path22.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
|
|
5369
|
+
if (fs21.existsSync(path22.join(dir, "bun.lockb")) || fs21.existsSync(path22.join(dir, "bun.lock")))
|
|
5370
|
+
return { cmd: "bun", args: ["run", "build"] };
|
|
5371
|
+
return { cmd: "npm", args: ["run", "build"] };
|
|
5372
|
+
}
|
|
5373
|
+
var initialUi = () => ({
|
|
5374
|
+
phase: "starting",
|
|
5375
|
+
projectName: "",
|
|
5376
|
+
port: 0,
|
|
5377
|
+
lanIp: "localhost",
|
|
5378
|
+
devUrl: "",
|
|
5379
|
+
wsUrl: "",
|
|
5380
|
+
lynxBundleFile: "main.lynx.bundle",
|
|
5381
|
+
bonjour: false,
|
|
5382
|
+
verbose: false,
|
|
5383
|
+
buildPhase: "idle",
|
|
5384
|
+
wsConnections: 0,
|
|
5385
|
+
logLines: [],
|
|
5386
|
+
showLogs: false,
|
|
5387
|
+
qrLines: []
|
|
5388
|
+
});
|
|
5389
|
+
function DevServerApp({ verbose }) {
|
|
5390
|
+
const { exit } = useApp();
|
|
5391
|
+
const [ui, setUi] = useState5(() => {
|
|
5392
|
+
const s = initialUi();
|
|
5393
|
+
s.verbose = verbose;
|
|
5394
|
+
return s;
|
|
5395
|
+
});
|
|
5396
|
+
const cleanupRef = useRef(null);
|
|
5397
|
+
const rebuildRef = useRef(() => Promise.resolve());
|
|
5398
|
+
const quitOnceRef = useRef(false);
|
|
5399
|
+
const appendLog = useCallback4((chunk) => {
|
|
5400
|
+
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
5401
|
+
setUi((prev) => ({
|
|
5402
|
+
...prev,
|
|
5403
|
+
logLines: [...prev.logLines, ...lines].slice(-400)
|
|
5404
|
+
}));
|
|
5405
|
+
}, []);
|
|
5406
|
+
const handleQuit = useCallback4(() => {
|
|
5407
|
+
if (quitOnceRef.current) return;
|
|
5408
|
+
quitOnceRef.current = true;
|
|
5409
|
+
void cleanupRef.current?.();
|
|
5410
|
+
exit();
|
|
5411
|
+
}, [exit]);
|
|
5412
|
+
useInput((input, key) => {
|
|
5413
|
+
if (key.ctrl && key.name === "c") {
|
|
5414
|
+
handleQuit();
|
|
4906
5415
|
return;
|
|
4907
5416
|
}
|
|
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));
|
|
5417
|
+
if (input === "q") {
|
|
5418
|
+
handleQuit();
|
|
4942
5419
|
return;
|
|
4943
5420
|
}
|
|
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
|
-
});
|
|
5421
|
+
if (input === "r") {
|
|
5422
|
+
void rebuildRef.current();
|
|
4955
5423
|
return;
|
|
4956
5424
|
}
|
|
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;
|
|
5004
|
-
}
|
|
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
|
-
});
|
|
5016
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
5017
|
-
httpServer.on("upgrade", (request, socket, head) => {
|
|
5018
|
-
const reqPath = (request.url || "").split("?")[0];
|
|
5019
|
-
if (reqPath === `${basePath}/__hmr` || reqPath === "/__hmr" || reqPath.endsWith("/__hmr")) {
|
|
5020
|
-
wss.handleUpgrade(request, socket, head, (ws) => wss.emit("connection", ws, request));
|
|
5021
|
-
} else {
|
|
5022
|
-
socket.destroy();
|
|
5425
|
+
if (input === "l") {
|
|
5426
|
+
setUi((s) => ({ ...s, showLogs: !s.showLogs }));
|
|
5023
5427
|
}
|
|
5024
5428
|
});
|
|
5025
|
-
|
|
5026
|
-
const
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5429
|
+
useEffect3(() => {
|
|
5430
|
+
const onSig = () => {
|
|
5431
|
+
handleQuit();
|
|
5432
|
+
};
|
|
5433
|
+
process.on("SIGINT", onSig);
|
|
5434
|
+
process.on("SIGTERM", onSig);
|
|
5435
|
+
return () => {
|
|
5436
|
+
process.off("SIGINT", onSig);
|
|
5437
|
+
process.off("SIGTERM", onSig);
|
|
5438
|
+
};
|
|
5439
|
+
}, [handleQuit]);
|
|
5440
|
+
useEffect3(() => {
|
|
5441
|
+
let alive = true;
|
|
5442
|
+
let buildProcess = null;
|
|
5443
|
+
let watcher = null;
|
|
5444
|
+
let stopBonjour;
|
|
5445
|
+
const run = async () => {
|
|
5033
5446
|
try {
|
|
5034
|
-
const
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
}
|
|
5045
|
-
});
|
|
5046
|
-
});
|
|
5047
|
-
function broadcastReload() {
|
|
5048
|
-
wss.clients.forEach((client) => {
|
|
5049
|
-
if (client.readyState === 1) client.send(JSON.stringify({ type: "reload" }));
|
|
5050
|
-
});
|
|
5051
|
-
}
|
|
5052
|
-
let chokidar = null;
|
|
5053
|
-
try {
|
|
5054
|
-
chokidar = await import("chokidar");
|
|
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);
|
|
5447
|
+
const resolved = resolveHostPaths();
|
|
5448
|
+
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5449
|
+
const distDir = path22.dirname(lynxBundlePath);
|
|
5450
|
+
const projectName = path22.basename(lynxProjectDir);
|
|
5451
|
+
const basePath = `/${projectName}`;
|
|
5452
|
+
setUi((s) => ({ ...s, projectName, lynxBundleFile }));
|
|
5453
|
+
const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
5454
|
+
const port = await findAvailablePort(preferredPort);
|
|
5455
|
+
if (port !== preferredPort) {
|
|
5456
|
+
appendLog(`Port ${preferredPort} in use, using ${port}`);
|
|
5072
5457
|
}
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
stopBonjour = advertise({
|
|
5086
|
-
name: projectName,
|
|
5087
|
-
type: "tamer",
|
|
5088
|
-
protocol: "tcp",
|
|
5089
|
-
port,
|
|
5090
|
-
txt: {
|
|
5091
|
-
name: projectName.slice(0, 255),
|
|
5092
|
-
path: basePath.slice(0, 255)
|
|
5458
|
+
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5459
|
+
let iconFilePath = null;
|
|
5460
|
+
if (iconPaths?.source && fs21.statSync(iconPaths.source).isFile()) {
|
|
5461
|
+
iconFilePath = iconPaths.source;
|
|
5462
|
+
} else if (iconPaths?.androidAdaptiveForeground && fs21.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
|
|
5463
|
+
iconFilePath = iconPaths.androidAdaptiveForeground;
|
|
5464
|
+
} else if (iconPaths?.android) {
|
|
5465
|
+
const androidIcon = path22.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
5466
|
+
if (fs21.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5467
|
+
} else if (iconPaths?.ios) {
|
|
5468
|
+
const iosIcon = path22.join(iconPaths.ios, "Icon-1024.png");
|
|
5469
|
+
if (fs21.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5093
5470
|
}
|
|
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
|
-
|
|
5471
|
+
const iconExt = iconFilePath ? path22.extname(iconFilePath) || ".png" : "";
|
|
5472
|
+
const runBuild = () => {
|
|
5473
|
+
return new Promise((resolve, reject) => {
|
|
5474
|
+
const { cmd, args } = detectPackageManager(lynxProjectDir);
|
|
5475
|
+
buildProcess = spawn(cmd, args, {
|
|
5476
|
+
cwd: lynxProjectDir,
|
|
5477
|
+
stdio: "pipe",
|
|
5478
|
+
shell: process.platform === "win32"
|
|
5479
|
+
});
|
|
5480
|
+
let stderr = "";
|
|
5481
|
+
buildProcess.stdout?.on("data", (d) => {
|
|
5482
|
+
appendLog(d.toString());
|
|
5483
|
+
});
|
|
5484
|
+
buildProcess.stderr?.on("data", (d) => {
|
|
5485
|
+
const t = d.toString();
|
|
5486
|
+
stderr += t;
|
|
5487
|
+
appendLog(t);
|
|
5488
|
+
});
|
|
5489
|
+
buildProcess.on("close", (code) => {
|
|
5490
|
+
buildProcess = null;
|
|
5491
|
+
if (code === 0) resolve();
|
|
5492
|
+
else reject(new Error(stderr || `Build exited ${code}`));
|
|
5493
|
+
});
|
|
5494
|
+
});
|
|
5495
|
+
};
|
|
5496
|
+
const doBuild = async () => {
|
|
5497
|
+
setUi((s) => ({ ...s, buildPhase: "building", buildError: void 0 }));
|
|
5498
|
+
try {
|
|
5499
|
+
await runBuild();
|
|
5500
|
+
if (!alive) return;
|
|
5501
|
+
setUi((s) => ({ ...s, buildPhase: "success" }));
|
|
5502
|
+
} catch (e) {
|
|
5503
|
+
if (!alive) return;
|
|
5504
|
+
const msg = e.message;
|
|
5505
|
+
setUi((s) => ({ ...s, buildPhase: "error", buildError: msg }));
|
|
5506
|
+
throw e;
|
|
5507
|
+
}
|
|
5508
|
+
};
|
|
5509
|
+
const httpSrv = http.createServer((req, res) => {
|
|
5510
|
+
let reqPath = (req.url || "/").split("?")[0];
|
|
5511
|
+
if (reqPath === `${basePath}/status`) {
|
|
5512
|
+
res.setHeader("Content-Type", "text/plain");
|
|
5513
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5514
|
+
res.end("packager-status:running");
|
|
5515
|
+
return;
|
|
5516
|
+
}
|
|
5517
|
+
if (reqPath === `${basePath}/meta.json`) {
|
|
5518
|
+
const lanIp2 = getLanIp();
|
|
5519
|
+
const nativeModules = discoverNativeExtensions(projectRoot);
|
|
5520
|
+
const androidPackageName = config.android?.packageName?.trim();
|
|
5521
|
+
const iosBundleId = config.ios?.bundleId?.trim();
|
|
5522
|
+
const idParts = [androidPackageName?.toLowerCase(), iosBundleId?.toLowerCase()].filter(
|
|
5523
|
+
(x) => Boolean(x)
|
|
5524
|
+
);
|
|
5525
|
+
const meta = {
|
|
5526
|
+
name: projectName,
|
|
5527
|
+
slug: projectName,
|
|
5528
|
+
bundleUrl: `http://${lanIp2}:${port}${basePath}/${lynxBundleFile}`,
|
|
5529
|
+
bundleFile: lynxBundleFile,
|
|
5530
|
+
hostUri: `http://${lanIp2}:${port}${basePath}`,
|
|
5531
|
+
debuggerHost: `${lanIp2}:${port}`,
|
|
5532
|
+
developer: { tool: "tamer4lynx" },
|
|
5533
|
+
packagerStatus: "running",
|
|
5534
|
+
nativeModules: nativeModules.map((m) => ({
|
|
5535
|
+
packageName: m.packageName,
|
|
5536
|
+
moduleClassName: m.moduleClassName
|
|
5537
|
+
}))
|
|
5538
|
+
};
|
|
5539
|
+
if (androidPackageName) meta.androidPackageName = androidPackageName;
|
|
5540
|
+
if (iosBundleId) meta.iosBundleId = iosBundleId;
|
|
5541
|
+
if (idParts.length > 0) meta.tamerAppKey = idParts.join("|");
|
|
5542
|
+
const rawIcon = config.icon;
|
|
5543
|
+
if (rawIcon && typeof rawIcon === "object" && "source" in rawIcon && typeof rawIcon.source === "string") {
|
|
5544
|
+
meta.iconSource = rawIcon.source;
|
|
5545
|
+
} else if (typeof rawIcon === "string") {
|
|
5546
|
+
meta.iconSource = rawIcon;
|
|
5547
|
+
}
|
|
5548
|
+
if (iconFilePath) {
|
|
5549
|
+
meta.icon = `http://${lanIp2}:${port}${basePath}/icon${iconExt}`;
|
|
5550
|
+
}
|
|
5551
|
+
res.setHeader("Content-Type", "application/json");
|
|
5552
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5553
|
+
res.end(JSON.stringify(meta, null, 2));
|
|
5554
|
+
return;
|
|
5555
|
+
}
|
|
5556
|
+
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5557
|
+
fs21.readFile(iconFilePath, (err, data) => {
|
|
5558
|
+
if (err) {
|
|
5559
|
+
res.writeHead(404);
|
|
5560
|
+
res.end();
|
|
5561
|
+
return;
|
|
5562
|
+
}
|
|
5563
|
+
res.setHeader("Content-Type", STATIC_MIME[iconExt] ?? "image/png");
|
|
5564
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5565
|
+
res.end(data);
|
|
5566
|
+
});
|
|
5567
|
+
return;
|
|
5568
|
+
}
|
|
5569
|
+
const lynxStaticMounts = [
|
|
5570
|
+
{ prefix: `${basePath}/src/assets/`, rootSub: "src/assets" },
|
|
5571
|
+
{ prefix: `${basePath}/assets/`, rootSub: "assets" }
|
|
5572
|
+
];
|
|
5573
|
+
for (const { prefix, rootSub } of lynxStaticMounts) {
|
|
5574
|
+
if (!reqPath.startsWith(prefix)) continue;
|
|
5575
|
+
let rel = reqPath.slice(prefix.length);
|
|
5576
|
+
try {
|
|
5577
|
+
rel = decodeURIComponent(rel);
|
|
5578
|
+
} catch {
|
|
5579
|
+
res.writeHead(400);
|
|
5580
|
+
res.end();
|
|
5581
|
+
return;
|
|
5582
|
+
}
|
|
5583
|
+
const safe = path22.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
5584
|
+
if (path22.isAbsolute(safe) || safe.startsWith("..")) {
|
|
5585
|
+
res.writeHead(403);
|
|
5586
|
+
res.end();
|
|
5587
|
+
return;
|
|
5588
|
+
}
|
|
5589
|
+
const allowedRoot = path22.resolve(lynxProjectDir, rootSub);
|
|
5590
|
+
const abs = path22.resolve(allowedRoot, safe);
|
|
5591
|
+
if (!abs.startsWith(allowedRoot + path22.sep) && abs !== allowedRoot) {
|
|
5592
|
+
res.writeHead(403);
|
|
5593
|
+
res.end();
|
|
5594
|
+
return;
|
|
5595
|
+
}
|
|
5596
|
+
if (!fs21.existsSync(abs) || !fs21.statSync(abs).isFile()) {
|
|
5597
|
+
res.writeHead(404);
|
|
5598
|
+
res.end("Not found");
|
|
5599
|
+
return;
|
|
5600
|
+
}
|
|
5601
|
+
sendFileFromDisk(res, abs);
|
|
5602
|
+
return;
|
|
5603
|
+
}
|
|
5604
|
+
if (reqPath === "/" || reqPath === basePath || reqPath === `${basePath}/`) {
|
|
5605
|
+
reqPath = `${basePath}/${lynxBundleFile}`;
|
|
5606
|
+
} else if (!reqPath.startsWith(basePath)) {
|
|
5607
|
+
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5608
|
+
}
|
|
5609
|
+
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5610
|
+
const filePath = path22.resolve(distDir, relPath);
|
|
5611
|
+
const distResolved = path22.resolve(distDir);
|
|
5612
|
+
if (!filePath.startsWith(distResolved + path22.sep) && filePath !== distResolved) {
|
|
5613
|
+
res.writeHead(403);
|
|
5614
|
+
res.end();
|
|
5615
|
+
return;
|
|
5616
|
+
}
|
|
5617
|
+
fs21.readFile(filePath, (err, data) => {
|
|
5618
|
+
if (err) {
|
|
5619
|
+
res.writeHead(404);
|
|
5620
|
+
res.end("Not found");
|
|
5621
|
+
return;
|
|
5622
|
+
}
|
|
5623
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5624
|
+
res.setHeader("Content-Type", reqPath.endsWith(".bundle") ? "application/octet-stream" : "application/javascript");
|
|
5625
|
+
res.end(data);
|
|
5626
|
+
});
|
|
5627
|
+
});
|
|
5628
|
+
const wssInst = new WebSocketServer({ noServer: true });
|
|
5629
|
+
rebuildRef.current = async () => {
|
|
5630
|
+
try {
|
|
5631
|
+
await doBuild();
|
|
5632
|
+
if (!alive) return;
|
|
5633
|
+
wssInst.clients.forEach((client) => {
|
|
5634
|
+
if (client.readyState === 1) client.send(JSON.stringify({ type: "reload" }));
|
|
5635
|
+
});
|
|
5636
|
+
appendLog("Rebuilt, clients notified");
|
|
5637
|
+
} catch {
|
|
5638
|
+
}
|
|
5639
|
+
};
|
|
5640
|
+
httpSrv.on("upgrade", (request, socket, head) => {
|
|
5641
|
+
const p = (request.url || "").split("?")[0];
|
|
5642
|
+
if (p === `${basePath}/__hmr` || p === "/__hmr" || p.endsWith("/__hmr")) {
|
|
5643
|
+
wssInst.handleUpgrade(request, socket, head, (ws) => wssInst.emit("connection", ws, request));
|
|
5644
|
+
} else {
|
|
5645
|
+
socket.destroy();
|
|
5646
|
+
}
|
|
5647
|
+
});
|
|
5648
|
+
wssInst.on("connection", (ws, req) => {
|
|
5649
|
+
const clientIp = req.socket.remoteAddress ?? "unknown";
|
|
5650
|
+
setUi((s) => ({ ...s, wsConnections: s.wsConnections + 1 }));
|
|
5651
|
+
appendLog(`[WS] connected: ${clientIp}`);
|
|
5652
|
+
ws.send(JSON.stringify({ type: "connected" }));
|
|
5653
|
+
ws.on("close", () => {
|
|
5654
|
+
setUi((s) => ({ ...s, wsConnections: Math.max(0, s.wsConnections - 1) }));
|
|
5655
|
+
appendLog(`[WS] disconnected: ${clientIp}`);
|
|
5656
|
+
});
|
|
5657
|
+
ws.on("message", (data) => {
|
|
5658
|
+
try {
|
|
5659
|
+
const msg = JSON.parse(data.toString());
|
|
5660
|
+
if (msg?.type === "console_log" && Array.isArray(msg.message)) {
|
|
5661
|
+
const skip = msg.message.includes("[rspeedy-dev-server]") || msg.message.includes("[HMR]");
|
|
5662
|
+
if (skip) return;
|
|
5663
|
+
const isJs = msg.tag === "lynx-console" || msg.tag == null;
|
|
5664
|
+
if (!verbose && !isJs) return;
|
|
5665
|
+
appendLog(`${isJs ? "[APP]" : "[NATIVE]"} ${msg.message.join(" ")}`);
|
|
5666
|
+
}
|
|
5667
|
+
} catch {
|
|
5668
|
+
}
|
|
5669
|
+
});
|
|
5670
|
+
});
|
|
5671
|
+
let chokidar = null;
|
|
5672
|
+
try {
|
|
5673
|
+
chokidar = await import("chokidar");
|
|
5674
|
+
} catch {
|
|
5126
5675
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5676
|
+
if (chokidar) {
|
|
5677
|
+
const watchPaths = [
|
|
5678
|
+
path22.join(lynxProjectDir, "src"),
|
|
5679
|
+
path22.join(lynxProjectDir, "lynx.config.ts"),
|
|
5680
|
+
path22.join(lynxProjectDir, "lynx.config.js")
|
|
5681
|
+
].filter((p) => fs21.existsSync(p));
|
|
5682
|
+
if (watchPaths.length > 0) {
|
|
5683
|
+
const w = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5684
|
+
w.on("change", async () => {
|
|
5685
|
+
try {
|
|
5686
|
+
await rebuildRef.current();
|
|
5687
|
+
} catch {
|
|
5688
|
+
}
|
|
5689
|
+
});
|
|
5690
|
+
watcher = {
|
|
5691
|
+
close: async () => {
|
|
5692
|
+
await w.close();
|
|
5693
|
+
}
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5142
5696
|
}
|
|
5143
|
-
|
|
5697
|
+
await doBuild();
|
|
5698
|
+
if (!alive) return;
|
|
5699
|
+
await new Promise((listenResolve, listenReject) => {
|
|
5700
|
+
httpSrv.listen(port, "0.0.0.0", () => {
|
|
5701
|
+
listenResolve();
|
|
5702
|
+
});
|
|
5703
|
+
httpSrv.once("error", (err) => listenReject(err));
|
|
5704
|
+
});
|
|
5705
|
+
if (!alive) return;
|
|
5706
|
+
void import("dnssd-advertise").then(({ advertise }) => {
|
|
5707
|
+
stopBonjour = advertise({
|
|
5708
|
+
name: projectName,
|
|
5709
|
+
type: "tamer",
|
|
5710
|
+
protocol: "tcp",
|
|
5711
|
+
port,
|
|
5712
|
+
txt: {
|
|
5713
|
+
name: projectName.slice(0, 255),
|
|
5714
|
+
path: basePath.slice(0, 255)
|
|
5715
|
+
}
|
|
5716
|
+
});
|
|
5717
|
+
setUi((s) => ({ ...s, bonjour: true }));
|
|
5718
|
+
}).catch(() => {
|
|
5719
|
+
});
|
|
5720
|
+
const lanIp = getLanIp();
|
|
5721
|
+
const devUrl = `http://${lanIp}:${port}${basePath}`;
|
|
5722
|
+
const wsUrl = `ws://${lanIp}:${port}${basePath}/__hmr`;
|
|
5723
|
+
setUi((s) => ({
|
|
5724
|
+
...s,
|
|
5725
|
+
phase: "running",
|
|
5726
|
+
port,
|
|
5727
|
+
lanIp,
|
|
5728
|
+
devUrl,
|
|
5729
|
+
wsUrl
|
|
5730
|
+
}));
|
|
5731
|
+
void import("qrcode-terminal").then((mod) => {
|
|
5732
|
+
const qrcode = mod.default ?? mod;
|
|
5733
|
+
qrcode.generate(devUrl, { small: true }, (qr) => {
|
|
5734
|
+
if (!alive) return;
|
|
5735
|
+
setUi((s) => ({ ...s, qrLines: qr.split("\n").filter(Boolean) }));
|
|
5736
|
+
});
|
|
5737
|
+
}).catch(() => {
|
|
5738
|
+
});
|
|
5739
|
+
cleanupRef.current = async () => {
|
|
5740
|
+
buildProcess?.kill();
|
|
5741
|
+
await watcher?.close().catch(() => {
|
|
5742
|
+
});
|
|
5743
|
+
await stopBonjour?.();
|
|
5744
|
+
httpSrv.close();
|
|
5745
|
+
wssInst.close();
|
|
5746
|
+
};
|
|
5747
|
+
} catch (e) {
|
|
5748
|
+
if (!alive) return;
|
|
5749
|
+
setUi((s) => ({
|
|
5750
|
+
...s,
|
|
5751
|
+
phase: "failed",
|
|
5752
|
+
startError: e.message
|
|
5753
|
+
}));
|
|
5754
|
+
}
|
|
5755
|
+
};
|
|
5756
|
+
void run();
|
|
5757
|
+
return () => {
|
|
5758
|
+
alive = false;
|
|
5759
|
+
void cleanupRef.current?.();
|
|
5760
|
+
};
|
|
5761
|
+
}, [appendLog, verbose]);
|
|
5762
|
+
return /* @__PURE__ */ jsx10(
|
|
5763
|
+
ServerDashboard,
|
|
5764
|
+
{
|
|
5765
|
+
projectName: ui.projectName,
|
|
5766
|
+
port: ui.port,
|
|
5767
|
+
lanIp: ui.lanIp,
|
|
5768
|
+
devUrl: ui.devUrl,
|
|
5769
|
+
wsUrl: ui.wsUrl,
|
|
5770
|
+
lynxBundleFile: ui.lynxBundleFile,
|
|
5771
|
+
bonjour: ui.bonjour,
|
|
5772
|
+
verbose: ui.verbose,
|
|
5773
|
+
buildPhase: ui.buildPhase,
|
|
5774
|
+
buildError: ui.buildError,
|
|
5775
|
+
wsConnections: ui.wsConnections,
|
|
5776
|
+
logLines: ui.logLines,
|
|
5777
|
+
showLogs: ui.showLogs,
|
|
5778
|
+
qrLines: ui.qrLines,
|
|
5779
|
+
phase: ui.phase,
|
|
5780
|
+
startError: ui.startError
|
|
5144
5781
|
}
|
|
5782
|
+
);
|
|
5783
|
+
}
|
|
5784
|
+
async function startDevServer(opts) {
|
|
5785
|
+
const verbose = opts?.verbose ?? false;
|
|
5786
|
+
const { waitUntilExit } = render2(/* @__PURE__ */ jsx10(DevServerApp, { verbose }), {
|
|
5787
|
+
exitOnCtrlC: false,
|
|
5788
|
+
patchConsole: false
|
|
5145
5789
|
});
|
|
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
|
-
});
|
|
5790
|
+
await waitUntilExit();
|
|
5161
5791
|
}
|
|
5162
5792
|
var devServer_default = startDevServer;
|
|
5163
5793
|
|
|
@@ -5168,10 +5798,10 @@ async function start(opts) {
|
|
|
5168
5798
|
var start_default = start;
|
|
5169
5799
|
|
|
5170
5800
|
// src/common/injectHost.ts
|
|
5171
|
-
import
|
|
5172
|
-
import
|
|
5801
|
+
import fs22 from "fs";
|
|
5802
|
+
import path23 from "path";
|
|
5173
5803
|
function readAndSubstitute(templatePath, vars) {
|
|
5174
|
-
const raw =
|
|
5804
|
+
const raw = fs22.readFileSync(templatePath, "utf-8");
|
|
5175
5805
|
return Object.entries(vars).reduce(
|
|
5176
5806
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5177
5807
|
raw
|
|
@@ -5192,32 +5822,32 @@ async function injectHostAndroid(opts) {
|
|
|
5192
5822
|
process.exit(1);
|
|
5193
5823
|
}
|
|
5194
5824
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5195
|
-
const rootDir =
|
|
5825
|
+
const rootDir = path23.join(projectRoot, androidDir);
|
|
5196
5826
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5197
|
-
const javaDir =
|
|
5198
|
-
const kotlinDir =
|
|
5199
|
-
if (!
|
|
5827
|
+
const javaDir = path23.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
5828
|
+
const kotlinDir = path23.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
5829
|
+
if (!fs22.existsSync(javaDir) || !fs22.existsSync(kotlinDir)) {
|
|
5200
5830
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5201
5831
|
process.exit(1);
|
|
5202
5832
|
}
|
|
5203
|
-
const templateDir =
|
|
5833
|
+
const templateDir = path23.join(hostPkg, "android", "templates");
|
|
5204
5834
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5205
5835
|
const files = [
|
|
5206
|
-
{ src: "App.java", dst:
|
|
5207
|
-
{ src: "TemplateProvider.java", dst:
|
|
5208
|
-
{ src: "MainActivity.kt", dst:
|
|
5836
|
+
{ src: "App.java", dst: path23.join(javaDir, "App.java") },
|
|
5837
|
+
{ src: "TemplateProvider.java", dst: path23.join(javaDir, "TemplateProvider.java") },
|
|
5838
|
+
{ src: "MainActivity.kt", dst: path23.join(kotlinDir, "MainActivity.kt") }
|
|
5209
5839
|
];
|
|
5210
5840
|
for (const { src, dst } of files) {
|
|
5211
|
-
const srcPath =
|
|
5212
|
-
if (!
|
|
5213
|
-
if (
|
|
5214
|
-
console.log(`\u23ED\uFE0F Skipping ${
|
|
5841
|
+
const srcPath = path23.join(templateDir, src);
|
|
5842
|
+
if (!fs22.existsSync(srcPath)) continue;
|
|
5843
|
+
if (fs22.existsSync(dst) && !opts?.force) {
|
|
5844
|
+
console.log(`\u23ED\uFE0F Skipping ${path23.basename(dst)} (use --force to overwrite)`);
|
|
5215
5845
|
continue;
|
|
5216
5846
|
}
|
|
5217
5847
|
const content = readAndSubstitute(srcPath, vars);
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
console.log(`\u2705 Injected ${
|
|
5848
|
+
fs22.mkdirSync(path23.dirname(dst), { recursive: true });
|
|
5849
|
+
fs22.writeFileSync(dst, content);
|
|
5850
|
+
console.log(`\u2705 Injected ${path23.basename(dst)}`);
|
|
5221
5851
|
}
|
|
5222
5852
|
}
|
|
5223
5853
|
async function injectHostIos(opts) {
|
|
@@ -5235,13 +5865,13 @@ async function injectHostIos(opts) {
|
|
|
5235
5865
|
process.exit(1);
|
|
5236
5866
|
}
|
|
5237
5867
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5238
|
-
const rootDir =
|
|
5239
|
-
const projectDir =
|
|
5240
|
-
if (!
|
|
5868
|
+
const rootDir = path23.join(projectRoot, iosDir);
|
|
5869
|
+
const projectDir = path23.join(rootDir, appName);
|
|
5870
|
+
if (!fs22.existsSync(projectDir)) {
|
|
5241
5871
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5242
5872
|
process.exit(1);
|
|
5243
5873
|
}
|
|
5244
|
-
const templateDir =
|
|
5874
|
+
const templateDir = path23.join(hostPkg, "ios", "templates");
|
|
5245
5875
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5246
5876
|
const files = [
|
|
5247
5877
|
"AppDelegate.swift",
|
|
@@ -5251,22 +5881,22 @@ async function injectHostIos(opts) {
|
|
|
5251
5881
|
"LynxInitProcessor.swift"
|
|
5252
5882
|
];
|
|
5253
5883
|
for (const f of files) {
|
|
5254
|
-
const srcPath =
|
|
5255
|
-
const dstPath =
|
|
5256
|
-
if (!
|
|
5257
|
-
if (
|
|
5884
|
+
const srcPath = path23.join(templateDir, f);
|
|
5885
|
+
const dstPath = path23.join(projectDir, f);
|
|
5886
|
+
if (!fs22.existsSync(srcPath)) continue;
|
|
5887
|
+
if (fs22.existsSync(dstPath) && !opts?.force) {
|
|
5258
5888
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5259
5889
|
continue;
|
|
5260
5890
|
}
|
|
5261
5891
|
const content = readAndSubstitute(srcPath, vars);
|
|
5262
|
-
|
|
5892
|
+
fs22.writeFileSync(dstPath, content);
|
|
5263
5893
|
console.log(`\u2705 Injected ${f}`);
|
|
5264
5894
|
}
|
|
5265
5895
|
}
|
|
5266
5896
|
|
|
5267
5897
|
// src/common/buildEmbeddable.ts
|
|
5268
|
-
import
|
|
5269
|
-
import
|
|
5898
|
+
import fs23 from "fs";
|
|
5899
|
+
import path24 from "path";
|
|
5270
5900
|
import { execSync as execSync9 } from "child_process";
|
|
5271
5901
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5272
5902
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
@@ -5343,14 +5973,14 @@ object LynxEmbeddable {
|
|
|
5343
5973
|
}
|
|
5344
5974
|
`;
|
|
5345
5975
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
5346
|
-
const libDir =
|
|
5347
|
-
const libSrcMain =
|
|
5348
|
-
const assetsDir =
|
|
5349
|
-
const kotlinDir =
|
|
5350
|
-
const generatedDir =
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5976
|
+
const libDir = path24.join(androidDir, "lib");
|
|
5977
|
+
const libSrcMain = path24.join(libDir, "src", "main");
|
|
5978
|
+
const assetsDir = path24.join(libSrcMain, "assets");
|
|
5979
|
+
const kotlinDir = path24.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
|
|
5980
|
+
const generatedDir = path24.join(kotlinDir, "generated");
|
|
5981
|
+
fs23.mkdirSync(path24.join(androidDir, "gradle"), { recursive: true });
|
|
5982
|
+
fs23.mkdirSync(generatedDir, { recursive: true });
|
|
5983
|
+
fs23.mkdirSync(assetsDir, { recursive: true });
|
|
5354
5984
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5355
5985
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5356
5986
|
const settingsContent = `pluginManagement {
|
|
@@ -5370,7 +6000,7 @@ include(":lib")
|
|
|
5370
6000
|
${androidModules.map((p) => {
|
|
5371
6001
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
5372
6002
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
5373
|
-
const absPath =
|
|
6003
|
+
const absPath = path24.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
5374
6004
|
return `include(":${gradleName}")
|
|
5375
6005
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
5376
6006
|
}).join("\n")}
|
|
@@ -5419,10 +6049,10 @@ dependencies {
|
|
|
5419
6049
|
${libDeps}
|
|
5420
6050
|
}
|
|
5421
6051
|
`;
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
6052
|
+
fs23.writeFileSync(path24.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
|
|
6053
|
+
fs23.writeFileSync(path24.join(androidDir, "settings.gradle.kts"), settingsContent);
|
|
6054
|
+
fs23.writeFileSync(
|
|
6055
|
+
path24.join(androidDir, "build.gradle.kts"),
|
|
5426
6056
|
`plugins {
|
|
5427
6057
|
alias(libs.plugins.android.library) apply false
|
|
5428
6058
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -5430,26 +6060,26 @@ ${libDeps}
|
|
|
5430
6060
|
}
|
|
5431
6061
|
`
|
|
5432
6062
|
);
|
|
5433
|
-
|
|
5434
|
-
|
|
6063
|
+
fs23.writeFileSync(
|
|
6064
|
+
path24.join(androidDir, "gradle.properties"),
|
|
5435
6065
|
`org.gradle.jvmargs=-Xmx2048m
|
|
5436
6066
|
android.useAndroidX=true
|
|
5437
6067
|
kotlin.code.style=official
|
|
5438
6068
|
`
|
|
5439
6069
|
);
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
6070
|
+
fs23.writeFileSync(path24.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
6071
|
+
fs23.writeFileSync(
|
|
6072
|
+
path24.join(libSrcMain, "AndroidManifest.xml"),
|
|
5443
6073
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
5444
6074
|
);
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
6075
|
+
fs23.copyFileSync(lynxBundlePath, path24.join(assetsDir, lynxBundleFile));
|
|
6076
|
+
fs23.writeFileSync(path24.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
|
|
6077
|
+
fs23.writeFileSync(
|
|
6078
|
+
path24.join(generatedDir, "GeneratedLynxExtensions.kt"),
|
|
5449
6079
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
5450
6080
|
);
|
|
5451
|
-
|
|
5452
|
-
|
|
6081
|
+
fs23.writeFileSync(
|
|
6082
|
+
path24.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
5453
6083
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
5454
6084
|
);
|
|
5455
6085
|
}
|
|
@@ -5458,20 +6088,20 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5458
6088
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
5459
6089
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
5460
6090
|
execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
5461
|
-
if (!
|
|
6091
|
+
if (!fs23.existsSync(lynxBundlePath)) {
|
|
5462
6092
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
5463
6093
|
process.exit(1);
|
|
5464
6094
|
}
|
|
5465
|
-
const outDir =
|
|
5466
|
-
|
|
5467
|
-
const distDir =
|
|
6095
|
+
const outDir = path24.join(projectRoot, EMBEDDABLE_DIR);
|
|
6096
|
+
fs23.mkdirSync(outDir, { recursive: true });
|
|
6097
|
+
const distDir = path24.dirname(lynxBundlePath);
|
|
5468
6098
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
5469
6099
|
const modules = discoverModules(projectRoot);
|
|
5470
6100
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5471
6101
|
const abiFilters = resolveAbiFilters(config);
|
|
5472
|
-
const androidDir =
|
|
5473
|
-
if (
|
|
5474
|
-
|
|
6102
|
+
const androidDir = path24.join(outDir, "android");
|
|
6103
|
+
if (fs23.existsSync(androidDir)) fs23.rmSync(androidDir, { recursive: true });
|
|
6104
|
+
fs23.mkdirSync(androidDir, { recursive: true });
|
|
5475
6105
|
generateAndroidLibrary(
|
|
5476
6106
|
outDir,
|
|
5477
6107
|
androidDir,
|
|
@@ -5481,23 +6111,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5481
6111
|
modules,
|
|
5482
6112
|
abiFilters
|
|
5483
6113
|
);
|
|
5484
|
-
const gradlewPath =
|
|
6114
|
+
const gradlewPath = path24.join(androidDir, "gradlew");
|
|
5485
6115
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
5486
6116
|
const existingGradleDirs = [
|
|
5487
|
-
|
|
5488
|
-
devAppDir ?
|
|
6117
|
+
path24.join(projectRoot, "android"),
|
|
6118
|
+
devAppDir ? path24.join(devAppDir, "android") : null
|
|
5489
6119
|
].filter(Boolean);
|
|
5490
6120
|
let hasWrapper = false;
|
|
5491
6121
|
for (const d of existingGradleDirs) {
|
|
5492
|
-
if (
|
|
6122
|
+
if (fs23.existsSync(path24.join(d, "gradlew"))) {
|
|
5493
6123
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
5494
|
-
const src =
|
|
5495
|
-
if (
|
|
5496
|
-
const dest =
|
|
5497
|
-
if (
|
|
5498
|
-
|
|
6124
|
+
const src = path24.join(d, name);
|
|
6125
|
+
if (fs23.existsSync(src)) {
|
|
6126
|
+
const dest = path24.join(androidDir, name);
|
|
6127
|
+
if (fs23.statSync(src).isDirectory()) {
|
|
6128
|
+
fs23.cpSync(src, dest, { recursive: true });
|
|
5499
6129
|
} else {
|
|
5500
|
-
|
|
6130
|
+
fs23.copyFileSync(src, dest);
|
|
5501
6131
|
}
|
|
5502
6132
|
}
|
|
5503
6133
|
}
|
|
@@ -5516,10 +6146,10 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5516
6146
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
5517
6147
|
throw e;
|
|
5518
6148
|
}
|
|
5519
|
-
const aarSrc =
|
|
5520
|
-
const aarDest =
|
|
5521
|
-
if (
|
|
5522
|
-
|
|
6149
|
+
const aarSrc = path24.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
|
|
6150
|
+
const aarDest = path24.join(outDir, "tamer-embeddable.aar");
|
|
6151
|
+
if (fs23.existsSync(aarSrc)) {
|
|
6152
|
+
fs23.copyFileSync(aarSrc, aarDest);
|
|
5523
6153
|
console.log(` - tamer-embeddable.aar`);
|
|
5524
6154
|
}
|
|
5525
6155
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -5530,7 +6160,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5530
6160
|
// LynxEmbeddable.init(applicationContext)
|
|
5531
6161
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
5532
6162
|
`;
|
|
5533
|
-
|
|
6163
|
+
fs23.writeFileSync(path24.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
5534
6164
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
5535
6165
|
const readme = `# Embeddable Lynx Bundle
|
|
5536
6166
|
|
|
@@ -5561,7 +6191,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
5561
6191
|
|
|
5562
6192
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
5563
6193
|
`;
|
|
5564
|
-
|
|
6194
|
+
fs23.writeFileSync(path24.join(outDir, "README.md"), readme);
|
|
5565
6195
|
console.log(`
|
|
5566
6196
|
\u2705 Embeddable output at ${outDir}/`);
|
|
5567
6197
|
console.log(" - main.lynx.bundle");
|
|
@@ -5573,20 +6203,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
5573
6203
|
console.log(" - README.md");
|
|
5574
6204
|
}
|
|
5575
6205
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
5576
|
-
const iosDir =
|
|
5577
|
-
const podDir =
|
|
5578
|
-
const resourcesDir =
|
|
5579
|
-
|
|
5580
|
-
|
|
6206
|
+
const iosDir = path24.join(outDir, "ios");
|
|
6207
|
+
const podDir = path24.join(iosDir, "TamerEmbeddable");
|
|
6208
|
+
const resourcesDir = path24.join(podDir, "Resources");
|
|
6209
|
+
fs23.mkdirSync(resourcesDir, { recursive: true });
|
|
6210
|
+
fs23.copyFileSync(lynxBundlePath, path24.join(resourcesDir, lynxBundleFile));
|
|
5581
6211
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
5582
6212
|
const podDeps = iosModules.map((p) => {
|
|
5583
6213
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
5584
|
-
const podspecDir =
|
|
5585
|
-
if (!
|
|
5586
|
-
const files =
|
|
6214
|
+
const podspecDir = path24.join(p.packagePath, podspecPath);
|
|
6215
|
+
if (!fs23.existsSync(podspecDir)) return null;
|
|
6216
|
+
const files = fs23.readdirSync(podspecDir);
|
|
5587
6217
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
5588
6218
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
5589
|
-
const absPath =
|
|
6219
|
+
const absPath = path24.resolve(podspecDir);
|
|
5590
6220
|
return { podName, absPath };
|
|
5591
6221
|
}).filter(Boolean);
|
|
5592
6222
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -5626,9 +6256,9 @@ end
|
|
|
5626
6256
|
});
|
|
5627
6257
|
const swiftImports = iosModules.map((p) => {
|
|
5628
6258
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
5629
|
-
const podspecDir =
|
|
5630
|
-
if (!
|
|
5631
|
-
const files =
|
|
6259
|
+
const podspecDir = path24.join(p.packagePath, podspecPath);
|
|
6260
|
+
if (!fs23.existsSync(podspecDir)) return null;
|
|
6261
|
+
const files = fs23.readdirSync(podspecDir);
|
|
5632
6262
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
5633
6263
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
5634
6264
|
}).filter(Boolean);
|
|
@@ -5647,17 +6277,17 @@ ${regBlock}
|
|
|
5647
6277
|
}
|
|
5648
6278
|
}
|
|
5649
6279
|
`;
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
const absIosDir =
|
|
6280
|
+
fs23.writeFileSync(path24.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
|
|
6281
|
+
fs23.writeFileSync(path24.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
|
|
6282
|
+
const absIosDir = path24.resolve(iosDir);
|
|
5653
6283
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
5654
6284
|
|
|
5655
6285
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
5656
6286
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
5657
6287
|
`;
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
6288
|
+
fs23.writeFileSync(path24.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
6289
|
+
fs23.writeFileSync(
|
|
6290
|
+
path24.join(outDir, "snippet-ios.swift"),
|
|
5661
6291
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
5662
6292
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
5663
6293
|
`
|
|
@@ -5665,8 +6295,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
5665
6295
|
}
|
|
5666
6296
|
|
|
5667
6297
|
// src/common/add.ts
|
|
5668
|
-
import
|
|
5669
|
-
import
|
|
6298
|
+
import fs24 from "fs";
|
|
6299
|
+
import path25 from "path";
|
|
5670
6300
|
import { execFile, execSync as execSync10 } from "child_process";
|
|
5671
6301
|
import { promisify } from "util";
|
|
5672
6302
|
import semver from "semver";
|
|
@@ -5680,6 +6310,23 @@ var CORE_PACKAGES = [
|
|
|
5680
6310
|
"@tamer4lynx/tamer-system-ui",
|
|
5681
6311
|
"@tamer4lynx/tamer-icons"
|
|
5682
6312
|
];
|
|
6313
|
+
var DEV_STACK_PACKAGES = [
|
|
6314
|
+
"@tamer4lynx/jiggle",
|
|
6315
|
+
"@tamer4lynx/tamer-app-shell",
|
|
6316
|
+
"@tamer4lynx/tamer-biometric",
|
|
6317
|
+
"@tamer4lynx/tamer-dev-app",
|
|
6318
|
+
"@tamer4lynx/tamer-dev-client",
|
|
6319
|
+
"@tamer4lynx/tamer-display-browser",
|
|
6320
|
+
"@tamer4lynx/tamer-icons",
|
|
6321
|
+
"@tamer4lynx/tamer-insets",
|
|
6322
|
+
"@tamer4lynx/tamer-linking",
|
|
6323
|
+
"@tamer4lynx/tamer-plugin",
|
|
6324
|
+
"@tamer4lynx/tamer-router",
|
|
6325
|
+
"@tamer4lynx/tamer-screen",
|
|
6326
|
+
"@tamer4lynx/tamer-secure-store",
|
|
6327
|
+
"@tamer4lynx/tamer-system-ui",
|
|
6328
|
+
"@tamer4lynx/tamer-transports"
|
|
6329
|
+
];
|
|
5683
6330
|
var PACKAGE_ALIASES = {};
|
|
5684
6331
|
async function getHighestPublishedVersion(fullName) {
|
|
5685
6332
|
try {
|
|
@@ -5705,10 +6352,10 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
5705
6352
|
console.warn(`\u26A0\uFE0F Could not resolve published versions for ${pkg}; using @prerelease`);
|
|
5706
6353
|
return `${pkg}@prerelease`;
|
|
5707
6354
|
}
|
|
5708
|
-
function
|
|
5709
|
-
const dir =
|
|
5710
|
-
if (
|
|
5711
|
-
if (
|
|
6355
|
+
function detectPackageManager2(cwd) {
|
|
6356
|
+
const dir = path25.resolve(cwd);
|
|
6357
|
+
if (fs24.existsSync(path25.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
6358
|
+
if (fs24.existsSync(path25.join(dir, "bun.lockb"))) return "bun";
|
|
5712
6359
|
return "npm";
|
|
5713
6360
|
}
|
|
5714
6361
|
function runInstall(cwd, packages, pm) {
|
|
@@ -5718,13 +6365,22 @@ function runInstall(cwd, packages, pm) {
|
|
|
5718
6365
|
}
|
|
5719
6366
|
async function addCore() {
|
|
5720
6367
|
const { lynxProjectDir } = resolveHostPaths();
|
|
5721
|
-
const pm =
|
|
6368
|
+
const pm = detectPackageManager2(lynxProjectDir);
|
|
5722
6369
|
console.log(`Resolving latest published versions (npm)\u2026`);
|
|
5723
6370
|
const resolved = await Promise.all(CORE_PACKAGES.map(normalizeTamerInstallSpec));
|
|
5724
6371
|
console.log(`Adding core packages to ${lynxProjectDir} (using ${pm})\u2026`);
|
|
5725
6372
|
runInstall(lynxProjectDir, resolved, pm);
|
|
5726
6373
|
console.log("\u2705 Core packages installed. Run `t4l link` to link native modules.");
|
|
5727
6374
|
}
|
|
6375
|
+
async function addDev() {
|
|
6376
|
+
const { lynxProjectDir } = resolveHostPaths();
|
|
6377
|
+
const pm = detectPackageManager2(lynxProjectDir);
|
|
6378
|
+
console.log(`Resolving latest published versions (npm)\u2026`);
|
|
6379
|
+
const resolved = await Promise.all([...DEV_STACK_PACKAGES].map(normalizeTamerInstallSpec));
|
|
6380
|
+
console.log(`Adding dev stack (${DEV_STACK_PACKAGES.length} @tamer4lynx packages) to ${lynxProjectDir} (using ${pm})\u2026`);
|
|
6381
|
+
runInstall(lynxProjectDir, resolved, pm);
|
|
6382
|
+
console.log("\u2705 Dev stack installed. Run `t4l link` to link native modules.");
|
|
6383
|
+
}
|
|
5728
6384
|
async function add(packages = []) {
|
|
5729
6385
|
const list = Array.isArray(packages) ? packages : [];
|
|
5730
6386
|
if (list.length === 0) {
|
|
@@ -5735,7 +6391,7 @@ async function add(packages = []) {
|
|
|
5735
6391
|
return;
|
|
5736
6392
|
}
|
|
5737
6393
|
const { lynxProjectDir } = resolveHostPaths();
|
|
5738
|
-
const pm =
|
|
6394
|
+
const pm = detectPackageManager2(lynxProjectDir);
|
|
5739
6395
|
console.log(`Resolving latest published versions (npm)\u2026`);
|
|
5740
6396
|
const normalized = await Promise.all(
|
|
5741
6397
|
list.map(async (p) => {
|
|
@@ -5748,18 +6404,682 @@ async function add(packages = []) {
|
|
|
5748
6404
|
console.log("\u2705 Packages installed. Run `t4l link` to link native modules.");
|
|
5749
6405
|
}
|
|
5750
6406
|
|
|
6407
|
+
// src/common/signing.tsx
|
|
6408
|
+
import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
6409
|
+
import { render as render3, Text as Text10, Box as Box9 } from "ink";
|
|
6410
|
+
import fs27 from "fs";
|
|
6411
|
+
import path28 from "path";
|
|
6412
|
+
|
|
6413
|
+
// src/common/androidKeystore.ts
|
|
6414
|
+
import { execFileSync } from "child_process";
|
|
6415
|
+
import fs25 from "fs";
|
|
6416
|
+
import path26 from "path";
|
|
6417
|
+
function normalizeJavaHome(raw) {
|
|
6418
|
+
if (!raw) return void 0;
|
|
6419
|
+
const t = raw.trim().replace(/^["']|["']$/g, "");
|
|
6420
|
+
return t || void 0;
|
|
6421
|
+
}
|
|
6422
|
+
function discoverJavaHomeMacOs() {
|
|
6423
|
+
if (process.platform !== "darwin") return void 0;
|
|
6424
|
+
try {
|
|
6425
|
+
const out = execFileSync("/usr/libexec/java_home", [], {
|
|
6426
|
+
encoding: "utf8",
|
|
6427
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6428
|
+
}).trim().split("\n")[0]?.trim();
|
|
6429
|
+
if (out && fs25.existsSync(path26.join(out, "bin", "keytool"))) return out;
|
|
6430
|
+
} catch {
|
|
6431
|
+
}
|
|
6432
|
+
return void 0;
|
|
6433
|
+
}
|
|
6434
|
+
function resolveKeytoolPath() {
|
|
6435
|
+
const jh = normalizeJavaHome(process.env.JAVA_HOME);
|
|
6436
|
+
const win = process.platform === "win32";
|
|
6437
|
+
const keytoolName = win ? "keytool.exe" : "keytool";
|
|
6438
|
+
if (jh) {
|
|
6439
|
+
const p = path26.join(jh, "bin", keytoolName);
|
|
6440
|
+
if (fs25.existsSync(p)) return p;
|
|
6441
|
+
}
|
|
6442
|
+
const mac = discoverJavaHomeMacOs();
|
|
6443
|
+
if (mac) {
|
|
6444
|
+
const p = path26.join(mac, "bin", keytoolName);
|
|
6445
|
+
if (fs25.existsSync(p)) return p;
|
|
6446
|
+
}
|
|
6447
|
+
return "keytool";
|
|
6448
|
+
}
|
|
6449
|
+
function keytoolAvailable() {
|
|
6450
|
+
const tryRun = (cmd) => {
|
|
6451
|
+
try {
|
|
6452
|
+
execFileSync(cmd, ["-help"], { stdio: "pipe" });
|
|
6453
|
+
return true;
|
|
6454
|
+
} catch {
|
|
6455
|
+
return false;
|
|
6456
|
+
}
|
|
6457
|
+
};
|
|
6458
|
+
if (tryRun("keytool")) return true;
|
|
6459
|
+
const fromJavaHome = resolveKeytoolPath();
|
|
6460
|
+
if (fromJavaHome !== "keytool" && fs25.existsSync(fromJavaHome)) {
|
|
6461
|
+
return tryRun(fromJavaHome);
|
|
6462
|
+
}
|
|
6463
|
+
return false;
|
|
6464
|
+
}
|
|
6465
|
+
function generateReleaseKeystore(opts) {
|
|
6466
|
+
const keytool = resolveKeytoolPath();
|
|
6467
|
+
const dir = path26.dirname(opts.keystoreAbsPath);
|
|
6468
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
6469
|
+
if (fs25.existsSync(opts.keystoreAbsPath)) {
|
|
6470
|
+
throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
|
|
6471
|
+
}
|
|
6472
|
+
if (!opts.storePassword || !opts.keyPassword) {
|
|
6473
|
+
throw new Error(
|
|
6474
|
+
"JDK keytool requires a keystore and key password of at least 6 characters for -genkeypair. Enter a password or use an existing keystore."
|
|
6475
|
+
);
|
|
6476
|
+
}
|
|
6477
|
+
const args = [
|
|
6478
|
+
"-genkeypair",
|
|
6479
|
+
"-v",
|
|
6480
|
+
"-keystore",
|
|
6481
|
+
opts.keystoreAbsPath,
|
|
6482
|
+
"-alias",
|
|
6483
|
+
opts.alias,
|
|
6484
|
+
"-keyalg",
|
|
6485
|
+
"RSA",
|
|
6486
|
+
"-keysize",
|
|
6487
|
+
"2048",
|
|
6488
|
+
"-validity",
|
|
6489
|
+
"10000",
|
|
6490
|
+
"-storepass",
|
|
6491
|
+
opts.storePassword,
|
|
6492
|
+
"-keypass",
|
|
6493
|
+
opts.keyPassword,
|
|
6494
|
+
"-dname",
|
|
6495
|
+
opts.dname
|
|
6496
|
+
];
|
|
6497
|
+
try {
|
|
6498
|
+
execFileSync(keytool, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
6499
|
+
} catch (e) {
|
|
6500
|
+
const err = e;
|
|
6501
|
+
const fromKeytool = [err.stdout, err.stderr].filter(Boolean).map((b) => Buffer.from(b).toString("utf8")).join("\n").trim();
|
|
6502
|
+
throw new Error(fromKeytool || err.message || "keytool failed");
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
|
|
6506
|
+
// src/common/appendEnvFile.ts
|
|
6507
|
+
import fs26 from "fs";
|
|
6508
|
+
import path27 from "path";
|
|
6509
|
+
import { parse } from "dotenv";
|
|
6510
|
+
function keysDefinedInFile(filePath) {
|
|
6511
|
+
if (!fs26.existsSync(filePath)) return /* @__PURE__ */ new Set();
|
|
6512
|
+
try {
|
|
6513
|
+
return new Set(Object.keys(parse(fs26.readFileSync(filePath, "utf8"))));
|
|
6514
|
+
} catch {
|
|
6515
|
+
return /* @__PURE__ */ new Set();
|
|
6516
|
+
}
|
|
6517
|
+
}
|
|
6518
|
+
function formatEnvLine(key, value) {
|
|
6519
|
+
if (/[\r\n]/.test(value) || /^\s|\s$/.test(value) || /[#"'\\=]/.test(value)) {
|
|
6520
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
6521
|
+
return `${key}="${escaped}"`;
|
|
6522
|
+
}
|
|
6523
|
+
return `${key}=${value}`;
|
|
6524
|
+
}
|
|
6525
|
+
function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
6526
|
+
const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
|
|
6527
|
+
if (entries.length === 0) return null;
|
|
6528
|
+
const envLocal = path27.join(projectRoot, ".env.local");
|
|
6529
|
+
const envDefault = path27.join(projectRoot, ".env");
|
|
6530
|
+
let target;
|
|
6531
|
+
if (fs26.existsSync(envLocal)) target = envLocal;
|
|
6532
|
+
else if (fs26.existsSync(envDefault)) target = envDefault;
|
|
6533
|
+
else target = envLocal;
|
|
6534
|
+
const existing = keysDefinedInFile(target);
|
|
6535
|
+
const lines = [];
|
|
6536
|
+
const appendedKeys = [];
|
|
6537
|
+
for (const [k, v] of entries) {
|
|
6538
|
+
if (existing.has(k)) continue;
|
|
6539
|
+
lines.push(formatEnvLine(k, v));
|
|
6540
|
+
appendedKeys.push(k);
|
|
6541
|
+
}
|
|
6542
|
+
if (lines.length === 0) {
|
|
6543
|
+
return {
|
|
6544
|
+
file: path27.basename(target),
|
|
6545
|
+
keys: [],
|
|
6546
|
+
skippedAll: entries.length > 0
|
|
6547
|
+
};
|
|
6548
|
+
}
|
|
6549
|
+
let prefix = "";
|
|
6550
|
+
if (fs26.existsSync(target)) {
|
|
6551
|
+
const cur = fs26.readFileSync(target, "utf8");
|
|
6552
|
+
prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
|
|
6553
|
+
`;
|
|
6554
|
+
}
|
|
6555
|
+
const block = lines.join("\n") + "\n";
|
|
6556
|
+
fs26.writeFileSync(target, prefix + block, "utf8");
|
|
6557
|
+
return { file: path27.basename(target), keys: appendedKeys };
|
|
6558
|
+
}
|
|
6559
|
+
|
|
6560
|
+
// src/common/signing.tsx
|
|
6561
|
+
import { Fragment, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
6562
|
+
function AndroidKeystoreModeSelect({
|
|
6563
|
+
onSelect
|
|
6564
|
+
}) {
|
|
6565
|
+
const canGen = keytoolAvailable();
|
|
6566
|
+
const items = canGen ? [
|
|
6567
|
+
{ label: "Generate a new release keystore (JDK keytool)", value: "generate" },
|
|
6568
|
+
{ label: "Use an existing keystore file", value: "existing" }
|
|
6569
|
+
] : [
|
|
6570
|
+
{
|
|
6571
|
+
label: "Use an existing keystore file (install a JDK for keytool to generate)",
|
|
6572
|
+
value: "existing"
|
|
6573
|
+
}
|
|
6574
|
+
];
|
|
6575
|
+
return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
|
|
6576
|
+
/* @__PURE__ */ jsx11(
|
|
6577
|
+
TuiSelectInput,
|
|
6578
|
+
{
|
|
6579
|
+
label: "Android release keystore:",
|
|
6580
|
+
items,
|
|
6581
|
+
onSelect
|
|
6582
|
+
}
|
|
6583
|
+
),
|
|
6584
|
+
!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." })
|
|
6585
|
+
] });
|
|
6586
|
+
}
|
|
6587
|
+
function firstStepForPlatform(p) {
|
|
6588
|
+
if (p === "ios") return "ios-team";
|
|
6589
|
+
if (p === "android" || p === "both") return "android-keystore-mode";
|
|
6590
|
+
return "platform";
|
|
6591
|
+
}
|
|
6592
|
+
function SigningWizard({ platform: initialPlatform }) {
|
|
6593
|
+
const [state, setState] = useState6({
|
|
6594
|
+
platform: initialPlatform || null,
|
|
6595
|
+
android: {
|
|
6596
|
+
keystoreFile: "",
|
|
6597
|
+
keyAlias: "release",
|
|
6598
|
+
storePasswordEnv: "ANDROID_KEYSTORE_PASSWORD",
|
|
6599
|
+
keyPasswordEnv: "ANDROID_KEY_PASSWORD",
|
|
6600
|
+
keystoreMode: null,
|
|
6601
|
+
genKeystorePath: "android/release.keystore",
|
|
6602
|
+
genPassword: ""
|
|
6603
|
+
},
|
|
6604
|
+
ios: {
|
|
6605
|
+
developmentTeam: "",
|
|
6606
|
+
codeSignIdentity: "",
|
|
6607
|
+
provisioningProfileSpecifier: ""
|
|
6608
|
+
},
|
|
6609
|
+
step: initialPlatform ? firstStepForPlatform(initialPlatform) : "platform",
|
|
6610
|
+
generateError: null,
|
|
6611
|
+
androidEnvAppend: null
|
|
6612
|
+
});
|
|
6613
|
+
const nextStep = () => {
|
|
6614
|
+
setState((s) => {
|
|
6615
|
+
if (s.step === "android-gen-path") {
|
|
6616
|
+
return { ...s, step: "android-gen-alias" };
|
|
6617
|
+
}
|
|
6618
|
+
if (s.step === "android-gen-alias") {
|
|
6619
|
+
return { ...s, step: "android-gen-password" };
|
|
6620
|
+
}
|
|
6621
|
+
if (s.step === "android-keystore") {
|
|
6622
|
+
return { ...s, step: "android-alias" };
|
|
6623
|
+
}
|
|
6624
|
+
if (s.step === "android-alias") {
|
|
6625
|
+
return { ...s, step: "android-password-env" };
|
|
6626
|
+
}
|
|
6627
|
+
if (s.step === "android-password-env") {
|
|
6628
|
+
return { ...s, step: "android-key-password-env" };
|
|
6629
|
+
}
|
|
6630
|
+
if (s.step === "android-key-password-env") {
|
|
6631
|
+
if (s.platform === "both") {
|
|
6632
|
+
return { ...s, step: "ios-team" };
|
|
6633
|
+
}
|
|
6634
|
+
return { ...s, step: "saving" };
|
|
6635
|
+
}
|
|
6636
|
+
if (s.step === "ios-team") {
|
|
6637
|
+
return { ...s, step: "ios-identity" };
|
|
6638
|
+
}
|
|
6639
|
+
if (s.step === "ios-identity") {
|
|
6640
|
+
return { ...s, step: "ios-profile" };
|
|
6641
|
+
}
|
|
6642
|
+
if (s.step === "ios-profile") {
|
|
6643
|
+
return { ...s, step: "saving" };
|
|
6644
|
+
}
|
|
6645
|
+
return s;
|
|
6646
|
+
});
|
|
6647
|
+
};
|
|
6648
|
+
useEffect4(() => {
|
|
6649
|
+
if (state.step === "saving") {
|
|
6650
|
+
saveConfig();
|
|
6651
|
+
}
|
|
6652
|
+
}, [state.step]);
|
|
6653
|
+
const generateRunId = useRef2(0);
|
|
6654
|
+
useEffect4(() => {
|
|
6655
|
+
if (state.step !== "android-generating") return;
|
|
6656
|
+
const runId = ++generateRunId.current;
|
|
6657
|
+
let cancelled = false;
|
|
6658
|
+
const run = () => {
|
|
6659
|
+
let abs = "";
|
|
6660
|
+
try {
|
|
6661
|
+
const resolved = resolveHostPaths();
|
|
6662
|
+
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6663
|
+
abs = path28.isAbsolute(rel) ? rel : path28.join(resolved.projectRoot, rel);
|
|
6664
|
+
const alias = state.android.keyAlias.trim() || "release";
|
|
6665
|
+
const pw = state.android.genPassword;
|
|
6666
|
+
const pkg = resolved.config.android?.packageName ?? "com.example.app";
|
|
6667
|
+
const safeOU = pkg.replace(/[,=+]/g, "_");
|
|
6668
|
+
const dname = `CN=Android Release, OU=${safeOU}, O=Android, C=US`;
|
|
6669
|
+
generateReleaseKeystore({
|
|
6670
|
+
keystoreAbsPath: abs,
|
|
6671
|
+
alias,
|
|
6672
|
+
storePassword: pw,
|
|
6673
|
+
keyPassword: pw,
|
|
6674
|
+
dname
|
|
6675
|
+
});
|
|
6676
|
+
if (cancelled || runId !== generateRunId.current) return;
|
|
6677
|
+
setState((s) => ({
|
|
6678
|
+
...s,
|
|
6679
|
+
android: {
|
|
6680
|
+
...s.android,
|
|
6681
|
+
keystoreFile: rel,
|
|
6682
|
+
keyAlias: alias,
|
|
6683
|
+
keystoreMode: "generate"
|
|
6684
|
+
},
|
|
6685
|
+
step: "android-password-env",
|
|
6686
|
+
generateError: null
|
|
6687
|
+
}));
|
|
6688
|
+
} catch (e) {
|
|
6689
|
+
const msg = e.message;
|
|
6690
|
+
if (abs && fs27.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
|
|
6691
|
+
if (cancelled || runId !== generateRunId.current) return;
|
|
6692
|
+
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6693
|
+
const alias = state.android.keyAlias.trim() || "release";
|
|
6694
|
+
setState((s) => ({
|
|
6695
|
+
...s,
|
|
6696
|
+
android: {
|
|
6697
|
+
...s.android,
|
|
6698
|
+
keystoreFile: rel,
|
|
6699
|
+
keyAlias: alias,
|
|
6700
|
+
keystoreMode: "generate"
|
|
6701
|
+
},
|
|
6702
|
+
step: "android-password-env",
|
|
6703
|
+
generateError: null
|
|
6704
|
+
}));
|
|
6705
|
+
return;
|
|
6706
|
+
}
|
|
6707
|
+
if (cancelled || runId !== generateRunId.current) return;
|
|
6708
|
+
setState((s) => ({
|
|
6709
|
+
...s,
|
|
6710
|
+
step: "android-gen-password",
|
|
6711
|
+
generateError: msg
|
|
6712
|
+
}));
|
|
6713
|
+
}
|
|
6714
|
+
};
|
|
6715
|
+
run();
|
|
6716
|
+
return () => {
|
|
6717
|
+
cancelled = true;
|
|
6718
|
+
};
|
|
6719
|
+
}, [state.step, state.android.genKeystorePath, state.android.keyAlias, state.android.genPassword]);
|
|
6720
|
+
useEffect4(() => {
|
|
6721
|
+
if (state.step === "done") {
|
|
6722
|
+
setTimeout(() => {
|
|
6723
|
+
process.exit(0);
|
|
6724
|
+
}, 3e3);
|
|
6725
|
+
}
|
|
6726
|
+
}, [state.step]);
|
|
6727
|
+
const saveConfig = async () => {
|
|
6728
|
+
try {
|
|
6729
|
+
const resolved = resolveHostPaths();
|
|
6730
|
+
const configPath = path28.join(resolved.projectRoot, "tamer.config.json");
|
|
6731
|
+
let config = {};
|
|
6732
|
+
let androidEnvAppend = null;
|
|
6733
|
+
if (fs27.existsSync(configPath)) {
|
|
6734
|
+
config = JSON.parse(fs27.readFileSync(configPath, "utf8"));
|
|
6735
|
+
}
|
|
6736
|
+
if (state.platform === "android" || state.platform === "both") {
|
|
6737
|
+
config.android = config.android || {};
|
|
6738
|
+
config.android.signing = {
|
|
6739
|
+
keystoreFile: state.android.keystoreFile,
|
|
6740
|
+
keyAlias: state.android.keyAlias,
|
|
6741
|
+
storePasswordEnv: state.android.storePasswordEnv,
|
|
6742
|
+
keyPasswordEnv: state.android.keyPasswordEnv
|
|
6743
|
+
};
|
|
6744
|
+
if (state.android.keystoreMode === "generate" && state.android.genPassword) {
|
|
6745
|
+
const storeEnv = state.android.storePasswordEnv.trim() || "ANDROID_KEYSTORE_PASSWORD";
|
|
6746
|
+
const keyEnv = state.android.keyPasswordEnv.trim() || "ANDROID_KEY_PASSWORD";
|
|
6747
|
+
androidEnvAppend = appendEnvVarsIfMissing(resolved.projectRoot, {
|
|
6748
|
+
[storeEnv]: state.android.genPassword,
|
|
6749
|
+
[keyEnv]: state.android.genPassword
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
if (state.platform === "ios" || state.platform === "both") {
|
|
6754
|
+
config.ios = config.ios || {};
|
|
6755
|
+
config.ios.signing = {
|
|
6756
|
+
developmentTeam: state.ios.developmentTeam,
|
|
6757
|
+
...state.ios.codeSignIdentity && { codeSignIdentity: state.ios.codeSignIdentity },
|
|
6758
|
+
...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
|
|
6759
|
+
};
|
|
6760
|
+
}
|
|
6761
|
+
fs27.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
6762
|
+
const gitignorePath = path28.join(resolved.projectRoot, ".gitignore");
|
|
6763
|
+
if (fs27.existsSync(gitignorePath)) {
|
|
6764
|
+
let gitignore = fs27.readFileSync(gitignorePath, "utf8");
|
|
6765
|
+
const additions = [
|
|
6766
|
+
".env.local",
|
|
6767
|
+
"*.jks",
|
|
6768
|
+
"*.keystore"
|
|
6769
|
+
];
|
|
6770
|
+
for (const addition of additions) {
|
|
6771
|
+
if (!gitignore.includes(addition)) {
|
|
6772
|
+
gitignore += `
|
|
6773
|
+
${addition}
|
|
6774
|
+
`;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
fs27.writeFileSync(gitignorePath, gitignore);
|
|
6778
|
+
}
|
|
6779
|
+
setState((s) => ({
|
|
6780
|
+
...s,
|
|
6781
|
+
step: "done",
|
|
6782
|
+
androidEnvAppend: state.platform === "android" || state.platform === "both" ? androidEnvAppend : null
|
|
6783
|
+
}));
|
|
6784
|
+
} catch (error) {
|
|
6785
|
+
console.error("Error saving config:", error);
|
|
6786
|
+
process.exit(1);
|
|
6787
|
+
}
|
|
6788
|
+
};
|
|
6789
|
+
if (state.step === "done") {
|
|
6790
|
+
return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
|
|
6791
|
+
/* @__PURE__ */ jsx11(Text10, { color: "green", children: "\u2705 Signing configuration saved to tamer.config.json" }),
|
|
6792
|
+
(state.platform === "android" || state.platform === "both") && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
6793
|
+
/* @__PURE__ */ jsx11(Text10, { children: "Android signing configured:" }),
|
|
6794
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6795
|
+
" Keystore: ",
|
|
6796
|
+
state.android.keystoreFile
|
|
6797
|
+
] }),
|
|
6798
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6799
|
+
" Alias: ",
|
|
6800
|
+
state.android.keyAlias
|
|
6801
|
+
] }),
|
|
6802
|
+
state.androidEnvAppend?.keys.length ? /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
6803
|
+
"Appended ",
|
|
6804
|
+
state.androidEnvAppend.keys.join(", "),
|
|
6805
|
+
" to ",
|
|
6806
|
+
state.androidEnvAppend.file,
|
|
6807
|
+
" (existing keys left unchanged)."
|
|
6808
|
+
] }) : state.androidEnvAppend?.skippedAll ? /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6809
|
+
state.androidEnvAppend.file,
|
|
6810
|
+
" already defines the signing env vars; left unchanged."
|
|
6811
|
+
] }) : /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
6812
|
+
/* @__PURE__ */ jsx11(Text10, { children: "Set environment variables (or add them to .env / .env.local):" }),
|
|
6813
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6814
|
+
" export ",
|
|
6815
|
+
state.android.storePasswordEnv,
|
|
6816
|
+
'="your-keystore-password"'
|
|
6817
|
+
] }),
|
|
6818
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6819
|
+
" export ",
|
|
6820
|
+
state.android.keyPasswordEnv,
|
|
6821
|
+
'="your-key-password"'
|
|
6822
|
+
] })
|
|
6823
|
+
] })
|
|
6824
|
+
] }),
|
|
6825
|
+
(state.platform === "ios" || state.platform === "both") && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
6826
|
+
/* @__PURE__ */ jsx11(Text10, { children: "iOS signing configured:" }),
|
|
6827
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6828
|
+
" Team ID: ",
|
|
6829
|
+
state.ios.developmentTeam
|
|
6830
|
+
] }),
|
|
6831
|
+
state.ios.codeSignIdentity && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
6832
|
+
" Identity: ",
|
|
6833
|
+
state.ios.codeSignIdentity
|
|
6834
|
+
] })
|
|
6835
|
+
] }),
|
|
6836
|
+
/* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
6837
|
+
state.platform === "android" && /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
6838
|
+
/* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build android -p` to build this platform with signing." }),
|
|
6839
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "`t4l build -p` (no platform) builds both Android and iOS." })
|
|
6840
|
+
] }),
|
|
6841
|
+
state.platform === "ios" && /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
6842
|
+
/* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build ios -p` to build this platform with signing." }),
|
|
6843
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "`t4l build -p` (no platform) builds both Android and iOS." })
|
|
6844
|
+
] }),
|
|
6845
|
+
state.platform === "both" && /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
6846
|
+
/* @__PURE__ */ jsx11(Text10, { children: "Run `t4l build -p` to build both platforms with signing." }),
|
|
6847
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "Or: `t4l build android -p` / `t4l build ios -p` for one platform." })
|
|
6848
|
+
] })
|
|
6849
|
+
] })
|
|
6850
|
+
] });
|
|
6851
|
+
}
|
|
6852
|
+
if (state.step === "saving") {
|
|
6853
|
+
return /* @__PURE__ */ jsx11(Box9, { children: /* @__PURE__ */ jsx11(TuiSpinner, { label: "Saving configuration..." }) });
|
|
6854
|
+
}
|
|
6855
|
+
if (state.step === "android-generating") {
|
|
6856
|
+
return /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx11(TuiSpinner, { label: "Running keytool to create release keystore..." }) });
|
|
6857
|
+
}
|
|
6858
|
+
return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
|
|
6859
|
+
state.step === "platform" && /* @__PURE__ */ jsx11(
|
|
6860
|
+
TuiSelectInput,
|
|
6861
|
+
{
|
|
6862
|
+
label: "Select platform(s) to configure signing:",
|
|
6863
|
+
items: [
|
|
6864
|
+
{ label: "Android", value: "android" },
|
|
6865
|
+
{ label: "iOS", value: "ios" },
|
|
6866
|
+
{ label: "Both", value: "both" }
|
|
6867
|
+
],
|
|
6868
|
+
onSelect: (platform) => {
|
|
6869
|
+
setState((s) => ({ ...s, platform, step: firstStepForPlatform(platform) }));
|
|
6870
|
+
}
|
|
6871
|
+
}
|
|
6872
|
+
),
|
|
6873
|
+
state.step === "android-keystore-mode" && /* @__PURE__ */ jsx11(
|
|
6874
|
+
AndroidKeystoreModeSelect,
|
|
6875
|
+
{
|
|
6876
|
+
onSelect: (mode) => {
|
|
6877
|
+
setState((s) => ({
|
|
6878
|
+
...s,
|
|
6879
|
+
android: { ...s.android, keystoreMode: mode },
|
|
6880
|
+
step: mode === "generate" ? "android-gen-path" : "android-keystore",
|
|
6881
|
+
generateError: null
|
|
6882
|
+
}));
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
),
|
|
6886
|
+
state.step === "android-gen-path" && /* @__PURE__ */ jsx11(
|
|
6887
|
+
TuiTextInput,
|
|
6888
|
+
{
|
|
6889
|
+
label: "Keystore output path (relative to project root):",
|
|
6890
|
+
defaultValue: state.android.genKeystorePath,
|
|
6891
|
+
onSubmitValue: (v) => {
|
|
6892
|
+
const p = v.trim() || "android/release.keystore";
|
|
6893
|
+
setState((s) => ({ ...s, android: { ...s.android, genKeystorePath: p } }));
|
|
6894
|
+
},
|
|
6895
|
+
onSubmit: nextStep,
|
|
6896
|
+
hint: "Default: android/release.keystore (gitignored pattern *.keystore)"
|
|
6897
|
+
}
|
|
6898
|
+
),
|
|
6899
|
+
state.step === "android-gen-alias" && /* @__PURE__ */ jsx11(
|
|
6900
|
+
TuiTextInput,
|
|
6901
|
+
{
|
|
6902
|
+
label: "Android key alias:",
|
|
6903
|
+
defaultValue: state.android.keyAlias,
|
|
6904
|
+
onSubmitValue: (v) => {
|
|
6905
|
+
setState((s) => ({ ...s, android: { ...s.android, keyAlias: v } }));
|
|
6906
|
+
},
|
|
6907
|
+
onSubmit: nextStep
|
|
6908
|
+
}
|
|
6909
|
+
),
|
|
6910
|
+
state.step === "android-gen-password" && /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
|
|
6911
|
+
state.generateError ? /* @__PURE__ */ jsx11(Text10, { color: "red", children: state.generateError }) : null,
|
|
6912
|
+
/* @__PURE__ */ jsx11(
|
|
6913
|
+
TuiTextInput,
|
|
6914
|
+
{
|
|
6915
|
+
label: "Keystore and key password (same for both; shown as you type):",
|
|
6916
|
+
value: state.android.genPassword,
|
|
6917
|
+
onChange: (v) => setState((s) => ({ ...s, android: { ...s.android, genPassword: v } })),
|
|
6918
|
+
onSubmitValue: (pw) => {
|
|
6919
|
+
setState((s) => ({
|
|
6920
|
+
...s,
|
|
6921
|
+
android: { ...s.android, genPassword: pw.trim() },
|
|
6922
|
+
step: "android-generating",
|
|
6923
|
+
generateError: null
|
|
6924
|
+
}));
|
|
6925
|
+
},
|
|
6926
|
+
onSubmit: () => {
|
|
6927
|
+
},
|
|
6928
|
+
hint: "At least 6 characters (JDK keytool). Same value used for -storepass and -keypass."
|
|
6929
|
+
}
|
|
6930
|
+
)
|
|
6931
|
+
] }),
|
|
6932
|
+
state.step === "android-keystore" && /* @__PURE__ */ jsx11(
|
|
6933
|
+
TuiTextInput,
|
|
6934
|
+
{
|
|
6935
|
+
label: "Android keystore file path (relative to project root or android/):",
|
|
6936
|
+
defaultValue: state.android.keystoreFile,
|
|
6937
|
+
onSubmitValue: (v) => {
|
|
6938
|
+
setState((s) => ({ ...s, android: { ...s.android, keystoreFile: v } }));
|
|
6939
|
+
},
|
|
6940
|
+
onSubmit: nextStep,
|
|
6941
|
+
hint: "Example: android/app/my-release-key.keystore or ./my-release-key.keystore"
|
|
6942
|
+
}
|
|
6943
|
+
),
|
|
6944
|
+
state.step === "android-alias" && /* @__PURE__ */ jsx11(
|
|
6945
|
+
TuiTextInput,
|
|
6946
|
+
{
|
|
6947
|
+
label: "Android key alias:",
|
|
6948
|
+
defaultValue: state.android.keyAlias,
|
|
6949
|
+
onSubmitValue: (v) => {
|
|
6950
|
+
setState((s) => ({ ...s, android: { ...s.android, keyAlias: v } }));
|
|
6951
|
+
},
|
|
6952
|
+
onSubmit: nextStep
|
|
6953
|
+
}
|
|
6954
|
+
),
|
|
6955
|
+
state.step === "android-password-env" && /* @__PURE__ */ jsx11(
|
|
6956
|
+
TuiTextInput,
|
|
6957
|
+
{
|
|
6958
|
+
label: "Keystore password environment variable name:",
|
|
6959
|
+
defaultValue: state.android.storePasswordEnv || "ANDROID_KEYSTORE_PASSWORD",
|
|
6960
|
+
onSubmitValue: (v) => {
|
|
6961
|
+
setState((s) => ({ ...s, android: { ...s.android, storePasswordEnv: v } }));
|
|
6962
|
+
},
|
|
6963
|
+
onSubmit: () => {
|
|
6964
|
+
setState((s) => ({ ...s, step: "android-key-password-env" }));
|
|
6965
|
+
},
|
|
6966
|
+
hint: "Default: ANDROID_KEYSTORE_PASSWORD (will be written to .env / .env.local)"
|
|
6967
|
+
}
|
|
6968
|
+
),
|
|
6969
|
+
state.step === "android-key-password-env" && /* @__PURE__ */ jsx11(
|
|
6970
|
+
TuiTextInput,
|
|
6971
|
+
{
|
|
6972
|
+
label: "Key password environment variable name:",
|
|
6973
|
+
defaultValue: state.android.keyPasswordEnv || "ANDROID_KEY_PASSWORD",
|
|
6974
|
+
onSubmitValue: (v) => {
|
|
6975
|
+
setState((s) => ({ ...s, android: { ...s.android, keyPasswordEnv: v } }));
|
|
6976
|
+
},
|
|
6977
|
+
onSubmit: () => {
|
|
6978
|
+
if (state.platform === "both") {
|
|
6979
|
+
setState((s) => ({ ...s, step: "ios-team" }));
|
|
6980
|
+
} else {
|
|
6981
|
+
setState((s) => ({ ...s, step: "saving" }));
|
|
6982
|
+
}
|
|
6983
|
+
},
|
|
6984
|
+
hint: "Default: ANDROID_KEY_PASSWORD (will be written to .env / .env.local)"
|
|
6985
|
+
}
|
|
6986
|
+
),
|
|
6987
|
+
state.step === "ios-team" && /* @__PURE__ */ jsx11(
|
|
6988
|
+
TuiTextInput,
|
|
6989
|
+
{
|
|
6990
|
+
label: "iOS Development Team ID:",
|
|
6991
|
+
defaultValue: state.ios.developmentTeam,
|
|
6992
|
+
onSubmitValue: (v) => {
|
|
6993
|
+
setState((s) => ({ ...s, ios: { ...s.ios, developmentTeam: v } }));
|
|
6994
|
+
},
|
|
6995
|
+
onSubmit: nextStep,
|
|
6996
|
+
hint: "Example: ABC123DEF4 (found in Apple Developer account)"
|
|
6997
|
+
}
|
|
6998
|
+
),
|
|
6999
|
+
state.step === "ios-identity" && /* @__PURE__ */ jsx11(
|
|
7000
|
+
TuiTextInput,
|
|
7001
|
+
{
|
|
7002
|
+
label: "iOS Code Sign Identity (optional, press Enter to skip):",
|
|
7003
|
+
defaultValue: state.ios.codeSignIdentity,
|
|
7004
|
+
onSubmitValue: (v) => {
|
|
7005
|
+
setState((s) => ({ ...s, ios: { ...s.ios, codeSignIdentity: v } }));
|
|
7006
|
+
},
|
|
7007
|
+
onSubmit: () => {
|
|
7008
|
+
setState((s) => ({ ...s, step: "ios-profile" }));
|
|
7009
|
+
},
|
|
7010
|
+
hint: 'Example: "iPhone Developer" or "Apple Development"'
|
|
7011
|
+
}
|
|
7012
|
+
),
|
|
7013
|
+
state.step === "ios-profile" && /* @__PURE__ */ jsx11(
|
|
7014
|
+
TuiTextInput,
|
|
7015
|
+
{
|
|
7016
|
+
label: "iOS Provisioning Profile Specifier (optional, press Enter to skip):",
|
|
7017
|
+
defaultValue: state.ios.provisioningProfileSpecifier,
|
|
7018
|
+
onSubmitValue: (v) => {
|
|
7019
|
+
setState((s) => ({ ...s, ios: { ...s.ios, provisioningProfileSpecifier: v } }));
|
|
7020
|
+
},
|
|
7021
|
+
onSubmit: () => {
|
|
7022
|
+
setState((s) => ({ ...s, step: "saving" }));
|
|
7023
|
+
},
|
|
7024
|
+
hint: "UUID of the provisioning profile"
|
|
7025
|
+
}
|
|
7026
|
+
)
|
|
7027
|
+
] });
|
|
7028
|
+
}
|
|
7029
|
+
async function signing(platform) {
|
|
7030
|
+
const { waitUntilExit } = render3(/* @__PURE__ */ jsx11(SigningWizard, { platform }));
|
|
7031
|
+
await waitUntilExit();
|
|
7032
|
+
}
|
|
7033
|
+
|
|
7034
|
+
// src/common/productionSigning.ts
|
|
7035
|
+
import fs28 from "fs";
|
|
7036
|
+
import path29 from "path";
|
|
7037
|
+
function isAndroidSigningConfigured(resolved) {
|
|
7038
|
+
const signing2 = resolved.config.android?.signing;
|
|
7039
|
+
const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
|
|
7040
|
+
const signingProps = path29.join(resolved.androidDir, "signing.properties");
|
|
7041
|
+
const hasProps = fs28.existsSync(signingProps);
|
|
7042
|
+
return hasConfig || hasProps;
|
|
7043
|
+
}
|
|
7044
|
+
function isIosSigningConfigured(resolved) {
|
|
7045
|
+
const team = resolved.config.ios?.signing?.developmentTeam?.trim();
|
|
7046
|
+
return Boolean(team);
|
|
7047
|
+
}
|
|
7048
|
+
function assertProductionSigningReady(filter) {
|
|
7049
|
+
const resolved = resolveHostPaths();
|
|
7050
|
+
const needAndroid = filter === "android" || filter === "all";
|
|
7051
|
+
const needIos = filter === "ios" || filter === "all";
|
|
7052
|
+
const missing = [];
|
|
7053
|
+
if (needAndroid && !isAndroidSigningConfigured(resolved)) {
|
|
7054
|
+
missing.push("Android: run `t4l signing android`, then `t4l build android -p`.");
|
|
7055
|
+
}
|
|
7056
|
+
if (needIos && !isIosSigningConfigured(resolved)) {
|
|
7057
|
+
missing.push("iOS: run `t4l signing ios`, then `t4l build ios -p`.");
|
|
7058
|
+
}
|
|
7059
|
+
if (missing.length === 0) return;
|
|
7060
|
+
console.error("\n\u274C Production build (`-p`) needs signing configured for the platform(s) you are building.");
|
|
7061
|
+
for (const line of missing) {
|
|
7062
|
+
console.error(` ${line}`);
|
|
7063
|
+
}
|
|
7064
|
+
console.error(
|
|
7065
|
+
"\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"
|
|
7066
|
+
);
|
|
7067
|
+
process.exit(1);
|
|
7068
|
+
}
|
|
7069
|
+
|
|
5751
7070
|
// index.ts
|
|
5752
7071
|
function readCliVersion() {
|
|
5753
|
-
const root =
|
|
5754
|
-
const here =
|
|
5755
|
-
const parent =
|
|
5756
|
-
const pkgPath =
|
|
5757
|
-
return JSON.parse(
|
|
7072
|
+
const root = path30.dirname(fileURLToPath(import.meta.url));
|
|
7073
|
+
const here = path30.join(root, "package.json");
|
|
7074
|
+
const parent = path30.join(root, "..", "package.json");
|
|
7075
|
+
const pkgPath = fs29.existsSync(here) ? here : parent;
|
|
7076
|
+
return JSON.parse(fs29.readFileSync(pkgPath, "utf8")).version;
|
|
5758
7077
|
}
|
|
5759
7078
|
var version = readCliVersion();
|
|
5760
|
-
function
|
|
5761
|
-
|
|
5762
|
-
|
|
7079
|
+
function validateBuildMode(debug, release, production) {
|
|
7080
|
+
const modes = [debug, release, production].filter(Boolean).length;
|
|
7081
|
+
if (modes > 1) {
|
|
7082
|
+
console.error("Cannot use --debug, --release, and --production together. Use only one.");
|
|
5763
7083
|
process.exit(1);
|
|
5764
7084
|
}
|
|
5765
7085
|
}
|
|
@@ -5771,7 +7091,7 @@ function parsePlatform(value) {
|
|
|
5771
7091
|
}
|
|
5772
7092
|
program.version(version).description("Tamer4Lynx CLI - A tool for managing Lynx projects");
|
|
5773
7093
|
program.command("init").description("Initialize tamer.config.json interactively").action(() => {
|
|
5774
|
-
|
|
7094
|
+
init();
|
|
5775
7095
|
});
|
|
5776
7096
|
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
7097
|
const t = target.toLowerCase();
|
|
@@ -5794,22 +7114,26 @@ program.command("create <target>").description("Create a project or extension. T
|
|
|
5794
7114
|
console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
|
|
5795
7115
|
process.exit(1);
|
|
5796
7116
|
});
|
|
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;
|
|
7117
|
+
program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option("-i, --install", "Install after building").action(async (platform, opts) => {
|
|
7118
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7119
|
+
const release = opts.release === true || opts.production === true;
|
|
7120
|
+
const production = opts.production === true;
|
|
5800
7121
|
if (opts.embeddable) {
|
|
5801
7122
|
await buildEmbeddable({ release: true });
|
|
5802
7123
|
return;
|
|
5803
7124
|
}
|
|
5804
7125
|
const p = parsePlatform(platform ?? "all") ?? "all";
|
|
7126
|
+
if (production) {
|
|
7127
|
+
assertProductionSigningReady(p);
|
|
7128
|
+
}
|
|
5805
7129
|
if (p === "android" || p === "all") {
|
|
5806
|
-
await build_default({ install: opts.install, release });
|
|
7130
|
+
await build_default({ install: opts.install, release, production });
|
|
5807
7131
|
}
|
|
5808
7132
|
if (p === "ios" || p === "all") {
|
|
5809
|
-
await build_default2({ install: opts.install, release });
|
|
7133
|
+
await build_default2({ install: opts.install, release, production });
|
|
5810
7134
|
}
|
|
5811
7135
|
});
|
|
5812
|
-
program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run
|
|
7136
|
+
program.command("link [platform]").description("Link native modules. Platform: ios | android | both (default: both)").option("-s, --silent", "Run without output").action((platform, opts) => {
|
|
5813
7137
|
if (opts.silent) {
|
|
5814
7138
|
console.log = () => {
|
|
5815
7139
|
};
|
|
@@ -5830,14 +7154,15 @@ program.command("link [platform]").description("Link native modules. Platform: i
|
|
|
5830
7154
|
autolink_default2();
|
|
5831
7155
|
autolink_default();
|
|
5832
7156
|
});
|
|
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;
|
|
7157
|
+
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) => {
|
|
7158
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7159
|
+
const release = opts.release === true || opts.production === true;
|
|
7160
|
+
const production = opts.production === true;
|
|
5836
7161
|
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 });
|
|
7162
|
+
if (p === "android" || p === "all") await bundle_default({ release, production });
|
|
7163
|
+
if (p === "ios" || p === "all") bundle_default2({ release, production });
|
|
5839
7164
|
});
|
|
5840
|
-
program.command("inject <platform>").description("Inject
|
|
7165
|
+
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
7166
|
const p = platform?.toLowerCase();
|
|
5842
7167
|
if (p === "ios") {
|
|
5843
7168
|
await injectHostIos({ force: opts.force });
|
|
@@ -5850,7 +7175,7 @@ program.command("inject <platform>").description("Inject tamer-host templates in
|
|
|
5850
7175
|
console.error(`Invalid inject platform: ${platform}. Use ios | android`);
|
|
5851
7176
|
process.exit(1);
|
|
5852
7177
|
});
|
|
5853
|
-
program.command("sync [platform]").description("Sync dev client
|
|
7178
|
+
program.command("sync [platform]").description("Sync dev client. Platform: android (default)").action(async (platform) => {
|
|
5854
7179
|
const p = (platform ?? "android").toLowerCase();
|
|
5855
7180
|
if (p !== "android") {
|
|
5856
7181
|
console.error("sync only supports android.");
|
|
@@ -5871,16 +7196,27 @@ program.command("build-dev-app").option("-p, --platform <platform>", "Platform:
|
|
|
5871
7196
|
await build_default2({ install: opts.install, release: false });
|
|
5872
7197
|
}
|
|
5873
7198
|
});
|
|
5874
|
-
program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project
|
|
7199
|
+
program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project").action(async (packages) => {
|
|
5875
7200
|
await add(packages);
|
|
5876
7201
|
});
|
|
5877
|
-
program.command("add-core").description("Add core packages
|
|
7202
|
+
program.command("add-core").description("Add core packages").action(async () => {
|
|
5878
7203
|
await addCore();
|
|
5879
7204
|
});
|
|
7205
|
+
program.command("add-dev").description("Add dev-app, dev-client, and their dependencies").action(async () => {
|
|
7206
|
+
await addDev();
|
|
7207
|
+
});
|
|
7208
|
+
program.command("signing [platform]").description("Configure Android and iOS signing interactively").action(async (platform) => {
|
|
7209
|
+
const p = platform?.toLowerCase();
|
|
7210
|
+
if (p === "android" || p === "ios") {
|
|
7211
|
+
await signing(p);
|
|
7212
|
+
} else {
|
|
7213
|
+
await signing();
|
|
7214
|
+
}
|
|
7215
|
+
});
|
|
5880
7216
|
program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
|
|
5881
7217
|
codegen_default();
|
|
5882
7218
|
});
|
|
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) => {
|
|
7219
|
+
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
7220
|
const sub = subcommand?.toLowerCase();
|
|
5885
7221
|
if (sub === "create") {
|
|
5886
7222
|
if (opts.debug && opts.release) {
|
|
@@ -5895,14 +7231,19 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
5895
7231
|
return;
|
|
5896
7232
|
}
|
|
5897
7233
|
if (sub === "bundle") {
|
|
5898
|
-
|
|
5899
|
-
|
|
7234
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7235
|
+
const release = opts.release === true || opts.production === true;
|
|
7236
|
+
await bundle_default({ release, production: opts.production === true });
|
|
5900
7237
|
return;
|
|
5901
7238
|
}
|
|
5902
7239
|
if (sub === "build") {
|
|
5903
|
-
|
|
7240
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7241
|
+
const release = opts.release === true || opts.production === true;
|
|
5904
7242
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
5905
|
-
else
|
|
7243
|
+
else {
|
|
7244
|
+
if (opts.production === true) assertProductionSigningReady("android");
|
|
7245
|
+
await build_default({ install: opts.install, release, production: opts.production === true });
|
|
7246
|
+
}
|
|
5906
7247
|
return;
|
|
5907
7248
|
}
|
|
5908
7249
|
if (sub === "sync") {
|
|
@@ -5916,7 +7257,7 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
5916
7257
|
console.error(`Unknown android subcommand: ${subcommand}. Use: create | link | bundle | build | sync | inject`);
|
|
5917
7258
|
process.exit(1);
|
|
5918
7259
|
});
|
|
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) => {
|
|
7260
|
+
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
7261
|
const sub = subcommand?.toLowerCase();
|
|
5921
7262
|
if (sub === "create") {
|
|
5922
7263
|
create_default2();
|
|
@@ -5927,14 +7268,19 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
5927
7268
|
return;
|
|
5928
7269
|
}
|
|
5929
7270
|
if (sub === "bundle") {
|
|
5930
|
-
|
|
5931
|
-
|
|
7271
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7272
|
+
const release = opts.release === true || opts.production === true;
|
|
7273
|
+
bundle_default2({ release, production: opts.production === true });
|
|
5932
7274
|
return;
|
|
5933
7275
|
}
|
|
5934
7276
|
if (sub === "build") {
|
|
5935
|
-
|
|
7277
|
+
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7278
|
+
const release = opts.release === true || opts.production === true;
|
|
5936
7279
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
5937
|
-
else
|
|
7280
|
+
else {
|
|
7281
|
+
if (opts.production === true) assertProductionSigningReady("ios");
|
|
7282
|
+
await build_default2({ install: opts.install, release, production: opts.production === true });
|
|
7283
|
+
}
|
|
5938
7284
|
return;
|
|
5939
7285
|
}
|
|
5940
7286
|
if (sub === "inject") {
|
|
@@ -5945,10 +7291,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
5945
7291
|
process.exit(1);
|
|
5946
7292
|
});
|
|
5947
7293
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
5948
|
-
const configPath =
|
|
7294
|
+
const configPath = path30.join(process.cwd(), "tamer.config.json");
|
|
5949
7295
|
let config = {};
|
|
5950
|
-
if (
|
|
5951
|
-
config = JSON.parse(
|
|
7296
|
+
if (fs29.existsSync(configPath)) {
|
|
7297
|
+
config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
|
|
5952
7298
|
}
|
|
5953
7299
|
if (config.autolink) {
|
|
5954
7300
|
delete config.autolink;
|
|
@@ -5957,11 +7303,11 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
5957
7303
|
config.autolink = true;
|
|
5958
7304
|
console.log("Autolink enabled in tamer.config.json");
|
|
5959
7305
|
}
|
|
5960
|
-
|
|
7306
|
+
fs29.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
5961
7307
|
console.log(`Updated ${configPath}`);
|
|
5962
7308
|
});
|
|
5963
7309
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|
|
5964
|
-
Promise.resolve(
|
|
7310
|
+
Promise.resolve(init()).then(() => process.exit(0));
|
|
5965
7311
|
} else {
|
|
5966
7312
|
program.parseAsync().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
5967
7313
|
}
|