@tamer4lynx/cli 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +571 -497
- package/package.json +3 -2
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 fs31 from "fs";
|
|
11
|
+
import path32 from "path";
|
|
12
12
|
import { program } from "commander";
|
|
13
13
|
|
|
14
14
|
// src/common/cliVersion.ts
|
|
@@ -1581,8 +1581,8 @@ object GeneratedLynxExtensions {
|
|
|
1581
1581
|
var create_default = create;
|
|
1582
1582
|
|
|
1583
1583
|
// src/android/autolink.ts
|
|
1584
|
-
import
|
|
1585
|
-
import
|
|
1584
|
+
import fs9 from "fs";
|
|
1585
|
+
import path9 from "path";
|
|
1586
1586
|
import { execSync as execSync2 } from "child_process";
|
|
1587
1587
|
|
|
1588
1588
|
// src/common/discoverModules.ts
|
|
@@ -1974,6 +1974,69 @@ ${createDelayedBody}
|
|
|
1974
1974
|
`;
|
|
1975
1975
|
}
|
|
1976
1976
|
|
|
1977
|
+
// src/android/cleanTamerAndroidCaches.ts
|
|
1978
|
+
import fs8 from "fs";
|
|
1979
|
+
import path8 from "path";
|
|
1980
|
+
var MARKER_REL = path8.join(".tamer", "android-lib-versions.json");
|
|
1981
|
+
function readTamerPackageVersions(projectRoot) {
|
|
1982
|
+
const out = {};
|
|
1983
|
+
const nm = path8.join(projectRoot, "node_modules", "@tamer4lynx");
|
|
1984
|
+
if (!fs8.existsSync(nm)) return out;
|
|
1985
|
+
for (const name of fs8.readdirSync(nm, { withFileTypes: true })) {
|
|
1986
|
+
if (!name.isDirectory()) continue;
|
|
1987
|
+
const pj = path8.join(nm, name.name, "package.json");
|
|
1988
|
+
if (!fs8.existsSync(pj)) continue;
|
|
1989
|
+
try {
|
|
1990
|
+
const v = JSON.parse(fs8.readFileSync(pj, "utf-8")).version;
|
|
1991
|
+
if (typeof v === "string") out[name.name] = v;
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return out;
|
|
1996
|
+
}
|
|
1997
|
+
function versionsEqual(a, b) {
|
|
1998
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
1999
|
+
for (const k of keys) {
|
|
2000
|
+
if (a[k] !== b[k]) return false;
|
|
2001
|
+
}
|
|
2002
|
+
return true;
|
|
2003
|
+
}
|
|
2004
|
+
function cleanTamerAndroidLibBuildsIfVersionsChanged(projectRoot) {
|
|
2005
|
+
const markerPath = path8.join(projectRoot, MARKER_REL);
|
|
2006
|
+
let previous = {};
|
|
2007
|
+
if (fs8.existsSync(markerPath)) {
|
|
2008
|
+
try {
|
|
2009
|
+
previous = JSON.parse(fs8.readFileSync(markerPath, "utf-8"));
|
|
2010
|
+
} catch {
|
|
2011
|
+
previous = {};
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const current = readTamerPackageVersions(projectRoot);
|
|
2015
|
+
if (versionsEqual(previous, current)) return;
|
|
2016
|
+
const nm = path8.join(projectRoot, "node_modules", "@tamer4lynx");
|
|
2017
|
+
if (fs8.existsSync(nm)) {
|
|
2018
|
+
for (const name of fs8.readdirSync(nm, { withFileTypes: true })) {
|
|
2019
|
+
if (!name.isDirectory()) continue;
|
|
2020
|
+
const buildDir = path8.join(nm, name.name, "android", "build");
|
|
2021
|
+
if (fs8.existsSync(buildDir)) {
|
|
2022
|
+
fs8.rmSync(buildDir, { recursive: true, force: true });
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
fs8.mkdirSync(path8.dirname(markerPath), { recursive: true });
|
|
2027
|
+
fs8.writeFileSync(markerPath, JSON.stringify(current, null, 0), "utf-8");
|
|
2028
|
+
console.log(
|
|
2029
|
+
"\u2139\uFE0F Cleared @tamer4lynx Android library build dirs (linked package versions changed)."
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
function invalidateTamerAndroidLibVersionMarker(projectRoot) {
|
|
2033
|
+
const markerPath = path8.join(projectRoot, MARKER_REL);
|
|
2034
|
+
try {
|
|
2035
|
+
if (fs8.existsSync(markerPath)) fs8.unlinkSync(markerPath);
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
1977
2040
|
// src/android/autolink.ts
|
|
1978
2041
|
var TAMER_DEV_CLIENT_FALLBACK = {
|
|
1979
2042
|
name: "@tamer4lynx/tamer-dev-client",
|
|
@@ -2019,11 +2082,11 @@ var autolink = (opts) => {
|
|
|
2019
2082
|
const packageName = config.android.packageName;
|
|
2020
2083
|
const projectRoot = resolved.projectRoot;
|
|
2021
2084
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
2022
|
-
if (!
|
|
2085
|
+
if (!fs9.existsSync(filePath)) {
|
|
2023
2086
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
2024
2087
|
return;
|
|
2025
2088
|
}
|
|
2026
|
-
let fileContent =
|
|
2089
|
+
let fileContent = fs9.readFileSync(filePath, "utf8");
|
|
2027
2090
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2028
2091
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2029
2092
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -2033,16 +2096,16 @@ ${endMarker}`;
|
|
|
2033
2096
|
if (regex.test(fileContent)) {
|
|
2034
2097
|
fileContent = fileContent.replace(regex, replacementBlock);
|
|
2035
2098
|
} else {
|
|
2036
|
-
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${
|
|
2099
|
+
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path9.basename(filePath)}. Appending to the end of the file.`);
|
|
2037
2100
|
fileContent += `
|
|
2038
2101
|
${replacementBlock}
|
|
2039
2102
|
`;
|
|
2040
2103
|
}
|
|
2041
|
-
|
|
2042
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
2104
|
+
fs9.writeFileSync(filePath, fileContent);
|
|
2105
|
+
console.log(`\u2705 Updated autolinked section in ${path9.basename(filePath)}`);
|
|
2043
2106
|
}
|
|
2044
2107
|
function updateSettingsGradle(packages) {
|
|
2045
|
-
const settingsFilePath =
|
|
2108
|
+
const settingsFilePath = path9.join(appAndroidPath, "settings.gradle.kts");
|
|
2046
2109
|
let scriptContent = `// This section is automatically generated by Tamer4Lynx.
|
|
2047
2110
|
// Manual edits will be overwritten.`;
|
|
2048
2111
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
@@ -2050,7 +2113,7 @@ ${replacementBlock}
|
|
|
2050
2113
|
androidPackages.forEach((pkg) => {
|
|
2051
2114
|
const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
|
|
2052
2115
|
const sourceDir = pkg.config.android?.sourceDir || "android";
|
|
2053
|
-
const projectPath =
|
|
2116
|
+
const projectPath = path9.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
|
|
2054
2117
|
scriptContent += `
|
|
2055
2118
|
include(":${gradleProjectName}")`;
|
|
2056
2119
|
scriptContent += `
|
|
@@ -2063,7 +2126,7 @@ println("No native modules found by Tamer4Lynx autolinker.")`;
|
|
|
2063
2126
|
updateGeneratedSection(settingsFilePath, scriptContent.trim(), "// GENERATED AUTOLINK START", "// GENERATED AUTOLINK END");
|
|
2064
2127
|
}
|
|
2065
2128
|
function updateAppBuildGradle(packages) {
|
|
2066
|
-
const appBuildGradlePath =
|
|
2129
|
+
const appBuildGradlePath = path9.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2067
2130
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
2068
2131
|
const implementationLines = androidPackages.map((p) => {
|
|
2069
2132
|
const gradleProjectName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
@@ -2081,35 +2144,35 @@ ${implementationLines || " // No native dependencies found to link."}`;
|
|
|
2081
2144
|
}
|
|
2082
2145
|
function generateKotlinExtensionsFile(packages, projectPackage) {
|
|
2083
2146
|
const packagePath = projectPackage.replace(/\./g, "/");
|
|
2084
|
-
const generatedDir =
|
|
2085
|
-
const kotlinExtensionsPath =
|
|
2147
|
+
const generatedDir = path9.join(appAndroidPath, "app", "src", "main", "kotlin", packagePath, "generated");
|
|
2148
|
+
const kotlinExtensionsPath = path9.join(generatedDir, "GeneratedLynxExtensions.kt");
|
|
2086
2149
|
const content = `/**
|
|
2087
2150
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2088
2151
|
* Do not edit this file manually.
|
|
2089
2152
|
*/
|
|
2090
2153
|
${generateLynxExtensionsKotlin(packages, projectPackage)}`;
|
|
2091
|
-
|
|
2092
|
-
|
|
2154
|
+
fs9.mkdirSync(generatedDir, { recursive: true });
|
|
2155
|
+
fs9.writeFileSync(kotlinExtensionsPath, content.trimStart());
|
|
2093
2156
|
console.log(`\u2705 Generated Kotlin extensions at ${kotlinExtensionsPath}`);
|
|
2094
2157
|
}
|
|
2095
2158
|
function generateActivityLifecycleFile(packages, projectPackage) {
|
|
2096
2159
|
const packageKotlinPath = projectPackage.replace(/\./g, "/");
|
|
2097
|
-
const generatedDir =
|
|
2098
|
-
const outputPath =
|
|
2160
|
+
const generatedDir = path9.join(appAndroidPath, "app", "src", "main", "kotlin", packageKotlinPath, "generated");
|
|
2161
|
+
const outputPath = path9.join(generatedDir, "GeneratedActivityLifecycle.kt");
|
|
2099
2162
|
const content = `/**
|
|
2100
2163
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2101
2164
|
* Do not edit this file manually.
|
|
2102
2165
|
*/
|
|
2103
2166
|
${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
2104
|
-
|
|
2105
|
-
|
|
2167
|
+
fs9.mkdirSync(generatedDir, { recursive: true });
|
|
2168
|
+
fs9.writeFileSync(outputPath, content);
|
|
2106
2169
|
console.log(`\u2705 Generated activity lifecycle patches at ${outputPath}`);
|
|
2107
2170
|
}
|
|
2108
2171
|
function syncDeepLinkIntentFilters() {
|
|
2109
2172
|
const deepLinks = config.android?.deepLinks;
|
|
2110
2173
|
if (!deepLinks || deepLinks.length === 0) return;
|
|
2111
|
-
const manifestPath =
|
|
2112
|
-
if (!
|
|
2174
|
+
const manifestPath = path9.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2175
|
+
if (!fs9.existsSync(manifestPath)) return;
|
|
2113
2176
|
const intentFilters = deepLinks.map((link) => {
|
|
2114
2177
|
const dataAttrs = [
|
|
2115
2178
|
`android:scheme="${link.scheme}"`,
|
|
@@ -2134,9 +2197,9 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2134
2197
|
console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
|
|
2135
2198
|
let packages = discoverModules(projectRoot).filter((p) => p.config.android);
|
|
2136
2199
|
const includeDevClient = opts?.includeDevClient === true;
|
|
2137
|
-
const devClientScoped =
|
|
2138
|
-
const devClientFlat =
|
|
2139
|
-
const devClientPath =
|
|
2200
|
+
const devClientScoped = path9.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client");
|
|
2201
|
+
const devClientFlat = path9.join(projectRoot, "node_modules", "tamer-dev-client");
|
|
2202
|
+
const devClientPath = fs9.existsSync(path9.join(devClientScoped, "android")) ? devClientScoped : fs9.existsSync(path9.join(devClientFlat, "android")) ? devClientFlat : null;
|
|
2140
2203
|
const hasDevClient = packages.some((p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client");
|
|
2141
2204
|
if (includeDevClient && devClientPath && !hasDevClient) {
|
|
2142
2205
|
packages = [{
|
|
@@ -2160,11 +2223,12 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2160
2223
|
ensureXElementDeps();
|
|
2161
2224
|
ensureReleaseSigning();
|
|
2162
2225
|
runGradleSync();
|
|
2226
|
+
invalidateTamerAndroidLibVersionMarker(projectRoot);
|
|
2163
2227
|
console.log("\u2728 Autolinking complete.");
|
|
2164
2228
|
}
|
|
2165
2229
|
function runGradleSync() {
|
|
2166
|
-
const gradlew =
|
|
2167
|
-
if (!
|
|
2230
|
+
const gradlew = path9.join(appAndroidPath, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2231
|
+
if (!fs9.existsSync(gradlew)) return;
|
|
2168
2232
|
try {
|
|
2169
2233
|
console.log("\u2139\uFE0F Running Gradle sync in android directory...");
|
|
2170
2234
|
execSync2(process.platform === "win32" ? "gradlew.bat projects" : "./gradlew projects", {
|
|
@@ -2178,14 +2242,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2178
2242
|
}
|
|
2179
2243
|
}
|
|
2180
2244
|
function syncVersionCatalog(packages) {
|
|
2181
|
-
const libsTomlPath =
|
|
2182
|
-
if (!
|
|
2245
|
+
const libsTomlPath = path9.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2246
|
+
if (!fs9.existsSync(libsTomlPath)) return;
|
|
2183
2247
|
const requiredAliases = /* @__PURE__ */ new Set();
|
|
2184
2248
|
const requiredPluginAliases = /* @__PURE__ */ new Set();
|
|
2185
2249
|
for (const pkg of packages) {
|
|
2186
|
-
const buildPath =
|
|
2187
|
-
if (!
|
|
2188
|
-
const content =
|
|
2250
|
+
const buildPath = path9.join(pkg.packagePath, pkg.config.android?.sourceDir || "android", "build.gradle.kts");
|
|
2251
|
+
if (!fs9.existsSync(buildPath)) continue;
|
|
2252
|
+
const content = fs9.readFileSync(buildPath, "utf8");
|
|
2189
2253
|
for (const m of content.matchAll(/libs\.([\w.]+)/g)) {
|
|
2190
2254
|
const alias = m[1];
|
|
2191
2255
|
if (alias && alias in REQUIRED_CATALOG_ENTRIES) requiredAliases.add(alias);
|
|
@@ -2196,7 +2260,7 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2196
2260
|
}
|
|
2197
2261
|
}
|
|
2198
2262
|
if (requiredAliases.size === 0 && requiredPluginAliases.size === 0) return;
|
|
2199
|
-
let toml =
|
|
2263
|
+
let toml = fs9.readFileSync(libsTomlPath, "utf8");
|
|
2200
2264
|
let updated = false;
|
|
2201
2265
|
for (const alias of requiredAliases) {
|
|
2202
2266
|
const entry = REQUIRED_CATALOG_ENTRIES[alias];
|
|
@@ -2220,14 +2284,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2220
2284
|
updated = true;
|
|
2221
2285
|
}
|
|
2222
2286
|
if (updated) {
|
|
2223
|
-
|
|
2287
|
+
fs9.writeFileSync(libsTomlPath, toml);
|
|
2224
2288
|
console.log("\u2705 Synced version catalog (libs.versions.toml) for linked modules.");
|
|
2225
2289
|
}
|
|
2226
2290
|
}
|
|
2227
2291
|
function ensureXElementDeps() {
|
|
2228
|
-
const libsTomlPath =
|
|
2229
|
-
if (
|
|
2230
|
-
let toml =
|
|
2292
|
+
const libsTomlPath = path9.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2293
|
+
if (fs9.existsSync(libsTomlPath)) {
|
|
2294
|
+
let toml = fs9.readFileSync(libsTomlPath, "utf8");
|
|
2231
2295
|
let updated = false;
|
|
2232
2296
|
if (!toml.includes("lynx-xelement =")) {
|
|
2233
2297
|
toml = toml.replace(
|
|
@@ -2239,13 +2303,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2239
2303
|
updated = true;
|
|
2240
2304
|
}
|
|
2241
2305
|
if (updated) {
|
|
2242
|
-
|
|
2306
|
+
fs9.writeFileSync(libsTomlPath, toml);
|
|
2243
2307
|
console.log("\u2705 Added XElement entries to version catalog.");
|
|
2244
2308
|
}
|
|
2245
2309
|
}
|
|
2246
|
-
const appBuildPath =
|
|
2247
|
-
if (
|
|
2248
|
-
let content =
|
|
2310
|
+
const appBuildPath = path9.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2311
|
+
if (fs9.existsSync(appBuildPath)) {
|
|
2312
|
+
let content = fs9.readFileSync(appBuildPath, "utf8");
|
|
2249
2313
|
if (!content.includes("lynx.xelement")) {
|
|
2250
2314
|
content = content.replace(
|
|
2251
2315
|
/(implementation\(libs\.lynx\.service\.http\))/,
|
|
@@ -2253,15 +2317,15 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2253
2317
|
implementation(libs.lynx.xelement)
|
|
2254
2318
|
implementation(libs.lynx.xelement.input)`
|
|
2255
2319
|
);
|
|
2256
|
-
|
|
2320
|
+
fs9.writeFileSync(appBuildPath, content);
|
|
2257
2321
|
console.log("\u2705 Added XElement dependencies to app build.gradle.kts.");
|
|
2258
2322
|
}
|
|
2259
2323
|
}
|
|
2260
2324
|
}
|
|
2261
2325
|
function ensureReleaseSigning() {
|
|
2262
|
-
const appBuildPath =
|
|
2263
|
-
if (!
|
|
2264
|
-
let content =
|
|
2326
|
+
const appBuildPath = path9.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2327
|
+
if (!fs9.existsSync(appBuildPath)) return;
|
|
2328
|
+
let content = fs9.readFileSync(appBuildPath, "utf8");
|
|
2265
2329
|
if (content.includes('signingConfig = signingConfigs.getByName("debug")')) return;
|
|
2266
2330
|
const releaseBlock = /(release\s*\{)([\s\S]*?)(\n \}\s*\n(\s*\}|\s*compileOptions))/;
|
|
2267
2331
|
const match = content.match(releaseBlock);
|
|
@@ -2272,13 +2336,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2272
2336
|
signingConfig = signingConfigs.getByName("debug")
|
|
2273
2337
|
$3`
|
|
2274
2338
|
);
|
|
2275
|
-
|
|
2339
|
+
fs9.writeFileSync(appBuildPath, content);
|
|
2276
2340
|
console.log("\u2705 Set release signing to debug so installRelease works without a keystore.");
|
|
2277
2341
|
}
|
|
2278
2342
|
}
|
|
2279
2343
|
function syncManifestPermissions(packages) {
|
|
2280
|
-
const manifestPath =
|
|
2281
|
-
if (!
|
|
2344
|
+
const manifestPath = path9.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2345
|
+
if (!fs9.existsSync(manifestPath)) return;
|
|
2282
2346
|
const allPermissions = /* @__PURE__ */ new Set();
|
|
2283
2347
|
for (const pkg of packages) {
|
|
2284
2348
|
const perms = pkg.config.android?.permissions;
|
|
@@ -2290,7 +2354,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2290
2354
|
}
|
|
2291
2355
|
}
|
|
2292
2356
|
if (allPermissions.size === 0) return;
|
|
2293
|
-
let manifest =
|
|
2357
|
+
let manifest = fs9.readFileSync(manifestPath, "utf8");
|
|
2294
2358
|
const existingMatch = [...manifest.matchAll(/<uses-permission android:name="(android\.permission\.\w+)"\s*\/>/g)];
|
|
2295
2359
|
const existing = new Set(existingMatch.map((m) => m[1]));
|
|
2296
2360
|
const toAdd = [...allPermissions].filter((p) => !existing.has(p));
|
|
@@ -2301,7 +2365,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2301
2365
|
`${newLines}
|
|
2302
2366
|
$1$2`
|
|
2303
2367
|
);
|
|
2304
|
-
|
|
2368
|
+
fs9.writeFileSync(manifestPath, manifest);
|
|
2305
2369
|
console.log(`\u2705 Synced manifest permissions: ${toAdd.map((p) => p.split(".").pop()).join(", ")}`);
|
|
2306
2370
|
}
|
|
2307
2371
|
run();
|
|
@@ -2309,26 +2373,26 @@ $1$2`
|
|
|
2309
2373
|
var autolink_default = autolink;
|
|
2310
2374
|
|
|
2311
2375
|
// src/android/bundle.ts
|
|
2312
|
-
import
|
|
2313
|
-
import
|
|
2376
|
+
import fs13 from "fs";
|
|
2377
|
+
import path13 from "path";
|
|
2314
2378
|
import { execSync as execSync3 } from "child_process";
|
|
2315
2379
|
|
|
2316
2380
|
// src/common/copyDistAssets.ts
|
|
2317
|
-
import
|
|
2318
|
-
import
|
|
2381
|
+
import fs10 from "fs";
|
|
2382
|
+
import path10 from "path";
|
|
2319
2383
|
var SKIP = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
2320
2384
|
function copyDistAssets(distDir, destDir, bundleFile) {
|
|
2321
|
-
if (!
|
|
2322
|
-
for (const entry of
|
|
2385
|
+
if (!fs10.existsSync(distDir)) return;
|
|
2386
|
+
for (const entry of fs10.readdirSync(distDir)) {
|
|
2323
2387
|
if (SKIP.has(entry)) continue;
|
|
2324
|
-
const src =
|
|
2325
|
-
const dest =
|
|
2326
|
-
const stat =
|
|
2388
|
+
const src = path10.join(distDir, entry);
|
|
2389
|
+
const dest = path10.join(destDir, entry);
|
|
2390
|
+
const stat = fs10.statSync(src);
|
|
2327
2391
|
if (stat.isDirectory()) {
|
|
2328
|
-
|
|
2392
|
+
fs10.mkdirSync(dest, { recursive: true });
|
|
2329
2393
|
copyDistAssets(src, dest, bundleFile);
|
|
2330
2394
|
} else {
|
|
2331
|
-
|
|
2395
|
+
fs10.copyFileSync(src, dest);
|
|
2332
2396
|
if (entry !== bundleFile) {
|
|
2333
2397
|
console.log(`\u2728 Copied asset: ${entry}`);
|
|
2334
2398
|
}
|
|
@@ -2337,8 +2401,8 @@ function copyDistAssets(distDir, destDir, bundleFile) {
|
|
|
2337
2401
|
}
|
|
2338
2402
|
|
|
2339
2403
|
// src/common/tsconfigUtils.ts
|
|
2340
|
-
import
|
|
2341
|
-
import
|
|
2404
|
+
import fs11 from "fs";
|
|
2405
|
+
import path11 from "path";
|
|
2342
2406
|
function stripJsonCommentsAndTrailingCommas(str) {
|
|
2343
2407
|
return str.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,\s*([\]}])/g, "$1");
|
|
2344
2408
|
}
|
|
@@ -2350,19 +2414,19 @@ function parseTsconfigJson(raw) {
|
|
|
2350
2414
|
}
|
|
2351
2415
|
}
|
|
2352
2416
|
function readTsconfig(filePath) {
|
|
2353
|
-
if (!
|
|
2417
|
+
if (!fs11.existsSync(filePath)) return null;
|
|
2354
2418
|
try {
|
|
2355
|
-
return parseTsconfigJson(
|
|
2419
|
+
return parseTsconfigJson(fs11.readFileSync(filePath, "utf-8"));
|
|
2356
2420
|
} catch {
|
|
2357
2421
|
return null;
|
|
2358
2422
|
}
|
|
2359
2423
|
}
|
|
2360
2424
|
function resolveExtends(tsconfig, dir) {
|
|
2361
2425
|
if (!tsconfig.extends) return tsconfig;
|
|
2362
|
-
const basePath =
|
|
2426
|
+
const basePath = path11.resolve(dir, tsconfig.extends);
|
|
2363
2427
|
const base = readTsconfig(basePath);
|
|
2364
2428
|
if (!base) return tsconfig;
|
|
2365
|
-
const baseDir =
|
|
2429
|
+
const baseDir = path11.dirname(basePath);
|
|
2366
2430
|
const resolved = resolveExtends(base, baseDir);
|
|
2367
2431
|
return {
|
|
2368
2432
|
...resolved,
|
|
@@ -2371,13 +2435,13 @@ function resolveExtends(tsconfig, dir) {
|
|
|
2371
2435
|
};
|
|
2372
2436
|
}
|
|
2373
2437
|
function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
2374
|
-
const dir =
|
|
2438
|
+
const dir = path11.dirname(tsconfigPath);
|
|
2375
2439
|
const tsconfig = readTsconfig(tsconfigPath);
|
|
2376
2440
|
if (!tsconfig?.references?.length) return false;
|
|
2377
2441
|
const refsWithNoEmit = [];
|
|
2378
2442
|
for (const ref of tsconfig.references) {
|
|
2379
|
-
const refPath =
|
|
2380
|
-
const refConfigPath = refPath.endsWith(".json") ? refPath :
|
|
2443
|
+
const refPath = path11.resolve(dir, ref.path);
|
|
2444
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path11.join(refPath, "tsconfig.json");
|
|
2381
2445
|
const refConfig = readTsconfig(refConfigPath);
|
|
2382
2446
|
if (refConfig?.compilerOptions?.noEmit === true) {
|
|
2383
2447
|
refsWithNoEmit.push(refConfigPath);
|
|
@@ -2392,16 +2456,16 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2392
2456
|
const includes = [];
|
|
2393
2457
|
const compilerOpts = { ...tsconfig.compilerOptions };
|
|
2394
2458
|
for (const ref of tsconfig.references) {
|
|
2395
|
-
const refPath =
|
|
2396
|
-
const refConfigPath = refPath.endsWith(".json") ? refPath :
|
|
2459
|
+
const refPath = path11.resolve(dir, ref.path);
|
|
2460
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path11.join(refPath, "tsconfig.json");
|
|
2397
2461
|
const refConfig = readTsconfig(refConfigPath);
|
|
2398
2462
|
if (!refConfig) continue;
|
|
2399
|
-
const refDir =
|
|
2463
|
+
const refDir = path11.dirname(refConfigPath);
|
|
2400
2464
|
const resolved = resolveExtends(refConfig, refDir);
|
|
2401
2465
|
const inc = resolved.include;
|
|
2402
2466
|
if (inc) {
|
|
2403
2467
|
const arr = Array.isArray(inc) ? inc : [inc];
|
|
2404
|
-
const baseDir =
|
|
2468
|
+
const baseDir = path11.relative(dir, refDir);
|
|
2405
2469
|
for (const p of arr) {
|
|
2406
2470
|
const clean = typeof p === "string" && p.startsWith("./") ? p.slice(2) : p;
|
|
2407
2471
|
includes.push(!baseDir || baseDir === "." ? clean : `${baseDir}/${clean}`);
|
|
@@ -2419,7 +2483,7 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2419
2483
|
if (includes.length > 0) merged.include = [...new Set(includes)];
|
|
2420
2484
|
compilerOpts.noEmit = true;
|
|
2421
2485
|
merged.compilerOptions = compilerOpts;
|
|
2422
|
-
|
|
2486
|
+
fs11.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
|
|
2423
2487
|
return true;
|
|
2424
2488
|
}
|
|
2425
2489
|
function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
@@ -2430,23 +2494,23 @@ function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
|
2430
2494
|
if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) return false;
|
|
2431
2495
|
arr.push(tamerTypesInclude);
|
|
2432
2496
|
tsconfig.include = arr;
|
|
2433
|
-
|
|
2497
|
+
fs11.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
2434
2498
|
return true;
|
|
2435
2499
|
}
|
|
2436
2500
|
|
|
2437
2501
|
// src/android/syncDevClient.ts
|
|
2438
|
-
import
|
|
2439
|
-
import
|
|
2502
|
+
import fs12 from "fs";
|
|
2503
|
+
import path12 from "path";
|
|
2440
2504
|
function readAndSubstituteTemplate2(templatePath, vars) {
|
|
2441
|
-
const raw =
|
|
2505
|
+
const raw = fs12.readFileSync(templatePath, "utf-8");
|
|
2442
2506
|
return Object.entries(vars).reduce(
|
|
2443
2507
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2444
2508
|
raw
|
|
2445
2509
|
);
|
|
2446
2510
|
}
|
|
2447
2511
|
function patchAppLogService(appPath) {
|
|
2448
|
-
if (!
|
|
2449
|
-
const raw =
|
|
2512
|
+
if (!fs12.existsSync(appPath)) return;
|
|
2513
|
+
const raw = fs12.readFileSync(appPath, "utf-8");
|
|
2450
2514
|
const patched = raw.replace(
|
|
2451
2515
|
/private void initLynxService\(\)\s*\{[\s\S]*?\n\s*}\s*\n\s*private void initFresco\(\)/,
|
|
2452
2516
|
`private void initLynxService() {
|
|
@@ -2465,7 +2529,7 @@ function patchAppLogService(appPath) {
|
|
|
2465
2529
|
private void initFresco()`
|
|
2466
2530
|
);
|
|
2467
2531
|
if (patched !== raw) {
|
|
2468
|
-
|
|
2532
|
+
fs12.writeFileSync(appPath, patched);
|
|
2469
2533
|
}
|
|
2470
2534
|
}
|
|
2471
2535
|
async function syncDevClient(opts) {
|
|
@@ -2480,9 +2544,9 @@ async function syncDevClient(opts) {
|
|
|
2480
2544
|
const packageName = config.android?.packageName;
|
|
2481
2545
|
const appName = config.android?.appName;
|
|
2482
2546
|
const packagePath = packageName.replace(/\./g, "/");
|
|
2483
|
-
const javaDir =
|
|
2484
|
-
const kotlinDir =
|
|
2485
|
-
if (!
|
|
2547
|
+
const javaDir = path12.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
2548
|
+
const kotlinDir = path12.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
2549
|
+
if (!fs12.existsSync(javaDir) || !fs12.existsSync(kotlinDir)) {
|
|
2486
2550
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2487
2551
|
process.exit(1);
|
|
2488
2552
|
}
|
|
@@ -2498,14 +2562,14 @@ async function syncDevClient(opts) {
|
|
|
2498
2562
|
const [templateProviderSource] = await Promise.all([
|
|
2499
2563
|
fetchAndPatchTemplateProvider(vars)
|
|
2500
2564
|
]);
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
patchAppLogService(
|
|
2504
|
-
const appDir =
|
|
2505
|
-
const mainDir =
|
|
2506
|
-
const manifestPath =
|
|
2565
|
+
fs12.writeFileSync(path12.join(javaDir, "TemplateProvider.java"), templateProviderSource);
|
|
2566
|
+
fs12.writeFileSync(path12.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
|
|
2567
|
+
patchAppLogService(path12.join(javaDir, "App.java"));
|
|
2568
|
+
const appDir = path12.join(rootDir, "app");
|
|
2569
|
+
const mainDir = path12.join(appDir, "src", "main");
|
|
2570
|
+
const manifestPath = path12.join(mainDir, "AndroidManifest.xml");
|
|
2507
2571
|
if (hasDevClient) {
|
|
2508
|
-
const templateDir =
|
|
2572
|
+
const templateDir = path12.join(devClientPkg, "android", "templates");
|
|
2509
2573
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
2510
2574
|
const devClientFiles = [
|
|
2511
2575
|
"DevClientManager.kt",
|
|
@@ -2514,13 +2578,13 @@ async function syncDevClient(opts) {
|
|
|
2514
2578
|
"PortraitCaptureActivity.kt"
|
|
2515
2579
|
];
|
|
2516
2580
|
for (const f of devClientFiles) {
|
|
2517
|
-
const src =
|
|
2518
|
-
if (
|
|
2581
|
+
const src = path12.join(templateDir, f);
|
|
2582
|
+
if (fs12.existsSync(src)) {
|
|
2519
2583
|
const content = readAndSubstituteTemplate2(src, templateVars);
|
|
2520
|
-
|
|
2584
|
+
fs12.writeFileSync(path12.join(kotlinDir, f), content);
|
|
2521
2585
|
}
|
|
2522
2586
|
}
|
|
2523
|
-
let manifest =
|
|
2587
|
+
let manifest = fs12.readFileSync(manifestPath, "utf-8");
|
|
2524
2588
|
const projectActivityEntry = ' <activity android:name=".ProjectActivity" android:exported="false" android:taskAffinity="" android:launchMode="singleTask" android:documentLaunchMode="always" android:windowSoftInputMode="adjustResize" />';
|
|
2525
2589
|
const portraitCaptureEntry = ' <activity android:name=".PortraitCaptureActivity" android:screenOrientation="portrait" android:stateNotNeeded="true" android:theme="@style/zxing_CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden" />';
|
|
2526
2590
|
if (!manifest.includes("ProjectActivity")) {
|
|
@@ -2542,16 +2606,16 @@ $1$2`);
|
|
|
2542
2606
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2543
2607
|
);
|
|
2544
2608
|
}
|
|
2545
|
-
|
|
2609
|
+
fs12.writeFileSync(manifestPath, manifest);
|
|
2546
2610
|
console.log("\u2705 Synced dev client (TemplateProvider, MainActivity, ProjectActivity, DevClientManager)");
|
|
2547
2611
|
} else {
|
|
2548
2612
|
for (const f of ["DevClientManager.kt", "DevServerPrefs.kt", "ProjectActivity.kt", "PortraitCaptureActivity.kt", "DevLauncherActivity.kt"]) {
|
|
2549
2613
|
try {
|
|
2550
|
-
|
|
2614
|
+
fs12.rmSync(path12.join(kotlinDir, f));
|
|
2551
2615
|
} catch {
|
|
2552
2616
|
}
|
|
2553
2617
|
}
|
|
2554
|
-
let manifest =
|
|
2618
|
+
let manifest = fs12.readFileSync(manifestPath, "utf-8");
|
|
2555
2619
|
manifest = manifest.replace(/\s*<activity android:name="\.ProjectActivity"[^\/]*\/>\n?/g, "");
|
|
2556
2620
|
manifest = manifest.replace(/\s*<activity android:name="\.PortraitCaptureActivity"[^\/]*\/>\n?/g, "");
|
|
2557
2621
|
const mainActivityTag = manifest.match(/<activity[^>]*android:name="\.MainActivity"[^>]*>/);
|
|
@@ -2561,7 +2625,7 @@ $1$2`);
|
|
|
2561
2625
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2562
2626
|
);
|
|
2563
2627
|
}
|
|
2564
|
-
|
|
2628
|
+
fs12.writeFileSync(manifestPath, manifest);
|
|
2565
2629
|
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2566
2630
|
}
|
|
2567
2631
|
}
|
|
@@ -2585,15 +2649,15 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2585
2649
|
await syncDevClient_default({ includeDevClient });
|
|
2586
2650
|
const iconPaths = resolveIconPaths(projectRoot, resolved.config);
|
|
2587
2651
|
if (iconPaths) {
|
|
2588
|
-
const resDir =
|
|
2652
|
+
const resDir = path13.join(resolved.androidAppDir, "src", "main", "res");
|
|
2589
2653
|
if (applyAndroidLauncherIcons(resDir, iconPaths)) {
|
|
2590
2654
|
console.log("\u2705 Synced Android launcher icon(s) from tamer.config.json");
|
|
2591
|
-
ensureAndroidManifestLauncherIcon(
|
|
2655
|
+
ensureAndroidManifestLauncherIcon(path13.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
|
|
2592
2656
|
}
|
|
2593
2657
|
}
|
|
2594
2658
|
try {
|
|
2595
|
-
const lynxTsconfig =
|
|
2596
|
-
if (
|
|
2659
|
+
const lynxTsconfig = path13.join(lynxProjectDir, "tsconfig.json");
|
|
2660
|
+
if (fs13.existsSync(lynxTsconfig)) {
|
|
2597
2661
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
2598
2662
|
}
|
|
2599
2663
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -2603,8 +2667,8 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2603
2667
|
console.error("\u274C Build process failed.");
|
|
2604
2668
|
process.exit(1);
|
|
2605
2669
|
}
|
|
2606
|
-
if (includeDevClient && devClientBundlePath && !
|
|
2607
|
-
const devClientDir =
|
|
2670
|
+
if (includeDevClient && devClientBundlePath && !fs13.existsSync(devClientBundlePath)) {
|
|
2671
|
+
const devClientDir = path13.dirname(path13.dirname(devClientBundlePath));
|
|
2608
2672
|
try {
|
|
2609
2673
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
2610
2674
|
execSync3("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
@@ -2615,22 +2679,22 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2615
2679
|
}
|
|
2616
2680
|
}
|
|
2617
2681
|
try {
|
|
2618
|
-
|
|
2682
|
+
fs13.mkdirSync(destinationDir, { recursive: true });
|
|
2619
2683
|
if (release) {
|
|
2620
|
-
const devClientAsset =
|
|
2621
|
-
if (
|
|
2622
|
-
|
|
2684
|
+
const devClientAsset = path13.join(destinationDir, "dev-client.lynx.bundle");
|
|
2685
|
+
if (fs13.existsSync(devClientAsset)) {
|
|
2686
|
+
fs13.rmSync(devClientAsset);
|
|
2623
2687
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2624
2688
|
}
|
|
2625
|
-
} else if (includeDevClient && devClientBundlePath &&
|
|
2626
|
-
|
|
2689
|
+
} else if (includeDevClient && devClientBundlePath && fs13.existsSync(devClientBundlePath)) {
|
|
2690
|
+
fs13.copyFileSync(devClientBundlePath, path13.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2627
2691
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2628
2692
|
}
|
|
2629
|
-
if (!
|
|
2693
|
+
if (!fs13.existsSync(lynxBundlePath)) {
|
|
2630
2694
|
console.error(`\u274C Build output not found at: ${lynxBundlePath}`);
|
|
2631
2695
|
process.exit(1);
|
|
2632
2696
|
}
|
|
2633
|
-
const distDir =
|
|
2697
|
+
const distDir = path13.dirname(lynxBundlePath);
|
|
2634
2698
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
2635
2699
|
console.log(`\u2728 Copied ${resolved.lynxBundleFile} to assets`);
|
|
2636
2700
|
} catch (error) {
|
|
@@ -2641,7 +2705,7 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2641
2705
|
var bundle_default = bundleAndDeploy;
|
|
2642
2706
|
|
|
2643
2707
|
// src/android/build.ts
|
|
2644
|
-
import
|
|
2708
|
+
import path14 from "path";
|
|
2645
2709
|
import { execSync as execSync4 } from "child_process";
|
|
2646
2710
|
async function buildApk(opts = {}) {
|
|
2647
2711
|
let resolved;
|
|
@@ -2652,12 +2716,17 @@ async function buildApk(opts = {}) {
|
|
|
2652
2716
|
}
|
|
2653
2717
|
const release = opts.release === true || opts.production === true;
|
|
2654
2718
|
await bundle_default({ release, production: opts.production });
|
|
2655
|
-
const androidDir = resolved
|
|
2656
|
-
|
|
2719
|
+
const { androidDir, projectRoot } = resolved;
|
|
2720
|
+
cleanTamerAndroidLibBuildsIfVersionsChanged(projectRoot);
|
|
2721
|
+
const gradlew = path14.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2657
2722
|
const variant = release ? "Release" : "Debug";
|
|
2658
2723
|
const task = opts.install ? `install${variant}` : `assemble${variant}`;
|
|
2659
2724
|
console.log(`
|
|
2660
2725
|
\u{1F528} Building ${variant.toLowerCase()} APK${opts.install ? " and installing" : ""}...`);
|
|
2726
|
+
if (opts.clean === true) {
|
|
2727
|
+
console.log("\u2139\uFE0F Running Gradle clean (--clean)...");
|
|
2728
|
+
execSync4(`"${gradlew}" clean`, { stdio: "inherit", cwd: androidDir });
|
|
2729
|
+
}
|
|
2661
2730
|
execSync4(`"${gradlew}" ${task}`, { stdio: "inherit", cwd: androidDir });
|
|
2662
2731
|
console.log(`\u2705 APK ${opts.install ? "installed" : "built"} successfully.`);
|
|
2663
2732
|
if (opts.install) {
|
|
@@ -2678,13 +2747,13 @@ async function buildApk(opts = {}) {
|
|
|
2678
2747
|
var build_default = buildApk;
|
|
2679
2748
|
|
|
2680
2749
|
// src/ios/create.ts
|
|
2681
|
-
import
|
|
2682
|
-
import
|
|
2750
|
+
import fs15 from "fs";
|
|
2751
|
+
import path16 from "path";
|
|
2683
2752
|
|
|
2684
2753
|
// src/ios/getPod.ts
|
|
2685
2754
|
import { execSync as execSync5 } from "child_process";
|
|
2686
|
-
import
|
|
2687
|
-
import
|
|
2755
|
+
import fs14 from "fs";
|
|
2756
|
+
import path15 from "path";
|
|
2688
2757
|
function isCocoaPodsInstalled() {
|
|
2689
2758
|
try {
|
|
2690
2759
|
execSync5("command -v pod >/dev/null 2>&1");
|
|
@@ -2706,8 +2775,8 @@ async function setupCocoaPods(rootDir) {
|
|
|
2706
2775
|
}
|
|
2707
2776
|
try {
|
|
2708
2777
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2709
|
-
const podfilePath =
|
|
2710
|
-
if (!
|
|
2778
|
+
const podfilePath = path15.join(rootDir, "Podfile");
|
|
2779
|
+
if (!fs14.existsSync(podfilePath)) {
|
|
2711
2780
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2712
2781
|
}
|
|
2713
2782
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2733,7 +2802,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2733
2802
|
// src/ios/create.ts
|
|
2734
2803
|
import { randomBytes } from "crypto";
|
|
2735
2804
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2736
|
-
const raw =
|
|
2805
|
+
const raw = fs15.readFileSync(templatePath, "utf-8");
|
|
2737
2806
|
return Object.entries(vars).reduce(
|
|
2738
2807
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2739
2808
|
raw
|
|
@@ -2756,17 +2825,17 @@ var create2 = () => {
|
|
|
2756
2825
|
process.exit(1);
|
|
2757
2826
|
}
|
|
2758
2827
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
2759
|
-
const rootDir =
|
|
2760
|
-
const projectDir =
|
|
2761
|
-
const xcodeprojDir =
|
|
2828
|
+
const rootDir = path16.join(process.cwd(), iosDir);
|
|
2829
|
+
const projectDir = path16.join(rootDir, appName);
|
|
2830
|
+
const xcodeprojDir = path16.join(rootDir, `${appName}.xcodeproj`);
|
|
2762
2831
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2763
2832
|
function writeFile2(filePath, content) {
|
|
2764
|
-
|
|
2765
|
-
|
|
2833
|
+
fs15.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
2834
|
+
fs15.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2766
2835
|
}
|
|
2767
|
-
if (
|
|
2836
|
+
if (fs15.existsSync(rootDir)) {
|
|
2768
2837
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2769
|
-
|
|
2838
|
+
fs15.rmSync(rootDir, { recursive: true, force: true });
|
|
2770
2839
|
}
|
|
2771
2840
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2772
2841
|
const ids = {
|
|
@@ -2802,7 +2871,7 @@ var create2 = () => {
|
|
|
2802
2871
|
targetDebugConfig: generateId(),
|
|
2803
2872
|
targetReleaseConfig: generateId()
|
|
2804
2873
|
};
|
|
2805
|
-
writeFile2(
|
|
2874
|
+
writeFile2(path16.join(rootDir, "Podfile"), `
|
|
2806
2875
|
source 'https://cdn.cocoapods.org/'
|
|
2807
2876
|
|
|
2808
2877
|
install! 'cocoapods', :incremental_installation => true, :generate_multiple_pod_projects => true
|
|
@@ -2889,15 +2958,15 @@ end
|
|
|
2889
2958
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
2890
2959
|
const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
2891
2960
|
if (hostPkg) {
|
|
2892
|
-
const templateDir =
|
|
2961
|
+
const templateDir = path16.join(hostPkg, "ios", "templates");
|
|
2893
2962
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2894
|
-
const srcPath =
|
|
2895
|
-
if (
|
|
2896
|
-
writeFile2(
|
|
2963
|
+
const srcPath = path16.join(templateDir, f);
|
|
2964
|
+
if (fs15.existsSync(srcPath)) {
|
|
2965
|
+
writeFile2(path16.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2897
2966
|
}
|
|
2898
2967
|
}
|
|
2899
2968
|
} else {
|
|
2900
|
-
writeFile2(
|
|
2969
|
+
writeFile2(path16.join(projectDir, "AppDelegate.swift"), `
|
|
2901
2970
|
import UIKit
|
|
2902
2971
|
|
|
2903
2972
|
@UIApplicationMain
|
|
@@ -2912,7 +2981,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2912
2981
|
}
|
|
2913
2982
|
}
|
|
2914
2983
|
`);
|
|
2915
|
-
writeFile2(
|
|
2984
|
+
writeFile2(path16.join(projectDir, "SceneDelegate.swift"), `
|
|
2916
2985
|
import UIKit
|
|
2917
2986
|
|
|
2918
2987
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2926,7 +2995,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2926
2995
|
}
|
|
2927
2996
|
}
|
|
2928
2997
|
`);
|
|
2929
|
-
writeFile2(
|
|
2998
|
+
writeFile2(path16.join(projectDir, "ViewController.swift"), `
|
|
2930
2999
|
import UIKit
|
|
2931
3000
|
import Lynx
|
|
2932
3001
|
import tamerinsets
|
|
@@ -2999,7 +3068,7 @@ class ViewController: UIViewController {
|
|
|
2999
3068
|
}
|
|
3000
3069
|
}
|
|
3001
3070
|
`);
|
|
3002
|
-
writeFile2(
|
|
3071
|
+
writeFile2(path16.join(projectDir, "LynxProvider.swift"), `
|
|
3003
3072
|
import Foundation
|
|
3004
3073
|
|
|
3005
3074
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -3018,7 +3087,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3018
3087
|
}
|
|
3019
3088
|
}
|
|
3020
3089
|
`);
|
|
3021
|
-
writeFile2(
|
|
3090
|
+
writeFile2(path16.join(projectDir, "LynxInitProcessor.swift"), `
|
|
3022
3091
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
3023
3092
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
3024
3093
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -3058,7 +3127,7 @@ final class LynxInitProcessor {
|
|
|
3058
3127
|
}
|
|
3059
3128
|
`);
|
|
3060
3129
|
}
|
|
3061
|
-
writeFile2(
|
|
3130
|
+
writeFile2(path16.join(projectDir, bridgingHeader), `
|
|
3062
3131
|
#import <Lynx/LynxConfig.h>
|
|
3063
3132
|
#import <Lynx/LynxEnv.h>
|
|
3064
3133
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -3067,7 +3136,7 @@ final class LynxInitProcessor {
|
|
|
3067
3136
|
#import <SDWebImage/SDWebImage.h>
|
|
3068
3137
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
3069
3138
|
`);
|
|
3070
|
-
writeFile2(
|
|
3139
|
+
writeFile2(path16.join(projectDir, "Info.plist"), `
|
|
3071
3140
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
3072
3141
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3073
3142
|
<plist version="1.0">
|
|
@@ -3125,21 +3194,21 @@ final class LynxInitProcessor {
|
|
|
3125
3194
|
</dict>
|
|
3126
3195
|
</plist>
|
|
3127
3196
|
`);
|
|
3128
|
-
const appIconDir =
|
|
3129
|
-
|
|
3197
|
+
const appIconDir = path16.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
3198
|
+
fs15.mkdirSync(appIconDir, { recursive: true });
|
|
3130
3199
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
3131
3200
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
3132
3201
|
console.log(iconPaths?.ios ? "\u2705 Copied iOS icon from tamer.config.json icon.ios" : "\u2705 Copied app icon from tamer.config.json icon.source");
|
|
3133
3202
|
} else {
|
|
3134
|
-
writeFile2(
|
|
3203
|
+
writeFile2(path16.join(appIconDir, "Contents.json"), `
|
|
3135
3204
|
{
|
|
3136
3205
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
3137
3206
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
3138
3207
|
}
|
|
3139
3208
|
`);
|
|
3140
3209
|
}
|
|
3141
|
-
|
|
3142
|
-
writeFile2(
|
|
3210
|
+
fs15.mkdirSync(xcodeprojDir, { recursive: true });
|
|
3211
|
+
writeFile2(path16.join(xcodeprojDir, "project.pbxproj"), `
|
|
3143
3212
|
// !$*UTF8*$!
|
|
3144
3213
|
{
|
|
3145
3214
|
archiveVersion = 1;
|
|
@@ -3425,8 +3494,8 @@ final class LynxInitProcessor {
|
|
|
3425
3494
|
var create_default2 = create2;
|
|
3426
3495
|
|
|
3427
3496
|
// src/ios/autolink.ts
|
|
3428
|
-
import
|
|
3429
|
-
import
|
|
3497
|
+
import fs17 from "fs";
|
|
3498
|
+
import path18 from "path";
|
|
3430
3499
|
import { execSync as execSync6 } from "child_process";
|
|
3431
3500
|
|
|
3432
3501
|
// src/common/hostNativeModulesManifest.ts
|
|
@@ -3437,8 +3506,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
|
|
|
3437
3506
|
}
|
|
3438
3507
|
|
|
3439
3508
|
// src/ios/syncHost.ts
|
|
3440
|
-
import
|
|
3441
|
-
import
|
|
3509
|
+
import fs16 from "fs";
|
|
3510
|
+
import path17 from "path";
|
|
3442
3511
|
import crypto from "crypto";
|
|
3443
3512
|
function deterministicUUID(seed) {
|
|
3444
3513
|
return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
|
|
@@ -3486,7 +3555,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3486
3555
|
`;
|
|
3487
3556
|
}
|
|
3488
3557
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3489
|
-
let content =
|
|
3558
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3490
3559
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3491
3560
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3492
3561
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3523,11 +3592,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3523
3592
|
);
|
|
3524
3593
|
content = content.replace(groupPattern, `$1
|
|
3525
3594
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3526
|
-
|
|
3595
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3527
3596
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3528
3597
|
}
|
|
3529
3598
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3530
|
-
let content =
|
|
3599
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3531
3600
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3532
3601
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3533
3602
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3552,11 +3621,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3552
3621
|
);
|
|
3553
3622
|
content = content.replace(groupPattern, `$1
|
|
3554
3623
|
${fileRefUUID} /* ${filename} */,`);
|
|
3555
|
-
|
|
3624
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3556
3625
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3557
3626
|
}
|
|
3558
3627
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3559
|
-
let content =
|
|
3628
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3560
3629
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3561
3630
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3562
3631
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3581,12 +3650,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3581
3650
|
);
|
|
3582
3651
|
content = content.replace(groupPattern, `$1
|
|
3583
3652
|
${fileRefUUID} /* ${filename} */,`);
|
|
3584
|
-
|
|
3653
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3585
3654
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3586
3655
|
}
|
|
3587
3656
|
function writeFile(filePath, content) {
|
|
3588
|
-
|
|
3589
|
-
|
|
3657
|
+
fs16.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
3658
|
+
fs16.writeFileSync(filePath, content, "utf8");
|
|
3590
3659
|
}
|
|
3591
3660
|
function getAppDelegateSwift() {
|
|
3592
3661
|
return `import UIKit
|
|
@@ -3803,8 +3872,8 @@ class ViewController: UIViewController {
|
|
|
3803
3872
|
`;
|
|
3804
3873
|
}
|
|
3805
3874
|
function patchInfoPlist(infoPlistPath) {
|
|
3806
|
-
if (!
|
|
3807
|
-
let content =
|
|
3875
|
+
if (!fs16.existsSync(infoPlistPath)) return;
|
|
3876
|
+
let content = fs16.readFileSync(infoPlistPath, "utf8");
|
|
3808
3877
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3809
3878
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3810
3879
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3836,7 +3905,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3836
3905
|
</plist>`);
|
|
3837
3906
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3838
3907
|
}
|
|
3839
|
-
|
|
3908
|
+
fs16.writeFileSync(infoPlistPath, content, "utf8");
|
|
3840
3909
|
}
|
|
3841
3910
|
function getSimpleLynxProviderSwift() {
|
|
3842
3911
|
return `import Foundation
|
|
@@ -3861,9 +3930,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3861
3930
|
}
|
|
3862
3931
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3863
3932
|
if (devClientPkg) {
|
|
3864
|
-
const tplPath =
|
|
3865
|
-
if (
|
|
3866
|
-
let content =
|
|
3933
|
+
const tplPath = path17.join(devClientPkg, "ios", "templates", templateName);
|
|
3934
|
+
if (fs16.existsSync(tplPath)) {
|
|
3935
|
+
let content = fs16.readFileSync(tplPath, "utf8");
|
|
3867
3936
|
for (const [k, v] of Object.entries(vars)) {
|
|
3868
3937
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3869
3938
|
}
|
|
@@ -3881,19 +3950,19 @@ function syncHostIos(opts) {
|
|
|
3881
3950
|
if (!appName) {
|
|
3882
3951
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3883
3952
|
}
|
|
3884
|
-
const projectDir =
|
|
3885
|
-
const infoPlistPath =
|
|
3886
|
-
if (!
|
|
3953
|
+
const projectDir = path17.join(resolved.iosDir, appName);
|
|
3954
|
+
const infoPlistPath = path17.join(projectDir, "Info.plist");
|
|
3955
|
+
if (!fs16.existsSync(projectDir)) {
|
|
3887
3956
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3888
3957
|
}
|
|
3889
|
-
const pbxprojPath =
|
|
3890
|
-
const baseLprojDir =
|
|
3891
|
-
const launchScreenPath =
|
|
3958
|
+
const pbxprojPath = path17.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3959
|
+
const baseLprojDir = path17.join(projectDir, "Base.lproj");
|
|
3960
|
+
const launchScreenPath = path17.join(baseLprojDir, "LaunchScreen.storyboard");
|
|
3892
3961
|
patchInfoPlist(infoPlistPath);
|
|
3893
|
-
writeFile(
|
|
3894
|
-
writeFile(
|
|
3895
|
-
if (!
|
|
3896
|
-
|
|
3962
|
+
writeFile(path17.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
|
|
3963
|
+
writeFile(path17.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
|
|
3964
|
+
if (!fs16.existsSync(launchScreenPath)) {
|
|
3965
|
+
fs16.mkdirSync(baseLprojDir, { recursive: true });
|
|
3897
3966
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3898
3967
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3899
3968
|
}
|
|
@@ -3902,33 +3971,33 @@ function syncHostIos(opts) {
|
|
|
3902
3971
|
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3903
3972
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3904
3973
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3905
|
-
writeFile(
|
|
3906
|
-
writeFile(
|
|
3974
|
+
writeFile(path17.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3975
|
+
writeFile(path17.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3907
3976
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3908
3977
|
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3909
3978
|
if (devTPContent) {
|
|
3910
|
-
writeFile(
|
|
3979
|
+
writeFile(path17.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3911
3980
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3912
3981
|
}
|
|
3913
3982
|
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3914
3983
|
if (projectVCContent) {
|
|
3915
|
-
writeFile(
|
|
3984
|
+
writeFile(path17.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3916
3985
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3917
3986
|
}
|
|
3918
3987
|
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3919
3988
|
if (devCMContent) {
|
|
3920
|
-
writeFile(
|
|
3989
|
+
writeFile(path17.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3921
3990
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3922
3991
|
}
|
|
3923
3992
|
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3924
3993
|
if (qrContent) {
|
|
3925
|
-
writeFile(
|
|
3994
|
+
writeFile(path17.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3926
3995
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
3927
3996
|
}
|
|
3928
3997
|
console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
|
|
3929
3998
|
} else {
|
|
3930
|
-
writeFile(
|
|
3931
|
-
writeFile(
|
|
3999
|
+
writeFile(path17.join(projectDir, "ViewController.swift"), getViewControllerSwift());
|
|
4000
|
+
writeFile(path17.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3932
4001
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3933
4002
|
console.log("\u2705 Synced iOS host app controller files");
|
|
3934
4003
|
}
|
|
@@ -3947,11 +4016,11 @@ var autolink2 = (syncHostOpts) => {
|
|
|
3947
4016
|
const projectRoot = resolved.projectRoot;
|
|
3948
4017
|
const iosProjectPath = resolved.iosDir;
|
|
3949
4018
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
3950
|
-
if (!
|
|
4019
|
+
if (!fs17.existsSync(filePath)) {
|
|
3951
4020
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
3952
4021
|
return;
|
|
3953
4022
|
}
|
|
3954
|
-
let fileContent =
|
|
4023
|
+
let fileContent = fs17.readFileSync(filePath, "utf8");
|
|
3955
4024
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3956
4025
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3957
4026
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3971,33 +4040,33 @@ ${replacementBlock}
|
|
|
3971
4040
|
`;
|
|
3972
4041
|
}
|
|
3973
4042
|
} else {
|
|
3974
|
-
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${
|
|
4043
|
+
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path18.basename(filePath)}. Appending to the end of the file.`);
|
|
3975
4044
|
fileContent += `
|
|
3976
4045
|
${replacementBlock}
|
|
3977
4046
|
`;
|
|
3978
4047
|
}
|
|
3979
|
-
|
|
3980
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
4048
|
+
fs17.writeFileSync(filePath, fileContent, "utf8");
|
|
4049
|
+
console.log(`\u2705 Updated autolinked section in ${path18.basename(filePath)}`);
|
|
3981
4050
|
}
|
|
3982
4051
|
function resolvePodDirectory(pkg) {
|
|
3983
|
-
const configuredDir =
|
|
3984
|
-
if (
|
|
4052
|
+
const configuredDir = path18.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
|
|
4053
|
+
if (fs17.existsSync(configuredDir)) {
|
|
3985
4054
|
return configuredDir;
|
|
3986
4055
|
}
|
|
3987
|
-
const iosDir =
|
|
3988
|
-
if (
|
|
4056
|
+
const iosDir = path18.join(pkg.packagePath, "ios");
|
|
4057
|
+
if (fs17.existsSync(iosDir)) {
|
|
3989
4058
|
const stack = [iosDir];
|
|
3990
4059
|
while (stack.length > 0) {
|
|
3991
4060
|
const current = stack.pop();
|
|
3992
4061
|
try {
|
|
3993
|
-
const entries =
|
|
4062
|
+
const entries = fs17.readdirSync(current, { withFileTypes: true });
|
|
3994
4063
|
const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
|
|
3995
4064
|
if (podspec) {
|
|
3996
4065
|
return current;
|
|
3997
4066
|
}
|
|
3998
4067
|
for (const entry of entries) {
|
|
3999
4068
|
if (entry.isDirectory()) {
|
|
4000
|
-
stack.push(
|
|
4069
|
+
stack.push(path18.join(current, entry.name));
|
|
4001
4070
|
}
|
|
4002
4071
|
}
|
|
4003
4072
|
} catch {
|
|
@@ -4008,9 +4077,9 @@ ${replacementBlock}
|
|
|
4008
4077
|
}
|
|
4009
4078
|
function resolvePodName(pkg) {
|
|
4010
4079
|
const fullPodspecDir = resolvePodDirectory(pkg);
|
|
4011
|
-
if (
|
|
4080
|
+
if (fs17.existsSync(fullPodspecDir)) {
|
|
4012
4081
|
try {
|
|
4013
|
-
const files =
|
|
4082
|
+
const files = fs17.readdirSync(fullPodspecDir);
|
|
4014
4083
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
4015
4084
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
4016
4085
|
} catch {
|
|
@@ -4019,13 +4088,13 @@ ${replacementBlock}
|
|
|
4019
4088
|
return pkg.name.split("/").pop().replace(/-/g, "");
|
|
4020
4089
|
}
|
|
4021
4090
|
function updatePodfile(packages) {
|
|
4022
|
-
const podfilePath =
|
|
4091
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4023
4092
|
let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
|
|
4024
4093
|
# Manual edits will be overwritten.`;
|
|
4025
4094
|
const iosPackages = packages.filter((p) => p.config.ios);
|
|
4026
4095
|
if (iosPackages.length > 0) {
|
|
4027
4096
|
iosPackages.forEach((pkg) => {
|
|
4028
|
-
const relativePath =
|
|
4097
|
+
const relativePath = path18.relative(iosProjectPath, resolvePodDirectory(pkg));
|
|
4029
4098
|
const podName = resolvePodName(pkg);
|
|
4030
4099
|
scriptContent += `
|
|
4031
4100
|
pod '${podName}', :path => '${relativePath}'`;
|
|
@@ -4037,9 +4106,9 @@ ${replacementBlock}
|
|
|
4037
4106
|
updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
|
|
4038
4107
|
}
|
|
4039
4108
|
function ensureXElementPod() {
|
|
4040
|
-
const podfilePath =
|
|
4041
|
-
if (!
|
|
4042
|
-
let content =
|
|
4109
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4110
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4111
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4043
4112
|
if (content.includes("pod 'XElement'")) return;
|
|
4044
4113
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
4045
4114
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -4064,7 +4133,7 @@ ${replacementBlock}
|
|
|
4064
4133
|
`;
|
|
4065
4134
|
}
|
|
4066
4135
|
}
|
|
4067
|
-
|
|
4136
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4068
4137
|
console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
|
|
4069
4138
|
}
|
|
4070
4139
|
function ensureLynxDevToolPods(packages) {
|
|
@@ -4072,9 +4141,9 @@ ${replacementBlock}
|
|
|
4072
4141
|
(p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client"
|
|
4073
4142
|
);
|
|
4074
4143
|
if (!hasDevClient) return;
|
|
4075
|
-
const podfilePath =
|
|
4076
|
-
if (!
|
|
4077
|
-
let content =
|
|
4144
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4145
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4146
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4078
4147
|
if (content.includes("pod 'LynxDevtool'") || content.includes('pod "LynxDevtool"')) return;
|
|
4079
4148
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
4080
4149
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -4102,13 +4171,13 @@ ${replacementBlock}
|
|
|
4102
4171
|
`;
|
|
4103
4172
|
}
|
|
4104
4173
|
}
|
|
4105
|
-
|
|
4174
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4106
4175
|
console.log(`\u2705 Added Lynx DevTool pods (v${lynxVersion}) to Podfile`);
|
|
4107
4176
|
}
|
|
4108
4177
|
function ensureLynxPatchInPodfile() {
|
|
4109
|
-
const podfilePath =
|
|
4110
|
-
if (!
|
|
4111
|
-
let content =
|
|
4178
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4179
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4180
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4112
4181
|
if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
|
|
4113
4182
|
const patch = `
|
|
4114
4183
|
Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
|
|
@@ -4120,13 +4189,13 @@ ${replacementBlock}
|
|
|
4120
4189
|
end`;
|
|
4121
4190
|
content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
|
|
4122
4191
|
$2`);
|
|
4123
|
-
|
|
4192
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4124
4193
|
console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
|
|
4125
4194
|
}
|
|
4126
4195
|
function ensurePodBuildSettings() {
|
|
4127
|
-
const podfilePath =
|
|
4128
|
-
if (!
|
|
4129
|
-
let content =
|
|
4196
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4197
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4198
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4130
4199
|
let changed = false;
|
|
4131
4200
|
if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
|
|
4132
4201
|
content = content.replace(
|
|
@@ -4169,7 +4238,7 @@ $2`);
|
|
|
4169
4238
|
changed = true;
|
|
4170
4239
|
}
|
|
4171
4240
|
if (changed) {
|
|
4172
|
-
|
|
4241
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4173
4242
|
console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
|
|
4174
4243
|
}
|
|
4175
4244
|
}
|
|
@@ -4177,10 +4246,10 @@ $2`);
|
|
|
4177
4246
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4178
4247
|
const candidatePaths = [];
|
|
4179
4248
|
if (appNameFromConfig) {
|
|
4180
|
-
candidatePaths.push(
|
|
4249
|
+
candidatePaths.push(path18.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
4181
4250
|
}
|
|
4182
|
-
candidatePaths.push(
|
|
4183
|
-
const found = candidatePaths.find((p) =>
|
|
4251
|
+
candidatePaths.push(path18.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
4252
|
+
const found = candidatePaths.find((p) => fs17.existsSync(p));
|
|
4184
4253
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
4185
4254
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
4186
4255
|
const seenModules = /* @__PURE__ */ new Set();
|
|
@@ -4219,7 +4288,7 @@ $2`);
|
|
|
4219
4288
|
const podName = resolvePodName(pkg);
|
|
4220
4289
|
return `import ${podName}`;
|
|
4221
4290
|
}).join("\n");
|
|
4222
|
-
const fileContent =
|
|
4291
|
+
const fileContent = fs17.readFileSync(filePath, "utf8");
|
|
4223
4292
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
4224
4293
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
4225
4294
|
return;
|
|
@@ -4256,8 +4325,8 @@ ${after}`;
|
|
|
4256
4325
|
${fileContent}`;
|
|
4257
4326
|
}
|
|
4258
4327
|
}
|
|
4259
|
-
|
|
4260
|
-
console.log(`\u2705 Updated imports in ${
|
|
4328
|
+
fs17.writeFileSync(filePath, newContent, "utf8");
|
|
4329
|
+
console.log(`\u2705 Updated imports in ${path18.basename(filePath)}`);
|
|
4261
4330
|
}
|
|
4262
4331
|
updateImportsSection(lynxInitPath, importPackages);
|
|
4263
4332
|
if (importPackages.length === 0) {
|
|
@@ -4301,7 +4370,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4301
4370
|
} else {
|
|
4302
4371
|
devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
|
|
4303
4372
|
}
|
|
4304
|
-
if (
|
|
4373
|
+
if (fs17.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
|
|
4305
4374
|
updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
|
|
4306
4375
|
}
|
|
4307
4376
|
}
|
|
@@ -4309,13 +4378,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4309
4378
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4310
4379
|
const candidates = [];
|
|
4311
4380
|
if (appNameFromConfig) {
|
|
4312
|
-
candidates.push(
|
|
4381
|
+
candidates.push(path18.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
4313
4382
|
}
|
|
4314
|
-
candidates.push(
|
|
4315
|
-
return candidates.find((p) =>
|
|
4383
|
+
candidates.push(path18.join(iosProjectPath, "Info.plist"));
|
|
4384
|
+
return candidates.find((p) => fs17.existsSync(p)) ?? null;
|
|
4316
4385
|
}
|
|
4317
4386
|
function readPlistXml(plistPath) {
|
|
4318
|
-
return
|
|
4387
|
+
return fs17.readFileSync(plistPath, "utf8");
|
|
4319
4388
|
}
|
|
4320
4389
|
function syncInfoPlistPermissions(packages) {
|
|
4321
4390
|
const plistPath = findInfoPlist();
|
|
@@ -4346,7 +4415,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4346
4415
|
added++;
|
|
4347
4416
|
}
|
|
4348
4417
|
if (added > 0) {
|
|
4349
|
-
|
|
4418
|
+
fs17.writeFileSync(plistPath, plist, "utf8");
|
|
4350
4419
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
4351
4420
|
}
|
|
4352
4421
|
}
|
|
@@ -4393,16 +4462,16 @@ ${schemesXml}
|
|
|
4393
4462
|
$1`
|
|
4394
4463
|
);
|
|
4395
4464
|
}
|
|
4396
|
-
|
|
4465
|
+
fs17.writeFileSync(plistPath, plist, "utf8");
|
|
4397
4466
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
4398
4467
|
}
|
|
4399
4468
|
function runPodInstall(forcePath) {
|
|
4400
|
-
const podfilePath = forcePath ??
|
|
4401
|
-
if (!
|
|
4469
|
+
const podfilePath = forcePath ?? path18.join(iosProjectPath, "Podfile");
|
|
4470
|
+
if (!fs17.existsSync(podfilePath)) {
|
|
4402
4471
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
4403
4472
|
return;
|
|
4404
4473
|
}
|
|
4405
|
-
const cwd =
|
|
4474
|
+
const cwd = path18.dirname(podfilePath);
|
|
4406
4475
|
try {
|
|
4407
4476
|
console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
|
|
4408
4477
|
try {
|
|
@@ -4437,8 +4506,8 @@ $1`
|
|
|
4437
4506
|
syncInfoPlistUrlSchemes();
|
|
4438
4507
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4439
4508
|
if (appNameFromConfig) {
|
|
4440
|
-
const appPodfile =
|
|
4441
|
-
if (
|
|
4509
|
+
const appPodfile = path18.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
4510
|
+
if (fs17.existsSync(appPodfile)) {
|
|
4442
4511
|
runPodInstall(appPodfile);
|
|
4443
4512
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
4444
4513
|
return;
|
|
@@ -4453,13 +4522,13 @@ $1`
|
|
|
4453
4522
|
const appFolder = resolved.config.ios?.appName;
|
|
4454
4523
|
if (!hasDevClient || !appFolder) return;
|
|
4455
4524
|
const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
|
|
4456
|
-
const appDir =
|
|
4457
|
-
|
|
4458
|
-
const manifestPath =
|
|
4459
|
-
|
|
4525
|
+
const appDir = path18.join(iosProjectPath, appFolder);
|
|
4526
|
+
fs17.mkdirSync(appDir, { recursive: true });
|
|
4527
|
+
const manifestPath = path18.join(appDir, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4528
|
+
fs17.writeFileSync(manifestPath, buildHostNativeModulesManifestJson(androidNames), "utf8");
|
|
4460
4529
|
console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
|
|
4461
|
-
const pbxprojPath =
|
|
4462
|
-
if (
|
|
4530
|
+
const pbxprojPath = path18.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
|
|
4531
|
+
if (fs17.existsSync(pbxprojPath)) {
|
|
4463
4532
|
addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4464
4533
|
}
|
|
4465
4534
|
}
|
|
@@ -4468,8 +4537,8 @@ $1`
|
|
|
4468
4537
|
var autolink_default2 = autolink2;
|
|
4469
4538
|
|
|
4470
4539
|
// src/ios/bundle.ts
|
|
4471
|
-
import
|
|
4472
|
-
import
|
|
4540
|
+
import fs18 from "fs";
|
|
4541
|
+
import path19 from "path";
|
|
4473
4542
|
import { execSync as execSync7 } from "child_process";
|
|
4474
4543
|
function bundleAndDeploy2(opts = {}) {
|
|
4475
4544
|
const release = opts.release === true || opts.production === true;
|
|
@@ -4487,19 +4556,19 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4487
4556
|
const includeDevClient = !release && !!devClientPkg;
|
|
4488
4557
|
const appName = resolved.config.ios.appName;
|
|
4489
4558
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
4490
|
-
const destinationDir =
|
|
4491
|
-
const destinationBundlePath =
|
|
4559
|
+
const destinationDir = path19.join(resolved.iosDir, appName);
|
|
4560
|
+
const destinationBundlePath = path19.join(destinationDir, resolved.lynxBundleFile);
|
|
4492
4561
|
autolink_default2({ release, includeDevClient });
|
|
4493
4562
|
const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
|
|
4494
4563
|
if (iconPaths) {
|
|
4495
|
-
const appIconDir =
|
|
4564
|
+
const appIconDir = path19.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
4496
4565
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
4497
4566
|
console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
|
|
4498
4567
|
}
|
|
4499
4568
|
}
|
|
4500
4569
|
try {
|
|
4501
|
-
const lynxTsconfig =
|
|
4502
|
-
if (
|
|
4570
|
+
const lynxTsconfig = path19.join(resolved.lynxProjectDir, "tsconfig.json");
|
|
4571
|
+
if (fs18.existsSync(lynxTsconfig)) {
|
|
4503
4572
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
4504
4573
|
}
|
|
4505
4574
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -4510,40 +4579,40 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4510
4579
|
process.exit(1);
|
|
4511
4580
|
}
|
|
4512
4581
|
try {
|
|
4513
|
-
if (!
|
|
4582
|
+
if (!fs18.existsSync(sourceBundlePath)) {
|
|
4514
4583
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
4515
4584
|
process.exit(1);
|
|
4516
4585
|
}
|
|
4517
|
-
if (!
|
|
4586
|
+
if (!fs18.existsSync(destinationDir)) {
|
|
4518
4587
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
4519
4588
|
process.exit(1);
|
|
4520
4589
|
}
|
|
4521
|
-
const distDir =
|
|
4590
|
+
const distDir = path19.dirname(sourceBundlePath);
|
|
4522
4591
|
console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
|
|
4523
4592
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
4524
4593
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
4525
|
-
const pbxprojPath =
|
|
4526
|
-
if (
|
|
4594
|
+
const pbxprojPath = path19.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4595
|
+
if (fs18.existsSync(pbxprojPath)) {
|
|
4527
4596
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
4528
|
-
for (const entry of
|
|
4529
|
-
if (skip.has(entry) ||
|
|
4597
|
+
for (const entry of fs18.readdirSync(distDir)) {
|
|
4598
|
+
if (skip.has(entry) || fs18.statSync(path19.join(distDir, entry)).isDirectory()) continue;
|
|
4530
4599
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
4531
4600
|
}
|
|
4532
4601
|
}
|
|
4533
4602
|
if (includeDevClient && devClientPkg) {
|
|
4534
|
-
const devClientBundle =
|
|
4603
|
+
const devClientBundle = path19.join(destinationDir, "dev-client.lynx.bundle");
|
|
4535
4604
|
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
4536
4605
|
try {
|
|
4537
4606
|
execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
4538
4607
|
} catch {
|
|
4539
4608
|
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
4540
4609
|
}
|
|
4541
|
-
const builtBundle =
|
|
4542
|
-
if (
|
|
4543
|
-
|
|
4610
|
+
const builtBundle = path19.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
4611
|
+
if (fs18.existsSync(builtBundle)) {
|
|
4612
|
+
fs18.copyFileSync(builtBundle, devClientBundle);
|
|
4544
4613
|
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
4545
|
-
const pbxprojPath2 =
|
|
4546
|
-
if (
|
|
4614
|
+
const pbxprojPath2 = path19.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4615
|
+
if (fs18.existsSync(pbxprojPath2)) {
|
|
4547
4616
|
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
4548
4617
|
}
|
|
4549
4618
|
}
|
|
@@ -4557,8 +4626,8 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4557
4626
|
var bundle_default2 = bundleAndDeploy2;
|
|
4558
4627
|
|
|
4559
4628
|
// src/ios/build.ts
|
|
4560
|
-
import
|
|
4561
|
-
import
|
|
4629
|
+
import fs19 from "fs";
|
|
4630
|
+
import path20 from "path";
|
|
4562
4631
|
import os3 from "os";
|
|
4563
4632
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
4564
4633
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -4579,14 +4648,14 @@ function findBootedSimulator() {
|
|
|
4579
4648
|
return null;
|
|
4580
4649
|
}
|
|
4581
4650
|
function findFirstConnectedIosDeviceUdid() {
|
|
4582
|
-
const jsonPath =
|
|
4651
|
+
const jsonPath = path20.join(os3.tmpdir(), `t4l-devicectl-${randomBytes2(8).toString("hex")}.json`);
|
|
4583
4652
|
try {
|
|
4584
4653
|
execSync8(`xcrun devicectl list devices --json-output "${jsonPath}"`, {
|
|
4585
4654
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4586
4655
|
});
|
|
4587
|
-
if (!
|
|
4588
|
-
const raw =
|
|
4589
|
-
|
|
4656
|
+
if (!fs19.existsSync(jsonPath)) return null;
|
|
4657
|
+
const raw = fs19.readFileSync(jsonPath, "utf8");
|
|
4658
|
+
fs19.unlinkSync(jsonPath);
|
|
4590
4659
|
const data = JSON.parse(raw);
|
|
4591
4660
|
const devices = data.result?.devices ?? [];
|
|
4592
4661
|
for (const d of devices) {
|
|
@@ -4597,7 +4666,7 @@ function findFirstConnectedIosDeviceUdid() {
|
|
|
4597
4666
|
}
|
|
4598
4667
|
} catch {
|
|
4599
4668
|
try {
|
|
4600
|
-
if (
|
|
4669
|
+
if (fs19.existsSync(jsonPath)) fs19.unlinkSync(jsonPath);
|
|
4601
4670
|
} catch {
|
|
4602
4671
|
}
|
|
4603
4672
|
}
|
|
@@ -4615,11 +4684,11 @@ async function buildIpa(opts = {}) {
|
|
|
4615
4684
|
const configuration = release ? "Release" : "Debug";
|
|
4616
4685
|
bundle_default2({ release, production: opts.production });
|
|
4617
4686
|
const scheme = appName;
|
|
4618
|
-
const workspacePath =
|
|
4619
|
-
const projectPath =
|
|
4620
|
-
const xcproject =
|
|
4687
|
+
const workspacePath = path20.join(iosDir, `${appName}.xcworkspace`);
|
|
4688
|
+
const projectPath = path20.join(iosDir, `${appName}.xcodeproj`);
|
|
4689
|
+
const xcproject = fs19.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
4621
4690
|
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
4622
|
-
const derivedDataPath =
|
|
4691
|
+
const derivedDataPath = path20.join(iosDir, "build");
|
|
4623
4692
|
const production = opts.production === true;
|
|
4624
4693
|
const sdk = production ? "iphoneos" : opts.install ? "iphonesimulator" : "iphoneos";
|
|
4625
4694
|
const signingArgs = production || opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
|
|
@@ -4638,14 +4707,14 @@ async function buildIpa(opts = {}) {
|
|
|
4638
4707
|
console.log(`\u2705 Build completed.`);
|
|
4639
4708
|
if (opts.install) {
|
|
4640
4709
|
if (production) {
|
|
4641
|
-
const appPath =
|
|
4710
|
+
const appPath = path20.join(
|
|
4642
4711
|
derivedDataPath,
|
|
4643
4712
|
"Build",
|
|
4644
4713
|
"Products",
|
|
4645
4714
|
`${configuration}-iphoneos`,
|
|
4646
4715
|
`${appName}.app`
|
|
4647
4716
|
);
|
|
4648
|
-
if (!
|
|
4717
|
+
if (!fs19.existsSync(appPath)) {
|
|
4649
4718
|
console.error(`\u274C Built app not found at: ${appPath}`);
|
|
4650
4719
|
process.exit(1);
|
|
4651
4720
|
}
|
|
@@ -4675,14 +4744,14 @@ async function buildIpa(opts = {}) {
|
|
|
4675
4744
|
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
4676
4745
|
}
|
|
4677
4746
|
} else {
|
|
4678
|
-
const appGlob =
|
|
4747
|
+
const appGlob = path20.join(
|
|
4679
4748
|
derivedDataPath,
|
|
4680
4749
|
"Build",
|
|
4681
4750
|
"Products",
|
|
4682
4751
|
`${configuration}-iphonesimulator`,
|
|
4683
4752
|
`${appName}.app`
|
|
4684
4753
|
);
|
|
4685
|
-
if (!
|
|
4754
|
+
if (!fs19.existsSync(appGlob)) {
|
|
4686
4755
|
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
4687
4756
|
process.exit(1);
|
|
4688
4757
|
}
|
|
@@ -4706,8 +4775,8 @@ async function buildIpa(opts = {}) {
|
|
|
4706
4775
|
var build_default2 = buildIpa;
|
|
4707
4776
|
|
|
4708
4777
|
// src/common/init.tsx
|
|
4709
|
-
import
|
|
4710
|
-
import
|
|
4778
|
+
import fs20 from "fs";
|
|
4779
|
+
import path21 from "path";
|
|
4711
4780
|
import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
4712
4781
|
import { render, Text as Text9, Box as Box8 } from "ink";
|
|
4713
4782
|
|
|
@@ -5025,22 +5094,22 @@ function InitWizard() {
|
|
|
5025
5094
|
paths: { androidDir: "android", iosDir: "ios" }
|
|
5026
5095
|
};
|
|
5027
5096
|
if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
|
|
5028
|
-
const configPath =
|
|
5029
|
-
|
|
5097
|
+
const configPath = path21.join(process.cwd(), "tamer.config.json");
|
|
5098
|
+
fs20.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
5030
5099
|
const lines = [`Generated tamer.config.json at ${configPath}`];
|
|
5031
5100
|
const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
|
|
5032
5101
|
const tsconfigCandidates = lynxProject.trim() ? [
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
] : [
|
|
5102
|
+
path21.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
|
|
5103
|
+
path21.join(process.cwd(), "tsconfig.json")
|
|
5104
|
+
] : [path21.join(process.cwd(), "tsconfig.json")];
|
|
5036
5105
|
for (const tsconfigPath of tsconfigCandidates) {
|
|
5037
|
-
if (!
|
|
5106
|
+
if (!fs20.existsSync(tsconfigPath)) continue;
|
|
5038
5107
|
try {
|
|
5039
5108
|
if (fixTsconfigReferencesForBuild(tsconfigPath)) {
|
|
5040
|
-
lines.push(`Flattened ${
|
|
5109
|
+
lines.push(`Flattened ${path21.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
|
|
5041
5110
|
}
|
|
5042
5111
|
if (addTamerTypesInclude(tsconfigPath, tamerTypesInclude)) {
|
|
5043
|
-
lines.push(`Updated ${
|
|
5112
|
+
lines.push(`Updated ${path21.relative(process.cwd(), tsconfigPath)} for tamer types`);
|
|
5044
5113
|
}
|
|
5045
5114
|
break;
|
|
5046
5115
|
} catch (e) {
|
|
@@ -5200,8 +5269,8 @@ async function init() {
|
|
|
5200
5269
|
}
|
|
5201
5270
|
|
|
5202
5271
|
// src/common/create.ts
|
|
5203
|
-
import
|
|
5204
|
-
import
|
|
5272
|
+
import fs21 from "fs";
|
|
5273
|
+
import path22 from "path";
|
|
5205
5274
|
import readline from "readline";
|
|
5206
5275
|
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5207
5276
|
function ask(question) {
|
|
@@ -5263,13 +5332,13 @@ async function create3(opts) {
|
|
|
5263
5332
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
5264
5333
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
5265
5334
|
const cwd = process.cwd();
|
|
5266
|
-
const root =
|
|
5267
|
-
if (
|
|
5335
|
+
const root = path22.join(cwd, extName);
|
|
5336
|
+
if (fs21.existsSync(root)) {
|
|
5268
5337
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
5269
5338
|
rl.close();
|
|
5270
5339
|
process.exit(1);
|
|
5271
5340
|
}
|
|
5272
|
-
|
|
5341
|
+
fs21.mkdirSync(root, { recursive: true });
|
|
5273
5342
|
const lynxExt = {
|
|
5274
5343
|
platforms: {
|
|
5275
5344
|
android: {
|
|
@@ -5284,7 +5353,7 @@ async function create3(opts) {
|
|
|
5284
5353
|
web: {}
|
|
5285
5354
|
}
|
|
5286
5355
|
};
|
|
5287
|
-
|
|
5356
|
+
fs21.writeFileSync(path22.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
5288
5357
|
const pkg = {
|
|
5289
5358
|
name: extName,
|
|
5290
5359
|
version: "0.0.1",
|
|
@@ -5297,20 +5366,20 @@ async function create3(opts) {
|
|
|
5297
5366
|
engines: { node: ">=18" }
|
|
5298
5367
|
};
|
|
5299
5368
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5300
|
-
|
|
5369
|
+
fs21.writeFileSync(path22.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5301
5370
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5302
5371
|
const hasSrc = includeModule || includeElement || includeService;
|
|
5303
5372
|
if (hasSrc) {
|
|
5304
|
-
|
|
5373
|
+
fs21.mkdirSync(path22.join(root, "src"), { recursive: true });
|
|
5305
5374
|
}
|
|
5306
5375
|
if (includeModule) {
|
|
5307
|
-
|
|
5376
|
+
fs21.writeFileSync(path22.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5308
5377
|
export declare class ${simpleModuleName} {
|
|
5309
5378
|
// Add your module methods here
|
|
5310
5379
|
}
|
|
5311
5380
|
`);
|
|
5312
|
-
|
|
5313
|
-
|
|
5381
|
+
fs21.mkdirSync(path22.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
5382
|
+
fs21.writeFileSync(path22.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5314
5383
|
id("com.android.library")
|
|
5315
5384
|
id("org.jetbrains.kotlin.android")
|
|
5316
5385
|
}
|
|
@@ -5331,7 +5400,7 @@ dependencies {
|
|
|
5331
5400
|
implementation(libs.lynx.jssdk)
|
|
5332
5401
|
}
|
|
5333
5402
|
`);
|
|
5334
|
-
|
|
5403
|
+
fs21.writeFileSync(path22.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5335
5404
|
<manifest />
|
|
5336
5405
|
`);
|
|
5337
5406
|
const ktContent = `package ${packageName}
|
|
@@ -5348,8 +5417,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5348
5417
|
}
|
|
5349
5418
|
}
|
|
5350
5419
|
`;
|
|
5351
|
-
|
|
5352
|
-
|
|
5420
|
+
fs21.writeFileSync(path22.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
|
|
5421
|
+
fs21.mkdirSync(path22.join(root, "ios", extName, extName, "Classes"), { recursive: true });
|
|
5353
5422
|
const podspec = `Pod::Spec.new do |s|
|
|
5354
5423
|
s.name = '${extName}'
|
|
5355
5424
|
s.version = '0.0.1'
|
|
@@ -5363,7 +5432,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5363
5432
|
s.dependency 'Lynx'
|
|
5364
5433
|
end
|
|
5365
5434
|
`;
|
|
5366
|
-
|
|
5435
|
+
fs21.writeFileSync(path22.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5367
5436
|
const swiftContent = `import Foundation
|
|
5368
5437
|
|
|
5369
5438
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5372,18 +5441,18 @@ end
|
|
|
5372
5441
|
}
|
|
5373
5442
|
}
|
|
5374
5443
|
`;
|
|
5375
|
-
|
|
5444
|
+
fs21.writeFileSync(path22.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5376
5445
|
}
|
|
5377
5446
|
if (includeElement && !includeModule) {
|
|
5378
5447
|
const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
5379
|
-
|
|
5448
|
+
fs21.writeFileSync(path22.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
|
|
5380
5449
|
|
|
5381
5450
|
export const ${elementName}: FC = () => {
|
|
5382
5451
|
return null;
|
|
5383
5452
|
};
|
|
5384
5453
|
`);
|
|
5385
5454
|
}
|
|
5386
|
-
|
|
5455
|
+
fs21.writeFileSync(path22.join(root, "index.js"), `'use strict';
|
|
5387
5456
|
module.exports = {};
|
|
5388
5457
|
`);
|
|
5389
5458
|
const tsconfigCompiler = {
|
|
@@ -5396,11 +5465,11 @@ module.exports = {};
|
|
|
5396
5465
|
tsconfigCompiler.jsx = "preserve";
|
|
5397
5466
|
tsconfigCompiler.jsxImportSource = "@lynx-js/react";
|
|
5398
5467
|
}
|
|
5399
|
-
|
|
5468
|
+
fs21.writeFileSync(path22.join(root, "tsconfig.json"), JSON.stringify({
|
|
5400
5469
|
compilerOptions: tsconfigCompiler,
|
|
5401
5470
|
include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
|
|
5402
5471
|
}, null, 2));
|
|
5403
|
-
|
|
5472
|
+
fs21.writeFileSync(path22.join(root, "README.md"), `# ${extName}
|
|
5404
5473
|
|
|
5405
5474
|
Lynx extension for ${extName}.
|
|
5406
5475
|
|
|
@@ -5425,8 +5494,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5425
5494
|
var create_default3 = create3;
|
|
5426
5495
|
|
|
5427
5496
|
// src/common/codegen.ts
|
|
5428
|
-
import
|
|
5429
|
-
import
|
|
5497
|
+
import fs22 from "fs";
|
|
5498
|
+
import path23 from "path";
|
|
5430
5499
|
function codegen() {
|
|
5431
5500
|
const cwd = process.cwd();
|
|
5432
5501
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5434,9 +5503,9 @@ function codegen() {
|
|
|
5434
5503
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5435
5504
|
process.exit(1);
|
|
5436
5505
|
}
|
|
5437
|
-
const srcDir =
|
|
5438
|
-
const generatedDir =
|
|
5439
|
-
|
|
5506
|
+
const srcDir = path23.join(cwd, "src");
|
|
5507
|
+
const generatedDir = path23.join(cwd, "generated");
|
|
5508
|
+
fs22.mkdirSync(generatedDir, { recursive: true });
|
|
5440
5509
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5441
5510
|
const modules = extractLynxModules(dtsFiles);
|
|
5442
5511
|
if (modules.length === 0) {
|
|
@@ -5446,28 +5515,28 @@ function codegen() {
|
|
|
5446
5515
|
for (const mod of modules) {
|
|
5447
5516
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5448
5517
|
`;
|
|
5449
|
-
const outPath =
|
|
5450
|
-
|
|
5518
|
+
const outPath = path23.join(generatedDir, `${mod}.ts`);
|
|
5519
|
+
fs22.writeFileSync(outPath, tsContent);
|
|
5451
5520
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5452
5521
|
}
|
|
5453
5522
|
if (config.android) {
|
|
5454
|
-
const androidGenerated =
|
|
5455
|
-
|
|
5523
|
+
const androidGenerated = path23.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
5524
|
+
fs22.mkdirSync(androidGenerated, { recursive: true });
|
|
5456
5525
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5457
5526
|
}
|
|
5458
5527
|
if (config.ios) {
|
|
5459
|
-
const iosGenerated =
|
|
5460
|
-
|
|
5528
|
+
const iosGenerated = path23.join(cwd, "ios", "generated");
|
|
5529
|
+
fs22.mkdirSync(iosGenerated, { recursive: true });
|
|
5461
5530
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5462
5531
|
}
|
|
5463
5532
|
console.log("\u2728 Codegen complete.");
|
|
5464
5533
|
}
|
|
5465
5534
|
function findDtsFiles(dir) {
|
|
5466
5535
|
const result = [];
|
|
5467
|
-
if (!
|
|
5468
|
-
const entries =
|
|
5536
|
+
if (!fs22.existsSync(dir)) return result;
|
|
5537
|
+
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
5469
5538
|
for (const e of entries) {
|
|
5470
|
-
const full =
|
|
5539
|
+
const full = path23.join(dir, e.name);
|
|
5471
5540
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5472
5541
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5473
5542
|
}
|
|
@@ -5477,7 +5546,7 @@ function extractLynxModules(files) {
|
|
|
5477
5546
|
const modules = [];
|
|
5478
5547
|
const seen = /* @__PURE__ */ new Set();
|
|
5479
5548
|
for (const file of files) {
|
|
5480
|
-
const content =
|
|
5549
|
+
const content = fs22.readFileSync(file, "utf8");
|
|
5481
5550
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5482
5551
|
let m;
|
|
5483
5552
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5494,10 +5563,10 @@ var codegen_default = codegen;
|
|
|
5494
5563
|
// src/common/devServer.tsx
|
|
5495
5564
|
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
5496
5565
|
import { spawn } from "child_process";
|
|
5497
|
-
import
|
|
5566
|
+
import fs23 from "fs";
|
|
5498
5567
|
import http from "http";
|
|
5499
5568
|
import os4 from "os";
|
|
5500
|
-
import
|
|
5569
|
+
import path24 from "path";
|
|
5501
5570
|
import { render as render2, useInput, useApp } from "ink";
|
|
5502
5571
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5503
5572
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
@@ -5515,13 +5584,13 @@ var STATIC_MIME = {
|
|
|
5515
5584
|
".pdf": "application/pdf"
|
|
5516
5585
|
};
|
|
5517
5586
|
function sendFileFromDisk(res, absPath) {
|
|
5518
|
-
|
|
5587
|
+
fs23.readFile(absPath, (err, data) => {
|
|
5519
5588
|
if (err) {
|
|
5520
5589
|
res.writeHead(404);
|
|
5521
5590
|
res.end("Not found");
|
|
5522
5591
|
return;
|
|
5523
5592
|
}
|
|
5524
|
-
const ext =
|
|
5593
|
+
const ext = path24.extname(absPath).toLowerCase();
|
|
5525
5594
|
res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
|
|
5526
5595
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5527
5596
|
res.end(data);
|
|
@@ -5558,9 +5627,9 @@ function getLanIp() {
|
|
|
5558
5627
|
return "localhost";
|
|
5559
5628
|
}
|
|
5560
5629
|
function detectPackageManager(cwd) {
|
|
5561
|
-
const dir =
|
|
5562
|
-
if (
|
|
5563
|
-
if (
|
|
5630
|
+
const dir = path24.resolve(cwd);
|
|
5631
|
+
if (fs23.existsSync(path24.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
|
|
5632
|
+
if (fs23.existsSync(path24.join(dir, "bun.lockb")) || fs23.existsSync(path24.join(dir, "bun.lock")))
|
|
5564
5633
|
return { cmd: "bun", args: ["run", "build"] };
|
|
5565
5634
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5566
5635
|
}
|
|
@@ -5647,8 +5716,8 @@ function DevServerApp({ verbose }) {
|
|
|
5647
5716
|
try {
|
|
5648
5717
|
const resolved = resolveHostPaths();
|
|
5649
5718
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5650
|
-
const distDir =
|
|
5651
|
-
const projectName =
|
|
5719
|
+
const distDir = path24.dirname(lynxBundlePath);
|
|
5720
|
+
const projectName = path24.basename(lynxProjectDir);
|
|
5652
5721
|
const basePath = `/${projectName}`;
|
|
5653
5722
|
setUi((s) => ({ ...s, projectName, lynxBundleFile }));
|
|
5654
5723
|
const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
@@ -5658,18 +5727,18 @@ function DevServerApp({ verbose }) {
|
|
|
5658
5727
|
}
|
|
5659
5728
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5660
5729
|
let iconFilePath = null;
|
|
5661
|
-
if (iconPaths?.source &&
|
|
5730
|
+
if (iconPaths?.source && fs23.statSync(iconPaths.source).isFile()) {
|
|
5662
5731
|
iconFilePath = iconPaths.source;
|
|
5663
|
-
} else if (iconPaths?.androidAdaptiveForeground &&
|
|
5732
|
+
} else if (iconPaths?.androidAdaptiveForeground && fs23.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
|
|
5664
5733
|
iconFilePath = iconPaths.androidAdaptiveForeground;
|
|
5665
5734
|
} else if (iconPaths?.android) {
|
|
5666
|
-
const androidIcon =
|
|
5667
|
-
if (
|
|
5735
|
+
const androidIcon = path24.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
5736
|
+
if (fs23.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5668
5737
|
} else if (iconPaths?.ios) {
|
|
5669
|
-
const iosIcon =
|
|
5670
|
-
if (
|
|
5738
|
+
const iosIcon = path24.join(iconPaths.ios, "Icon-1024.png");
|
|
5739
|
+
if (fs23.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5671
5740
|
}
|
|
5672
|
-
const iconExt = iconFilePath ?
|
|
5741
|
+
const iconExt = iconFilePath ? path24.extname(iconFilePath) || ".png" : "";
|
|
5673
5742
|
const runBuild = () => {
|
|
5674
5743
|
return new Promise((resolve, reject) => {
|
|
5675
5744
|
const { cmd, args } = detectPackageManager(lynxProjectDir);
|
|
@@ -5751,7 +5820,7 @@ function DevServerApp({ verbose }) {
|
|
|
5751
5820
|
return;
|
|
5752
5821
|
}
|
|
5753
5822
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5754
|
-
|
|
5823
|
+
fs23.readFile(iconFilePath, (err, data) => {
|
|
5755
5824
|
if (err) {
|
|
5756
5825
|
res.writeHead(404);
|
|
5757
5826
|
res.end();
|
|
@@ -5777,20 +5846,20 @@ function DevServerApp({ verbose }) {
|
|
|
5777
5846
|
res.end();
|
|
5778
5847
|
return;
|
|
5779
5848
|
}
|
|
5780
|
-
const safe =
|
|
5781
|
-
if (
|
|
5849
|
+
const safe = path24.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
5850
|
+
if (path24.isAbsolute(safe) || safe.startsWith("..")) {
|
|
5782
5851
|
res.writeHead(403);
|
|
5783
5852
|
res.end();
|
|
5784
5853
|
return;
|
|
5785
5854
|
}
|
|
5786
|
-
const allowedRoot =
|
|
5787
|
-
const abs =
|
|
5788
|
-
if (!abs.startsWith(allowedRoot +
|
|
5855
|
+
const allowedRoot = path24.resolve(lynxProjectDir, rootSub);
|
|
5856
|
+
const abs = path24.resolve(allowedRoot, safe);
|
|
5857
|
+
if (!abs.startsWith(allowedRoot + path24.sep) && abs !== allowedRoot) {
|
|
5789
5858
|
res.writeHead(403);
|
|
5790
5859
|
res.end();
|
|
5791
5860
|
return;
|
|
5792
5861
|
}
|
|
5793
|
-
if (!
|
|
5862
|
+
if (!fs23.existsSync(abs) || !fs23.statSync(abs).isFile()) {
|
|
5794
5863
|
res.writeHead(404);
|
|
5795
5864
|
res.end("Not found");
|
|
5796
5865
|
return;
|
|
@@ -5804,14 +5873,14 @@ function DevServerApp({ verbose }) {
|
|
|
5804
5873
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5805
5874
|
}
|
|
5806
5875
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5807
|
-
const filePath =
|
|
5808
|
-
const distResolved =
|
|
5809
|
-
if (!filePath.startsWith(distResolved +
|
|
5876
|
+
const filePath = path24.resolve(distDir, relPath);
|
|
5877
|
+
const distResolved = path24.resolve(distDir);
|
|
5878
|
+
if (!filePath.startsWith(distResolved + path24.sep) && filePath !== distResolved) {
|
|
5810
5879
|
res.writeHead(403);
|
|
5811
5880
|
res.end();
|
|
5812
5881
|
return;
|
|
5813
5882
|
}
|
|
5814
|
-
|
|
5883
|
+
fs23.readFile(filePath, (err, data) => {
|
|
5815
5884
|
if (err) {
|
|
5816
5885
|
res.writeHead(404);
|
|
5817
5886
|
res.end("Not found");
|
|
@@ -5883,10 +5952,10 @@ function DevServerApp({ verbose }) {
|
|
|
5883
5952
|
}
|
|
5884
5953
|
if (chokidar) {
|
|
5885
5954
|
const watchPaths = [
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
].filter((p) =>
|
|
5955
|
+
path24.join(lynxProjectDir, "src"),
|
|
5956
|
+
path24.join(lynxProjectDir, "lynx.config.ts"),
|
|
5957
|
+
path24.join(lynxProjectDir, "lynx.config.js")
|
|
5958
|
+
].filter((p) => fs23.existsSync(p));
|
|
5890
5959
|
if (watchPaths.length > 0) {
|
|
5891
5960
|
const w = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5892
5961
|
w.on("change", async () => {
|
|
@@ -6006,10 +6075,10 @@ async function start(opts) {
|
|
|
6006
6075
|
var start_default = start;
|
|
6007
6076
|
|
|
6008
6077
|
// src/common/injectHost.ts
|
|
6009
|
-
import
|
|
6010
|
-
import
|
|
6078
|
+
import fs24 from "fs";
|
|
6079
|
+
import path25 from "path";
|
|
6011
6080
|
function readAndSubstitute(templatePath, vars) {
|
|
6012
|
-
const raw =
|
|
6081
|
+
const raw = fs24.readFileSync(templatePath, "utf-8");
|
|
6013
6082
|
return Object.entries(vars).reduce(
|
|
6014
6083
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
6015
6084
|
raw
|
|
@@ -6030,32 +6099,32 @@ async function injectHostAndroid(opts) {
|
|
|
6030
6099
|
process.exit(1);
|
|
6031
6100
|
}
|
|
6032
6101
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
6033
|
-
const rootDir =
|
|
6102
|
+
const rootDir = path25.join(projectRoot, androidDir);
|
|
6034
6103
|
const packagePath = packageName.replace(/\./g, "/");
|
|
6035
|
-
const javaDir =
|
|
6036
|
-
const kotlinDir =
|
|
6037
|
-
if (!
|
|
6104
|
+
const javaDir = path25.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
6105
|
+
const kotlinDir = path25.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
6106
|
+
if (!fs24.existsSync(javaDir) || !fs24.existsSync(kotlinDir)) {
|
|
6038
6107
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
6039
6108
|
process.exit(1);
|
|
6040
6109
|
}
|
|
6041
|
-
const templateDir =
|
|
6110
|
+
const templateDir = path25.join(hostPkg, "android", "templates");
|
|
6042
6111
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
6043
6112
|
const files = [
|
|
6044
|
-
{ src: "App.java", dst:
|
|
6045
|
-
{ src: "TemplateProvider.java", dst:
|
|
6046
|
-
{ src: "MainActivity.kt", dst:
|
|
6113
|
+
{ src: "App.java", dst: path25.join(javaDir, "App.java") },
|
|
6114
|
+
{ src: "TemplateProvider.java", dst: path25.join(javaDir, "TemplateProvider.java") },
|
|
6115
|
+
{ src: "MainActivity.kt", dst: path25.join(kotlinDir, "MainActivity.kt") }
|
|
6047
6116
|
];
|
|
6048
6117
|
for (const { src, dst } of files) {
|
|
6049
|
-
const srcPath =
|
|
6050
|
-
if (!
|
|
6051
|
-
if (
|
|
6052
|
-
console.log(`\u23ED\uFE0F Skipping ${
|
|
6118
|
+
const srcPath = path25.join(templateDir, src);
|
|
6119
|
+
if (!fs24.existsSync(srcPath)) continue;
|
|
6120
|
+
if (fs24.existsSync(dst) && !opts?.force) {
|
|
6121
|
+
console.log(`\u23ED\uFE0F Skipping ${path25.basename(dst)} (use --force to overwrite)`);
|
|
6053
6122
|
continue;
|
|
6054
6123
|
}
|
|
6055
6124
|
const content = readAndSubstitute(srcPath, vars);
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
console.log(`\u2705 Injected ${
|
|
6125
|
+
fs24.mkdirSync(path25.dirname(dst), { recursive: true });
|
|
6126
|
+
fs24.writeFileSync(dst, content);
|
|
6127
|
+
console.log(`\u2705 Injected ${path25.basename(dst)}`);
|
|
6059
6128
|
}
|
|
6060
6129
|
}
|
|
6061
6130
|
async function injectHostIos(opts) {
|
|
@@ -6073,13 +6142,13 @@ async function injectHostIos(opts) {
|
|
|
6073
6142
|
process.exit(1);
|
|
6074
6143
|
}
|
|
6075
6144
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
6076
|
-
const rootDir =
|
|
6077
|
-
const projectDir =
|
|
6078
|
-
if (!
|
|
6145
|
+
const rootDir = path25.join(projectRoot, iosDir);
|
|
6146
|
+
const projectDir = path25.join(rootDir, appName);
|
|
6147
|
+
if (!fs24.existsSync(projectDir)) {
|
|
6079
6148
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
6080
6149
|
process.exit(1);
|
|
6081
6150
|
}
|
|
6082
|
-
const templateDir =
|
|
6151
|
+
const templateDir = path25.join(hostPkg, "ios", "templates");
|
|
6083
6152
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
6084
6153
|
const files = [
|
|
6085
6154
|
"AppDelegate.swift",
|
|
@@ -6089,22 +6158,22 @@ async function injectHostIos(opts) {
|
|
|
6089
6158
|
"LynxInitProcessor.swift"
|
|
6090
6159
|
];
|
|
6091
6160
|
for (const f of files) {
|
|
6092
|
-
const srcPath =
|
|
6093
|
-
const dstPath =
|
|
6094
|
-
if (!
|
|
6095
|
-
if (
|
|
6161
|
+
const srcPath = path25.join(templateDir, f);
|
|
6162
|
+
const dstPath = path25.join(projectDir, f);
|
|
6163
|
+
if (!fs24.existsSync(srcPath)) continue;
|
|
6164
|
+
if (fs24.existsSync(dstPath) && !opts?.force) {
|
|
6096
6165
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
6097
6166
|
continue;
|
|
6098
6167
|
}
|
|
6099
6168
|
const content = readAndSubstitute(srcPath, vars);
|
|
6100
|
-
|
|
6169
|
+
fs24.writeFileSync(dstPath, content);
|
|
6101
6170
|
console.log(`\u2705 Injected ${f}`);
|
|
6102
6171
|
}
|
|
6103
6172
|
}
|
|
6104
6173
|
|
|
6105
6174
|
// src/common/buildEmbeddable.ts
|
|
6106
|
-
import
|
|
6107
|
-
import
|
|
6175
|
+
import fs25 from "fs";
|
|
6176
|
+
import path26 from "path";
|
|
6108
6177
|
import { execSync as execSync9 } from "child_process";
|
|
6109
6178
|
var EMBEDDABLE_DIR = "embeddable";
|
|
6110
6179
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
@@ -6181,14 +6250,14 @@ object LynxEmbeddable {
|
|
|
6181
6250
|
}
|
|
6182
6251
|
`;
|
|
6183
6252
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
6184
|
-
const libDir =
|
|
6185
|
-
const libSrcMain =
|
|
6186
|
-
const assetsDir =
|
|
6187
|
-
const kotlinDir =
|
|
6188
|
-
const generatedDir =
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6253
|
+
const libDir = path26.join(androidDir, "lib");
|
|
6254
|
+
const libSrcMain = path26.join(libDir, "src", "main");
|
|
6255
|
+
const assetsDir = path26.join(libSrcMain, "assets");
|
|
6256
|
+
const kotlinDir = path26.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
|
|
6257
|
+
const generatedDir = path26.join(kotlinDir, "generated");
|
|
6258
|
+
fs25.mkdirSync(path26.join(androidDir, "gradle"), { recursive: true });
|
|
6259
|
+
fs25.mkdirSync(generatedDir, { recursive: true });
|
|
6260
|
+
fs25.mkdirSync(assetsDir, { recursive: true });
|
|
6192
6261
|
const androidModules = modules.filter((m) => m.config.android);
|
|
6193
6262
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
6194
6263
|
const settingsContent = `pluginManagement {
|
|
@@ -6208,7 +6277,7 @@ include(":lib")
|
|
|
6208
6277
|
${androidModules.map((p) => {
|
|
6209
6278
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
6210
6279
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
6211
|
-
const absPath =
|
|
6280
|
+
const absPath = path26.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
6212
6281
|
return `include(":${gradleName}")
|
|
6213
6282
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
6214
6283
|
}).join("\n")}
|
|
@@ -6257,10 +6326,10 @@ dependencies {
|
|
|
6257
6326
|
${libDeps}
|
|
6258
6327
|
}
|
|
6259
6328
|
`;
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6329
|
+
fs25.writeFileSync(path26.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
|
|
6330
|
+
fs25.writeFileSync(path26.join(androidDir, "settings.gradle.kts"), settingsContent);
|
|
6331
|
+
fs25.writeFileSync(
|
|
6332
|
+
path26.join(androidDir, "build.gradle.kts"),
|
|
6264
6333
|
`plugins {
|
|
6265
6334
|
alias(libs.plugins.android.library) apply false
|
|
6266
6335
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -6268,26 +6337,26 @@ ${libDeps}
|
|
|
6268
6337
|
}
|
|
6269
6338
|
`
|
|
6270
6339
|
);
|
|
6271
|
-
|
|
6272
|
-
|
|
6340
|
+
fs25.writeFileSync(
|
|
6341
|
+
path26.join(androidDir, "gradle.properties"),
|
|
6273
6342
|
`org.gradle.jvmargs=-Xmx2048m
|
|
6274
6343
|
android.useAndroidX=true
|
|
6275
6344
|
kotlin.code.style=official
|
|
6276
6345
|
`
|
|
6277
6346
|
);
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6347
|
+
fs25.writeFileSync(path26.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
6348
|
+
fs25.writeFileSync(
|
|
6349
|
+
path26.join(libSrcMain, "AndroidManifest.xml"),
|
|
6281
6350
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
6282
6351
|
);
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6352
|
+
fs25.copyFileSync(lynxBundlePath, path26.join(assetsDir, lynxBundleFile));
|
|
6353
|
+
fs25.writeFileSync(path26.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
|
|
6354
|
+
fs25.writeFileSync(
|
|
6355
|
+
path26.join(generatedDir, "GeneratedLynxExtensions.kt"),
|
|
6287
6356
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
6288
6357
|
);
|
|
6289
|
-
|
|
6290
|
-
|
|
6358
|
+
fs25.writeFileSync(
|
|
6359
|
+
path26.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
6291
6360
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
6292
6361
|
);
|
|
6293
6362
|
}
|
|
@@ -6296,20 +6365,20 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6296
6365
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
6297
6366
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
6298
6367
|
execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
6299
|
-
if (!
|
|
6368
|
+
if (!fs25.existsSync(lynxBundlePath)) {
|
|
6300
6369
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
6301
6370
|
process.exit(1);
|
|
6302
6371
|
}
|
|
6303
|
-
const outDir =
|
|
6304
|
-
|
|
6305
|
-
const distDir =
|
|
6372
|
+
const outDir = path26.join(projectRoot, EMBEDDABLE_DIR);
|
|
6373
|
+
fs25.mkdirSync(outDir, { recursive: true });
|
|
6374
|
+
const distDir = path26.dirname(lynxBundlePath);
|
|
6306
6375
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
6307
6376
|
const modules = discoverModules(projectRoot);
|
|
6308
6377
|
const androidModules = modules.filter((m) => m.config.android);
|
|
6309
6378
|
const abiFilters = resolveAbiFilters(config);
|
|
6310
|
-
const androidDir =
|
|
6311
|
-
if (
|
|
6312
|
-
|
|
6379
|
+
const androidDir = path26.join(outDir, "android");
|
|
6380
|
+
if (fs25.existsSync(androidDir)) fs25.rmSync(androidDir, { recursive: true });
|
|
6381
|
+
fs25.mkdirSync(androidDir, { recursive: true });
|
|
6313
6382
|
generateAndroidLibrary(
|
|
6314
6383
|
outDir,
|
|
6315
6384
|
androidDir,
|
|
@@ -6319,23 +6388,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6319
6388
|
modules,
|
|
6320
6389
|
abiFilters
|
|
6321
6390
|
);
|
|
6322
|
-
const gradlewPath =
|
|
6391
|
+
const gradlewPath = path26.join(androidDir, "gradlew");
|
|
6323
6392
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
6324
6393
|
const existingGradleDirs = [
|
|
6325
|
-
|
|
6326
|
-
devAppDir ?
|
|
6394
|
+
path26.join(projectRoot, "android"),
|
|
6395
|
+
devAppDir ? path26.join(devAppDir, "android") : null
|
|
6327
6396
|
].filter(Boolean);
|
|
6328
6397
|
let hasWrapper = false;
|
|
6329
6398
|
for (const d of existingGradleDirs) {
|
|
6330
|
-
if (
|
|
6399
|
+
if (fs25.existsSync(path26.join(d, "gradlew"))) {
|
|
6331
6400
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
6332
|
-
const src =
|
|
6333
|
-
if (
|
|
6334
|
-
const dest =
|
|
6335
|
-
if (
|
|
6336
|
-
|
|
6401
|
+
const src = path26.join(d, name);
|
|
6402
|
+
if (fs25.existsSync(src)) {
|
|
6403
|
+
const dest = path26.join(androidDir, name);
|
|
6404
|
+
if (fs25.statSync(src).isDirectory()) {
|
|
6405
|
+
fs25.cpSync(src, dest, { recursive: true });
|
|
6337
6406
|
} else {
|
|
6338
|
-
|
|
6407
|
+
fs25.copyFileSync(src, dest);
|
|
6339
6408
|
}
|
|
6340
6409
|
}
|
|
6341
6410
|
}
|
|
@@ -6354,10 +6423,10 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6354
6423
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6355
6424
|
throw e;
|
|
6356
6425
|
}
|
|
6357
|
-
const aarSrc =
|
|
6358
|
-
const aarDest =
|
|
6359
|
-
if (
|
|
6360
|
-
|
|
6426
|
+
const aarSrc = path26.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
|
|
6427
|
+
const aarDest = path26.join(outDir, "tamer-embeddable.aar");
|
|
6428
|
+
if (fs25.existsSync(aarSrc)) {
|
|
6429
|
+
fs25.copyFileSync(aarSrc, aarDest);
|
|
6361
6430
|
console.log(` - tamer-embeddable.aar`);
|
|
6362
6431
|
}
|
|
6363
6432
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6368,7 +6437,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6368
6437
|
// LynxEmbeddable.init(applicationContext)
|
|
6369
6438
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6370
6439
|
`;
|
|
6371
|
-
|
|
6440
|
+
fs25.writeFileSync(path26.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6372
6441
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6373
6442
|
const readme = `# Embeddable Lynx Bundle
|
|
6374
6443
|
|
|
@@ -6399,7 +6468,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6399
6468
|
|
|
6400
6469
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6401
6470
|
`;
|
|
6402
|
-
|
|
6471
|
+
fs25.writeFileSync(path26.join(outDir, "README.md"), readme);
|
|
6403
6472
|
console.log(`
|
|
6404
6473
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6405
6474
|
console.log(" - main.lynx.bundle");
|
|
@@ -6411,20 +6480,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6411
6480
|
console.log(" - README.md");
|
|
6412
6481
|
}
|
|
6413
6482
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6414
|
-
const iosDir =
|
|
6415
|
-
const podDir =
|
|
6416
|
-
const resourcesDir =
|
|
6417
|
-
|
|
6418
|
-
|
|
6483
|
+
const iosDir = path26.join(outDir, "ios");
|
|
6484
|
+
const podDir = path26.join(iosDir, "TamerEmbeddable");
|
|
6485
|
+
const resourcesDir = path26.join(podDir, "Resources");
|
|
6486
|
+
fs25.mkdirSync(resourcesDir, { recursive: true });
|
|
6487
|
+
fs25.copyFileSync(lynxBundlePath, path26.join(resourcesDir, lynxBundleFile));
|
|
6419
6488
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6420
6489
|
const podDeps = iosModules.map((p) => {
|
|
6421
6490
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6422
|
-
const podspecDir =
|
|
6423
|
-
if (!
|
|
6424
|
-
const files =
|
|
6491
|
+
const podspecDir = path26.join(p.packagePath, podspecPath);
|
|
6492
|
+
if (!fs25.existsSync(podspecDir)) return null;
|
|
6493
|
+
const files = fs25.readdirSync(podspecDir);
|
|
6425
6494
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6426
6495
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6427
|
-
const absPath =
|
|
6496
|
+
const absPath = path26.resolve(podspecDir);
|
|
6428
6497
|
return { podName, absPath };
|
|
6429
6498
|
}).filter(Boolean);
|
|
6430
6499
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6464,9 +6533,9 @@ end
|
|
|
6464
6533
|
});
|
|
6465
6534
|
const swiftImports = iosModules.map((p) => {
|
|
6466
6535
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6467
|
-
const podspecDir =
|
|
6468
|
-
if (!
|
|
6469
|
-
const files =
|
|
6536
|
+
const podspecDir = path26.join(p.packagePath, podspecPath);
|
|
6537
|
+
if (!fs25.existsSync(podspecDir)) return null;
|
|
6538
|
+
const files = fs25.readdirSync(podspecDir);
|
|
6470
6539
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6471
6540
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6472
6541
|
}).filter(Boolean);
|
|
@@ -6485,17 +6554,17 @@ ${regBlock}
|
|
|
6485
6554
|
}
|
|
6486
6555
|
}
|
|
6487
6556
|
`;
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
const absIosDir =
|
|
6557
|
+
fs25.writeFileSync(path26.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
|
|
6558
|
+
fs25.writeFileSync(path26.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
|
|
6559
|
+
const absIosDir = path26.resolve(iosDir);
|
|
6491
6560
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6492
6561
|
|
|
6493
6562
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6494
6563
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6495
6564
|
`;
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6565
|
+
fs25.writeFileSync(path26.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
6566
|
+
fs25.writeFileSync(
|
|
6567
|
+
path26.join(outDir, "snippet-ios.swift"),
|
|
6499
6568
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6500
6569
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6501
6570
|
`
|
|
@@ -6503,8 +6572,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6503
6572
|
}
|
|
6504
6573
|
|
|
6505
6574
|
// src/common/add.ts
|
|
6506
|
-
import
|
|
6507
|
-
import
|
|
6575
|
+
import fs26 from "fs";
|
|
6576
|
+
import path27 from "path";
|
|
6508
6577
|
import { execFile, execSync as execSync10 } from "child_process";
|
|
6509
6578
|
import { promisify } from "util";
|
|
6510
6579
|
import semver from "semver";
|
|
@@ -6555,9 +6624,9 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
6555
6624
|
return `${pkg}@prerelease`;
|
|
6556
6625
|
}
|
|
6557
6626
|
function detectPackageManager2(cwd) {
|
|
6558
|
-
const dir =
|
|
6559
|
-
if (
|
|
6560
|
-
if (
|
|
6627
|
+
const dir = path27.resolve(cwd);
|
|
6628
|
+
if (fs26.existsSync(path27.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
6629
|
+
if (fs26.existsSync(path27.join(dir, "bun.lockb"))) return "bun";
|
|
6561
6630
|
return "npm";
|
|
6562
6631
|
}
|
|
6563
6632
|
function runInstall(cwd, packages, pm) {
|
|
@@ -6609,13 +6678,13 @@ async function add(packages = []) {
|
|
|
6609
6678
|
// src/common/signing.tsx
|
|
6610
6679
|
import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
6611
6680
|
import { render as render3, Text as Text10, Box as Box9 } from "ink";
|
|
6612
|
-
import
|
|
6613
|
-
import
|
|
6681
|
+
import fs29 from "fs";
|
|
6682
|
+
import path30 from "path";
|
|
6614
6683
|
|
|
6615
6684
|
// src/common/androidKeystore.ts
|
|
6616
6685
|
import { execFileSync } from "child_process";
|
|
6617
|
-
import
|
|
6618
|
-
import
|
|
6686
|
+
import fs27 from "fs";
|
|
6687
|
+
import path28 from "path";
|
|
6619
6688
|
function normalizeJavaHome(raw) {
|
|
6620
6689
|
if (!raw) return void 0;
|
|
6621
6690
|
const t = raw.trim().replace(/^["']|["']$/g, "");
|
|
@@ -6628,7 +6697,7 @@ function discoverJavaHomeMacOs() {
|
|
|
6628
6697
|
encoding: "utf8",
|
|
6629
6698
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6630
6699
|
}).trim().split("\n")[0]?.trim();
|
|
6631
|
-
if (out &&
|
|
6700
|
+
if (out && fs27.existsSync(path28.join(out, "bin", "keytool"))) return out;
|
|
6632
6701
|
} catch {
|
|
6633
6702
|
}
|
|
6634
6703
|
return void 0;
|
|
@@ -6638,13 +6707,13 @@ function resolveKeytoolPath() {
|
|
|
6638
6707
|
const win = process.platform === "win32";
|
|
6639
6708
|
const keytoolName = win ? "keytool.exe" : "keytool";
|
|
6640
6709
|
if (jh) {
|
|
6641
|
-
const p =
|
|
6642
|
-
if (
|
|
6710
|
+
const p = path28.join(jh, "bin", keytoolName);
|
|
6711
|
+
if (fs27.existsSync(p)) return p;
|
|
6643
6712
|
}
|
|
6644
6713
|
const mac = discoverJavaHomeMacOs();
|
|
6645
6714
|
if (mac) {
|
|
6646
|
-
const p =
|
|
6647
|
-
if (
|
|
6715
|
+
const p = path28.join(mac, "bin", keytoolName);
|
|
6716
|
+
if (fs27.existsSync(p)) return p;
|
|
6648
6717
|
}
|
|
6649
6718
|
return "keytool";
|
|
6650
6719
|
}
|
|
@@ -6659,16 +6728,16 @@ function keytoolAvailable() {
|
|
|
6659
6728
|
};
|
|
6660
6729
|
if (tryRun("keytool")) return true;
|
|
6661
6730
|
const fromJavaHome = resolveKeytoolPath();
|
|
6662
|
-
if (fromJavaHome !== "keytool" &&
|
|
6731
|
+
if (fromJavaHome !== "keytool" && fs27.existsSync(fromJavaHome)) {
|
|
6663
6732
|
return tryRun(fromJavaHome);
|
|
6664
6733
|
}
|
|
6665
6734
|
return false;
|
|
6666
6735
|
}
|
|
6667
6736
|
function generateReleaseKeystore(opts) {
|
|
6668
6737
|
const keytool = resolveKeytoolPath();
|
|
6669
|
-
const dir =
|
|
6670
|
-
|
|
6671
|
-
if (
|
|
6738
|
+
const dir = path28.dirname(opts.keystoreAbsPath);
|
|
6739
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
6740
|
+
if (fs27.existsSync(opts.keystoreAbsPath)) {
|
|
6672
6741
|
throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
|
|
6673
6742
|
}
|
|
6674
6743
|
if (!opts.storePassword || !opts.keyPassword) {
|
|
@@ -6706,13 +6775,13 @@ function generateReleaseKeystore(opts) {
|
|
|
6706
6775
|
}
|
|
6707
6776
|
|
|
6708
6777
|
// src/common/appendEnvFile.ts
|
|
6709
|
-
import
|
|
6710
|
-
import
|
|
6778
|
+
import fs28 from "fs";
|
|
6779
|
+
import path29 from "path";
|
|
6711
6780
|
import { parse } from "dotenv";
|
|
6712
6781
|
function keysDefinedInFile(filePath) {
|
|
6713
|
-
if (!
|
|
6782
|
+
if (!fs28.existsSync(filePath)) return /* @__PURE__ */ new Set();
|
|
6714
6783
|
try {
|
|
6715
|
-
return new Set(Object.keys(parse(
|
|
6784
|
+
return new Set(Object.keys(parse(fs28.readFileSync(filePath, "utf8"))));
|
|
6716
6785
|
} catch {
|
|
6717
6786
|
return /* @__PURE__ */ new Set();
|
|
6718
6787
|
}
|
|
@@ -6727,11 +6796,11 @@ function formatEnvLine(key, value) {
|
|
|
6727
6796
|
function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
6728
6797
|
const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
|
|
6729
6798
|
if (entries.length === 0) return null;
|
|
6730
|
-
const envLocal =
|
|
6731
|
-
const envDefault =
|
|
6799
|
+
const envLocal = path29.join(projectRoot, ".env.local");
|
|
6800
|
+
const envDefault = path29.join(projectRoot, ".env");
|
|
6732
6801
|
let target;
|
|
6733
|
-
if (
|
|
6734
|
-
else if (
|
|
6802
|
+
if (fs28.existsSync(envLocal)) target = envLocal;
|
|
6803
|
+
else if (fs28.existsSync(envDefault)) target = envDefault;
|
|
6735
6804
|
else target = envLocal;
|
|
6736
6805
|
const existing = keysDefinedInFile(target);
|
|
6737
6806
|
const lines = [];
|
|
@@ -6743,20 +6812,20 @@ function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
|
6743
6812
|
}
|
|
6744
6813
|
if (lines.length === 0) {
|
|
6745
6814
|
return {
|
|
6746
|
-
file:
|
|
6815
|
+
file: path29.basename(target),
|
|
6747
6816
|
keys: [],
|
|
6748
6817
|
skippedAll: entries.length > 0
|
|
6749
6818
|
};
|
|
6750
6819
|
}
|
|
6751
6820
|
let prefix = "";
|
|
6752
|
-
if (
|
|
6753
|
-
const cur =
|
|
6821
|
+
if (fs28.existsSync(target)) {
|
|
6822
|
+
const cur = fs28.readFileSync(target, "utf8");
|
|
6754
6823
|
prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
|
|
6755
6824
|
`;
|
|
6756
6825
|
}
|
|
6757
6826
|
const block = lines.join("\n") + "\n";
|
|
6758
|
-
|
|
6759
|
-
return { file:
|
|
6827
|
+
fs28.writeFileSync(target, prefix + block, "utf8");
|
|
6828
|
+
return { file: path29.basename(target), keys: appendedKeys };
|
|
6760
6829
|
}
|
|
6761
6830
|
|
|
6762
6831
|
// src/common/signing.tsx
|
|
@@ -6862,7 +6931,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6862
6931
|
try {
|
|
6863
6932
|
const resolved = resolveHostPaths();
|
|
6864
6933
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6865
|
-
abs =
|
|
6934
|
+
abs = path30.isAbsolute(rel) ? rel : path30.join(resolved.projectRoot, rel);
|
|
6866
6935
|
const alias = state.android.keyAlias.trim() || "release";
|
|
6867
6936
|
const pw = state.android.genPassword;
|
|
6868
6937
|
const pkg = resolved.config.android?.packageName ?? "com.example.app";
|
|
@@ -6889,7 +6958,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6889
6958
|
}));
|
|
6890
6959
|
} catch (e) {
|
|
6891
6960
|
const msg = e.message;
|
|
6892
|
-
if (abs &&
|
|
6961
|
+
if (abs && fs29.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
|
|
6893
6962
|
if (cancelled || runId !== generateRunId.current) return;
|
|
6894
6963
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6895
6964
|
const alias = state.android.keyAlias.trim() || "release";
|
|
@@ -6929,11 +6998,11 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6929
6998
|
const saveConfig = async () => {
|
|
6930
6999
|
try {
|
|
6931
7000
|
const resolved = resolveHostPaths();
|
|
6932
|
-
const configPath =
|
|
7001
|
+
const configPath = path30.join(resolved.projectRoot, "tamer.config.json");
|
|
6933
7002
|
let config = {};
|
|
6934
7003
|
let androidEnvAppend = null;
|
|
6935
|
-
if (
|
|
6936
|
-
config = JSON.parse(
|
|
7004
|
+
if (fs29.existsSync(configPath)) {
|
|
7005
|
+
config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
|
|
6937
7006
|
}
|
|
6938
7007
|
if (state.platform === "android" || state.platform === "both") {
|
|
6939
7008
|
config.android = config.android || {};
|
|
@@ -6960,10 +7029,10 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6960
7029
|
...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
|
|
6961
7030
|
};
|
|
6962
7031
|
}
|
|
6963
|
-
|
|
6964
|
-
const gitignorePath =
|
|
6965
|
-
if (
|
|
6966
|
-
let gitignore =
|
|
7032
|
+
fs29.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
7033
|
+
const gitignorePath = path30.join(resolved.projectRoot, ".gitignore");
|
|
7034
|
+
if (fs29.existsSync(gitignorePath)) {
|
|
7035
|
+
let gitignore = fs29.readFileSync(gitignorePath, "utf8");
|
|
6967
7036
|
const additions = [
|
|
6968
7037
|
".env.local",
|
|
6969
7038
|
"*.jks",
|
|
@@ -6976,7 +7045,7 @@ ${addition}
|
|
|
6976
7045
|
`;
|
|
6977
7046
|
}
|
|
6978
7047
|
}
|
|
6979
|
-
|
|
7048
|
+
fs29.writeFileSync(gitignorePath, gitignore);
|
|
6980
7049
|
}
|
|
6981
7050
|
setState((s) => ({
|
|
6982
7051
|
...s,
|
|
@@ -7234,13 +7303,13 @@ async function signing(platform) {
|
|
|
7234
7303
|
}
|
|
7235
7304
|
|
|
7236
7305
|
// src/common/productionSigning.ts
|
|
7237
|
-
import
|
|
7238
|
-
import
|
|
7306
|
+
import fs30 from "fs";
|
|
7307
|
+
import path31 from "path";
|
|
7239
7308
|
function isAndroidSigningConfigured(resolved) {
|
|
7240
7309
|
const signing2 = resolved.config.android?.signing;
|
|
7241
7310
|
const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
|
|
7242
|
-
const signingProps =
|
|
7243
|
-
const hasProps =
|
|
7311
|
+
const signingProps = path31.join(resolved.androidDir, "signing.properties");
|
|
7312
|
+
const hasProps = fs30.existsSync(signingProps);
|
|
7244
7313
|
return hasConfig || hasProps;
|
|
7245
7314
|
}
|
|
7246
7315
|
function isIosSigningConfigured(resolved) {
|
|
@@ -7312,7 +7381,7 @@ program.command("create <target>").description("Create a project or extension. T
|
|
|
7312
7381
|
program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option(
|
|
7313
7382
|
"-i, --install",
|
|
7314
7383
|
"Install after build (iOS: simulator with -d; connected device with -p)"
|
|
7315
|
-
).action(async (platform, opts) => {
|
|
7384
|
+
).option("-C, --clean", "Run Gradle clean before Android build (fixes stubborn caches)").action(async (platform, opts) => {
|
|
7316
7385
|
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7317
7386
|
const release = opts.release === true || opts.production === true;
|
|
7318
7387
|
const production = opts.production === true;
|
|
@@ -7325,7 +7394,7 @@ program.command("build [platform]").description("Build app. Platform: ios | andr
|
|
|
7325
7394
|
assertProductionSigningReady(p);
|
|
7326
7395
|
}
|
|
7327
7396
|
if (p === "android" || p === "all") {
|
|
7328
|
-
await build_default({ install: opts.install, release, production });
|
|
7397
|
+
await build_default({ install: opts.install, release, production, clean: opts.clean });
|
|
7329
7398
|
}
|
|
7330
7399
|
if (p === "ios" || p === "all") {
|
|
7331
7400
|
await build_default2({ install: opts.install, release, production });
|
|
@@ -7414,7 +7483,7 @@ program.command("signing [platform]").description("Configure Android and iOS sig
|
|
|
7414
7483
|
program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
|
|
7415
7484
|
codegen_default();
|
|
7416
7485
|
});
|
|
7417
|
-
program.command("android <subcommand>").description("(Legacy) Use: t4l <command> android. e.g. t4l create android").option("-d, --debug", "Create: host project. Bundle/build: debug with dev client.").option("-r, --release", "Create: dev-app project. Bundle/build: release without dev client.").option("-p, --production", "Bundle/build: production for app store (signed)").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
|
|
7486
|
+
program.command("android <subcommand>").description("(Legacy) Use: t4l <command> android. e.g. t4l create android").option("-d, --debug", "Create: host project. Bundle/build: debug with dev client.").option("-r, --release", "Create: dev-app project. Bundle/build: release without dev client.").option("-p, --production", "Bundle/build: production for app store (signed)").option("-i, --install", "Install after build").option("-C, --clean", "Run Gradle clean before Android build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
|
|
7418
7487
|
const sub = subcommand?.toLowerCase();
|
|
7419
7488
|
if (sub === "create") {
|
|
7420
7489
|
if (opts.debug && opts.release) {
|
|
@@ -7440,7 +7509,12 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
7440
7509
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
7441
7510
|
else {
|
|
7442
7511
|
if (opts.production === true) assertProductionSigningReady("android");
|
|
7443
|
-
await build_default({
|
|
7512
|
+
await build_default({
|
|
7513
|
+
install: opts.install,
|
|
7514
|
+
release,
|
|
7515
|
+
production: opts.production === true,
|
|
7516
|
+
clean: opts.clean
|
|
7517
|
+
});
|
|
7444
7518
|
}
|
|
7445
7519
|
return;
|
|
7446
7520
|
}
|
|
@@ -7489,10 +7563,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
7489
7563
|
process.exit(1);
|
|
7490
7564
|
});
|
|
7491
7565
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
7492
|
-
const configPath =
|
|
7566
|
+
const configPath = path32.join(process.cwd(), "tamer.config.json");
|
|
7493
7567
|
let config = {};
|
|
7494
|
-
if (
|
|
7495
|
-
config = JSON.parse(
|
|
7568
|
+
if (fs31.existsSync(configPath)) {
|
|
7569
|
+
config = JSON.parse(fs31.readFileSync(configPath, "utf8"));
|
|
7496
7570
|
}
|
|
7497
7571
|
if (config.autolink) {
|
|
7498
7572
|
delete config.autolink;
|
|
@@ -7501,7 +7575,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
7501
7575
|
config.autolink = true;
|
|
7502
7576
|
console.log("Autolink enabled in tamer.config.json");
|
|
7503
7577
|
}
|
|
7504
|
-
|
|
7578
|
+
fs31.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
7505
7579
|
console.log(`Updated ${configPath}`);
|
|
7506
7580
|
});
|
|
7507
7581
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|