@tamer4lynx/cli 0.0.14 → 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 +542 -434
- package/package.json +9 -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
|
}
|
|
@@ -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;
|
|
@@ -2471,7 +2573,7 @@ async function buildApk(opts = {}) {
|
|
|
2471
2573
|
const release = opts.release === true || opts.production === true;
|
|
2472
2574
|
await bundle_default({ release, production: opts.production });
|
|
2473
2575
|
const androidDir = resolved.androidDir;
|
|
2474
|
-
const gradlew =
|
|
2576
|
+
const gradlew = path12.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2475
2577
|
const variant = release ? "Release" : "Debug";
|
|
2476
2578
|
const task = opts.install ? `install${variant}` : `assemble${variant}`;
|
|
2477
2579
|
console.log(`
|
|
@@ -2496,13 +2598,13 @@ async function buildApk(opts = {}) {
|
|
|
2496
2598
|
var build_default = buildApk;
|
|
2497
2599
|
|
|
2498
2600
|
// src/ios/create.ts
|
|
2499
|
-
import
|
|
2500
|
-
import
|
|
2601
|
+
import fs13 from "fs";
|
|
2602
|
+
import path14 from "path";
|
|
2501
2603
|
|
|
2502
2604
|
// src/ios/getPod.ts
|
|
2503
2605
|
import { execSync as execSync5 } from "child_process";
|
|
2504
|
-
import
|
|
2505
|
-
import
|
|
2606
|
+
import fs12 from "fs";
|
|
2607
|
+
import path13 from "path";
|
|
2506
2608
|
function isCocoaPodsInstalled() {
|
|
2507
2609
|
try {
|
|
2508
2610
|
execSync5("command -v pod >/dev/null 2>&1");
|
|
@@ -2524,8 +2626,8 @@ async function setupCocoaPods(rootDir) {
|
|
|
2524
2626
|
}
|
|
2525
2627
|
try {
|
|
2526
2628
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2527
|
-
const podfilePath =
|
|
2528
|
-
if (!
|
|
2629
|
+
const podfilePath = path13.join(rootDir, "Podfile");
|
|
2630
|
+
if (!fs12.existsSync(podfilePath)) {
|
|
2529
2631
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2530
2632
|
}
|
|
2531
2633
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2551,7 +2653,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2551
2653
|
// src/ios/create.ts
|
|
2552
2654
|
import { randomBytes } from "crypto";
|
|
2553
2655
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2554
|
-
const raw =
|
|
2656
|
+
const raw = fs13.readFileSync(templatePath, "utf-8");
|
|
2555
2657
|
return Object.entries(vars).reduce(
|
|
2556
2658
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2557
2659
|
raw
|
|
@@ -2574,17 +2676,17 @@ var create2 = () => {
|
|
|
2574
2676
|
process.exit(1);
|
|
2575
2677
|
}
|
|
2576
2678
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
2577
|
-
const rootDir =
|
|
2578
|
-
const projectDir =
|
|
2579
|
-
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`);
|
|
2580
2682
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2581
2683
|
function writeFile2(filePath, content) {
|
|
2582
|
-
|
|
2583
|
-
|
|
2684
|
+
fs13.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
2685
|
+
fs13.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2584
2686
|
}
|
|
2585
|
-
if (
|
|
2687
|
+
if (fs13.existsSync(rootDir)) {
|
|
2586
2688
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2587
|
-
|
|
2689
|
+
fs13.rmSync(rootDir, { recursive: true, force: true });
|
|
2588
2690
|
}
|
|
2589
2691
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2590
2692
|
const ids = {
|
|
@@ -2620,7 +2722,7 @@ var create2 = () => {
|
|
|
2620
2722
|
targetDebugConfig: generateId(),
|
|
2621
2723
|
targetReleaseConfig: generateId()
|
|
2622
2724
|
};
|
|
2623
|
-
writeFile2(
|
|
2725
|
+
writeFile2(path14.join(rootDir, "Podfile"), `
|
|
2624
2726
|
source 'https://cdn.cocoapods.org/'
|
|
2625
2727
|
|
|
2626
2728
|
platform :ios, '13.0'
|
|
@@ -2705,15 +2807,15 @@ end
|
|
|
2705
2807
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
2706
2808
|
const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
2707
2809
|
if (hostPkg) {
|
|
2708
|
-
const templateDir =
|
|
2810
|
+
const templateDir = path14.join(hostPkg, "ios", "templates");
|
|
2709
2811
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2710
|
-
const srcPath =
|
|
2711
|
-
if (
|
|
2712
|
-
writeFile2(
|
|
2812
|
+
const srcPath = path14.join(templateDir, f);
|
|
2813
|
+
if (fs13.existsSync(srcPath)) {
|
|
2814
|
+
writeFile2(path14.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2713
2815
|
}
|
|
2714
2816
|
}
|
|
2715
2817
|
} else {
|
|
2716
|
-
writeFile2(
|
|
2818
|
+
writeFile2(path14.join(projectDir, "AppDelegate.swift"), `
|
|
2717
2819
|
import UIKit
|
|
2718
2820
|
|
|
2719
2821
|
@UIApplicationMain
|
|
@@ -2728,7 +2830,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2728
2830
|
}
|
|
2729
2831
|
}
|
|
2730
2832
|
`);
|
|
2731
|
-
writeFile2(
|
|
2833
|
+
writeFile2(path14.join(projectDir, "SceneDelegate.swift"), `
|
|
2732
2834
|
import UIKit
|
|
2733
2835
|
|
|
2734
2836
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2742,7 +2844,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2742
2844
|
}
|
|
2743
2845
|
}
|
|
2744
2846
|
`);
|
|
2745
|
-
writeFile2(
|
|
2847
|
+
writeFile2(path14.join(projectDir, "ViewController.swift"), `
|
|
2746
2848
|
import UIKit
|
|
2747
2849
|
import Lynx
|
|
2748
2850
|
import tamerinsets
|
|
@@ -2758,7 +2860,9 @@ class ViewController: UIViewController {
|
|
|
2758
2860
|
additionalSafeAreaInsets = .zero
|
|
2759
2861
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
2760
2862
|
view.preservesSuperviewLayoutMargins = false
|
|
2761
|
-
|
|
2863
|
+
if #available(iOS 15.0, *) {
|
|
2864
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
2865
|
+
}
|
|
2762
2866
|
}
|
|
2763
2867
|
|
|
2764
2868
|
override func viewDidLayoutSubviews() {
|
|
@@ -2813,7 +2917,7 @@ class ViewController: UIViewController {
|
|
|
2813
2917
|
}
|
|
2814
2918
|
}
|
|
2815
2919
|
`);
|
|
2816
|
-
writeFile2(
|
|
2920
|
+
writeFile2(path14.join(projectDir, "LynxProvider.swift"), `
|
|
2817
2921
|
import Foundation
|
|
2818
2922
|
|
|
2819
2923
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2832,7 +2936,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2832
2936
|
}
|
|
2833
2937
|
}
|
|
2834
2938
|
`);
|
|
2835
|
-
writeFile2(
|
|
2939
|
+
writeFile2(path14.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2836
2940
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2837
2941
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2838
2942
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2872,7 +2976,7 @@ final class LynxInitProcessor {
|
|
|
2872
2976
|
}
|
|
2873
2977
|
`);
|
|
2874
2978
|
}
|
|
2875
|
-
writeFile2(
|
|
2979
|
+
writeFile2(path14.join(projectDir, bridgingHeader), `
|
|
2876
2980
|
#import <Lynx/LynxConfig.h>
|
|
2877
2981
|
#import <Lynx/LynxEnv.h>
|
|
2878
2982
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2881,7 +2985,7 @@ final class LynxInitProcessor {
|
|
|
2881
2985
|
#import <SDWebImage/SDWebImage.h>
|
|
2882
2986
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2883
2987
|
`);
|
|
2884
|
-
writeFile2(
|
|
2988
|
+
writeFile2(path14.join(projectDir, "Info.plist"), `
|
|
2885
2989
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2886
2990
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2887
2991
|
<plist version="1.0">
|
|
@@ -2939,21 +3043,21 @@ final class LynxInitProcessor {
|
|
|
2939
3043
|
</dict>
|
|
2940
3044
|
</plist>
|
|
2941
3045
|
`);
|
|
2942
|
-
const appIconDir =
|
|
2943
|
-
|
|
3046
|
+
const appIconDir = path14.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
3047
|
+
fs13.mkdirSync(appIconDir, { recursive: true });
|
|
2944
3048
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
2945
3049
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
2946
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");
|
|
2947
3051
|
} else {
|
|
2948
|
-
writeFile2(
|
|
3052
|
+
writeFile2(path14.join(appIconDir, "Contents.json"), `
|
|
2949
3053
|
{
|
|
2950
3054
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
2951
3055
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
2952
3056
|
}
|
|
2953
3057
|
`);
|
|
2954
3058
|
}
|
|
2955
|
-
|
|
2956
|
-
writeFile2(
|
|
3059
|
+
fs13.mkdirSync(xcodeprojDir, { recursive: true });
|
|
3060
|
+
writeFile2(path14.join(xcodeprojDir, "project.pbxproj"), `
|
|
2957
3061
|
// !$*UTF8*$!
|
|
2958
3062
|
{
|
|
2959
3063
|
archiveVersion = 1;
|
|
@@ -3239,8 +3343,8 @@ final class LynxInitProcessor {
|
|
|
3239
3343
|
var create_default2 = create2;
|
|
3240
3344
|
|
|
3241
3345
|
// src/ios/autolink.ts
|
|
3242
|
-
import
|
|
3243
|
-
import
|
|
3346
|
+
import fs15 from "fs";
|
|
3347
|
+
import path16 from "path";
|
|
3244
3348
|
import { execSync as execSync6 } from "child_process";
|
|
3245
3349
|
|
|
3246
3350
|
// src/common/hostNativeModulesManifest.ts
|
|
@@ -3251,8 +3355,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
|
|
|
3251
3355
|
}
|
|
3252
3356
|
|
|
3253
3357
|
// src/ios/syncHost.ts
|
|
3254
|
-
import
|
|
3255
|
-
import
|
|
3358
|
+
import fs14 from "fs";
|
|
3359
|
+
import path15 from "path";
|
|
3256
3360
|
import crypto from "crypto";
|
|
3257
3361
|
function deterministicUUID(seed) {
|
|
3258
3362
|
return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
|
|
@@ -3300,7 +3404,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3300
3404
|
`;
|
|
3301
3405
|
}
|
|
3302
3406
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3303
|
-
let content =
|
|
3407
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3304
3408
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3305
3409
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3306
3410
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3337,11 +3441,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3337
3441
|
);
|
|
3338
3442
|
content = content.replace(groupPattern, `$1
|
|
3339
3443
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3340
|
-
|
|
3444
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3341
3445
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3342
3446
|
}
|
|
3343
3447
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3344
|
-
let content =
|
|
3448
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3345
3449
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3346
3450
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3347
3451
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3366,11 +3470,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3366
3470
|
);
|
|
3367
3471
|
content = content.replace(groupPattern, `$1
|
|
3368
3472
|
${fileRefUUID} /* ${filename} */,`);
|
|
3369
|
-
|
|
3473
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3370
3474
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3371
3475
|
}
|
|
3372
3476
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3373
|
-
let content =
|
|
3477
|
+
let content = fs14.readFileSync(pbxprojPath, "utf8");
|
|
3374
3478
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3375
3479
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3376
3480
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3395,12 +3499,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3395
3499
|
);
|
|
3396
3500
|
content = content.replace(groupPattern, `$1
|
|
3397
3501
|
${fileRefUUID} /* ${filename} */,`);
|
|
3398
|
-
|
|
3502
|
+
fs14.writeFileSync(pbxprojPath, content, "utf8");
|
|
3399
3503
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3400
3504
|
}
|
|
3401
3505
|
function writeFile(filePath, content) {
|
|
3402
|
-
|
|
3403
|
-
|
|
3506
|
+
fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
|
|
3507
|
+
fs14.writeFileSync(filePath, content, "utf8");
|
|
3404
3508
|
}
|
|
3405
3509
|
function getAppDelegateSwift() {
|
|
3406
3510
|
return `import UIKit
|
|
@@ -3460,7 +3564,9 @@ class ViewController: UIViewController {
|
|
|
3460
3564
|
additionalSafeAreaInsets = .zero
|
|
3461
3565
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
3462
3566
|
view.preservesSuperviewLayoutMargins = false
|
|
3463
|
-
|
|
3567
|
+
if #available(iOS 15.0, *) {
|
|
3568
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
3569
|
+
}
|
|
3464
3570
|
}
|
|
3465
3571
|
|
|
3466
3572
|
override func viewDidLayoutSubviews() {
|
|
@@ -3521,6 +3627,7 @@ function getDevViewControllerSwift() {
|
|
|
3521
3627
|
import Lynx
|
|
3522
3628
|
import tamerdevclient
|
|
3523
3629
|
import tamerinsets
|
|
3630
|
+
import tamersystemui
|
|
3524
3631
|
|
|
3525
3632
|
class ViewController: UIViewController {
|
|
3526
3633
|
private var lynxView: LynxView?
|
|
@@ -3533,7 +3640,9 @@ class ViewController: UIViewController {
|
|
|
3533
3640
|
additionalSafeAreaInsets = .zero
|
|
3534
3641
|
view.insetsLayoutMarginsFromSafeArea = false
|
|
3535
3642
|
view.preservesSuperviewLayoutMargins = false
|
|
3536
|
-
|
|
3643
|
+
if #available(iOS 15.0, *) {
|
|
3644
|
+
viewRespectsSystemMinimumLayoutMargins = false
|
|
3645
|
+
}
|
|
3537
3646
|
setupLynxView()
|
|
3538
3647
|
setupDevClientModule()
|
|
3539
3648
|
}
|
|
@@ -3550,7 +3659,7 @@ class ViewController: UIViewController {
|
|
|
3550
3659
|
TamerInsetsModule.reRequestInsets()
|
|
3551
3660
|
}
|
|
3552
3661
|
|
|
3553
|
-
override var preferredStatusBarStyle: UIStatusBarStyle {
|
|
3662
|
+
override var preferredStatusBarStyle: UIStatusBarStyle { SystemUIModule.statusBarStyleForHost }
|
|
3554
3663
|
|
|
3555
3664
|
private func setupLynxView() {
|
|
3556
3665
|
let size = fullscreenBounds().size
|
|
@@ -3612,8 +3721,8 @@ class ViewController: UIViewController {
|
|
|
3612
3721
|
`;
|
|
3613
3722
|
}
|
|
3614
3723
|
function patchInfoPlist(infoPlistPath) {
|
|
3615
|
-
if (!
|
|
3616
|
-
let content =
|
|
3724
|
+
if (!fs14.existsSync(infoPlistPath)) return;
|
|
3725
|
+
let content = fs14.readFileSync(infoPlistPath, "utf8");
|
|
3617
3726
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3618
3727
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3619
3728
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3645,7 +3754,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3645
3754
|
</plist>`);
|
|
3646
3755
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3647
3756
|
}
|
|
3648
|
-
|
|
3757
|
+
fs14.writeFileSync(infoPlistPath, content, "utf8");
|
|
3649
3758
|
}
|
|
3650
3759
|
function getSimpleLynxProviderSwift() {
|
|
3651
3760
|
return `import Foundation
|
|
@@ -3670,9 +3779,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3670
3779
|
}
|
|
3671
3780
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3672
3781
|
if (devClientPkg) {
|
|
3673
|
-
const tplPath =
|
|
3674
|
-
if (
|
|
3675
|
-
let content =
|
|
3782
|
+
const tplPath = path15.join(devClientPkg, "ios", "templates", templateName);
|
|
3783
|
+
if (fs14.existsSync(tplPath)) {
|
|
3784
|
+
let content = fs14.readFileSync(tplPath, "utf8");
|
|
3676
3785
|
for (const [k, v] of Object.entries(vars)) {
|
|
3677
3786
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3678
3787
|
}
|
|
@@ -3690,19 +3799,19 @@ function syncHostIos(opts) {
|
|
|
3690
3799
|
if (!appName) {
|
|
3691
3800
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3692
3801
|
}
|
|
3693
|
-
const projectDir =
|
|
3694
|
-
const infoPlistPath =
|
|
3695
|
-
if (!
|
|
3802
|
+
const projectDir = path15.join(resolved.iosDir, appName);
|
|
3803
|
+
const infoPlistPath = path15.join(projectDir, "Info.plist");
|
|
3804
|
+
if (!fs14.existsSync(projectDir)) {
|
|
3696
3805
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3697
3806
|
}
|
|
3698
|
-
const pbxprojPath =
|
|
3699
|
-
const baseLprojDir =
|
|
3700
|
-
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");
|
|
3701
3810
|
patchInfoPlist(infoPlistPath);
|
|
3702
|
-
writeFile(
|
|
3703
|
-
writeFile(
|
|
3704
|
-
if (!
|
|
3705
|
-
|
|
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 });
|
|
3706
3815
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3707
3816
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3708
3817
|
}
|
|
@@ -3711,33 +3820,33 @@ function syncHostIos(opts) {
|
|
|
3711
3820
|
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3712
3821
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3713
3822
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3714
|
-
writeFile(
|
|
3715
|
-
writeFile(
|
|
3823
|
+
writeFile(path15.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3824
|
+
writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3716
3825
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3717
3826
|
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3718
3827
|
if (devTPContent) {
|
|
3719
|
-
writeFile(
|
|
3828
|
+
writeFile(path15.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3720
3829
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3721
3830
|
}
|
|
3722
3831
|
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3723
3832
|
if (projectVCContent) {
|
|
3724
|
-
writeFile(
|
|
3833
|
+
writeFile(path15.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3725
3834
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3726
3835
|
}
|
|
3727
3836
|
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3728
3837
|
if (devCMContent) {
|
|
3729
|
-
writeFile(
|
|
3838
|
+
writeFile(path15.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3730
3839
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3731
3840
|
}
|
|
3732
3841
|
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3733
3842
|
if (qrContent) {
|
|
3734
|
-
writeFile(
|
|
3843
|
+
writeFile(path15.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3735
3844
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
3736
3845
|
}
|
|
3737
3846
|
console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
|
|
3738
3847
|
} else {
|
|
3739
|
-
writeFile(
|
|
3740
|
-
writeFile(
|
|
3848
|
+
writeFile(path15.join(projectDir, "ViewController.swift"), getViewControllerSwift());
|
|
3849
|
+
writeFile(path15.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3741
3850
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3742
3851
|
console.log("\u2705 Synced iOS host app controller files");
|
|
3743
3852
|
}
|
|
@@ -3756,11 +3865,11 @@ var autolink2 = () => {
|
|
|
3756
3865
|
const projectRoot = resolved.projectRoot;
|
|
3757
3866
|
const iosProjectPath = resolved.iosDir;
|
|
3758
3867
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
3759
|
-
if (!
|
|
3868
|
+
if (!fs15.existsSync(filePath)) {
|
|
3760
3869
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
3761
3870
|
return;
|
|
3762
3871
|
}
|
|
3763
|
-
let fileContent =
|
|
3872
|
+
let fileContent = fs15.readFileSync(filePath, "utf8");
|
|
3764
3873
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3765
3874
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3766
3875
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3780,33 +3889,33 @@ ${replacementBlock}
|
|
|
3780
3889
|
`;
|
|
3781
3890
|
}
|
|
3782
3891
|
} else {
|
|
3783
|
-
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.`);
|
|
3784
3893
|
fileContent += `
|
|
3785
3894
|
${replacementBlock}
|
|
3786
3895
|
`;
|
|
3787
3896
|
}
|
|
3788
|
-
|
|
3789
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
3897
|
+
fs15.writeFileSync(filePath, fileContent, "utf8");
|
|
3898
|
+
console.log(`\u2705 Updated autolinked section in ${path16.basename(filePath)}`);
|
|
3790
3899
|
}
|
|
3791
3900
|
function resolvePodDirectory(pkg) {
|
|
3792
|
-
const configuredDir =
|
|
3793
|
-
if (
|
|
3901
|
+
const configuredDir = path16.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
|
|
3902
|
+
if (fs15.existsSync(configuredDir)) {
|
|
3794
3903
|
return configuredDir;
|
|
3795
3904
|
}
|
|
3796
|
-
const iosDir =
|
|
3797
|
-
if (
|
|
3905
|
+
const iosDir = path16.join(pkg.packagePath, "ios");
|
|
3906
|
+
if (fs15.existsSync(iosDir)) {
|
|
3798
3907
|
const stack = [iosDir];
|
|
3799
3908
|
while (stack.length > 0) {
|
|
3800
3909
|
const current = stack.pop();
|
|
3801
3910
|
try {
|
|
3802
|
-
const entries =
|
|
3911
|
+
const entries = fs15.readdirSync(current, { withFileTypes: true });
|
|
3803
3912
|
const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
|
|
3804
3913
|
if (podspec) {
|
|
3805
3914
|
return current;
|
|
3806
3915
|
}
|
|
3807
3916
|
for (const entry of entries) {
|
|
3808
3917
|
if (entry.isDirectory()) {
|
|
3809
|
-
stack.push(
|
|
3918
|
+
stack.push(path16.join(current, entry.name));
|
|
3810
3919
|
}
|
|
3811
3920
|
}
|
|
3812
3921
|
} catch {
|
|
@@ -3817,9 +3926,9 @@ ${replacementBlock}
|
|
|
3817
3926
|
}
|
|
3818
3927
|
function resolvePodName(pkg) {
|
|
3819
3928
|
const fullPodspecDir = resolvePodDirectory(pkg);
|
|
3820
|
-
if (
|
|
3929
|
+
if (fs15.existsSync(fullPodspecDir)) {
|
|
3821
3930
|
try {
|
|
3822
|
-
const files =
|
|
3931
|
+
const files = fs15.readdirSync(fullPodspecDir);
|
|
3823
3932
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
3824
3933
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
3825
3934
|
} catch {
|
|
@@ -3828,13 +3937,13 @@ ${replacementBlock}
|
|
|
3828
3937
|
return pkg.name.split("/").pop().replace(/-/g, "");
|
|
3829
3938
|
}
|
|
3830
3939
|
function updatePodfile(packages) {
|
|
3831
|
-
const podfilePath =
|
|
3940
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3832
3941
|
let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
|
|
3833
3942
|
# Manual edits will be overwritten.`;
|
|
3834
3943
|
const iosPackages = packages.filter((p) => p.config.ios);
|
|
3835
3944
|
if (iosPackages.length > 0) {
|
|
3836
3945
|
iosPackages.forEach((pkg) => {
|
|
3837
|
-
const relativePath =
|
|
3946
|
+
const relativePath = path16.relative(iosProjectPath, resolvePodDirectory(pkg));
|
|
3838
3947
|
const podName = resolvePodName(pkg);
|
|
3839
3948
|
scriptContent += `
|
|
3840
3949
|
pod '${podName}', :path => '${relativePath}'`;
|
|
@@ -3846,9 +3955,9 @@ ${replacementBlock}
|
|
|
3846
3955
|
updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
|
|
3847
3956
|
}
|
|
3848
3957
|
function ensureXElementPod() {
|
|
3849
|
-
const podfilePath =
|
|
3850
|
-
if (!
|
|
3851
|
-
let content =
|
|
3958
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3959
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
3960
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3852
3961
|
if (content.includes("pod 'XElement'")) return;
|
|
3853
3962
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
3854
3963
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -3873,13 +3982,13 @@ ${replacementBlock}
|
|
|
3873
3982
|
`;
|
|
3874
3983
|
}
|
|
3875
3984
|
}
|
|
3876
|
-
|
|
3985
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3877
3986
|
console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
|
|
3878
3987
|
}
|
|
3879
3988
|
function ensureLynxPatchInPodfile() {
|
|
3880
|
-
const podfilePath =
|
|
3881
|
-
if (!
|
|
3882
|
-
let content =
|
|
3989
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
3990
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
3991
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3883
3992
|
if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
|
|
3884
3993
|
const patch = `
|
|
3885
3994
|
Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
|
|
@@ -3891,13 +4000,13 @@ ${replacementBlock}
|
|
|
3891
4000
|
end`;
|
|
3892
4001
|
content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
|
|
3893
4002
|
$2`);
|
|
3894
|
-
|
|
4003
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3895
4004
|
console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
|
|
3896
4005
|
}
|
|
3897
4006
|
function ensurePodBuildSettings() {
|
|
3898
|
-
const podfilePath =
|
|
3899
|
-
if (!
|
|
3900
|
-
let content =
|
|
4007
|
+
const podfilePath = path16.join(iosProjectPath, "Podfile");
|
|
4008
|
+
if (!fs15.existsSync(podfilePath)) return;
|
|
4009
|
+
let content = fs15.readFileSync(podfilePath, "utf8");
|
|
3901
4010
|
let changed = false;
|
|
3902
4011
|
if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
|
|
3903
4012
|
content = content.replace(
|
|
@@ -3940,7 +4049,7 @@ $2`);
|
|
|
3940
4049
|
changed = true;
|
|
3941
4050
|
}
|
|
3942
4051
|
if (changed) {
|
|
3943
|
-
|
|
4052
|
+
fs15.writeFileSync(podfilePath, content, "utf8");
|
|
3944
4053
|
console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
|
|
3945
4054
|
}
|
|
3946
4055
|
}
|
|
@@ -3948,10 +4057,10 @@ $2`);
|
|
|
3948
4057
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
3949
4058
|
const candidatePaths = [];
|
|
3950
4059
|
if (appNameFromConfig) {
|
|
3951
|
-
candidatePaths.push(
|
|
4060
|
+
candidatePaths.push(path16.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
3952
4061
|
}
|
|
3953
|
-
candidatePaths.push(
|
|
3954
|
-
const found = candidatePaths.find((p) =>
|
|
4062
|
+
candidatePaths.push(path16.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
4063
|
+
const found = candidatePaths.find((p) => fs15.existsSync(p));
|
|
3955
4064
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
3956
4065
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
3957
4066
|
const seenModules = /* @__PURE__ */ new Set();
|
|
@@ -3990,7 +4099,7 @@ $2`);
|
|
|
3990
4099
|
const podName = resolvePodName(pkg);
|
|
3991
4100
|
return `import ${podName}`;
|
|
3992
4101
|
}).join("\n");
|
|
3993
|
-
const fileContent =
|
|
4102
|
+
const fileContent = fs15.readFileSync(filePath, "utf8");
|
|
3994
4103
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
3995
4104
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
3996
4105
|
return;
|
|
@@ -4027,8 +4136,8 @@ ${after}`;
|
|
|
4027
4136
|
${fileContent}`;
|
|
4028
4137
|
}
|
|
4029
4138
|
}
|
|
4030
|
-
|
|
4031
|
-
console.log(`\u2705 Updated imports in ${
|
|
4139
|
+
fs15.writeFileSync(filePath, newContent, "utf8");
|
|
4140
|
+
console.log(`\u2705 Updated imports in ${path16.basename(filePath)}`);
|
|
4032
4141
|
}
|
|
4033
4142
|
updateImportsSection(lynxInitPath, importPackages);
|
|
4034
4143
|
if (importPackages.length === 0) {
|
|
@@ -4072,7 +4181,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4072
4181
|
} else {
|
|
4073
4182
|
devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
|
|
4074
4183
|
}
|
|
4075
|
-
if (
|
|
4184
|
+
if (fs15.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
|
|
4076
4185
|
updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
|
|
4077
4186
|
}
|
|
4078
4187
|
}
|
|
@@ -4080,13 +4189,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4080
4189
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4081
4190
|
const candidates = [];
|
|
4082
4191
|
if (appNameFromConfig) {
|
|
4083
|
-
candidates.push(
|
|
4192
|
+
candidates.push(path16.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
4084
4193
|
}
|
|
4085
|
-
candidates.push(
|
|
4086
|
-
return candidates.find((p) =>
|
|
4194
|
+
candidates.push(path16.join(iosProjectPath, "Info.plist"));
|
|
4195
|
+
return candidates.find((p) => fs15.existsSync(p)) ?? null;
|
|
4087
4196
|
}
|
|
4088
4197
|
function readPlistXml(plistPath) {
|
|
4089
|
-
return
|
|
4198
|
+
return fs15.readFileSync(plistPath, "utf8");
|
|
4090
4199
|
}
|
|
4091
4200
|
function syncInfoPlistPermissions(packages) {
|
|
4092
4201
|
const plistPath = findInfoPlist();
|
|
@@ -4117,7 +4226,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4117
4226
|
added++;
|
|
4118
4227
|
}
|
|
4119
4228
|
if (added > 0) {
|
|
4120
|
-
|
|
4229
|
+
fs15.writeFileSync(plistPath, plist, "utf8");
|
|
4121
4230
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
4122
4231
|
}
|
|
4123
4232
|
}
|
|
@@ -4164,16 +4273,16 @@ ${schemesXml}
|
|
|
4164
4273
|
$1`
|
|
4165
4274
|
);
|
|
4166
4275
|
}
|
|
4167
|
-
|
|
4276
|
+
fs15.writeFileSync(plistPath, plist, "utf8");
|
|
4168
4277
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
4169
4278
|
}
|
|
4170
4279
|
function runPodInstall(forcePath) {
|
|
4171
|
-
const podfilePath = forcePath ??
|
|
4172
|
-
if (!
|
|
4280
|
+
const podfilePath = forcePath ?? path16.join(iosProjectPath, "Podfile");
|
|
4281
|
+
if (!fs15.existsSync(podfilePath)) {
|
|
4173
4282
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
4174
4283
|
return;
|
|
4175
4284
|
}
|
|
4176
|
-
const cwd =
|
|
4285
|
+
const cwd = path16.dirname(podfilePath);
|
|
4177
4286
|
try {
|
|
4178
4287
|
console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
|
|
4179
4288
|
try {
|
|
@@ -4207,8 +4316,8 @@ $1`
|
|
|
4207
4316
|
syncInfoPlistUrlSchemes();
|
|
4208
4317
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4209
4318
|
if (appNameFromConfig) {
|
|
4210
|
-
const appPodfile =
|
|
4211
|
-
if (
|
|
4319
|
+
const appPodfile = path16.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
4320
|
+
if (fs15.existsSync(appPodfile)) {
|
|
4212
4321
|
runPodInstall(appPodfile);
|
|
4213
4322
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
4214
4323
|
return;
|
|
@@ -4223,13 +4332,13 @@ $1`
|
|
|
4223
4332
|
const appFolder = resolved.config.ios?.appName;
|
|
4224
4333
|
if (!hasDevClient || !appFolder) return;
|
|
4225
4334
|
const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
|
|
4226
|
-
const appDir =
|
|
4227
|
-
|
|
4228
|
-
const manifestPath =
|
|
4229
|
-
|
|
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");
|
|
4230
4339
|
console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
|
|
4231
|
-
const pbxprojPath =
|
|
4232
|
-
if (
|
|
4340
|
+
const pbxprojPath = path16.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
|
|
4341
|
+
if (fs15.existsSync(pbxprojPath)) {
|
|
4233
4342
|
addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4234
4343
|
}
|
|
4235
4344
|
}
|
|
@@ -4238,8 +4347,8 @@ $1`
|
|
|
4238
4347
|
var autolink_default2 = autolink2;
|
|
4239
4348
|
|
|
4240
4349
|
// src/ios/bundle.ts
|
|
4241
|
-
import
|
|
4242
|
-
import
|
|
4350
|
+
import fs16 from "fs";
|
|
4351
|
+
import path17 from "path";
|
|
4243
4352
|
import { execSync as execSync7 } from "child_process";
|
|
4244
4353
|
function bundleAndDeploy2(opts = {}) {
|
|
4245
4354
|
const release = opts.release === true || opts.production === true;
|
|
@@ -4257,18 +4366,22 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4257
4366
|
const includeDevClient = !release && !!devClientPkg;
|
|
4258
4367
|
const appName = resolved.config.ios.appName;
|
|
4259
4368
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
4260
|
-
const destinationDir =
|
|
4261
|
-
const destinationBundlePath =
|
|
4369
|
+
const destinationDir = path17.join(resolved.iosDir, appName);
|
|
4370
|
+
const destinationBundlePath = path17.join(destinationDir, resolved.lynxBundleFile);
|
|
4262
4371
|
syncHost_default({ release, includeDevClient });
|
|
4263
4372
|
autolink_default2();
|
|
4264
4373
|
const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
|
|
4265
4374
|
if (iconPaths) {
|
|
4266
|
-
const appIconDir =
|
|
4375
|
+
const appIconDir = path17.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
4267
4376
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
4268
4377
|
console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
|
|
4269
4378
|
}
|
|
4270
4379
|
}
|
|
4271
4380
|
try {
|
|
4381
|
+
const lynxTsconfig = path17.join(resolved.lynxProjectDir, "tsconfig.json");
|
|
4382
|
+
if (fs16.existsSync(lynxTsconfig)) {
|
|
4383
|
+
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
4384
|
+
}
|
|
4272
4385
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
4273
4386
|
execSync7("npm run build", { stdio: "inherit", cwd: resolved.lynxProjectDir });
|
|
4274
4387
|
console.log("\u2705 Build completed successfully.");
|
|
@@ -4277,40 +4390,40 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4277
4390
|
process.exit(1);
|
|
4278
4391
|
}
|
|
4279
4392
|
try {
|
|
4280
|
-
if (!
|
|
4393
|
+
if (!fs16.existsSync(sourceBundlePath)) {
|
|
4281
4394
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
4282
4395
|
process.exit(1);
|
|
4283
4396
|
}
|
|
4284
|
-
if (!
|
|
4397
|
+
if (!fs16.existsSync(destinationDir)) {
|
|
4285
4398
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
4286
4399
|
process.exit(1);
|
|
4287
4400
|
}
|
|
4288
|
-
const distDir =
|
|
4401
|
+
const distDir = path17.dirname(sourceBundlePath);
|
|
4289
4402
|
console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
|
|
4290
4403
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
4291
4404
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
4292
|
-
const pbxprojPath =
|
|
4293
|
-
if (
|
|
4405
|
+
const pbxprojPath = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4406
|
+
if (fs16.existsSync(pbxprojPath)) {
|
|
4294
4407
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
4295
|
-
for (const entry of
|
|
4296
|
-
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;
|
|
4297
4410
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
4298
4411
|
}
|
|
4299
4412
|
}
|
|
4300
4413
|
if (includeDevClient && devClientPkg) {
|
|
4301
|
-
const devClientBundle =
|
|
4414
|
+
const devClientBundle = path17.join(destinationDir, "dev-client.lynx.bundle");
|
|
4302
4415
|
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
4303
4416
|
try {
|
|
4304
4417
|
execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
4305
4418
|
} catch {
|
|
4306
4419
|
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
4307
4420
|
}
|
|
4308
|
-
const builtBundle =
|
|
4309
|
-
if (
|
|
4310
|
-
|
|
4421
|
+
const builtBundle = path17.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
4422
|
+
if (fs16.existsSync(builtBundle)) {
|
|
4423
|
+
fs16.copyFileSync(builtBundle, devClientBundle);
|
|
4311
4424
|
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
4312
|
-
const pbxprojPath2 =
|
|
4313
|
-
if (
|
|
4425
|
+
const pbxprojPath2 = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4426
|
+
if (fs16.existsSync(pbxprojPath2)) {
|
|
4314
4427
|
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
4315
4428
|
}
|
|
4316
4429
|
}
|
|
@@ -4324,8 +4437,8 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4324
4437
|
var bundle_default2 = bundleAndDeploy2;
|
|
4325
4438
|
|
|
4326
4439
|
// src/ios/build.ts
|
|
4327
|
-
import
|
|
4328
|
-
import
|
|
4440
|
+
import fs17 from "fs";
|
|
4441
|
+
import path18 from "path";
|
|
4329
4442
|
import os3 from "os";
|
|
4330
4443
|
import { execSync as execSync8 } from "child_process";
|
|
4331
4444
|
function hostArch() {
|
|
@@ -4356,11 +4469,11 @@ async function buildIpa(opts = {}) {
|
|
|
4356
4469
|
const configuration = release ? "Release" : "Debug";
|
|
4357
4470
|
bundle_default2({ release, production: opts.production });
|
|
4358
4471
|
const scheme = appName;
|
|
4359
|
-
const workspacePath =
|
|
4360
|
-
const projectPath =
|
|
4361
|
-
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;
|
|
4362
4475
|
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
4363
|
-
const derivedDataPath =
|
|
4476
|
+
const derivedDataPath = path18.join(iosDir, "build");
|
|
4364
4477
|
const sdk = opts.install ? "iphonesimulator" : "iphoneos";
|
|
4365
4478
|
const signingArgs = opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
|
|
4366
4479
|
const archFlag = opts.install ? `-arch ${hostArch()} ` : "";
|
|
@@ -4376,14 +4489,14 @@ async function buildIpa(opts = {}) {
|
|
|
4376
4489
|
);
|
|
4377
4490
|
console.log(`\u2705 Build completed.`);
|
|
4378
4491
|
if (opts.install) {
|
|
4379
|
-
const appGlob =
|
|
4492
|
+
const appGlob = path18.join(
|
|
4380
4493
|
derivedDataPath,
|
|
4381
4494
|
"Build",
|
|
4382
4495
|
"Products",
|
|
4383
4496
|
`${configuration}-iphonesimulator`,
|
|
4384
4497
|
`${appName}.app`
|
|
4385
4498
|
);
|
|
4386
|
-
if (!
|
|
4499
|
+
if (!fs17.existsSync(appGlob)) {
|
|
4387
4500
|
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
4388
4501
|
process.exit(1);
|
|
4389
4502
|
}
|
|
@@ -4406,8 +4519,8 @@ async function buildIpa(opts = {}) {
|
|
|
4406
4519
|
var build_default2 = buildIpa;
|
|
4407
4520
|
|
|
4408
4521
|
// src/common/init.tsx
|
|
4409
|
-
import
|
|
4410
|
-
import
|
|
4522
|
+
import fs18 from "fs";
|
|
4523
|
+
import path19 from "path";
|
|
4411
4524
|
import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
4412
4525
|
import { render, Text as Text9, Box as Box8 } from "ink";
|
|
4413
4526
|
|
|
@@ -4720,34 +4833,23 @@ function InitWizard() {
|
|
|
4720
4833
|
paths: { androidDir: "android", iosDir: "ios" }
|
|
4721
4834
|
};
|
|
4722
4835
|
if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
|
|
4723
|
-
const configPath =
|
|
4724
|
-
|
|
4836
|
+
const configPath = path19.join(process.cwd(), "tamer.config.json");
|
|
4837
|
+
fs18.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4725
4838
|
const lines = [`Generated tamer.config.json at ${configPath}`];
|
|
4726
4839
|
const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
|
|
4727
4840
|
const tsconfigCandidates = lynxProject.trim() ? [
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
] : [
|
|
4731
|
-
function parseTsconfigJson(raw) {
|
|
4732
|
-
try {
|
|
4733
|
-
return JSON.parse(raw);
|
|
4734
|
-
} catch {
|
|
4735
|
-
const noTrailingCommas = raw.replace(/,\s*([\]}])/g, "$1");
|
|
4736
|
-
return JSON.parse(noTrailingCommas);
|
|
4737
|
-
}
|
|
4738
|
-
}
|
|
4841
|
+
path19.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
|
|
4842
|
+
path19.join(process.cwd(), "tsconfig.json")
|
|
4843
|
+
] : [path19.join(process.cwd(), "tsconfig.json")];
|
|
4739
4844
|
for (const tsconfigPath of tsconfigCandidates) {
|
|
4740
|
-
if (!
|
|
4845
|
+
if (!fs18.existsSync(tsconfigPath)) continue;
|
|
4741
4846
|
try {
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
tsconfig.include = arr;
|
|
4749
|
-
fs17.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
4750
|
-
lines.push(`Updated ${path18.relative(process.cwd(), tsconfigPath)} for tamer types`);
|
|
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
|
+
}
|
|
4751
4853
|
break;
|
|
4752
4854
|
} catch (e) {
|
|
4753
4855
|
lines.push(`Could not update ${tsconfigPath}: ${e.message}`);
|
|
@@ -4783,7 +4885,8 @@ function InitWizard() {
|
|
|
4783
4885
|
defaultValue: androidAppName,
|
|
4784
4886
|
onSubmitValue: (v) => setAndroidAppName(v),
|
|
4785
4887
|
onSubmit: () => setStep("android-pkg")
|
|
4786
|
-
}
|
|
4888
|
+
},
|
|
4889
|
+
step
|
|
4787
4890
|
) });
|
|
4788
4891
|
}
|
|
4789
4892
|
if (step === "android-pkg") {
|
|
@@ -4804,7 +4907,8 @@ function InitWizard() {
|
|
|
4804
4907
|
setPkgError(void 0);
|
|
4805
4908
|
},
|
|
4806
4909
|
onSubmit: () => setStep("android-sdk")
|
|
4807
|
-
}
|
|
4910
|
+
},
|
|
4911
|
+
step
|
|
4808
4912
|
) });
|
|
4809
4913
|
}
|
|
4810
4914
|
if (step === "android-sdk") {
|
|
@@ -4820,7 +4924,8 @@ function InitWizard() {
|
|
|
4820
4924
|
},
|
|
4821
4925
|
onSubmit: () => setStep("ios-reuse"),
|
|
4822
4926
|
hint: sdkHint
|
|
4823
|
-
}
|
|
4927
|
+
},
|
|
4928
|
+
step
|
|
4824
4929
|
) });
|
|
4825
4930
|
}
|
|
4826
4931
|
if (step === "ios-reuse") {
|
|
@@ -4850,7 +4955,8 @@ function InitWizard() {
|
|
|
4850
4955
|
defaultValue: iosAppName,
|
|
4851
4956
|
onSubmitValue: (v) => setIosAppName(v),
|
|
4852
4957
|
onSubmit: () => setStep("ios-bundle")
|
|
4853
|
-
}
|
|
4958
|
+
},
|
|
4959
|
+
step
|
|
4854
4960
|
) });
|
|
4855
4961
|
}
|
|
4856
4962
|
if (step === "ios-bundle") {
|
|
@@ -4871,7 +4977,8 @@ function InitWizard() {
|
|
|
4871
4977
|
setBundleError(void 0);
|
|
4872
4978
|
},
|
|
4873
4979
|
onSubmit: () => setStep("lynx-path")
|
|
4874
|
-
}
|
|
4980
|
+
},
|
|
4981
|
+
step
|
|
4875
4982
|
) });
|
|
4876
4983
|
}
|
|
4877
4984
|
if (step === "lynx-path") {
|
|
@@ -4883,7 +4990,8 @@ function InitWizard() {
|
|
|
4883
4990
|
onSubmitValue: (v) => setLynxProject(v),
|
|
4884
4991
|
onSubmit: () => setStep("saving"),
|
|
4885
4992
|
hint: "Press Enter with empty to skip"
|
|
4886
|
-
}
|
|
4993
|
+
},
|
|
4994
|
+
step
|
|
4887
4995
|
) });
|
|
4888
4996
|
}
|
|
4889
4997
|
if (step === "saving") {
|
|
@@ -4900,8 +5008,8 @@ async function init() {
|
|
|
4900
5008
|
}
|
|
4901
5009
|
|
|
4902
5010
|
// src/common/create.ts
|
|
4903
|
-
import
|
|
4904
|
-
import
|
|
5011
|
+
import fs19 from "fs";
|
|
5012
|
+
import path20 from "path";
|
|
4905
5013
|
import readline from "readline";
|
|
4906
5014
|
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
4907
5015
|
function ask(question) {
|
|
@@ -4963,13 +5071,13 @@ async function create3(opts) {
|
|
|
4963
5071
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
4964
5072
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
4965
5073
|
const cwd = process.cwd();
|
|
4966
|
-
const root =
|
|
4967
|
-
if (
|
|
5074
|
+
const root = path20.join(cwd, extName);
|
|
5075
|
+
if (fs19.existsSync(root)) {
|
|
4968
5076
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
4969
5077
|
rl.close();
|
|
4970
5078
|
process.exit(1);
|
|
4971
5079
|
}
|
|
4972
|
-
|
|
5080
|
+
fs19.mkdirSync(root, { recursive: true });
|
|
4973
5081
|
const lynxExt = {
|
|
4974
5082
|
platforms: {
|
|
4975
5083
|
android: {
|
|
@@ -4984,7 +5092,7 @@ async function create3(opts) {
|
|
|
4984
5092
|
web: {}
|
|
4985
5093
|
}
|
|
4986
5094
|
};
|
|
4987
|
-
|
|
5095
|
+
fs19.writeFileSync(path20.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
4988
5096
|
const pkg = {
|
|
4989
5097
|
name: extName,
|
|
4990
5098
|
version: "0.0.1",
|
|
@@ -4997,20 +5105,20 @@ async function create3(opts) {
|
|
|
4997
5105
|
engines: { node: ">=18" }
|
|
4998
5106
|
};
|
|
4999
5107
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5000
|
-
|
|
5108
|
+
fs19.writeFileSync(path20.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5001
5109
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5002
5110
|
const hasSrc = includeModule || includeElement || includeService;
|
|
5003
5111
|
if (hasSrc) {
|
|
5004
|
-
|
|
5112
|
+
fs19.mkdirSync(path20.join(root, "src"), { recursive: true });
|
|
5005
5113
|
}
|
|
5006
5114
|
if (includeModule) {
|
|
5007
|
-
|
|
5115
|
+
fs19.writeFileSync(path20.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5008
5116
|
export declare class ${simpleModuleName} {
|
|
5009
5117
|
// Add your module methods here
|
|
5010
5118
|
}
|
|
5011
5119
|
`);
|
|
5012
|
-
|
|
5013
|
-
|
|
5120
|
+
fs19.mkdirSync(path20.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
5121
|
+
fs19.writeFileSync(path20.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5014
5122
|
id("com.android.library")
|
|
5015
5123
|
id("org.jetbrains.kotlin.android")
|
|
5016
5124
|
}
|
|
@@ -5031,7 +5139,7 @@ dependencies {
|
|
|
5031
5139
|
implementation(libs.lynx.jssdk)
|
|
5032
5140
|
}
|
|
5033
5141
|
`);
|
|
5034
|
-
|
|
5142
|
+
fs19.writeFileSync(path20.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5035
5143
|
<manifest />
|
|
5036
5144
|
`);
|
|
5037
5145
|
const ktContent = `package ${packageName}
|
|
@@ -5048,8 +5156,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5048
5156
|
}
|
|
5049
5157
|
}
|
|
5050
5158
|
`;
|
|
5051
|
-
|
|
5052
|
-
|
|
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 });
|
|
5053
5161
|
const podspec = `Pod::Spec.new do |s|
|
|
5054
5162
|
s.name = '${extName}'
|
|
5055
5163
|
s.version = '0.0.1'
|
|
@@ -5063,7 +5171,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5063
5171
|
s.dependency 'Lynx'
|
|
5064
5172
|
end
|
|
5065
5173
|
`;
|
|
5066
|
-
|
|
5174
|
+
fs19.writeFileSync(path20.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5067
5175
|
const swiftContent = `import Foundation
|
|
5068
5176
|
|
|
5069
5177
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5072,18 +5180,18 @@ end
|
|
|
5072
5180
|
}
|
|
5073
5181
|
}
|
|
5074
5182
|
`;
|
|
5075
|
-
|
|
5183
|
+
fs19.writeFileSync(path20.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5076
5184
|
}
|
|
5077
5185
|
if (includeElement && !includeModule) {
|
|
5078
5186
|
const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
5079
|
-
|
|
5187
|
+
fs19.writeFileSync(path20.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
|
|
5080
5188
|
|
|
5081
5189
|
export const ${elementName}: FC = () => {
|
|
5082
5190
|
return null;
|
|
5083
5191
|
};
|
|
5084
5192
|
`);
|
|
5085
5193
|
}
|
|
5086
|
-
|
|
5194
|
+
fs19.writeFileSync(path20.join(root, "index.js"), `'use strict';
|
|
5087
5195
|
module.exports = {};
|
|
5088
5196
|
`);
|
|
5089
5197
|
const tsconfigCompiler = {
|
|
@@ -5096,11 +5204,11 @@ module.exports = {};
|
|
|
5096
5204
|
tsconfigCompiler.jsx = "preserve";
|
|
5097
5205
|
tsconfigCompiler.jsxImportSource = "@lynx-js/react";
|
|
5098
5206
|
}
|
|
5099
|
-
|
|
5207
|
+
fs19.writeFileSync(path20.join(root, "tsconfig.json"), JSON.stringify({
|
|
5100
5208
|
compilerOptions: tsconfigCompiler,
|
|
5101
5209
|
include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
|
|
5102
5210
|
}, null, 2));
|
|
5103
|
-
|
|
5211
|
+
fs19.writeFileSync(path20.join(root, "README.md"), `# ${extName}
|
|
5104
5212
|
|
|
5105
5213
|
Lynx extension for ${extName}.
|
|
5106
5214
|
|
|
@@ -5125,8 +5233,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5125
5233
|
var create_default3 = create3;
|
|
5126
5234
|
|
|
5127
5235
|
// src/common/codegen.ts
|
|
5128
|
-
import
|
|
5129
|
-
import
|
|
5236
|
+
import fs20 from "fs";
|
|
5237
|
+
import path21 from "path";
|
|
5130
5238
|
function codegen() {
|
|
5131
5239
|
const cwd = process.cwd();
|
|
5132
5240
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5134,9 +5242,9 @@ function codegen() {
|
|
|
5134
5242
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5135
5243
|
process.exit(1);
|
|
5136
5244
|
}
|
|
5137
|
-
const srcDir =
|
|
5138
|
-
const generatedDir =
|
|
5139
|
-
|
|
5245
|
+
const srcDir = path21.join(cwd, "src");
|
|
5246
|
+
const generatedDir = path21.join(cwd, "generated");
|
|
5247
|
+
fs20.mkdirSync(generatedDir, { recursive: true });
|
|
5140
5248
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5141
5249
|
const modules = extractLynxModules(dtsFiles);
|
|
5142
5250
|
if (modules.length === 0) {
|
|
@@ -5146,28 +5254,28 @@ function codegen() {
|
|
|
5146
5254
|
for (const mod of modules) {
|
|
5147
5255
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5148
5256
|
`;
|
|
5149
|
-
const outPath =
|
|
5150
|
-
|
|
5257
|
+
const outPath = path21.join(generatedDir, `${mod}.ts`);
|
|
5258
|
+
fs20.writeFileSync(outPath, tsContent);
|
|
5151
5259
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5152
5260
|
}
|
|
5153
5261
|
if (config.android) {
|
|
5154
|
-
const androidGenerated =
|
|
5155
|
-
|
|
5262
|
+
const androidGenerated = path21.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
5263
|
+
fs20.mkdirSync(androidGenerated, { recursive: true });
|
|
5156
5264
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5157
5265
|
}
|
|
5158
5266
|
if (config.ios) {
|
|
5159
|
-
const iosGenerated =
|
|
5160
|
-
|
|
5267
|
+
const iosGenerated = path21.join(cwd, "ios", "generated");
|
|
5268
|
+
fs20.mkdirSync(iosGenerated, { recursive: true });
|
|
5161
5269
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5162
5270
|
}
|
|
5163
5271
|
console.log("\u2728 Codegen complete.");
|
|
5164
5272
|
}
|
|
5165
5273
|
function findDtsFiles(dir) {
|
|
5166
5274
|
const result = [];
|
|
5167
|
-
if (!
|
|
5168
|
-
const entries =
|
|
5275
|
+
if (!fs20.existsSync(dir)) return result;
|
|
5276
|
+
const entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
5169
5277
|
for (const e of entries) {
|
|
5170
|
-
const full =
|
|
5278
|
+
const full = path21.join(dir, e.name);
|
|
5171
5279
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5172
5280
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5173
5281
|
}
|
|
@@ -5177,7 +5285,7 @@ function extractLynxModules(files) {
|
|
|
5177
5285
|
const modules = [];
|
|
5178
5286
|
const seen = /* @__PURE__ */ new Set();
|
|
5179
5287
|
for (const file of files) {
|
|
5180
|
-
const content =
|
|
5288
|
+
const content = fs20.readFileSync(file, "utf8");
|
|
5181
5289
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5182
5290
|
let m;
|
|
5183
5291
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5194,10 +5302,10 @@ var codegen_default = codegen;
|
|
|
5194
5302
|
// src/common/devServer.tsx
|
|
5195
5303
|
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
5196
5304
|
import { spawn } from "child_process";
|
|
5197
|
-
import
|
|
5305
|
+
import fs21 from "fs";
|
|
5198
5306
|
import http from "http";
|
|
5199
5307
|
import os4 from "os";
|
|
5200
|
-
import
|
|
5308
|
+
import path22 from "path";
|
|
5201
5309
|
import { render as render2, useInput, useApp } from "ink";
|
|
5202
5310
|
import { WebSocketServer } from "ws";
|
|
5203
5311
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
@@ -5213,13 +5321,13 @@ var STATIC_MIME = {
|
|
|
5213
5321
|
".pdf": "application/pdf"
|
|
5214
5322
|
};
|
|
5215
5323
|
function sendFileFromDisk(res, absPath) {
|
|
5216
|
-
|
|
5324
|
+
fs21.readFile(absPath, (err, data) => {
|
|
5217
5325
|
if (err) {
|
|
5218
5326
|
res.writeHead(404);
|
|
5219
5327
|
res.end("Not found");
|
|
5220
5328
|
return;
|
|
5221
5329
|
}
|
|
5222
|
-
const ext =
|
|
5330
|
+
const ext = path22.extname(absPath).toLowerCase();
|
|
5223
5331
|
res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
|
|
5224
5332
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5225
5333
|
res.end(data);
|
|
@@ -5256,9 +5364,9 @@ function getLanIp() {
|
|
|
5256
5364
|
return "localhost";
|
|
5257
5365
|
}
|
|
5258
5366
|
function detectPackageManager(cwd) {
|
|
5259
|
-
const dir =
|
|
5260
|
-
if (
|
|
5261
|
-
if (
|
|
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")))
|
|
5262
5370
|
return { cmd: "bun", args: ["run", "build"] };
|
|
5263
5371
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5264
5372
|
}
|
|
@@ -5338,8 +5446,8 @@ function DevServerApp({ verbose }) {
|
|
|
5338
5446
|
try {
|
|
5339
5447
|
const resolved = resolveHostPaths();
|
|
5340
5448
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5341
|
-
const distDir =
|
|
5342
|
-
const projectName =
|
|
5449
|
+
const distDir = path22.dirname(lynxBundlePath);
|
|
5450
|
+
const projectName = path22.basename(lynxProjectDir);
|
|
5343
5451
|
const basePath = `/${projectName}`;
|
|
5344
5452
|
setUi((s) => ({ ...s, projectName, lynxBundleFile }));
|
|
5345
5453
|
const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
@@ -5349,18 +5457,18 @@ function DevServerApp({ verbose }) {
|
|
|
5349
5457
|
}
|
|
5350
5458
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5351
5459
|
let iconFilePath = null;
|
|
5352
|
-
if (iconPaths?.source &&
|
|
5460
|
+
if (iconPaths?.source && fs21.statSync(iconPaths.source).isFile()) {
|
|
5353
5461
|
iconFilePath = iconPaths.source;
|
|
5354
|
-
} else if (iconPaths?.androidAdaptiveForeground &&
|
|
5462
|
+
} else if (iconPaths?.androidAdaptiveForeground && fs21.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
|
|
5355
5463
|
iconFilePath = iconPaths.androidAdaptiveForeground;
|
|
5356
5464
|
} else if (iconPaths?.android) {
|
|
5357
|
-
const androidIcon =
|
|
5358
|
-
if (
|
|
5465
|
+
const androidIcon = path22.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
5466
|
+
if (fs21.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5359
5467
|
} else if (iconPaths?.ios) {
|
|
5360
|
-
const iosIcon =
|
|
5361
|
-
if (
|
|
5468
|
+
const iosIcon = path22.join(iconPaths.ios, "Icon-1024.png");
|
|
5469
|
+
if (fs21.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5362
5470
|
}
|
|
5363
|
-
const iconExt = iconFilePath ?
|
|
5471
|
+
const iconExt = iconFilePath ? path22.extname(iconFilePath) || ".png" : "";
|
|
5364
5472
|
const runBuild = () => {
|
|
5365
5473
|
return new Promise((resolve, reject) => {
|
|
5366
5474
|
const { cmd, args } = detectPackageManager(lynxProjectDir);
|
|
@@ -5446,7 +5554,7 @@ function DevServerApp({ verbose }) {
|
|
|
5446
5554
|
return;
|
|
5447
5555
|
}
|
|
5448
5556
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5449
|
-
|
|
5557
|
+
fs21.readFile(iconFilePath, (err, data) => {
|
|
5450
5558
|
if (err) {
|
|
5451
5559
|
res.writeHead(404);
|
|
5452
5560
|
res.end();
|
|
@@ -5472,20 +5580,20 @@ function DevServerApp({ verbose }) {
|
|
|
5472
5580
|
res.end();
|
|
5473
5581
|
return;
|
|
5474
5582
|
}
|
|
5475
|
-
const safe =
|
|
5476
|
-
if (
|
|
5583
|
+
const safe = path22.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
5584
|
+
if (path22.isAbsolute(safe) || safe.startsWith("..")) {
|
|
5477
5585
|
res.writeHead(403);
|
|
5478
5586
|
res.end();
|
|
5479
5587
|
return;
|
|
5480
5588
|
}
|
|
5481
|
-
const allowedRoot =
|
|
5482
|
-
const abs =
|
|
5483
|
-
if (!abs.startsWith(allowedRoot +
|
|
5589
|
+
const allowedRoot = path22.resolve(lynxProjectDir, rootSub);
|
|
5590
|
+
const abs = path22.resolve(allowedRoot, safe);
|
|
5591
|
+
if (!abs.startsWith(allowedRoot + path22.sep) && abs !== allowedRoot) {
|
|
5484
5592
|
res.writeHead(403);
|
|
5485
5593
|
res.end();
|
|
5486
5594
|
return;
|
|
5487
5595
|
}
|
|
5488
|
-
if (!
|
|
5596
|
+
if (!fs21.existsSync(abs) || !fs21.statSync(abs).isFile()) {
|
|
5489
5597
|
res.writeHead(404);
|
|
5490
5598
|
res.end("Not found");
|
|
5491
5599
|
return;
|
|
@@ -5499,14 +5607,14 @@ function DevServerApp({ verbose }) {
|
|
|
5499
5607
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5500
5608
|
}
|
|
5501
5609
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5502
|
-
const filePath =
|
|
5503
|
-
const distResolved =
|
|
5504
|
-
if (!filePath.startsWith(distResolved +
|
|
5610
|
+
const filePath = path22.resolve(distDir, relPath);
|
|
5611
|
+
const distResolved = path22.resolve(distDir);
|
|
5612
|
+
if (!filePath.startsWith(distResolved + path22.sep) && filePath !== distResolved) {
|
|
5505
5613
|
res.writeHead(403);
|
|
5506
5614
|
res.end();
|
|
5507
5615
|
return;
|
|
5508
5616
|
}
|
|
5509
|
-
|
|
5617
|
+
fs21.readFile(filePath, (err, data) => {
|
|
5510
5618
|
if (err) {
|
|
5511
5619
|
res.writeHead(404);
|
|
5512
5620
|
res.end("Not found");
|
|
@@ -5567,10 +5675,10 @@ function DevServerApp({ verbose }) {
|
|
|
5567
5675
|
}
|
|
5568
5676
|
if (chokidar) {
|
|
5569
5677
|
const watchPaths = [
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
].filter((p) =>
|
|
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));
|
|
5574
5682
|
if (watchPaths.length > 0) {
|
|
5575
5683
|
const w = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5576
5684
|
w.on("change", async () => {
|
|
@@ -5690,10 +5798,10 @@ async function start(opts) {
|
|
|
5690
5798
|
var start_default = start;
|
|
5691
5799
|
|
|
5692
5800
|
// src/common/injectHost.ts
|
|
5693
|
-
import
|
|
5694
|
-
import
|
|
5801
|
+
import fs22 from "fs";
|
|
5802
|
+
import path23 from "path";
|
|
5695
5803
|
function readAndSubstitute(templatePath, vars) {
|
|
5696
|
-
const raw =
|
|
5804
|
+
const raw = fs22.readFileSync(templatePath, "utf-8");
|
|
5697
5805
|
return Object.entries(vars).reduce(
|
|
5698
5806
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5699
5807
|
raw
|
|
@@ -5714,32 +5822,32 @@ async function injectHostAndroid(opts) {
|
|
|
5714
5822
|
process.exit(1);
|
|
5715
5823
|
}
|
|
5716
5824
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5717
|
-
const rootDir =
|
|
5825
|
+
const rootDir = path23.join(projectRoot, androidDir);
|
|
5718
5826
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5719
|
-
const javaDir =
|
|
5720
|
-
const kotlinDir =
|
|
5721
|
-
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)) {
|
|
5722
5830
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5723
5831
|
process.exit(1);
|
|
5724
5832
|
}
|
|
5725
|
-
const templateDir =
|
|
5833
|
+
const templateDir = path23.join(hostPkg, "android", "templates");
|
|
5726
5834
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5727
5835
|
const files = [
|
|
5728
|
-
{ src: "App.java", dst:
|
|
5729
|
-
{ src: "TemplateProvider.java", dst:
|
|
5730
|
-
{ 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") }
|
|
5731
5839
|
];
|
|
5732
5840
|
for (const { src, dst } of files) {
|
|
5733
|
-
const srcPath =
|
|
5734
|
-
if (!
|
|
5735
|
-
if (
|
|
5736
|
-
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)`);
|
|
5737
5845
|
continue;
|
|
5738
5846
|
}
|
|
5739
5847
|
const content = readAndSubstitute(srcPath, vars);
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
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)}`);
|
|
5743
5851
|
}
|
|
5744
5852
|
}
|
|
5745
5853
|
async function injectHostIos(opts) {
|
|
@@ -5757,13 +5865,13 @@ async function injectHostIos(opts) {
|
|
|
5757
5865
|
process.exit(1);
|
|
5758
5866
|
}
|
|
5759
5867
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5760
|
-
const rootDir =
|
|
5761
|
-
const projectDir =
|
|
5762
|
-
if (!
|
|
5868
|
+
const rootDir = path23.join(projectRoot, iosDir);
|
|
5869
|
+
const projectDir = path23.join(rootDir, appName);
|
|
5870
|
+
if (!fs22.existsSync(projectDir)) {
|
|
5763
5871
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5764
5872
|
process.exit(1);
|
|
5765
5873
|
}
|
|
5766
|
-
const templateDir =
|
|
5874
|
+
const templateDir = path23.join(hostPkg, "ios", "templates");
|
|
5767
5875
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5768
5876
|
const files = [
|
|
5769
5877
|
"AppDelegate.swift",
|
|
@@ -5773,22 +5881,22 @@ async function injectHostIos(opts) {
|
|
|
5773
5881
|
"LynxInitProcessor.swift"
|
|
5774
5882
|
];
|
|
5775
5883
|
for (const f of files) {
|
|
5776
|
-
const srcPath =
|
|
5777
|
-
const dstPath =
|
|
5778
|
-
if (!
|
|
5779
|
-
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) {
|
|
5780
5888
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5781
5889
|
continue;
|
|
5782
5890
|
}
|
|
5783
5891
|
const content = readAndSubstitute(srcPath, vars);
|
|
5784
|
-
|
|
5892
|
+
fs22.writeFileSync(dstPath, content);
|
|
5785
5893
|
console.log(`\u2705 Injected ${f}`);
|
|
5786
5894
|
}
|
|
5787
5895
|
}
|
|
5788
5896
|
|
|
5789
5897
|
// src/common/buildEmbeddable.ts
|
|
5790
|
-
import
|
|
5791
|
-
import
|
|
5898
|
+
import fs23 from "fs";
|
|
5899
|
+
import path24 from "path";
|
|
5792
5900
|
import { execSync as execSync9 } from "child_process";
|
|
5793
5901
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5794
5902
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
@@ -5865,14 +5973,14 @@ object LynxEmbeddable {
|
|
|
5865
5973
|
}
|
|
5866
5974
|
`;
|
|
5867
5975
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
5868
|
-
const libDir =
|
|
5869
|
-
const libSrcMain =
|
|
5870
|
-
const assetsDir =
|
|
5871
|
-
const kotlinDir =
|
|
5872
|
-
const generatedDir =
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
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 });
|
|
5876
5984
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5877
5985
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5878
5986
|
const settingsContent = `pluginManagement {
|
|
@@ -5892,7 +6000,7 @@ include(":lib")
|
|
|
5892
6000
|
${androidModules.map((p) => {
|
|
5893
6001
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
5894
6002
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
5895
|
-
const absPath =
|
|
6003
|
+
const absPath = path24.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
5896
6004
|
return `include(":${gradleName}")
|
|
5897
6005
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
5898
6006
|
}).join("\n")}
|
|
@@ -5941,10 +6049,10 @@ dependencies {
|
|
|
5941
6049
|
${libDeps}
|
|
5942
6050
|
}
|
|
5943
6051
|
`;
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
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"),
|
|
5948
6056
|
`plugins {
|
|
5949
6057
|
alias(libs.plugins.android.library) apply false
|
|
5950
6058
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -5952,26 +6060,26 @@ ${libDeps}
|
|
|
5952
6060
|
}
|
|
5953
6061
|
`
|
|
5954
6062
|
);
|
|
5955
|
-
|
|
5956
|
-
|
|
6063
|
+
fs23.writeFileSync(
|
|
6064
|
+
path24.join(androidDir, "gradle.properties"),
|
|
5957
6065
|
`org.gradle.jvmargs=-Xmx2048m
|
|
5958
6066
|
android.useAndroidX=true
|
|
5959
6067
|
kotlin.code.style=official
|
|
5960
6068
|
`
|
|
5961
6069
|
);
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
6070
|
+
fs23.writeFileSync(path24.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
6071
|
+
fs23.writeFileSync(
|
|
6072
|
+
path24.join(libSrcMain, "AndroidManifest.xml"),
|
|
5965
6073
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
5966
6074
|
);
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
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"),
|
|
5971
6079
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
5972
6080
|
);
|
|
5973
|
-
|
|
5974
|
-
|
|
6081
|
+
fs23.writeFileSync(
|
|
6082
|
+
path24.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
5975
6083
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
5976
6084
|
);
|
|
5977
6085
|
}
|
|
@@ -5980,20 +6088,20 @@ async function buildEmbeddable(opts = {}) {
|
|
|
5980
6088
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
5981
6089
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
5982
6090
|
execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
5983
|
-
if (!
|
|
6091
|
+
if (!fs23.existsSync(lynxBundlePath)) {
|
|
5984
6092
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
5985
6093
|
process.exit(1);
|
|
5986
6094
|
}
|
|
5987
|
-
const outDir =
|
|
5988
|
-
|
|
5989
|
-
const distDir =
|
|
6095
|
+
const outDir = path24.join(projectRoot, EMBEDDABLE_DIR);
|
|
6096
|
+
fs23.mkdirSync(outDir, { recursive: true });
|
|
6097
|
+
const distDir = path24.dirname(lynxBundlePath);
|
|
5990
6098
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
5991
6099
|
const modules = discoverModules(projectRoot);
|
|
5992
6100
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5993
6101
|
const abiFilters = resolveAbiFilters(config);
|
|
5994
|
-
const androidDir =
|
|
5995
|
-
if (
|
|
5996
|
-
|
|
6102
|
+
const androidDir = path24.join(outDir, "android");
|
|
6103
|
+
if (fs23.existsSync(androidDir)) fs23.rmSync(androidDir, { recursive: true });
|
|
6104
|
+
fs23.mkdirSync(androidDir, { recursive: true });
|
|
5997
6105
|
generateAndroidLibrary(
|
|
5998
6106
|
outDir,
|
|
5999
6107
|
androidDir,
|
|
@@ -6003,23 +6111,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6003
6111
|
modules,
|
|
6004
6112
|
abiFilters
|
|
6005
6113
|
);
|
|
6006
|
-
const gradlewPath =
|
|
6114
|
+
const gradlewPath = path24.join(androidDir, "gradlew");
|
|
6007
6115
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
6008
6116
|
const existingGradleDirs = [
|
|
6009
|
-
|
|
6010
|
-
devAppDir ?
|
|
6117
|
+
path24.join(projectRoot, "android"),
|
|
6118
|
+
devAppDir ? path24.join(devAppDir, "android") : null
|
|
6011
6119
|
].filter(Boolean);
|
|
6012
6120
|
let hasWrapper = false;
|
|
6013
6121
|
for (const d of existingGradleDirs) {
|
|
6014
|
-
if (
|
|
6122
|
+
if (fs23.existsSync(path24.join(d, "gradlew"))) {
|
|
6015
6123
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
6016
|
-
const src =
|
|
6017
|
-
if (
|
|
6018
|
-
const dest =
|
|
6019
|
-
if (
|
|
6020
|
-
|
|
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 });
|
|
6021
6129
|
} else {
|
|
6022
|
-
|
|
6130
|
+
fs23.copyFileSync(src, dest);
|
|
6023
6131
|
}
|
|
6024
6132
|
}
|
|
6025
6133
|
}
|
|
@@ -6038,10 +6146,10 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6038
6146
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6039
6147
|
throw e;
|
|
6040
6148
|
}
|
|
6041
|
-
const aarSrc =
|
|
6042
|
-
const aarDest =
|
|
6043
|
-
if (
|
|
6044
|
-
|
|
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);
|
|
6045
6153
|
console.log(` - tamer-embeddable.aar`);
|
|
6046
6154
|
}
|
|
6047
6155
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6052,7 +6160,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6052
6160
|
// LynxEmbeddable.init(applicationContext)
|
|
6053
6161
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6054
6162
|
`;
|
|
6055
|
-
|
|
6163
|
+
fs23.writeFileSync(path24.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6056
6164
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6057
6165
|
const readme = `# Embeddable Lynx Bundle
|
|
6058
6166
|
|
|
@@ -6083,7 +6191,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6083
6191
|
|
|
6084
6192
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6085
6193
|
`;
|
|
6086
|
-
|
|
6194
|
+
fs23.writeFileSync(path24.join(outDir, "README.md"), readme);
|
|
6087
6195
|
console.log(`
|
|
6088
6196
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6089
6197
|
console.log(" - main.lynx.bundle");
|
|
@@ -6095,20 +6203,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6095
6203
|
console.log(" - README.md");
|
|
6096
6204
|
}
|
|
6097
6205
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6098
|
-
const iosDir =
|
|
6099
|
-
const podDir =
|
|
6100
|
-
const resourcesDir =
|
|
6101
|
-
|
|
6102
|
-
|
|
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));
|
|
6103
6211
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6104
6212
|
const podDeps = iosModules.map((p) => {
|
|
6105
6213
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6106
|
-
const podspecDir =
|
|
6107
|
-
if (!
|
|
6108
|
-
const files =
|
|
6214
|
+
const podspecDir = path24.join(p.packagePath, podspecPath);
|
|
6215
|
+
if (!fs23.existsSync(podspecDir)) return null;
|
|
6216
|
+
const files = fs23.readdirSync(podspecDir);
|
|
6109
6217
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6110
6218
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6111
|
-
const absPath =
|
|
6219
|
+
const absPath = path24.resolve(podspecDir);
|
|
6112
6220
|
return { podName, absPath };
|
|
6113
6221
|
}).filter(Boolean);
|
|
6114
6222
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6148,9 +6256,9 @@ end
|
|
|
6148
6256
|
});
|
|
6149
6257
|
const swiftImports = iosModules.map((p) => {
|
|
6150
6258
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6151
|
-
const podspecDir =
|
|
6152
|
-
if (!
|
|
6153
|
-
const files =
|
|
6259
|
+
const podspecDir = path24.join(p.packagePath, podspecPath);
|
|
6260
|
+
if (!fs23.existsSync(podspecDir)) return null;
|
|
6261
|
+
const files = fs23.readdirSync(podspecDir);
|
|
6154
6262
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6155
6263
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6156
6264
|
}).filter(Boolean);
|
|
@@ -6169,17 +6277,17 @@ ${regBlock}
|
|
|
6169
6277
|
}
|
|
6170
6278
|
}
|
|
6171
6279
|
`;
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
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);
|
|
6175
6283
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6176
6284
|
|
|
6177
6285
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6178
6286
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6179
6287
|
`;
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6288
|
+
fs23.writeFileSync(path24.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
6289
|
+
fs23.writeFileSync(
|
|
6290
|
+
path24.join(outDir, "snippet-ios.swift"),
|
|
6183
6291
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6184
6292
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6185
6293
|
`
|
|
@@ -6187,8 +6295,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6187
6295
|
}
|
|
6188
6296
|
|
|
6189
6297
|
// src/common/add.ts
|
|
6190
|
-
import
|
|
6191
|
-
import
|
|
6298
|
+
import fs24 from "fs";
|
|
6299
|
+
import path25 from "path";
|
|
6192
6300
|
import { execFile, execSync as execSync10 } from "child_process";
|
|
6193
6301
|
import { promisify } from "util";
|
|
6194
6302
|
import semver from "semver";
|
|
@@ -6245,9 +6353,9 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
6245
6353
|
return `${pkg}@prerelease`;
|
|
6246
6354
|
}
|
|
6247
6355
|
function detectPackageManager2(cwd) {
|
|
6248
|
-
const dir =
|
|
6249
|
-
if (
|
|
6250
|
-
if (
|
|
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";
|
|
6251
6359
|
return "npm";
|
|
6252
6360
|
}
|
|
6253
6361
|
function runInstall(cwd, packages, pm) {
|
|
@@ -6299,13 +6407,13 @@ async function add(packages = []) {
|
|
|
6299
6407
|
// src/common/signing.tsx
|
|
6300
6408
|
import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
6301
6409
|
import { render as render3, Text as Text10, Box as Box9 } from "ink";
|
|
6302
|
-
import
|
|
6303
|
-
import
|
|
6410
|
+
import fs27 from "fs";
|
|
6411
|
+
import path28 from "path";
|
|
6304
6412
|
|
|
6305
6413
|
// src/common/androidKeystore.ts
|
|
6306
6414
|
import { execFileSync } from "child_process";
|
|
6307
|
-
import
|
|
6308
|
-
import
|
|
6415
|
+
import fs25 from "fs";
|
|
6416
|
+
import path26 from "path";
|
|
6309
6417
|
function normalizeJavaHome(raw) {
|
|
6310
6418
|
if (!raw) return void 0;
|
|
6311
6419
|
const t = raw.trim().replace(/^["']|["']$/g, "");
|
|
@@ -6318,7 +6426,7 @@ function discoverJavaHomeMacOs() {
|
|
|
6318
6426
|
encoding: "utf8",
|
|
6319
6427
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6320
6428
|
}).trim().split("\n")[0]?.trim();
|
|
6321
|
-
if (out &&
|
|
6429
|
+
if (out && fs25.existsSync(path26.join(out, "bin", "keytool"))) return out;
|
|
6322
6430
|
} catch {
|
|
6323
6431
|
}
|
|
6324
6432
|
return void 0;
|
|
@@ -6328,13 +6436,13 @@ function resolveKeytoolPath() {
|
|
|
6328
6436
|
const win = process.platform === "win32";
|
|
6329
6437
|
const keytoolName = win ? "keytool.exe" : "keytool";
|
|
6330
6438
|
if (jh) {
|
|
6331
|
-
const p =
|
|
6332
|
-
if (
|
|
6439
|
+
const p = path26.join(jh, "bin", keytoolName);
|
|
6440
|
+
if (fs25.existsSync(p)) return p;
|
|
6333
6441
|
}
|
|
6334
6442
|
const mac = discoverJavaHomeMacOs();
|
|
6335
6443
|
if (mac) {
|
|
6336
|
-
const p =
|
|
6337
|
-
if (
|
|
6444
|
+
const p = path26.join(mac, "bin", keytoolName);
|
|
6445
|
+
if (fs25.existsSync(p)) return p;
|
|
6338
6446
|
}
|
|
6339
6447
|
return "keytool";
|
|
6340
6448
|
}
|
|
@@ -6349,16 +6457,16 @@ function keytoolAvailable() {
|
|
|
6349
6457
|
};
|
|
6350
6458
|
if (tryRun("keytool")) return true;
|
|
6351
6459
|
const fromJavaHome = resolveKeytoolPath();
|
|
6352
|
-
if (fromJavaHome !== "keytool" &&
|
|
6460
|
+
if (fromJavaHome !== "keytool" && fs25.existsSync(fromJavaHome)) {
|
|
6353
6461
|
return tryRun(fromJavaHome);
|
|
6354
6462
|
}
|
|
6355
6463
|
return false;
|
|
6356
6464
|
}
|
|
6357
6465
|
function generateReleaseKeystore(opts) {
|
|
6358
6466
|
const keytool = resolveKeytoolPath();
|
|
6359
|
-
const dir =
|
|
6360
|
-
|
|
6361
|
-
if (
|
|
6467
|
+
const dir = path26.dirname(opts.keystoreAbsPath);
|
|
6468
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
6469
|
+
if (fs25.existsSync(opts.keystoreAbsPath)) {
|
|
6362
6470
|
throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
|
|
6363
6471
|
}
|
|
6364
6472
|
if (!opts.storePassword || !opts.keyPassword) {
|
|
@@ -6396,13 +6504,13 @@ function generateReleaseKeystore(opts) {
|
|
|
6396
6504
|
}
|
|
6397
6505
|
|
|
6398
6506
|
// src/common/appendEnvFile.ts
|
|
6399
|
-
import
|
|
6400
|
-
import
|
|
6507
|
+
import fs26 from "fs";
|
|
6508
|
+
import path27 from "path";
|
|
6401
6509
|
import { parse } from "dotenv";
|
|
6402
6510
|
function keysDefinedInFile(filePath) {
|
|
6403
|
-
if (!
|
|
6511
|
+
if (!fs26.existsSync(filePath)) return /* @__PURE__ */ new Set();
|
|
6404
6512
|
try {
|
|
6405
|
-
return new Set(Object.keys(parse(
|
|
6513
|
+
return new Set(Object.keys(parse(fs26.readFileSync(filePath, "utf8"))));
|
|
6406
6514
|
} catch {
|
|
6407
6515
|
return /* @__PURE__ */ new Set();
|
|
6408
6516
|
}
|
|
@@ -6417,11 +6525,11 @@ function formatEnvLine(key, value) {
|
|
|
6417
6525
|
function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
6418
6526
|
const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
|
|
6419
6527
|
if (entries.length === 0) return null;
|
|
6420
|
-
const envLocal =
|
|
6421
|
-
const envDefault =
|
|
6528
|
+
const envLocal = path27.join(projectRoot, ".env.local");
|
|
6529
|
+
const envDefault = path27.join(projectRoot, ".env");
|
|
6422
6530
|
let target;
|
|
6423
|
-
if (
|
|
6424
|
-
else if (
|
|
6531
|
+
if (fs26.existsSync(envLocal)) target = envLocal;
|
|
6532
|
+
else if (fs26.existsSync(envDefault)) target = envDefault;
|
|
6425
6533
|
else target = envLocal;
|
|
6426
6534
|
const existing = keysDefinedInFile(target);
|
|
6427
6535
|
const lines = [];
|
|
@@ -6433,20 +6541,20 @@ function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
|
6433
6541
|
}
|
|
6434
6542
|
if (lines.length === 0) {
|
|
6435
6543
|
return {
|
|
6436
|
-
file:
|
|
6544
|
+
file: path27.basename(target),
|
|
6437
6545
|
keys: [],
|
|
6438
6546
|
skippedAll: entries.length > 0
|
|
6439
6547
|
};
|
|
6440
6548
|
}
|
|
6441
6549
|
let prefix = "";
|
|
6442
|
-
if (
|
|
6443
|
-
const cur =
|
|
6550
|
+
if (fs26.existsSync(target)) {
|
|
6551
|
+
const cur = fs26.readFileSync(target, "utf8");
|
|
6444
6552
|
prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
|
|
6445
6553
|
`;
|
|
6446
6554
|
}
|
|
6447
6555
|
const block = lines.join("\n") + "\n";
|
|
6448
|
-
|
|
6449
|
-
return { file:
|
|
6556
|
+
fs26.writeFileSync(target, prefix + block, "utf8");
|
|
6557
|
+
return { file: path27.basename(target), keys: appendedKeys };
|
|
6450
6558
|
}
|
|
6451
6559
|
|
|
6452
6560
|
// src/common/signing.tsx
|
|
@@ -6552,7 +6660,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6552
6660
|
try {
|
|
6553
6661
|
const resolved = resolveHostPaths();
|
|
6554
6662
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6555
|
-
abs =
|
|
6663
|
+
abs = path28.isAbsolute(rel) ? rel : path28.join(resolved.projectRoot, rel);
|
|
6556
6664
|
const alias = state.android.keyAlias.trim() || "release";
|
|
6557
6665
|
const pw = state.android.genPassword;
|
|
6558
6666
|
const pkg = resolved.config.android?.packageName ?? "com.example.app";
|
|
@@ -6579,7 +6687,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6579
6687
|
}));
|
|
6580
6688
|
} catch (e) {
|
|
6581
6689
|
const msg = e.message;
|
|
6582
|
-
if (abs &&
|
|
6690
|
+
if (abs && fs27.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
|
|
6583
6691
|
if (cancelled || runId !== generateRunId.current) return;
|
|
6584
6692
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6585
6693
|
const alias = state.android.keyAlias.trim() || "release";
|
|
@@ -6619,11 +6727,11 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6619
6727
|
const saveConfig = async () => {
|
|
6620
6728
|
try {
|
|
6621
6729
|
const resolved = resolveHostPaths();
|
|
6622
|
-
const configPath =
|
|
6730
|
+
const configPath = path28.join(resolved.projectRoot, "tamer.config.json");
|
|
6623
6731
|
let config = {};
|
|
6624
6732
|
let androidEnvAppend = null;
|
|
6625
|
-
if (
|
|
6626
|
-
config = JSON.parse(
|
|
6733
|
+
if (fs27.existsSync(configPath)) {
|
|
6734
|
+
config = JSON.parse(fs27.readFileSync(configPath, "utf8"));
|
|
6627
6735
|
}
|
|
6628
6736
|
if (state.platform === "android" || state.platform === "both") {
|
|
6629
6737
|
config.android = config.android || {};
|
|
@@ -6650,10 +6758,10 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6650
6758
|
...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
|
|
6651
6759
|
};
|
|
6652
6760
|
}
|
|
6653
|
-
|
|
6654
|
-
const gitignorePath =
|
|
6655
|
-
if (
|
|
6656
|
-
let gitignore =
|
|
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");
|
|
6657
6765
|
const additions = [
|
|
6658
6766
|
".env.local",
|
|
6659
6767
|
"*.jks",
|
|
@@ -6666,7 +6774,7 @@ ${addition}
|
|
|
6666
6774
|
`;
|
|
6667
6775
|
}
|
|
6668
6776
|
}
|
|
6669
|
-
|
|
6777
|
+
fs27.writeFileSync(gitignorePath, gitignore);
|
|
6670
6778
|
}
|
|
6671
6779
|
setState((s) => ({
|
|
6672
6780
|
...s,
|
|
@@ -6924,13 +7032,13 @@ async function signing(platform) {
|
|
|
6924
7032
|
}
|
|
6925
7033
|
|
|
6926
7034
|
// src/common/productionSigning.ts
|
|
6927
|
-
import
|
|
6928
|
-
import
|
|
7035
|
+
import fs28 from "fs";
|
|
7036
|
+
import path29 from "path";
|
|
6929
7037
|
function isAndroidSigningConfigured(resolved) {
|
|
6930
7038
|
const signing2 = resolved.config.android?.signing;
|
|
6931
7039
|
const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
|
|
6932
|
-
const signingProps =
|
|
6933
|
-
const hasProps =
|
|
7040
|
+
const signingProps = path29.join(resolved.androidDir, "signing.properties");
|
|
7041
|
+
const hasProps = fs28.existsSync(signingProps);
|
|
6934
7042
|
return hasConfig || hasProps;
|
|
6935
7043
|
}
|
|
6936
7044
|
function isIosSigningConfigured(resolved) {
|
|
@@ -6961,11 +7069,11 @@ function assertProductionSigningReady(filter) {
|
|
|
6961
7069
|
|
|
6962
7070
|
// index.ts
|
|
6963
7071
|
function readCliVersion() {
|
|
6964
|
-
const root =
|
|
6965
|
-
const here =
|
|
6966
|
-
const parent =
|
|
6967
|
-
const pkgPath =
|
|
6968
|
-
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;
|
|
6969
7077
|
}
|
|
6970
7078
|
var version = readCliVersion();
|
|
6971
7079
|
function validateBuildMode(debug, release, production) {
|
|
@@ -7183,10 +7291,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
7183
7291
|
process.exit(1);
|
|
7184
7292
|
});
|
|
7185
7293
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
7186
|
-
const configPath =
|
|
7294
|
+
const configPath = path30.join(process.cwd(), "tamer.config.json");
|
|
7187
7295
|
let config = {};
|
|
7188
|
-
if (
|
|
7189
|
-
config = JSON.parse(
|
|
7296
|
+
if (fs29.existsSync(configPath)) {
|
|
7297
|
+
config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
|
|
7190
7298
|
}
|
|
7191
7299
|
if (config.autolink) {
|
|
7192
7300
|
delete config.autolink;
|
|
@@ -7195,7 +7303,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
7195
7303
|
config.autolink = true;
|
|
7196
7304
|
console.log("Autolink enabled in tamer.config.json");
|
|
7197
7305
|
}
|
|
7198
|
-
|
|
7306
|
+
fs29.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
7199
7307
|
console.log(`Updated ${configPath}`);
|
|
7200
7308
|
});
|
|
7201
7309
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|