@tamer4lynx/cli 0.0.17 → 0.0.19
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 +1041 -769
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -7,41 +7,64 @@ process.on("warning", (w) => {
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// index.ts
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
10
|
+
import fs31 from "fs";
|
|
11
|
+
import path32 from "path";
|
|
13
12
|
import { program } from "commander";
|
|
14
13
|
|
|
14
|
+
// src/common/cliVersion.ts
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
function getCliVersion() {
|
|
19
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
for (let i = 0; i < 12; i++) {
|
|
21
|
+
const pkgPath = path.join(dir, "package.json");
|
|
22
|
+
if (fs.existsSync(pkgPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
25
|
+
if (pkg.name === "@tamer4lynx/cli" && typeof pkg.version === "string") {
|
|
26
|
+
return pkg.version;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const next = path.dirname(dir);
|
|
32
|
+
if (next === dir) break;
|
|
33
|
+
dir = next;
|
|
34
|
+
}
|
|
35
|
+
return "0.0.0";
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
// src/android/create.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
39
|
+
import fs5 from "fs";
|
|
40
|
+
import path5 from "path";
|
|
18
41
|
import os2 from "os";
|
|
19
42
|
|
|
20
43
|
// src/android/getGradle.ts
|
|
21
|
-
import
|
|
22
|
-
import
|
|
44
|
+
import fs2 from "fs";
|
|
45
|
+
import path2 from "path";
|
|
23
46
|
import os from "os";
|
|
24
47
|
import { execSync } from "child_process";
|
|
25
48
|
import fetch2 from "node-fetch";
|
|
26
49
|
import AdmZip from "adm-zip";
|
|
27
50
|
async function downloadGradle(gradleVersion) {
|
|
28
51
|
const gradleBaseUrl = `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`;
|
|
29
|
-
const downloadDir =
|
|
30
|
-
const zipPath =
|
|
31
|
-
const extractedPath =
|
|
32
|
-
if (
|
|
52
|
+
const downloadDir = path2.join(process.cwd(), "gradle");
|
|
53
|
+
const zipPath = path2.join(downloadDir, `gradle-${gradleVersion}.zip`);
|
|
54
|
+
const extractedPath = path2.join(downloadDir, `gradle-${gradleVersion}`);
|
|
55
|
+
if (fs2.existsSync(extractedPath)) {
|
|
33
56
|
console.log(`\u2705 Gradle ${gradleVersion} already exists at ${extractedPath}. Skipping download.`);
|
|
34
57
|
return;
|
|
35
58
|
}
|
|
36
|
-
if (!
|
|
37
|
-
|
|
59
|
+
if (!fs2.existsSync(downloadDir)) {
|
|
60
|
+
fs2.mkdirSync(downloadDir, { recursive: true });
|
|
38
61
|
}
|
|
39
62
|
console.log(`\u{1F4E5} Downloading Gradle ${gradleVersion} from ${gradleBaseUrl}...`);
|
|
40
63
|
const response = await fetch2(gradleBaseUrl);
|
|
41
64
|
if (!response.ok || !response.body) {
|
|
42
65
|
throw new Error(`Failed to download: ${response.statusText}`);
|
|
43
66
|
}
|
|
44
|
-
const fileStream =
|
|
67
|
+
const fileStream = fs2.createWriteStream(zipPath);
|
|
45
68
|
await new Promise((resolve, reject) => {
|
|
46
69
|
response.body?.pipe(fileStream);
|
|
47
70
|
response.body?.on("error", reject);
|
|
@@ -55,26 +78,26 @@ async function downloadGradle(gradleVersion) {
|
|
|
55
78
|
console.error(`\u274C Failed to extract Gradle zip: ${err}`);
|
|
56
79
|
throw err;
|
|
57
80
|
}
|
|
58
|
-
|
|
81
|
+
fs2.unlinkSync(zipPath);
|
|
59
82
|
console.log(`\u2705 Gradle ${gradleVersion} extracted to ${extractedPath}`);
|
|
60
83
|
}
|
|
61
84
|
async function setupGradleWrapper(rootDir, gradleVersion) {
|
|
62
85
|
try {
|
|
63
86
|
console.log("\u{1F4E6} Setting up Gradle wrapper...");
|
|
64
87
|
await downloadGradle(gradleVersion);
|
|
65
|
-
const gradleBinDir =
|
|
88
|
+
const gradleBinDir = path2.join(
|
|
66
89
|
process.cwd(),
|
|
67
90
|
"gradle",
|
|
68
91
|
`gradle-${gradleVersion}`,
|
|
69
92
|
"bin"
|
|
70
93
|
);
|
|
71
94
|
const gradleExecutable = os.platform() === "win32" ? "gradle.bat" : "gradle";
|
|
72
|
-
const gradleExecutablePath =
|
|
73
|
-
if (!
|
|
95
|
+
const gradleExecutablePath = path2.join(gradleBinDir, gradleExecutable);
|
|
96
|
+
if (!fs2.existsSync(gradleExecutablePath)) {
|
|
74
97
|
throw new Error(`Gradle executable not found at ${gradleExecutablePath}`);
|
|
75
98
|
}
|
|
76
99
|
if (os.platform() !== "win32") {
|
|
77
|
-
|
|
100
|
+
fs2.chmodSync(gradleExecutablePath, "755");
|
|
78
101
|
}
|
|
79
102
|
console.log(`\u{1F680} Executing Gradle wrapper in: ${rootDir}`);
|
|
80
103
|
execSync(`"${gradleExecutablePath}" wrapper`, {
|
|
@@ -89,8 +112,8 @@ async function setupGradleWrapper(rootDir, gradleVersion) {
|
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
// src/common/hostConfig.ts
|
|
92
|
-
import
|
|
93
|
-
import
|
|
115
|
+
import fs3 from "fs";
|
|
116
|
+
import path3 from "path";
|
|
94
117
|
var DEFAULT_ABI_FILTERS = ["armeabi-v7a", "arm64-v8a"];
|
|
95
118
|
var TAMER_CONFIG = "tamer.config.json";
|
|
96
119
|
var LYNX_CONFIG_FILES = ["lynx.config.ts", "lynx.config.js", "lynx.config.mjs"];
|
|
@@ -99,27 +122,27 @@ var DEFAULT_IOS_DIR = "ios";
|
|
|
99
122
|
var DEFAULT_BUNDLE_FILE = "main.lynx.bundle";
|
|
100
123
|
var DEFAULT_BUNDLE_ROOT = "dist";
|
|
101
124
|
function findProjectRoot(start2) {
|
|
102
|
-
let dir =
|
|
103
|
-
const root =
|
|
125
|
+
let dir = path3.resolve(start2);
|
|
126
|
+
const root = path3.parse(dir).root;
|
|
104
127
|
while (dir !== root) {
|
|
105
|
-
const p =
|
|
106
|
-
if (
|
|
107
|
-
dir =
|
|
128
|
+
const p = path3.join(dir, TAMER_CONFIG);
|
|
129
|
+
if (fs3.existsSync(p)) return dir;
|
|
130
|
+
dir = path3.dirname(dir);
|
|
108
131
|
}
|
|
109
132
|
return start2;
|
|
110
133
|
}
|
|
111
134
|
function loadTamerConfig(cwd) {
|
|
112
|
-
const p =
|
|
113
|
-
if (!
|
|
135
|
+
const p = path3.join(cwd, TAMER_CONFIG);
|
|
136
|
+
if (!fs3.existsSync(p)) return null;
|
|
114
137
|
try {
|
|
115
|
-
return JSON.parse(
|
|
138
|
+
return JSON.parse(fs3.readFileSync(p, "utf8"));
|
|
116
139
|
} catch {
|
|
117
140
|
return null;
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
function extractDistPathRoot(configPath) {
|
|
121
144
|
try {
|
|
122
|
-
const content =
|
|
145
|
+
const content = fs3.readFileSync(configPath, "utf8");
|
|
123
146
|
const rootMatch = content.match(/distPath\s*:\s*\{\s*root\s*:\s*['"]([^'"]+)['"]/);
|
|
124
147
|
if (rootMatch?.[1]) return rootMatch[1];
|
|
125
148
|
const rootMatch2 = content.match(/root\s*:\s*['"]([^'"]+)['"]/);
|
|
@@ -130,16 +153,16 @@ function extractDistPathRoot(configPath) {
|
|
|
130
153
|
}
|
|
131
154
|
function findLynxConfigInDir(dir) {
|
|
132
155
|
for (const name of LYNX_CONFIG_FILES) {
|
|
133
|
-
const p =
|
|
134
|
-
if (
|
|
156
|
+
const p = path3.join(dir, name);
|
|
157
|
+
if (fs3.existsSync(p)) return p;
|
|
135
158
|
}
|
|
136
159
|
return null;
|
|
137
160
|
}
|
|
138
161
|
function hasRspeedy(pkgDir) {
|
|
139
|
-
const pkgJsonPath =
|
|
140
|
-
if (!
|
|
162
|
+
const pkgJsonPath = path3.join(pkgDir, "package.json");
|
|
163
|
+
if (!fs3.existsSync(pkgJsonPath)) return false;
|
|
141
164
|
try {
|
|
142
|
-
const pkg = JSON.parse(
|
|
165
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
|
|
143
166
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
144
167
|
return Object.keys(deps).some((k) => k === "@lynx-js/rspeedy");
|
|
145
168
|
} catch {
|
|
@@ -148,17 +171,17 @@ function hasRspeedy(pkgDir) {
|
|
|
148
171
|
}
|
|
149
172
|
function discoverLynxProject(cwd, explicitPath) {
|
|
150
173
|
if (explicitPath) {
|
|
151
|
-
const resolved =
|
|
152
|
-
if (
|
|
174
|
+
const resolved = path3.isAbsolute(explicitPath) ? explicitPath : path3.join(cwd, explicitPath);
|
|
175
|
+
if (fs3.existsSync(resolved)) {
|
|
153
176
|
const lynxConfig2 = findLynxConfigInDir(resolved);
|
|
154
177
|
const bundleRoot = lynxConfig2 ? extractDistPathRoot(lynxConfig2) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
|
|
155
178
|
return { dir: resolved, bundleRoot };
|
|
156
179
|
}
|
|
157
180
|
}
|
|
158
|
-
const rootPkgPath =
|
|
159
|
-
if (
|
|
181
|
+
const rootPkgPath = path3.join(cwd, "package.json");
|
|
182
|
+
if (fs3.existsSync(rootPkgPath)) {
|
|
160
183
|
try {
|
|
161
|
-
const rootPkg = JSON.parse(
|
|
184
|
+
const rootPkg = JSON.parse(fs3.readFileSync(rootPkgPath, "utf8"));
|
|
162
185
|
const lynxConfig2 = findLynxConfigInDir(cwd);
|
|
163
186
|
if (lynxConfig2 || (rootPkg.dependencies?.["@lynx-js/rspeedy"] || rootPkg.devDependencies?.["@lynx-js/rspeedy"])) {
|
|
164
187
|
const bundleRoot = lynxConfig2 ? extractDistPathRoot(lynxConfig2) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
|
|
@@ -169,12 +192,12 @@ function discoverLynxProject(cwd, explicitPath) {
|
|
|
169
192
|
for (const ws of workspaces) {
|
|
170
193
|
const isGlob = typeof ws === "string" && ws.includes("*");
|
|
171
194
|
const dirsToCheck = isGlob ? (() => {
|
|
172
|
-
const parentDir =
|
|
173
|
-
if (!
|
|
174
|
-
return
|
|
175
|
-
})() : [
|
|
195
|
+
const parentDir = path3.join(cwd, ws.replace(/\/\*$/, ""));
|
|
196
|
+
if (!fs3.existsSync(parentDir)) return [];
|
|
197
|
+
return fs3.readdirSync(parentDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path3.join(parentDir, e.name));
|
|
198
|
+
})() : [path3.join(cwd, ws)];
|
|
176
199
|
for (const pkgDir of dirsToCheck) {
|
|
177
|
-
if (!
|
|
200
|
+
if (!fs3.existsSync(pkgDir)) continue;
|
|
178
201
|
const lynxConfig3 = findLynxConfigInDir(pkgDir);
|
|
179
202
|
if (lynxConfig3 || hasRspeedy(pkgDir)) {
|
|
180
203
|
const bundleRoot = lynxConfig3 ? extractDistPathRoot(lynxConfig3) ?? DEFAULT_BUNDLE_ROOT : DEFAULT_BUNDLE_ROOT;
|
|
@@ -194,30 +217,30 @@ function discoverLynxProject(cwd, explicitPath) {
|
|
|
194
217
|
return null;
|
|
195
218
|
}
|
|
196
219
|
function findRepoRoot(start2) {
|
|
197
|
-
let dir =
|
|
198
|
-
const root =
|
|
220
|
+
let dir = path3.resolve(start2);
|
|
221
|
+
const root = path3.parse(dir).root;
|
|
199
222
|
while (dir !== root) {
|
|
200
|
-
const pkgPath =
|
|
201
|
-
if (
|
|
223
|
+
const pkgPath = path3.join(dir, "package.json");
|
|
224
|
+
if (fs3.existsSync(pkgPath)) {
|
|
202
225
|
try {
|
|
203
|
-
const pkg = JSON.parse(
|
|
226
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
|
|
204
227
|
if (pkg.workspaces) return dir;
|
|
205
228
|
} catch {
|
|
206
229
|
}
|
|
207
230
|
}
|
|
208
|
-
dir =
|
|
231
|
+
dir = path3.dirname(dir);
|
|
209
232
|
}
|
|
210
233
|
return start2;
|
|
211
234
|
}
|
|
212
235
|
function findDevAppPackage(projectRoot) {
|
|
213
236
|
const candidates = [
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
237
|
+
path3.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-app"),
|
|
238
|
+
path3.join(projectRoot, "node_modules", "tamer-dev-app"),
|
|
239
|
+
path3.join(projectRoot, "packages", "tamer-dev-app"),
|
|
240
|
+
path3.join(path3.dirname(projectRoot), "tamer-dev-app")
|
|
218
241
|
];
|
|
219
242
|
for (const pkg of candidates) {
|
|
220
|
-
if (
|
|
243
|
+
if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
|
|
221
244
|
return pkg;
|
|
222
245
|
}
|
|
223
246
|
}
|
|
@@ -225,13 +248,13 @@ function findDevAppPackage(projectRoot) {
|
|
|
225
248
|
}
|
|
226
249
|
function findDevClientPackage(projectRoot) {
|
|
227
250
|
const candidates = [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
251
|
+
path3.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client"),
|
|
252
|
+
path3.join(projectRoot, "node_modules", "tamer-dev-client"),
|
|
253
|
+
path3.join(projectRoot, "packages", "tamer-dev-client"),
|
|
254
|
+
path3.join(path3.dirname(projectRoot), "tamer-dev-client")
|
|
232
255
|
];
|
|
233
256
|
for (const pkg of candidates) {
|
|
234
|
-
if (
|
|
257
|
+
if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
|
|
235
258
|
return pkg;
|
|
236
259
|
}
|
|
237
260
|
}
|
|
@@ -239,11 +262,11 @@ function findDevClientPackage(projectRoot) {
|
|
|
239
262
|
}
|
|
240
263
|
function findTamerHostPackage(projectRoot) {
|
|
241
264
|
const candidates = [
|
|
242
|
-
|
|
243
|
-
|
|
265
|
+
path3.join(projectRoot, "node_modules", "tamer-host"),
|
|
266
|
+
path3.join(projectRoot, "packages", "tamer-host")
|
|
244
267
|
];
|
|
245
268
|
for (const pkg of candidates) {
|
|
246
|
-
if (
|
|
269
|
+
if (fs3.existsSync(pkg) && fs3.existsSync(path3.join(pkg, "package.json"))) {
|
|
247
270
|
return pkg;
|
|
248
271
|
}
|
|
249
272
|
}
|
|
@@ -261,18 +284,18 @@ function resolveHostPaths(cwd = process.cwd()) {
|
|
|
261
284
|
const lynxProjectDir = discovered?.dir ?? cwd;
|
|
262
285
|
const bundleRoot = paths.lynxBundleRoot ?? discovered?.bundleRoot ?? DEFAULT_BUNDLE_ROOT;
|
|
263
286
|
const bundleFile = paths.lynxBundleFile ?? DEFAULT_BUNDLE_FILE;
|
|
264
|
-
const lynxBundlePath =
|
|
265
|
-
const androidDir =
|
|
287
|
+
const lynxBundlePath = path3.join(lynxProjectDir, bundleRoot, bundleFile);
|
|
288
|
+
const androidDir = path3.join(projectRoot, androidDirRel);
|
|
266
289
|
const devMode = resolveDevMode(config);
|
|
267
290
|
const devClientPkg = findDevClientPackage(projectRoot);
|
|
268
|
-
const devClientBundlePath = devClientPkg ?
|
|
291
|
+
const devClientBundlePath = devClientPkg ? path3.join(devClientPkg, DEFAULT_BUNDLE_ROOT, "dev-client.lynx.bundle") : void 0;
|
|
269
292
|
return {
|
|
270
293
|
projectRoot,
|
|
271
294
|
androidDir,
|
|
272
|
-
iosDir:
|
|
273
|
-
androidAppDir:
|
|
274
|
-
androidAssetsDir:
|
|
275
|
-
androidKotlinDir:
|
|
295
|
+
iosDir: path3.join(projectRoot, iosDirRel),
|
|
296
|
+
androidAppDir: path3.join(projectRoot, androidDirRel, "app"),
|
|
297
|
+
androidAssetsDir: path3.join(projectRoot, androidDirRel, "app", "src", "main", "assets"),
|
|
298
|
+
androidKotlinDir: path3.join(projectRoot, androidDirRel, "app", "src", "main", "kotlin", packageName.replace(/\./g, "/")),
|
|
276
299
|
lynxProjectDir,
|
|
277
300
|
lynxBundlePath,
|
|
278
301
|
lynxBundleFile: bundleFile,
|
|
@@ -347,32 +370,32 @@ function normalizeAndroidAdaptiveColor(input) {
|
|
|
347
370
|
function resolveIconPaths(projectRoot, config) {
|
|
348
371
|
const raw = config.icon;
|
|
349
372
|
if (!raw) return null;
|
|
350
|
-
const join = (p) =>
|
|
373
|
+
const join = (p) => path3.isAbsolute(p) ? p : path3.join(projectRoot, p);
|
|
351
374
|
if (typeof raw === "string") {
|
|
352
375
|
const p = join(raw);
|
|
353
|
-
return
|
|
376
|
+
return fs3.existsSync(p) ? { source: p } : null;
|
|
354
377
|
}
|
|
355
378
|
const out = {};
|
|
356
379
|
if (raw.source) {
|
|
357
380
|
const p = join(raw.source);
|
|
358
|
-
if (
|
|
381
|
+
if (fs3.existsSync(p)) out.source = p;
|
|
359
382
|
}
|
|
360
383
|
if (raw.android) {
|
|
361
384
|
const p = join(raw.android);
|
|
362
|
-
if (
|
|
385
|
+
if (fs3.existsSync(p)) out.android = p;
|
|
363
386
|
}
|
|
364
387
|
if (raw.ios) {
|
|
365
388
|
const p = join(raw.ios);
|
|
366
|
-
if (
|
|
389
|
+
if (fs3.existsSync(p)) out.ios = p;
|
|
367
390
|
}
|
|
368
391
|
const ad = raw.androidAdaptive;
|
|
369
392
|
if (ad?.foreground) {
|
|
370
393
|
const fg = join(ad.foreground);
|
|
371
|
-
if (
|
|
394
|
+
if (fs3.existsSync(fg)) {
|
|
372
395
|
let usedAdaptive = false;
|
|
373
396
|
if (ad.background) {
|
|
374
397
|
const bg = join(ad.background);
|
|
375
|
-
if (
|
|
398
|
+
if (fs3.existsSync(bg)) {
|
|
376
399
|
out.androidAdaptiveForeground = fg;
|
|
377
400
|
out.androidAdaptiveBackground = bg;
|
|
378
401
|
usedAdaptive = true;
|
|
@@ -395,19 +418,19 @@ function resolveIconPaths(projectRoot, config) {
|
|
|
395
418
|
}
|
|
396
419
|
|
|
397
420
|
// src/common/syncAppIcons.ts
|
|
398
|
-
import
|
|
399
|
-
import
|
|
421
|
+
import fs4 from "fs";
|
|
422
|
+
import path4 from "path";
|
|
400
423
|
function purgeAdaptiveForegroundArtifacts(drawableDir) {
|
|
401
424
|
for (const base of ["ic_launcher_fg_src", "ic_launcher_fg_bm", "ic_launcher_fg_sc"]) {
|
|
402
425
|
for (const ext of [".png", ".webp", ".jpg", ".jpeg", ".xml"]) {
|
|
403
426
|
try {
|
|
404
|
-
|
|
427
|
+
fs4.unlinkSync(path4.join(drawableDir, base + ext));
|
|
405
428
|
} catch {
|
|
406
429
|
}
|
|
407
430
|
}
|
|
408
431
|
}
|
|
409
432
|
try {
|
|
410
|
-
|
|
433
|
+
fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_foreground.xml"));
|
|
411
434
|
} catch {
|
|
412
435
|
}
|
|
413
436
|
}
|
|
@@ -420,15 +443,15 @@ function writeAdaptiveForegroundLayer(drawableDir, fgSourcePath, fgExt, layout)
|
|
|
420
443
|
purgeAdaptiveForegroundArtifacts(drawableDir);
|
|
421
444
|
for (const ext of [".png", ".webp", ".jpg", ".jpeg"]) {
|
|
422
445
|
try {
|
|
423
|
-
|
|
446
|
+
fs4.unlinkSync(path4.join(drawableDir, `ic_launcher_foreground${ext}`));
|
|
424
447
|
} catch {
|
|
425
448
|
}
|
|
426
449
|
}
|
|
427
450
|
if (!layout) {
|
|
428
|
-
|
|
451
|
+
fs4.copyFileSync(fgSourcePath, path4.join(drawableDir, `ic_launcher_foreground${fgExt}`));
|
|
429
452
|
return;
|
|
430
453
|
}
|
|
431
|
-
|
|
454
|
+
fs4.copyFileSync(fgSourcePath, path4.join(drawableDir, `ic_launcher_fg_src${fgExt}`));
|
|
432
455
|
const CANVAS_DP = 108;
|
|
433
456
|
const scale = layout.scale ?? 1;
|
|
434
457
|
const shrinkDp = (1 - scale) / 2 * CANVAS_DP;
|
|
@@ -452,11 +475,11 @@ function writeAdaptiveForegroundLayer(drawableDir, fgSourcePath, fgExt, layout)
|
|
|
452
475
|
android:bottom="${insetBottom}dp"
|
|
453
476
|
android:drawable="@drawable/ic_launcher_fg_src" />
|
|
454
477
|
</layer-list>`;
|
|
455
|
-
|
|
478
|
+
fs4.writeFileSync(path4.join(drawableDir, "ic_launcher_foreground.xml"), layerXml);
|
|
456
479
|
}
|
|
457
480
|
function ensureAndroidManifestLauncherIcon(manifestPath) {
|
|
458
|
-
if (!
|
|
459
|
-
let content =
|
|
481
|
+
if (!fs4.existsSync(manifestPath)) return;
|
|
482
|
+
let content = fs4.readFileSync(manifestPath, "utf8");
|
|
460
483
|
if (content.includes("android:icon=")) return;
|
|
461
484
|
const next = content.replace(/<application(\s[^>]*)>/, (full, attrs) => {
|
|
462
485
|
if (String(attrs).includes("android:icon")) return full;
|
|
@@ -465,30 +488,30 @@ function ensureAndroidManifestLauncherIcon(manifestPath) {
|
|
|
465
488
|
android:roundIcon="@mipmap/ic_launcher">`;
|
|
466
489
|
});
|
|
467
490
|
if (next !== content) {
|
|
468
|
-
|
|
491
|
+
fs4.writeFileSync(manifestPath, next, "utf8");
|
|
469
492
|
console.log("\u2705 Added android:icon / roundIcon to AndroidManifest.xml");
|
|
470
493
|
}
|
|
471
494
|
}
|
|
472
495
|
function applyAndroidLauncherIcons(resDir, iconPaths) {
|
|
473
496
|
if (!iconPaths) return false;
|
|
474
|
-
|
|
497
|
+
fs4.mkdirSync(resDir, { recursive: true });
|
|
475
498
|
const fgAd = iconPaths.androidAdaptiveForeground;
|
|
476
499
|
const bgAd = iconPaths.androidAdaptiveBackground;
|
|
477
500
|
const bgColor = iconPaths.androidAdaptiveBackgroundColor;
|
|
478
501
|
if (fgAd && (bgAd || bgColor)) {
|
|
479
|
-
const drawableDir =
|
|
480
|
-
|
|
481
|
-
const fgExt =
|
|
502
|
+
const drawableDir = path4.join(resDir, "drawable");
|
|
503
|
+
fs4.mkdirSync(drawableDir, { recursive: true });
|
|
504
|
+
const fgExt = path4.extname(fgAd) || ".png";
|
|
482
505
|
writeAdaptiveForegroundLayer(drawableDir, fgAd, fgExt, iconPaths.androidAdaptiveForegroundLayout);
|
|
483
506
|
if (bgColor) {
|
|
484
507
|
for (const ext of [".png", ".webp", ".jpg", ".jpeg"]) {
|
|
485
508
|
try {
|
|
486
|
-
|
|
509
|
+
fs4.unlinkSync(path4.join(drawableDir, `ic_launcher_background${ext}`));
|
|
487
510
|
} catch {
|
|
488
511
|
}
|
|
489
512
|
}
|
|
490
513
|
try {
|
|
491
|
-
|
|
514
|
+
fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_background.xml"));
|
|
492
515
|
} catch {
|
|
493
516
|
}
|
|
494
517
|
const shapeXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
@@ -496,44 +519,44 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
|
|
|
496
519
|
<solid android:color="${bgColor}" />
|
|
497
520
|
</shape>
|
|
498
521
|
`;
|
|
499
|
-
|
|
522
|
+
fs4.writeFileSync(path4.join(drawableDir, "ic_launcher_background.xml"), shapeXml);
|
|
500
523
|
} else {
|
|
501
524
|
try {
|
|
502
|
-
|
|
525
|
+
fs4.unlinkSync(path4.join(drawableDir, "ic_launcher_background.xml"));
|
|
503
526
|
} catch {
|
|
504
527
|
}
|
|
505
|
-
const bgExt =
|
|
506
|
-
|
|
528
|
+
const bgExt = path4.extname(bgAd) || ".png";
|
|
529
|
+
fs4.copyFileSync(bgAd, path4.join(drawableDir, `ic_launcher_background${bgExt}`));
|
|
507
530
|
}
|
|
508
|
-
const anyDpi =
|
|
509
|
-
|
|
531
|
+
const anyDpi = path4.join(resDir, "mipmap-anydpi-v26");
|
|
532
|
+
fs4.mkdirSync(anyDpi, { recursive: true });
|
|
510
533
|
const adaptiveXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
511
534
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
512
535
|
<background android:drawable="@drawable/ic_launcher_background" />
|
|
513
536
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
514
537
|
</adaptive-icon>
|
|
515
538
|
`;
|
|
516
|
-
|
|
517
|
-
|
|
539
|
+
fs4.writeFileSync(path4.join(anyDpi, "ic_launcher.xml"), adaptiveXml);
|
|
540
|
+
fs4.writeFileSync(path4.join(anyDpi, "ic_launcher_round.xml"), adaptiveXml);
|
|
518
541
|
const legacySrc = iconPaths.source ?? fgAd;
|
|
519
|
-
const legacyExt =
|
|
542
|
+
const legacyExt = path4.extname(legacySrc) || ".png";
|
|
520
543
|
const mipmapDensities = ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"];
|
|
521
544
|
for (const d of mipmapDensities) {
|
|
522
|
-
const dir =
|
|
523
|
-
|
|
524
|
-
|
|
545
|
+
const dir = path4.join(resDir, `mipmap-${d}`);
|
|
546
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
547
|
+
fs4.copyFileSync(legacySrc, path4.join(dir, `ic_launcher${legacyExt}`));
|
|
525
548
|
}
|
|
526
549
|
return true;
|
|
527
550
|
}
|
|
528
551
|
if (iconPaths.android) {
|
|
529
552
|
const src = iconPaths.android;
|
|
530
|
-
const entries =
|
|
553
|
+
const entries = fs4.readdirSync(src, { withFileTypes: true });
|
|
531
554
|
for (const e of entries) {
|
|
532
|
-
const dest =
|
|
555
|
+
const dest = path4.join(resDir, e.name);
|
|
533
556
|
if (e.isDirectory()) {
|
|
534
|
-
|
|
557
|
+
fs4.cpSync(path4.join(src, e.name), dest, { recursive: true });
|
|
535
558
|
} else {
|
|
536
|
-
|
|
559
|
+
fs4.copyFileSync(path4.join(src, e.name), dest);
|
|
537
560
|
}
|
|
538
561
|
}
|
|
539
562
|
return true;
|
|
@@ -541,9 +564,9 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
|
|
|
541
564
|
if (iconPaths.source) {
|
|
542
565
|
const mipmapDensities = ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"];
|
|
543
566
|
for (const d of mipmapDensities) {
|
|
544
|
-
const dir =
|
|
545
|
-
|
|
546
|
-
|
|
567
|
+
const dir = path4.join(resDir, `mipmap-${d}`);
|
|
568
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
569
|
+
fs4.copyFileSync(iconPaths.source, path4.join(dir, "ic_launcher.png"));
|
|
547
570
|
}
|
|
548
571
|
return true;
|
|
549
572
|
}
|
|
@@ -551,25 +574,25 @@ function applyAndroidLauncherIcons(resDir, iconPaths) {
|
|
|
551
574
|
}
|
|
552
575
|
function applyIosAppIconAssets(appIconDir, iconPaths) {
|
|
553
576
|
if (!iconPaths) return false;
|
|
554
|
-
|
|
577
|
+
fs4.mkdirSync(appIconDir, { recursive: true });
|
|
555
578
|
if (iconPaths.ios) {
|
|
556
|
-
const entries =
|
|
579
|
+
const entries = fs4.readdirSync(iconPaths.ios, { withFileTypes: true });
|
|
557
580
|
for (const e of entries) {
|
|
558
|
-
const dest =
|
|
581
|
+
const dest = path4.join(appIconDir, e.name);
|
|
559
582
|
if (e.isDirectory()) {
|
|
560
|
-
|
|
583
|
+
fs4.cpSync(path4.join(iconPaths.ios, e.name), dest, { recursive: true });
|
|
561
584
|
} else {
|
|
562
|
-
|
|
585
|
+
fs4.copyFileSync(path4.join(iconPaths.ios, e.name), dest);
|
|
563
586
|
}
|
|
564
587
|
}
|
|
565
588
|
return true;
|
|
566
589
|
}
|
|
567
590
|
if (iconPaths.source) {
|
|
568
|
-
const ext =
|
|
591
|
+
const ext = path4.extname(iconPaths.source) || ".png";
|
|
569
592
|
const icon1024 = `Icon-1024${ext}`;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
593
|
+
fs4.copyFileSync(iconPaths.source, path4.join(appIconDir, icon1024));
|
|
594
|
+
fs4.writeFileSync(
|
|
595
|
+
path4.join(appIconDir, "Contents.json"),
|
|
573
596
|
JSON.stringify(
|
|
574
597
|
{
|
|
575
598
|
images: [{ filename: icon1024, idiom: "universal", platform: "ios", size: "1024x1024" }],
|
|
@@ -1121,7 +1144,7 @@ object DevServerPrefs {
|
|
|
1121
1144
|
|
|
1122
1145
|
// src/android/create.ts
|
|
1123
1146
|
function readAndSubstituteTemplate(templatePath, vars) {
|
|
1124
|
-
const raw =
|
|
1147
|
+
const raw = fs5.readFileSync(templatePath, "utf-8");
|
|
1125
1148
|
return Object.entries(vars).reduce(
|
|
1126
1149
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
1127
1150
|
raw
|
|
@@ -1132,7 +1155,7 @@ var create = async (opts = {}) => {
|
|
|
1132
1155
|
const origCwd = process.cwd();
|
|
1133
1156
|
if (target === "dev-app") {
|
|
1134
1157
|
const devAppDir = findDevAppPackage(origCwd) ?? findDevAppPackage(findRepoRoot(origCwd));
|
|
1135
|
-
if (!devAppDir || !
|
|
1158
|
+
if (!devAppDir || !fs5.existsSync(path5.join(devAppDir, "tamer.config.json"))) {
|
|
1136
1159
|
console.error("\u274C tamer-dev-app not found. Add @tamer4lynx/tamer-dev-app to dependencies.");
|
|
1137
1160
|
process.exit(1);
|
|
1138
1161
|
}
|
|
@@ -1162,30 +1185,30 @@ var create = async (opts = {}) => {
|
|
|
1162
1185
|
const packagePath = packageName.replace(/\./g, "/");
|
|
1163
1186
|
const gradleVersion = "8.14.2";
|
|
1164
1187
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
1165
|
-
const rootDir =
|
|
1166
|
-
const appDir =
|
|
1167
|
-
const mainDir =
|
|
1168
|
-
const javaDir =
|
|
1169
|
-
const kotlinDir =
|
|
1170
|
-
const kotlinGeneratedDir =
|
|
1171
|
-
const assetsDir =
|
|
1172
|
-
const themesDir =
|
|
1173
|
-
const gradleDir =
|
|
1188
|
+
const rootDir = path5.join(process.cwd(), androidDir);
|
|
1189
|
+
const appDir = path5.join(rootDir, "app");
|
|
1190
|
+
const mainDir = path5.join(appDir, "src", "main");
|
|
1191
|
+
const javaDir = path5.join(mainDir, "java", packagePath);
|
|
1192
|
+
const kotlinDir = path5.join(mainDir, "kotlin", packagePath);
|
|
1193
|
+
const kotlinGeneratedDir = path5.join(kotlinDir, "generated");
|
|
1194
|
+
const assetsDir = path5.join(mainDir, "assets");
|
|
1195
|
+
const themesDir = path5.join(mainDir, "res", "values");
|
|
1196
|
+
const gradleDir = path5.join(rootDir, "gradle");
|
|
1174
1197
|
function writeFile2(filePath, content, options) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1198
|
+
fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
1199
|
+
fs5.writeFileSync(
|
|
1177
1200
|
filePath,
|
|
1178
1201
|
content.trimStart(),
|
|
1179
1202
|
options?.encoding ?? "utf8"
|
|
1180
1203
|
);
|
|
1181
1204
|
}
|
|
1182
|
-
if (
|
|
1205
|
+
if (fs5.existsSync(rootDir)) {
|
|
1183
1206
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
1184
|
-
|
|
1207
|
+
fs5.rmSync(rootDir, { recursive: true, force: true });
|
|
1185
1208
|
}
|
|
1186
1209
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
1187
1210
|
writeFile2(
|
|
1188
|
-
|
|
1211
|
+
path5.join(gradleDir, "libs.versions.toml"),
|
|
1189
1212
|
`
|
|
1190
1213
|
[versions]
|
|
1191
1214
|
agp = "8.9.1"
|
|
@@ -1246,7 +1269,7 @@ kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
|
|
1246
1269
|
`
|
|
1247
1270
|
);
|
|
1248
1271
|
writeFile2(
|
|
1249
|
-
|
|
1272
|
+
path5.join(rootDir, "settings.gradle.kts"),
|
|
1250
1273
|
`
|
|
1251
1274
|
pluginManagement {
|
|
1252
1275
|
repositories {
|
|
@@ -1280,7 +1303,7 @@ println("If you have native modules please run tamer android link")
|
|
|
1280
1303
|
`
|
|
1281
1304
|
);
|
|
1282
1305
|
writeFile2(
|
|
1283
|
-
|
|
1306
|
+
path5.join(rootDir, "build.gradle.kts"),
|
|
1284
1307
|
`
|
|
1285
1308
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
1286
1309
|
plugins {
|
|
@@ -1292,7 +1315,7 @@ plugins {
|
|
|
1292
1315
|
`
|
|
1293
1316
|
);
|
|
1294
1317
|
writeFile2(
|
|
1295
|
-
|
|
1318
|
+
path5.join(rootDir, "gradle.properties"),
|
|
1296
1319
|
`
|
|
1297
1320
|
org.gradle.jvmargs=-Xmx2048m
|
|
1298
1321
|
android.useAndroidX=true
|
|
@@ -1301,7 +1324,7 @@ android.enableJetifier=true
|
|
|
1301
1324
|
`
|
|
1302
1325
|
);
|
|
1303
1326
|
writeFile2(
|
|
1304
|
-
|
|
1327
|
+
path5.join(appDir, "build.gradle.kts"),
|
|
1305
1328
|
`
|
|
1306
1329
|
plugins {
|
|
1307
1330
|
alias(libs.plugins.android.application)
|
|
@@ -1393,7 +1416,7 @@ dependencies {
|
|
|
1393
1416
|
`
|
|
1394
1417
|
);
|
|
1395
1418
|
writeFile2(
|
|
1396
|
-
|
|
1419
|
+
path5.join(themesDir, "themes.xml"),
|
|
1397
1420
|
`
|
|
1398
1421
|
<resources>
|
|
1399
1422
|
<style name="Theme.MyApp" parent="Theme.AppCompat.Light.NoActionBar">
|
|
@@ -1427,7 +1450,7 @@ dependencies {
|
|
|
1427
1450
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
1428
1451
|
const manifestIconAttrs = iconPaths ? ' android:icon="@mipmap/ic_launcher"\n android:roundIcon="@mipmap/ic_launcher"\n' : "";
|
|
1429
1452
|
writeFile2(
|
|
1430
|
-
|
|
1453
|
+
path5.join(mainDir, "AndroidManifest.xml"),
|
|
1431
1454
|
`
|
|
1432
1455
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
1433
1456
|
${manifestPermissions}
|
|
@@ -1441,7 +1464,7 @@ ${manifestIconAttrs} android:usesCleartextTraffic="true"
|
|
|
1441
1464
|
`
|
|
1442
1465
|
);
|
|
1443
1466
|
writeFile2(
|
|
1444
|
-
|
|
1467
|
+
path5.join(kotlinGeneratedDir, "GeneratedLynxExtensions.kt"),
|
|
1445
1468
|
`
|
|
1446
1469
|
package ${packageName}.generated
|
|
1447
1470
|
|
|
@@ -1469,14 +1492,14 @@ object GeneratedLynxExtensions {
|
|
|
1469
1492
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
1470
1493
|
const devClientPkg = findDevClientPackage(process.cwd());
|
|
1471
1494
|
if (!hasDevLauncher && hostPkg) {
|
|
1472
|
-
const templateDir =
|
|
1495
|
+
const templateDir = path5.join(hostPkg, "android", "templates");
|
|
1473
1496
|
for (const [src, dst] of [
|
|
1474
|
-
["App.java",
|
|
1475
|
-
["TemplateProvider.java",
|
|
1476
|
-
["MainActivity.kt",
|
|
1497
|
+
["App.java", path5.join(javaDir, "App.java")],
|
|
1498
|
+
["TemplateProvider.java", path5.join(javaDir, "TemplateProvider.java")],
|
|
1499
|
+
["MainActivity.kt", path5.join(kotlinDir, "MainActivity.kt")]
|
|
1477
1500
|
]) {
|
|
1478
|
-
const srcPath =
|
|
1479
|
-
if (
|
|
1501
|
+
const srcPath = path5.join(templateDir, src);
|
|
1502
|
+
if (fs5.existsSync(srcPath)) {
|
|
1480
1503
|
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1481
1504
|
}
|
|
1482
1505
|
}
|
|
@@ -1485,34 +1508,34 @@ object GeneratedLynxExtensions {
|
|
|
1485
1508
|
fetchAndPatchApplication(vars),
|
|
1486
1509
|
fetchAndPatchTemplateProvider(vars)
|
|
1487
1510
|
]);
|
|
1488
|
-
writeFile2(
|
|
1489
|
-
writeFile2(
|
|
1490
|
-
writeFile2(
|
|
1511
|
+
writeFile2(path5.join(javaDir, "App.java"), applicationSource);
|
|
1512
|
+
writeFile2(path5.join(javaDir, "TemplateProvider.java"), templateProviderSource);
|
|
1513
|
+
writeFile2(path5.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
|
|
1491
1514
|
if (hasDevLauncher) {
|
|
1492
1515
|
if (devClientPkg) {
|
|
1493
|
-
const templateDir =
|
|
1516
|
+
const templateDir = path5.join(devClientPkg, "android", "templates");
|
|
1494
1517
|
for (const [src, dst] of [
|
|
1495
|
-
["ProjectActivity.kt",
|
|
1496
|
-
["DevClientManager.kt",
|
|
1497
|
-
["DevServerPrefs.kt",
|
|
1518
|
+
["ProjectActivity.kt", path5.join(kotlinDir, "ProjectActivity.kt")],
|
|
1519
|
+
["DevClientManager.kt", path5.join(kotlinDir, "DevClientManager.kt")],
|
|
1520
|
+
["DevServerPrefs.kt", path5.join(kotlinDir, "DevServerPrefs.kt")]
|
|
1498
1521
|
]) {
|
|
1499
|
-
const srcPath =
|
|
1500
|
-
if (
|
|
1522
|
+
const srcPath = path5.join(templateDir, src);
|
|
1523
|
+
if (fs5.existsSync(srcPath)) {
|
|
1501
1524
|
writeFile2(dst, readAndSubstituteTemplate(srcPath, templateVars));
|
|
1502
1525
|
}
|
|
1503
1526
|
}
|
|
1504
1527
|
} else {
|
|
1505
|
-
writeFile2(
|
|
1528
|
+
writeFile2(path5.join(kotlinDir, "ProjectActivity.kt"), getProjectActivity(vars));
|
|
1506
1529
|
const devClientManagerSource = getDevClientManager(vars);
|
|
1507
1530
|
if (devClientManagerSource) {
|
|
1508
|
-
writeFile2(
|
|
1509
|
-
writeFile2(
|
|
1531
|
+
writeFile2(path5.join(kotlinDir, "DevClientManager.kt"), devClientManagerSource);
|
|
1532
|
+
writeFile2(path5.join(kotlinDir, "DevServerPrefs.kt"), getDevServerPrefs(vars));
|
|
1510
1533
|
}
|
|
1511
1534
|
}
|
|
1512
1535
|
}
|
|
1513
1536
|
}
|
|
1514
1537
|
if (iconPaths) {
|
|
1515
|
-
const resDir =
|
|
1538
|
+
const resDir = path5.join(mainDir, "res");
|
|
1516
1539
|
if (applyAndroidLauncherIcons(resDir, iconPaths)) {
|
|
1517
1540
|
if (iconPaths.androidAdaptiveForeground && (iconPaths.androidAdaptiveBackground || iconPaths.androidAdaptiveBackgroundColor)) {
|
|
1518
1541
|
console.log("\u2705 Android adaptive launcher from tamer.config.json icon.androidAdaptive");
|
|
@@ -1523,23 +1546,23 @@ object GeneratedLynxExtensions {
|
|
|
1523
1546
|
}
|
|
1524
1547
|
}
|
|
1525
1548
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1549
|
+
fs5.mkdirSync(assetsDir, { recursive: true });
|
|
1550
|
+
fs5.writeFileSync(path5.join(assetsDir, ".gitkeep"), "");
|
|
1528
1551
|
console.log(`\u2705 Android Kotlin project created at ${rootDir}`);
|
|
1529
1552
|
async function finalizeProjectSetup() {
|
|
1530
1553
|
if (androidSdk) {
|
|
1531
1554
|
try {
|
|
1532
1555
|
const sdkDirContent = `sdk.dir=${androidSdk.replace(/\\/g, "/")}`;
|
|
1533
|
-
writeFile2(
|
|
1556
|
+
writeFile2(path5.join(rootDir, "local.properties"), sdkDirContent);
|
|
1534
1557
|
console.log("\u{1F4E6} Created local.properties from tamer.config.json.");
|
|
1535
1558
|
} catch (err) {
|
|
1536
1559
|
console.error(`\u274C Failed to create local.properties: ${err.message}`);
|
|
1537
1560
|
}
|
|
1538
1561
|
} else {
|
|
1539
|
-
const localPropsPath =
|
|
1540
|
-
if (
|
|
1562
|
+
const localPropsPath = path5.join(process.cwd(), "local.properties");
|
|
1563
|
+
if (fs5.existsSync(localPropsPath)) {
|
|
1541
1564
|
try {
|
|
1542
|
-
|
|
1565
|
+
fs5.copyFileSync(localPropsPath, path5.join(rootDir, "local.properties"));
|
|
1543
1566
|
console.log("\u{1F4E6} Copied existing local.properties to the android project.");
|
|
1544
1567
|
} catch (err) {
|
|
1545
1568
|
console.error("\u274C Failed to copy local.properties:", err);
|
|
@@ -1558,33 +1581,33 @@ object GeneratedLynxExtensions {
|
|
|
1558
1581
|
var create_default = create;
|
|
1559
1582
|
|
|
1560
1583
|
// src/android/autolink.ts
|
|
1561
|
-
import
|
|
1562
|
-
import
|
|
1584
|
+
import fs9 from "fs";
|
|
1585
|
+
import path9 from "path";
|
|
1563
1586
|
import { execSync as execSync2 } from "child_process";
|
|
1564
1587
|
|
|
1565
1588
|
// src/common/discoverModules.ts
|
|
1566
|
-
import
|
|
1567
|
-
import
|
|
1589
|
+
import fs7 from "fs";
|
|
1590
|
+
import path7 from "path";
|
|
1568
1591
|
|
|
1569
1592
|
// src/common/config.ts
|
|
1570
|
-
import
|
|
1571
|
-
import
|
|
1593
|
+
import fs6 from "fs";
|
|
1594
|
+
import path6 from "path";
|
|
1572
1595
|
var LYNX_EXT_JSON = "lynx.ext.json";
|
|
1573
1596
|
var TAMER_JSON = "tamer.json";
|
|
1574
1597
|
function loadLynxExtJson(packagePath) {
|
|
1575
|
-
const p =
|
|
1576
|
-
if (!
|
|
1598
|
+
const p = path6.join(packagePath, LYNX_EXT_JSON);
|
|
1599
|
+
if (!fs6.existsSync(p)) return null;
|
|
1577
1600
|
try {
|
|
1578
|
-
return JSON.parse(
|
|
1601
|
+
return JSON.parse(fs6.readFileSync(p, "utf8"));
|
|
1579
1602
|
} catch {
|
|
1580
1603
|
return null;
|
|
1581
1604
|
}
|
|
1582
1605
|
}
|
|
1583
1606
|
function loadTamerJson(packagePath) {
|
|
1584
|
-
const p =
|
|
1585
|
-
if (!
|
|
1607
|
+
const p = path6.join(packagePath, TAMER_JSON);
|
|
1608
|
+
if (!fs6.existsSync(p)) return null;
|
|
1586
1609
|
try {
|
|
1587
|
-
return JSON.parse(
|
|
1610
|
+
return JSON.parse(fs6.readFileSync(p, "utf8"));
|
|
1588
1611
|
} catch {
|
|
1589
1612
|
return null;
|
|
1590
1613
|
}
|
|
@@ -1637,7 +1660,7 @@ function loadExtensionConfig(packagePath) {
|
|
|
1637
1660
|
return normalized;
|
|
1638
1661
|
}
|
|
1639
1662
|
function hasExtensionConfig(packagePath) {
|
|
1640
|
-
return
|
|
1663
|
+
return fs6.existsSync(path6.join(packagePath, LYNX_EXT_JSON)) || fs6.existsSync(path6.join(packagePath, TAMER_JSON));
|
|
1641
1664
|
}
|
|
1642
1665
|
function getAndroidModuleClassNames(config) {
|
|
1643
1666
|
if (!config) return [];
|
|
@@ -1655,15 +1678,15 @@ function getIosElements(config) {
|
|
|
1655
1678
|
return config?.elements ?? {};
|
|
1656
1679
|
}
|
|
1657
1680
|
function getNodeModulesPath(projectRoot) {
|
|
1658
|
-
let nodeModulesPath =
|
|
1659
|
-
const workspaceRoot =
|
|
1660
|
-
const rootNodeModules =
|
|
1661
|
-
if (
|
|
1681
|
+
let nodeModulesPath = path6.join(projectRoot, "node_modules");
|
|
1682
|
+
const workspaceRoot = path6.join(projectRoot, "..", "..");
|
|
1683
|
+
const rootNodeModules = path6.join(workspaceRoot, "node_modules");
|
|
1684
|
+
if (fs6.existsSync(path6.join(workspaceRoot, "package.json")) && fs6.existsSync(rootNodeModules) && path6.basename(path6.dirname(projectRoot)) === "packages") {
|
|
1662
1685
|
nodeModulesPath = rootNodeModules;
|
|
1663
|
-
} else if (!
|
|
1664
|
-
const altRoot =
|
|
1665
|
-
const altNodeModules =
|
|
1666
|
-
if (
|
|
1686
|
+
} else if (!fs6.existsSync(nodeModulesPath)) {
|
|
1687
|
+
const altRoot = path6.join(projectRoot, "..", "..");
|
|
1688
|
+
const altNodeModules = path6.join(altRoot, "node_modules");
|
|
1689
|
+
if (fs6.existsSync(path6.join(altRoot, "package.json")) && fs6.existsSync(altNodeModules)) {
|
|
1667
1690
|
nodeModulesPath = altNodeModules;
|
|
1668
1691
|
}
|
|
1669
1692
|
}
|
|
@@ -1672,8 +1695,8 @@ function getNodeModulesPath(projectRoot) {
|
|
|
1672
1695
|
function discoverNativeExtensions(projectRoot) {
|
|
1673
1696
|
const nodeModulesPath = getNodeModulesPath(projectRoot);
|
|
1674
1697
|
const result = [];
|
|
1675
|
-
if (!
|
|
1676
|
-
const packageDirs =
|
|
1698
|
+
if (!fs6.existsSync(nodeModulesPath)) return result;
|
|
1699
|
+
const packageDirs = fs6.readdirSync(nodeModulesPath);
|
|
1677
1700
|
const check = (name, packagePath) => {
|
|
1678
1701
|
if (!hasExtensionConfig(packagePath)) return;
|
|
1679
1702
|
const config = loadExtensionConfig(packagePath);
|
|
@@ -1683,11 +1706,11 @@ function discoverNativeExtensions(projectRoot) {
|
|
|
1683
1706
|
}
|
|
1684
1707
|
};
|
|
1685
1708
|
for (const dirName of packageDirs) {
|
|
1686
|
-
const fullPath =
|
|
1709
|
+
const fullPath = path6.join(nodeModulesPath, dirName);
|
|
1687
1710
|
if (dirName.startsWith("@")) {
|
|
1688
1711
|
try {
|
|
1689
|
-
for (const scopedDirName of
|
|
1690
|
-
check(`${dirName}/${scopedDirName}`,
|
|
1712
|
+
for (const scopedDirName of fs6.readdirSync(fullPath)) {
|
|
1713
|
+
check(`${dirName}/${scopedDirName}`, path6.join(fullPath, scopedDirName));
|
|
1691
1714
|
}
|
|
1692
1715
|
} catch {
|
|
1693
1716
|
}
|
|
@@ -1700,29 +1723,80 @@ function discoverNativeExtensions(projectRoot) {
|
|
|
1700
1723
|
|
|
1701
1724
|
// src/common/discoverModules.ts
|
|
1702
1725
|
function resolveNodeModulesPath(projectRoot) {
|
|
1703
|
-
let nodeModulesPath =
|
|
1704
|
-
const workspaceRoot =
|
|
1705
|
-
const rootNodeModules =
|
|
1706
|
-
if (
|
|
1726
|
+
let nodeModulesPath = path7.join(projectRoot, "node_modules");
|
|
1727
|
+
const workspaceRoot = path7.join(projectRoot, "..", "..");
|
|
1728
|
+
const rootNodeModules = path7.join(workspaceRoot, "node_modules");
|
|
1729
|
+
if (fs7.existsSync(path7.join(workspaceRoot, "package.json")) && fs7.existsSync(rootNodeModules) && path7.basename(path7.dirname(projectRoot)) === "packages") {
|
|
1707
1730
|
nodeModulesPath = rootNodeModules;
|
|
1708
|
-
} else if (!
|
|
1709
|
-
const altRoot =
|
|
1710
|
-
const altNodeModules =
|
|
1711
|
-
if (
|
|
1731
|
+
} else if (!fs7.existsSync(nodeModulesPath)) {
|
|
1732
|
+
const altRoot = path7.join(projectRoot, "..", "..");
|
|
1733
|
+
const altNodeModules = path7.join(altRoot, "node_modules");
|
|
1734
|
+
if (fs7.existsSync(path7.join(altRoot, "package.json")) && fs7.existsSync(altNodeModules)) {
|
|
1712
1735
|
nodeModulesPath = altNodeModules;
|
|
1713
1736
|
}
|
|
1714
1737
|
}
|
|
1715
1738
|
return nodeModulesPath;
|
|
1716
1739
|
}
|
|
1740
|
+
function resolvePackageRoot(projectRoot, packageName) {
|
|
1741
|
+
const nodeModulesPath = resolveNodeModulesPath(projectRoot);
|
|
1742
|
+
const hasPkg = (dir) => fs7.existsSync(path7.join(dir, "package.json"));
|
|
1743
|
+
if (packageName.startsWith("@")) {
|
|
1744
|
+
const parts = packageName.split("/");
|
|
1745
|
+
if (parts.length !== 2 || !parts[0].startsWith("@")) return null;
|
|
1746
|
+
const direct2 = path7.join(nodeModulesPath, parts[0], parts[1]);
|
|
1747
|
+
if (hasPkg(direct2)) return direct2;
|
|
1748
|
+
const underDevClient = path7.join(
|
|
1749
|
+
nodeModulesPath,
|
|
1750
|
+
"@tamer4lynx",
|
|
1751
|
+
"tamer-dev-client",
|
|
1752
|
+
"node_modules",
|
|
1753
|
+
parts[0],
|
|
1754
|
+
parts[1]
|
|
1755
|
+
);
|
|
1756
|
+
if (hasPkg(underDevClient)) return underDevClient;
|
|
1757
|
+
return null;
|
|
1758
|
+
}
|
|
1759
|
+
const direct = path7.join(nodeModulesPath, packageName);
|
|
1760
|
+
return hasPkg(direct) ? direct : null;
|
|
1761
|
+
}
|
|
1762
|
+
function collectTransitiveTamer4LynxPackageNames(projectRoot) {
|
|
1763
|
+
const hostPjPath = path7.join(projectRoot, "package.json");
|
|
1764
|
+
if (!fs7.existsSync(hostPjPath)) return null;
|
|
1765
|
+
const hostPj = JSON.parse(fs7.readFileSync(hostPjPath, "utf8"));
|
|
1766
|
+
if (hostPj.name !== "@tamer4lynx/tamer-dev-app") return null;
|
|
1767
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1768
|
+
const queue = [];
|
|
1769
|
+
const enqueue = (deps) => {
|
|
1770
|
+
if (!deps) return;
|
|
1771
|
+
for (const name of Object.keys(deps)) {
|
|
1772
|
+
if (name.startsWith("@tamer4lynx/")) queue.push(name);
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
enqueue(hostPj.dependencies);
|
|
1776
|
+
enqueue(hostPj.optionalDependencies);
|
|
1777
|
+
while (queue.length) {
|
|
1778
|
+
const name = queue.shift();
|
|
1779
|
+
if (visited.has(name)) continue;
|
|
1780
|
+
visited.add(name);
|
|
1781
|
+
const pkgRoot = resolvePackageRoot(projectRoot, name);
|
|
1782
|
+
if (!pkgRoot || !fs7.existsSync(path7.join(pkgRoot, "package.json"))) continue;
|
|
1783
|
+
const sub = JSON.parse(fs7.readFileSync(path7.join(pkgRoot, "package.json"), "utf8"));
|
|
1784
|
+
const merged = { ...sub.dependencies, ...sub.optionalDependencies };
|
|
1785
|
+
for (const dep of Object.keys(merged)) {
|
|
1786
|
+
if (dep.startsWith("@tamer4lynx/") && !visited.has(dep)) queue.push(dep);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return visited;
|
|
1790
|
+
}
|
|
1717
1791
|
function discoverModules(projectRoot) {
|
|
1718
1792
|
const nodeModulesPath = resolveNodeModulesPath(projectRoot);
|
|
1719
1793
|
const packages = [];
|
|
1720
|
-
if (!
|
|
1794
|
+
if (!fs7.existsSync(nodeModulesPath)) {
|
|
1721
1795
|
return [];
|
|
1722
1796
|
}
|
|
1723
|
-
const packageDirs =
|
|
1797
|
+
const packageDirs = fs7.readdirSync(nodeModulesPath);
|
|
1724
1798
|
for (const dirName of packageDirs) {
|
|
1725
|
-
const fullPath =
|
|
1799
|
+
const fullPath = path7.join(nodeModulesPath, dirName);
|
|
1726
1800
|
const checkPackage = (name, packagePath) => {
|
|
1727
1801
|
if (!hasExtensionConfig(packagePath)) return;
|
|
1728
1802
|
const config = loadExtensionConfig(packagePath);
|
|
@@ -1731,9 +1805,9 @@ function discoverModules(projectRoot) {
|
|
|
1731
1805
|
};
|
|
1732
1806
|
if (dirName.startsWith("@")) {
|
|
1733
1807
|
try {
|
|
1734
|
-
const scopedDirs =
|
|
1808
|
+
const scopedDirs = fs7.readdirSync(fullPath);
|
|
1735
1809
|
for (const scopedDirName of scopedDirs) {
|
|
1736
|
-
const scopedPackagePath =
|
|
1810
|
+
const scopedPackagePath = path7.join(fullPath, scopedDirName);
|
|
1737
1811
|
checkPackage(`${dirName}/${scopedDirName}`, scopedPackagePath);
|
|
1738
1812
|
}
|
|
1739
1813
|
} catch (e) {
|
|
@@ -1744,6 +1818,10 @@ function discoverModules(projectRoot) {
|
|
|
1744
1818
|
checkPackage(dirName, fullPath);
|
|
1745
1819
|
}
|
|
1746
1820
|
}
|
|
1821
|
+
const allowed = collectTransitiveTamer4LynxPackageNames(projectRoot);
|
|
1822
|
+
if (allowed) {
|
|
1823
|
+
return packages.filter((p) => allowed.has(p.name));
|
|
1824
|
+
}
|
|
1747
1825
|
return packages;
|
|
1748
1826
|
}
|
|
1749
1827
|
|
|
@@ -1795,6 +1873,8 @@ ${allModuleClasses.map((c) => ` "${c.replace(/\\/g, "\\\\").replace(/
|
|
|
1795
1873
|
fun onHostViewChanged(view: android.view.View?) {
|
|
1796
1874
|
${hostViewLines}
|
|
1797
1875
|
}` : "\n fun onHostViewChanged(view: android.view.View?) {}\n";
|
|
1876
|
+
const devToolBootstrapPrefix = hasDevClient ? " com.nanofuxion.tamerdevclient.LynxDevToolBootstrap.configure(context)\n" : "";
|
|
1877
|
+
const devToolBootstrapSuffix = hasDevClient ? " com.nanofuxion.tamerdevclient.LynxDevToolBootstrap.enableLynxDebugFlags()\n" : "";
|
|
1798
1878
|
return `package ${projectPackage}.generated
|
|
1799
1879
|
|
|
1800
1880
|
import android.content.Context
|
|
@@ -1806,7 +1886,7 @@ ${elementImports}
|
|
|
1806
1886
|
|
|
1807
1887
|
object GeneratedLynxExtensions {
|
|
1808
1888
|
fun register(context: Context) {
|
|
1809
|
-
${allRegistrations}${devClientSupportedBlock}
|
|
1889
|
+
${devToolBootstrapPrefix}${allRegistrations}${devClientSupportedBlock}${devToolBootstrapSuffix}
|
|
1810
1890
|
}
|
|
1811
1891
|
|
|
1812
1892
|
fun configureViewBuilder(viewBuilder: LynxViewBuilder) {
|
|
@@ -1894,6 +1974,69 @@ ${createDelayedBody}
|
|
|
1894
1974
|
`;
|
|
1895
1975
|
}
|
|
1896
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
|
+
|
|
1897
2040
|
// src/android/autolink.ts
|
|
1898
2041
|
var TAMER_DEV_CLIENT_FALLBACK = {
|
|
1899
2042
|
name: "@tamer4lynx/tamer-dev-client",
|
|
@@ -1939,11 +2082,11 @@ var autolink = (opts) => {
|
|
|
1939
2082
|
const packageName = config.android.packageName;
|
|
1940
2083
|
const projectRoot = resolved.projectRoot;
|
|
1941
2084
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
1942
|
-
if (!
|
|
2085
|
+
if (!fs9.existsSync(filePath)) {
|
|
1943
2086
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
1944
2087
|
return;
|
|
1945
2088
|
}
|
|
1946
|
-
let fileContent =
|
|
2089
|
+
let fileContent = fs9.readFileSync(filePath, "utf8");
|
|
1947
2090
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1948
2091
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1949
2092
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -1953,16 +2096,16 @@ ${endMarker}`;
|
|
|
1953
2096
|
if (regex.test(fileContent)) {
|
|
1954
2097
|
fileContent = fileContent.replace(regex, replacementBlock);
|
|
1955
2098
|
} else {
|
|
1956
|
-
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.`);
|
|
1957
2100
|
fileContent += `
|
|
1958
2101
|
${replacementBlock}
|
|
1959
2102
|
`;
|
|
1960
2103
|
}
|
|
1961
|
-
|
|
1962
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
2104
|
+
fs9.writeFileSync(filePath, fileContent);
|
|
2105
|
+
console.log(`\u2705 Updated autolinked section in ${path9.basename(filePath)}`);
|
|
1963
2106
|
}
|
|
1964
2107
|
function updateSettingsGradle(packages) {
|
|
1965
|
-
const settingsFilePath =
|
|
2108
|
+
const settingsFilePath = path9.join(appAndroidPath, "settings.gradle.kts");
|
|
1966
2109
|
let scriptContent = `// This section is automatically generated by Tamer4Lynx.
|
|
1967
2110
|
// Manual edits will be overwritten.`;
|
|
1968
2111
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
@@ -1970,7 +2113,7 @@ ${replacementBlock}
|
|
|
1970
2113
|
androidPackages.forEach((pkg) => {
|
|
1971
2114
|
const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
|
|
1972
2115
|
const sourceDir = pkg.config.android?.sourceDir || "android";
|
|
1973
|
-
const projectPath =
|
|
2116
|
+
const projectPath = path9.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
|
|
1974
2117
|
scriptContent += `
|
|
1975
2118
|
include(":${gradleProjectName}")`;
|
|
1976
2119
|
scriptContent += `
|
|
@@ -1983,7 +2126,7 @@ println("No native modules found by Tamer4Lynx autolinker.")`;
|
|
|
1983
2126
|
updateGeneratedSection(settingsFilePath, scriptContent.trim(), "// GENERATED AUTOLINK START", "// GENERATED AUTOLINK END");
|
|
1984
2127
|
}
|
|
1985
2128
|
function updateAppBuildGradle(packages) {
|
|
1986
|
-
const appBuildGradlePath =
|
|
2129
|
+
const appBuildGradlePath = path9.join(appAndroidPath, "app", "build.gradle.kts");
|
|
1987
2130
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
1988
2131
|
const implementationLines = androidPackages.map((p) => {
|
|
1989
2132
|
const gradleProjectName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
@@ -2001,35 +2144,35 @@ ${implementationLines || " // No native dependencies found to link."}`;
|
|
|
2001
2144
|
}
|
|
2002
2145
|
function generateKotlinExtensionsFile(packages, projectPackage) {
|
|
2003
2146
|
const packagePath = projectPackage.replace(/\./g, "/");
|
|
2004
|
-
const generatedDir =
|
|
2005
|
-
const kotlinExtensionsPath =
|
|
2147
|
+
const generatedDir = path9.join(appAndroidPath, "app", "src", "main", "kotlin", packagePath, "generated");
|
|
2148
|
+
const kotlinExtensionsPath = path9.join(generatedDir, "GeneratedLynxExtensions.kt");
|
|
2006
2149
|
const content = `/**
|
|
2007
2150
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2008
2151
|
* Do not edit this file manually.
|
|
2009
2152
|
*/
|
|
2010
2153
|
${generateLynxExtensionsKotlin(packages, projectPackage)}`;
|
|
2011
|
-
|
|
2012
|
-
|
|
2154
|
+
fs9.mkdirSync(generatedDir, { recursive: true });
|
|
2155
|
+
fs9.writeFileSync(kotlinExtensionsPath, content.trimStart());
|
|
2013
2156
|
console.log(`\u2705 Generated Kotlin extensions at ${kotlinExtensionsPath}`);
|
|
2014
2157
|
}
|
|
2015
2158
|
function generateActivityLifecycleFile(packages, projectPackage) {
|
|
2016
2159
|
const packageKotlinPath = projectPackage.replace(/\./g, "/");
|
|
2017
|
-
const generatedDir =
|
|
2018
|
-
const outputPath =
|
|
2160
|
+
const generatedDir = path9.join(appAndroidPath, "app", "src", "main", "kotlin", packageKotlinPath, "generated");
|
|
2161
|
+
const outputPath = path9.join(generatedDir, "GeneratedActivityLifecycle.kt");
|
|
2019
2162
|
const content = `/**
|
|
2020
2163
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2021
2164
|
* Do not edit this file manually.
|
|
2022
2165
|
*/
|
|
2023
2166
|
${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
2024
|
-
|
|
2025
|
-
|
|
2167
|
+
fs9.mkdirSync(generatedDir, { recursive: true });
|
|
2168
|
+
fs9.writeFileSync(outputPath, content);
|
|
2026
2169
|
console.log(`\u2705 Generated activity lifecycle patches at ${outputPath}`);
|
|
2027
2170
|
}
|
|
2028
2171
|
function syncDeepLinkIntentFilters() {
|
|
2029
2172
|
const deepLinks = config.android?.deepLinks;
|
|
2030
2173
|
if (!deepLinks || deepLinks.length === 0) return;
|
|
2031
|
-
const manifestPath =
|
|
2032
|
-
if (!
|
|
2174
|
+
const manifestPath = path9.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2175
|
+
if (!fs9.existsSync(manifestPath)) return;
|
|
2033
2176
|
const intentFilters = deepLinks.map((link) => {
|
|
2034
2177
|
const dataAttrs = [
|
|
2035
2178
|
`android:scheme="${link.scheme}"`,
|
|
@@ -2054,9 +2197,9 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2054
2197
|
console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
|
|
2055
2198
|
let packages = discoverModules(projectRoot).filter((p) => p.config.android);
|
|
2056
2199
|
const includeDevClient = opts?.includeDevClient === true;
|
|
2057
|
-
const devClientScoped =
|
|
2058
|
-
const devClientFlat =
|
|
2059
|
-
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;
|
|
2060
2203
|
const hasDevClient = packages.some((p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client");
|
|
2061
2204
|
if (includeDevClient && devClientPath && !hasDevClient) {
|
|
2062
2205
|
packages = [{
|
|
@@ -2080,11 +2223,12 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2080
2223
|
ensureXElementDeps();
|
|
2081
2224
|
ensureReleaseSigning();
|
|
2082
2225
|
runGradleSync();
|
|
2226
|
+
invalidateTamerAndroidLibVersionMarker(projectRoot);
|
|
2083
2227
|
console.log("\u2728 Autolinking complete.");
|
|
2084
2228
|
}
|
|
2085
2229
|
function runGradleSync() {
|
|
2086
|
-
const gradlew =
|
|
2087
|
-
if (!
|
|
2230
|
+
const gradlew = path9.join(appAndroidPath, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2231
|
+
if (!fs9.existsSync(gradlew)) return;
|
|
2088
2232
|
try {
|
|
2089
2233
|
console.log("\u2139\uFE0F Running Gradle sync in android directory...");
|
|
2090
2234
|
execSync2(process.platform === "win32" ? "gradlew.bat projects" : "./gradlew projects", {
|
|
@@ -2098,14 +2242,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2098
2242
|
}
|
|
2099
2243
|
}
|
|
2100
2244
|
function syncVersionCatalog(packages) {
|
|
2101
|
-
const libsTomlPath =
|
|
2102
|
-
if (!
|
|
2245
|
+
const libsTomlPath = path9.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2246
|
+
if (!fs9.existsSync(libsTomlPath)) return;
|
|
2103
2247
|
const requiredAliases = /* @__PURE__ */ new Set();
|
|
2104
2248
|
const requiredPluginAliases = /* @__PURE__ */ new Set();
|
|
2105
2249
|
for (const pkg of packages) {
|
|
2106
|
-
const buildPath =
|
|
2107
|
-
if (!
|
|
2108
|
-
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");
|
|
2109
2253
|
for (const m of content.matchAll(/libs\.([\w.]+)/g)) {
|
|
2110
2254
|
const alias = m[1];
|
|
2111
2255
|
if (alias && alias in REQUIRED_CATALOG_ENTRIES) requiredAliases.add(alias);
|
|
@@ -2116,7 +2260,7 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2116
2260
|
}
|
|
2117
2261
|
}
|
|
2118
2262
|
if (requiredAliases.size === 0 && requiredPluginAliases.size === 0) return;
|
|
2119
|
-
let toml =
|
|
2263
|
+
let toml = fs9.readFileSync(libsTomlPath, "utf8");
|
|
2120
2264
|
let updated = false;
|
|
2121
2265
|
for (const alias of requiredAliases) {
|
|
2122
2266
|
const entry = REQUIRED_CATALOG_ENTRIES[alias];
|
|
@@ -2140,14 +2284,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2140
2284
|
updated = true;
|
|
2141
2285
|
}
|
|
2142
2286
|
if (updated) {
|
|
2143
|
-
|
|
2287
|
+
fs9.writeFileSync(libsTomlPath, toml);
|
|
2144
2288
|
console.log("\u2705 Synced version catalog (libs.versions.toml) for linked modules.");
|
|
2145
2289
|
}
|
|
2146
2290
|
}
|
|
2147
2291
|
function ensureXElementDeps() {
|
|
2148
|
-
const libsTomlPath =
|
|
2149
|
-
if (
|
|
2150
|
-
let toml =
|
|
2292
|
+
const libsTomlPath = path9.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2293
|
+
if (fs9.existsSync(libsTomlPath)) {
|
|
2294
|
+
let toml = fs9.readFileSync(libsTomlPath, "utf8");
|
|
2151
2295
|
let updated = false;
|
|
2152
2296
|
if (!toml.includes("lynx-xelement =")) {
|
|
2153
2297
|
toml = toml.replace(
|
|
@@ -2159,13 +2303,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2159
2303
|
updated = true;
|
|
2160
2304
|
}
|
|
2161
2305
|
if (updated) {
|
|
2162
|
-
|
|
2306
|
+
fs9.writeFileSync(libsTomlPath, toml);
|
|
2163
2307
|
console.log("\u2705 Added XElement entries to version catalog.");
|
|
2164
2308
|
}
|
|
2165
2309
|
}
|
|
2166
|
-
const appBuildPath =
|
|
2167
|
-
if (
|
|
2168
|
-
let content =
|
|
2310
|
+
const appBuildPath = path9.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2311
|
+
if (fs9.existsSync(appBuildPath)) {
|
|
2312
|
+
let content = fs9.readFileSync(appBuildPath, "utf8");
|
|
2169
2313
|
if (!content.includes("lynx.xelement")) {
|
|
2170
2314
|
content = content.replace(
|
|
2171
2315
|
/(implementation\(libs\.lynx\.service\.http\))/,
|
|
@@ -2173,15 +2317,15 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2173
2317
|
implementation(libs.lynx.xelement)
|
|
2174
2318
|
implementation(libs.lynx.xelement.input)`
|
|
2175
2319
|
);
|
|
2176
|
-
|
|
2320
|
+
fs9.writeFileSync(appBuildPath, content);
|
|
2177
2321
|
console.log("\u2705 Added XElement dependencies to app build.gradle.kts.");
|
|
2178
2322
|
}
|
|
2179
2323
|
}
|
|
2180
2324
|
}
|
|
2181
2325
|
function ensureReleaseSigning() {
|
|
2182
|
-
const appBuildPath =
|
|
2183
|
-
if (!
|
|
2184
|
-
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");
|
|
2185
2329
|
if (content.includes('signingConfig = signingConfigs.getByName("debug")')) return;
|
|
2186
2330
|
const releaseBlock = /(release\s*\{)([\s\S]*?)(\n \}\s*\n(\s*\}|\s*compileOptions))/;
|
|
2187
2331
|
const match = content.match(releaseBlock);
|
|
@@ -2192,13 +2336,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2192
2336
|
signingConfig = signingConfigs.getByName("debug")
|
|
2193
2337
|
$3`
|
|
2194
2338
|
);
|
|
2195
|
-
|
|
2339
|
+
fs9.writeFileSync(appBuildPath, content);
|
|
2196
2340
|
console.log("\u2705 Set release signing to debug so installRelease works without a keystore.");
|
|
2197
2341
|
}
|
|
2198
2342
|
}
|
|
2199
2343
|
function syncManifestPermissions(packages) {
|
|
2200
|
-
const manifestPath =
|
|
2201
|
-
if (!
|
|
2344
|
+
const manifestPath = path9.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2345
|
+
if (!fs9.existsSync(manifestPath)) return;
|
|
2202
2346
|
const allPermissions = /* @__PURE__ */ new Set();
|
|
2203
2347
|
for (const pkg of packages) {
|
|
2204
2348
|
const perms = pkg.config.android?.permissions;
|
|
@@ -2210,7 +2354,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2210
2354
|
}
|
|
2211
2355
|
}
|
|
2212
2356
|
if (allPermissions.size === 0) return;
|
|
2213
|
-
let manifest =
|
|
2357
|
+
let manifest = fs9.readFileSync(manifestPath, "utf8");
|
|
2214
2358
|
const existingMatch = [...manifest.matchAll(/<uses-permission android:name="(android\.permission\.\w+)"\s*\/>/g)];
|
|
2215
2359
|
const existing = new Set(existingMatch.map((m) => m[1]));
|
|
2216
2360
|
const toAdd = [...allPermissions].filter((p) => !existing.has(p));
|
|
@@ -2221,7 +2365,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2221
2365
|
`${newLines}
|
|
2222
2366
|
$1$2`
|
|
2223
2367
|
);
|
|
2224
|
-
|
|
2368
|
+
fs9.writeFileSync(manifestPath, manifest);
|
|
2225
2369
|
console.log(`\u2705 Synced manifest permissions: ${toAdd.map((p) => p.split(".").pop()).join(", ")}`);
|
|
2226
2370
|
}
|
|
2227
2371
|
run();
|
|
@@ -2229,26 +2373,26 @@ $1$2`
|
|
|
2229
2373
|
var autolink_default = autolink;
|
|
2230
2374
|
|
|
2231
2375
|
// src/android/bundle.ts
|
|
2232
|
-
import
|
|
2233
|
-
import
|
|
2376
|
+
import fs13 from "fs";
|
|
2377
|
+
import path13 from "path";
|
|
2234
2378
|
import { execSync as execSync3 } from "child_process";
|
|
2235
2379
|
|
|
2236
2380
|
// src/common/copyDistAssets.ts
|
|
2237
|
-
import
|
|
2238
|
-
import
|
|
2381
|
+
import fs10 from "fs";
|
|
2382
|
+
import path10 from "path";
|
|
2239
2383
|
var SKIP = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
2240
2384
|
function copyDistAssets(distDir, destDir, bundleFile) {
|
|
2241
|
-
if (!
|
|
2242
|
-
for (const entry of
|
|
2385
|
+
if (!fs10.existsSync(distDir)) return;
|
|
2386
|
+
for (const entry of fs10.readdirSync(distDir)) {
|
|
2243
2387
|
if (SKIP.has(entry)) continue;
|
|
2244
|
-
const src =
|
|
2245
|
-
const dest =
|
|
2246
|
-
const stat =
|
|
2388
|
+
const src = path10.join(distDir, entry);
|
|
2389
|
+
const dest = path10.join(destDir, entry);
|
|
2390
|
+
const stat = fs10.statSync(src);
|
|
2247
2391
|
if (stat.isDirectory()) {
|
|
2248
|
-
|
|
2392
|
+
fs10.mkdirSync(dest, { recursive: true });
|
|
2249
2393
|
copyDistAssets(src, dest, bundleFile);
|
|
2250
2394
|
} else {
|
|
2251
|
-
|
|
2395
|
+
fs10.copyFileSync(src, dest);
|
|
2252
2396
|
if (entry !== bundleFile) {
|
|
2253
2397
|
console.log(`\u2728 Copied asset: ${entry}`);
|
|
2254
2398
|
}
|
|
@@ -2257,8 +2401,8 @@ function copyDistAssets(distDir, destDir, bundleFile) {
|
|
|
2257
2401
|
}
|
|
2258
2402
|
|
|
2259
2403
|
// src/common/tsconfigUtils.ts
|
|
2260
|
-
import
|
|
2261
|
-
import
|
|
2404
|
+
import fs11 from "fs";
|
|
2405
|
+
import path11 from "path";
|
|
2262
2406
|
function stripJsonCommentsAndTrailingCommas(str) {
|
|
2263
2407
|
return str.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,\s*([\]}])/g, "$1");
|
|
2264
2408
|
}
|
|
@@ -2270,19 +2414,19 @@ function parseTsconfigJson(raw) {
|
|
|
2270
2414
|
}
|
|
2271
2415
|
}
|
|
2272
2416
|
function readTsconfig(filePath) {
|
|
2273
|
-
if (!
|
|
2417
|
+
if (!fs11.existsSync(filePath)) return null;
|
|
2274
2418
|
try {
|
|
2275
|
-
return parseTsconfigJson(
|
|
2419
|
+
return parseTsconfigJson(fs11.readFileSync(filePath, "utf-8"));
|
|
2276
2420
|
} catch {
|
|
2277
2421
|
return null;
|
|
2278
2422
|
}
|
|
2279
2423
|
}
|
|
2280
2424
|
function resolveExtends(tsconfig, dir) {
|
|
2281
2425
|
if (!tsconfig.extends) return tsconfig;
|
|
2282
|
-
const basePath =
|
|
2426
|
+
const basePath = path11.resolve(dir, tsconfig.extends);
|
|
2283
2427
|
const base = readTsconfig(basePath);
|
|
2284
2428
|
if (!base) return tsconfig;
|
|
2285
|
-
const baseDir =
|
|
2429
|
+
const baseDir = path11.dirname(basePath);
|
|
2286
2430
|
const resolved = resolveExtends(base, baseDir);
|
|
2287
2431
|
return {
|
|
2288
2432
|
...resolved,
|
|
@@ -2291,13 +2435,13 @@ function resolveExtends(tsconfig, dir) {
|
|
|
2291
2435
|
};
|
|
2292
2436
|
}
|
|
2293
2437
|
function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
2294
|
-
const dir =
|
|
2438
|
+
const dir = path11.dirname(tsconfigPath);
|
|
2295
2439
|
const tsconfig = readTsconfig(tsconfigPath);
|
|
2296
2440
|
if (!tsconfig?.references?.length) return false;
|
|
2297
2441
|
const refsWithNoEmit = [];
|
|
2298
2442
|
for (const ref of tsconfig.references) {
|
|
2299
|
-
const refPath =
|
|
2300
|
-
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");
|
|
2301
2445
|
const refConfig = readTsconfig(refConfigPath);
|
|
2302
2446
|
if (refConfig?.compilerOptions?.noEmit === true) {
|
|
2303
2447
|
refsWithNoEmit.push(refConfigPath);
|
|
@@ -2312,16 +2456,16 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2312
2456
|
const includes = [];
|
|
2313
2457
|
const compilerOpts = { ...tsconfig.compilerOptions };
|
|
2314
2458
|
for (const ref of tsconfig.references) {
|
|
2315
|
-
const refPath =
|
|
2316
|
-
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");
|
|
2317
2461
|
const refConfig = readTsconfig(refConfigPath);
|
|
2318
2462
|
if (!refConfig) continue;
|
|
2319
|
-
const refDir =
|
|
2463
|
+
const refDir = path11.dirname(refConfigPath);
|
|
2320
2464
|
const resolved = resolveExtends(refConfig, refDir);
|
|
2321
2465
|
const inc = resolved.include;
|
|
2322
2466
|
if (inc) {
|
|
2323
2467
|
const arr = Array.isArray(inc) ? inc : [inc];
|
|
2324
|
-
const baseDir =
|
|
2468
|
+
const baseDir = path11.relative(dir, refDir);
|
|
2325
2469
|
for (const p of arr) {
|
|
2326
2470
|
const clean = typeof p === "string" && p.startsWith("./") ? p.slice(2) : p;
|
|
2327
2471
|
includes.push(!baseDir || baseDir === "." ? clean : `${baseDir}/${clean}`);
|
|
@@ -2339,7 +2483,7 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2339
2483
|
if (includes.length > 0) merged.include = [...new Set(includes)];
|
|
2340
2484
|
compilerOpts.noEmit = true;
|
|
2341
2485
|
merged.compilerOptions = compilerOpts;
|
|
2342
|
-
|
|
2486
|
+
fs11.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
|
|
2343
2487
|
return true;
|
|
2344
2488
|
}
|
|
2345
2489
|
function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
@@ -2350,23 +2494,23 @@ function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
|
2350
2494
|
if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) return false;
|
|
2351
2495
|
arr.push(tamerTypesInclude);
|
|
2352
2496
|
tsconfig.include = arr;
|
|
2353
|
-
|
|
2497
|
+
fs11.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
2354
2498
|
return true;
|
|
2355
2499
|
}
|
|
2356
2500
|
|
|
2357
2501
|
// src/android/syncDevClient.ts
|
|
2358
|
-
import
|
|
2359
|
-
import
|
|
2502
|
+
import fs12 from "fs";
|
|
2503
|
+
import path12 from "path";
|
|
2360
2504
|
function readAndSubstituteTemplate2(templatePath, vars) {
|
|
2361
|
-
const raw =
|
|
2505
|
+
const raw = fs12.readFileSync(templatePath, "utf-8");
|
|
2362
2506
|
return Object.entries(vars).reduce(
|
|
2363
2507
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2364
2508
|
raw
|
|
2365
2509
|
);
|
|
2366
2510
|
}
|
|
2367
2511
|
function patchAppLogService(appPath) {
|
|
2368
|
-
if (!
|
|
2369
|
-
const raw =
|
|
2512
|
+
if (!fs12.existsSync(appPath)) return;
|
|
2513
|
+
const raw = fs12.readFileSync(appPath, "utf-8");
|
|
2370
2514
|
const patched = raw.replace(
|
|
2371
2515
|
/private void initLynxService\(\)\s*\{[\s\S]*?\n\s*}\s*\n\s*private void initFresco\(\)/,
|
|
2372
2516
|
`private void initLynxService() {
|
|
@@ -2385,7 +2529,7 @@ function patchAppLogService(appPath) {
|
|
|
2385
2529
|
private void initFresco()`
|
|
2386
2530
|
);
|
|
2387
2531
|
if (patched !== raw) {
|
|
2388
|
-
|
|
2532
|
+
fs12.writeFileSync(appPath, patched);
|
|
2389
2533
|
}
|
|
2390
2534
|
}
|
|
2391
2535
|
async function syncDevClient(opts) {
|
|
@@ -2400,9 +2544,9 @@ async function syncDevClient(opts) {
|
|
|
2400
2544
|
const packageName = config.android?.packageName;
|
|
2401
2545
|
const appName = config.android?.appName;
|
|
2402
2546
|
const packagePath = packageName.replace(/\./g, "/");
|
|
2403
|
-
const javaDir =
|
|
2404
|
-
const kotlinDir =
|
|
2405
|
-
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)) {
|
|
2406
2550
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2407
2551
|
process.exit(1);
|
|
2408
2552
|
}
|
|
@@ -2418,14 +2562,14 @@ async function syncDevClient(opts) {
|
|
|
2418
2562
|
const [templateProviderSource] = await Promise.all([
|
|
2419
2563
|
fetchAndPatchTemplateProvider(vars)
|
|
2420
2564
|
]);
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
patchAppLogService(
|
|
2424
|
-
const appDir =
|
|
2425
|
-
const mainDir =
|
|
2426
|
-
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");
|
|
2427
2571
|
if (hasDevClient) {
|
|
2428
|
-
const templateDir =
|
|
2572
|
+
const templateDir = path12.join(devClientPkg, "android", "templates");
|
|
2429
2573
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
2430
2574
|
const devClientFiles = [
|
|
2431
2575
|
"DevClientManager.kt",
|
|
@@ -2434,13 +2578,13 @@ async function syncDevClient(opts) {
|
|
|
2434
2578
|
"PortraitCaptureActivity.kt"
|
|
2435
2579
|
];
|
|
2436
2580
|
for (const f of devClientFiles) {
|
|
2437
|
-
const src =
|
|
2438
|
-
if (
|
|
2581
|
+
const src = path12.join(templateDir, f);
|
|
2582
|
+
if (fs12.existsSync(src)) {
|
|
2439
2583
|
const content = readAndSubstituteTemplate2(src, templateVars);
|
|
2440
|
-
|
|
2584
|
+
fs12.writeFileSync(path12.join(kotlinDir, f), content);
|
|
2441
2585
|
}
|
|
2442
2586
|
}
|
|
2443
|
-
let manifest =
|
|
2587
|
+
let manifest = fs12.readFileSync(manifestPath, "utf-8");
|
|
2444
2588
|
const projectActivityEntry = ' <activity android:name=".ProjectActivity" android:exported="false" android:taskAffinity="" android:launchMode="singleTask" android:documentLaunchMode="always" android:windowSoftInputMode="adjustResize" />';
|
|
2445
2589
|
const portraitCaptureEntry = ' <activity android:name=".PortraitCaptureActivity" android:screenOrientation="portrait" android:stateNotNeeded="true" android:theme="@style/zxing_CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden" />';
|
|
2446
2590
|
if (!manifest.includes("ProjectActivity")) {
|
|
@@ -2462,16 +2606,16 @@ $1$2`);
|
|
|
2462
2606
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2463
2607
|
);
|
|
2464
2608
|
}
|
|
2465
|
-
|
|
2609
|
+
fs12.writeFileSync(manifestPath, manifest);
|
|
2466
2610
|
console.log("\u2705 Synced dev client (TemplateProvider, MainActivity, ProjectActivity, DevClientManager)");
|
|
2467
2611
|
} else {
|
|
2468
2612
|
for (const f of ["DevClientManager.kt", "DevServerPrefs.kt", "ProjectActivity.kt", "PortraitCaptureActivity.kt", "DevLauncherActivity.kt"]) {
|
|
2469
2613
|
try {
|
|
2470
|
-
|
|
2614
|
+
fs12.rmSync(path12.join(kotlinDir, f));
|
|
2471
2615
|
} catch {
|
|
2472
2616
|
}
|
|
2473
2617
|
}
|
|
2474
|
-
let manifest =
|
|
2618
|
+
let manifest = fs12.readFileSync(manifestPath, "utf-8");
|
|
2475
2619
|
manifest = manifest.replace(/\s*<activity android:name="\.ProjectActivity"[^\/]*\/>\n?/g, "");
|
|
2476
2620
|
manifest = manifest.replace(/\s*<activity android:name="\.PortraitCaptureActivity"[^\/]*\/>\n?/g, "");
|
|
2477
2621
|
const mainActivityTag = manifest.match(/<activity[^>]*android:name="\.MainActivity"[^>]*>/);
|
|
@@ -2481,7 +2625,7 @@ $1$2`);
|
|
|
2481
2625
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2482
2626
|
);
|
|
2483
2627
|
}
|
|
2484
|
-
|
|
2628
|
+
fs12.writeFileSync(manifestPath, manifest);
|
|
2485
2629
|
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2486
2630
|
}
|
|
2487
2631
|
}
|
|
@@ -2505,15 +2649,15 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2505
2649
|
await syncDevClient_default({ includeDevClient });
|
|
2506
2650
|
const iconPaths = resolveIconPaths(projectRoot, resolved.config);
|
|
2507
2651
|
if (iconPaths) {
|
|
2508
|
-
const resDir =
|
|
2652
|
+
const resDir = path13.join(resolved.androidAppDir, "src", "main", "res");
|
|
2509
2653
|
if (applyAndroidLauncherIcons(resDir, iconPaths)) {
|
|
2510
2654
|
console.log("\u2705 Synced Android launcher icon(s) from tamer.config.json");
|
|
2511
|
-
ensureAndroidManifestLauncherIcon(
|
|
2655
|
+
ensureAndroidManifestLauncherIcon(path13.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
|
|
2512
2656
|
}
|
|
2513
2657
|
}
|
|
2514
2658
|
try {
|
|
2515
|
-
const lynxTsconfig =
|
|
2516
|
-
if (
|
|
2659
|
+
const lynxTsconfig = path13.join(lynxProjectDir, "tsconfig.json");
|
|
2660
|
+
if (fs13.existsSync(lynxTsconfig)) {
|
|
2517
2661
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
2518
2662
|
}
|
|
2519
2663
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -2523,8 +2667,8 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2523
2667
|
console.error("\u274C Build process failed.");
|
|
2524
2668
|
process.exit(1);
|
|
2525
2669
|
}
|
|
2526
|
-
if (includeDevClient && devClientBundlePath && !
|
|
2527
|
-
const devClientDir =
|
|
2670
|
+
if (includeDevClient && devClientBundlePath && !fs13.existsSync(devClientBundlePath)) {
|
|
2671
|
+
const devClientDir = path13.dirname(path13.dirname(devClientBundlePath));
|
|
2528
2672
|
try {
|
|
2529
2673
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
2530
2674
|
execSync3("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
@@ -2535,22 +2679,22 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2535
2679
|
}
|
|
2536
2680
|
}
|
|
2537
2681
|
try {
|
|
2538
|
-
|
|
2682
|
+
fs13.mkdirSync(destinationDir, { recursive: true });
|
|
2539
2683
|
if (release) {
|
|
2540
|
-
const devClientAsset =
|
|
2541
|
-
if (
|
|
2542
|
-
|
|
2684
|
+
const devClientAsset = path13.join(destinationDir, "dev-client.lynx.bundle");
|
|
2685
|
+
if (fs13.existsSync(devClientAsset)) {
|
|
2686
|
+
fs13.rmSync(devClientAsset);
|
|
2543
2687
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2544
2688
|
}
|
|
2545
|
-
} else if (includeDevClient && devClientBundlePath &&
|
|
2546
|
-
|
|
2689
|
+
} else if (includeDevClient && devClientBundlePath && fs13.existsSync(devClientBundlePath)) {
|
|
2690
|
+
fs13.copyFileSync(devClientBundlePath, path13.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2547
2691
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2548
2692
|
}
|
|
2549
|
-
if (!
|
|
2693
|
+
if (!fs13.existsSync(lynxBundlePath)) {
|
|
2550
2694
|
console.error(`\u274C Build output not found at: ${lynxBundlePath}`);
|
|
2551
2695
|
process.exit(1);
|
|
2552
2696
|
}
|
|
2553
|
-
const distDir =
|
|
2697
|
+
const distDir = path13.dirname(lynxBundlePath);
|
|
2554
2698
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
2555
2699
|
console.log(`\u2728 Copied ${resolved.lynxBundleFile} to assets`);
|
|
2556
2700
|
} catch (error) {
|
|
@@ -2561,7 +2705,7 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2561
2705
|
var bundle_default = bundleAndDeploy;
|
|
2562
2706
|
|
|
2563
2707
|
// src/android/build.ts
|
|
2564
|
-
import
|
|
2708
|
+
import path14 from "path";
|
|
2565
2709
|
import { execSync as execSync4 } from "child_process";
|
|
2566
2710
|
async function buildApk(opts = {}) {
|
|
2567
2711
|
let resolved;
|
|
@@ -2572,12 +2716,17 @@ async function buildApk(opts = {}) {
|
|
|
2572
2716
|
}
|
|
2573
2717
|
const release = opts.release === true || opts.production === true;
|
|
2574
2718
|
await bundle_default({ release, production: opts.production });
|
|
2575
|
-
const androidDir = resolved
|
|
2576
|
-
|
|
2719
|
+
const { androidDir, projectRoot } = resolved;
|
|
2720
|
+
cleanTamerAndroidLibBuildsIfVersionsChanged(projectRoot);
|
|
2721
|
+
const gradlew = path14.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2577
2722
|
const variant = release ? "Release" : "Debug";
|
|
2578
2723
|
const task = opts.install ? `install${variant}` : `assemble${variant}`;
|
|
2579
2724
|
console.log(`
|
|
2580
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
|
+
}
|
|
2581
2730
|
execSync4(`"${gradlew}" ${task}`, { stdio: "inherit", cwd: androidDir });
|
|
2582
2731
|
console.log(`\u2705 APK ${opts.install ? "installed" : "built"} successfully.`);
|
|
2583
2732
|
if (opts.install) {
|
|
@@ -2598,13 +2747,13 @@ async function buildApk(opts = {}) {
|
|
|
2598
2747
|
var build_default = buildApk;
|
|
2599
2748
|
|
|
2600
2749
|
// src/ios/create.ts
|
|
2601
|
-
import
|
|
2602
|
-
import
|
|
2750
|
+
import fs15 from "fs";
|
|
2751
|
+
import path16 from "path";
|
|
2603
2752
|
|
|
2604
2753
|
// src/ios/getPod.ts
|
|
2605
2754
|
import { execSync as execSync5 } from "child_process";
|
|
2606
|
-
import
|
|
2607
|
-
import
|
|
2755
|
+
import fs14 from "fs";
|
|
2756
|
+
import path15 from "path";
|
|
2608
2757
|
function isCocoaPodsInstalled() {
|
|
2609
2758
|
try {
|
|
2610
2759
|
execSync5("command -v pod >/dev/null 2>&1");
|
|
@@ -2626,8 +2775,8 @@ async function setupCocoaPods(rootDir) {
|
|
|
2626
2775
|
}
|
|
2627
2776
|
try {
|
|
2628
2777
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2629
|
-
const podfilePath =
|
|
2630
|
-
if (!
|
|
2778
|
+
const podfilePath = path15.join(rootDir, "Podfile");
|
|
2779
|
+
if (!fs14.existsSync(podfilePath)) {
|
|
2631
2780
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2632
2781
|
}
|
|
2633
2782
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2653,7 +2802,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2653
2802
|
// src/ios/create.ts
|
|
2654
2803
|
import { randomBytes } from "crypto";
|
|
2655
2804
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2656
|
-
const raw =
|
|
2805
|
+
const raw = fs15.readFileSync(templatePath, "utf-8");
|
|
2657
2806
|
return Object.entries(vars).reduce(
|
|
2658
2807
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2659
2808
|
raw
|
|
@@ -2676,17 +2825,17 @@ var create2 = () => {
|
|
|
2676
2825
|
process.exit(1);
|
|
2677
2826
|
}
|
|
2678
2827
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
2679
|
-
const rootDir =
|
|
2680
|
-
const projectDir =
|
|
2681
|
-
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`);
|
|
2682
2831
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2683
2832
|
function writeFile2(filePath, content) {
|
|
2684
|
-
|
|
2685
|
-
|
|
2833
|
+
fs15.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
2834
|
+
fs15.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2686
2835
|
}
|
|
2687
|
-
if (
|
|
2836
|
+
if (fs15.existsSync(rootDir)) {
|
|
2688
2837
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2689
|
-
|
|
2838
|
+
fs15.rmSync(rootDir, { recursive: true, force: true });
|
|
2690
2839
|
}
|
|
2691
2840
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2692
2841
|
const ids = {
|
|
@@ -2722,9 +2871,11 @@ var create2 = () => {
|
|
|
2722
2871
|
targetDebugConfig: generateId(),
|
|
2723
2872
|
targetReleaseConfig: generateId()
|
|
2724
2873
|
};
|
|
2725
|
-
writeFile2(
|
|
2874
|
+
writeFile2(path16.join(rootDir, "Podfile"), `
|
|
2726
2875
|
source 'https://cdn.cocoapods.org/'
|
|
2727
2876
|
|
|
2877
|
+
install! 'cocoapods', :incremental_installation => true, :generate_multiple_pod_projects => true
|
|
2878
|
+
|
|
2728
2879
|
platform :ios, '13.0'
|
|
2729
2880
|
|
|
2730
2881
|
target '${appName}' do
|
|
@@ -2807,15 +2958,15 @@ end
|
|
|
2807
2958
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
2808
2959
|
const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
2809
2960
|
if (hostPkg) {
|
|
2810
|
-
const templateDir =
|
|
2961
|
+
const templateDir = path16.join(hostPkg, "ios", "templates");
|
|
2811
2962
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2812
|
-
const srcPath =
|
|
2813
|
-
if (
|
|
2814
|
-
writeFile2(
|
|
2963
|
+
const srcPath = path16.join(templateDir, f);
|
|
2964
|
+
if (fs15.existsSync(srcPath)) {
|
|
2965
|
+
writeFile2(path16.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2815
2966
|
}
|
|
2816
2967
|
}
|
|
2817
2968
|
} else {
|
|
2818
|
-
writeFile2(
|
|
2969
|
+
writeFile2(path16.join(projectDir, "AppDelegate.swift"), `
|
|
2819
2970
|
import UIKit
|
|
2820
2971
|
|
|
2821
2972
|
@UIApplicationMain
|
|
@@ -2830,7 +2981,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2830
2981
|
}
|
|
2831
2982
|
}
|
|
2832
2983
|
`);
|
|
2833
|
-
writeFile2(
|
|
2984
|
+
writeFile2(path16.join(projectDir, "SceneDelegate.swift"), `
|
|
2834
2985
|
import UIKit
|
|
2835
2986
|
|
|
2836
2987
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2844,7 +2995,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2844
2995
|
}
|
|
2845
2996
|
}
|
|
2846
2997
|
`);
|
|
2847
|
-
writeFile2(
|
|
2998
|
+
writeFile2(path16.join(projectDir, "ViewController.swift"), `
|
|
2848
2999
|
import UIKit
|
|
2849
3000
|
import Lynx
|
|
2850
3001
|
import tamerinsets
|
|
@@ -2917,7 +3068,7 @@ class ViewController: UIViewController {
|
|
|
2917
3068
|
}
|
|
2918
3069
|
}
|
|
2919
3070
|
`);
|
|
2920
|
-
writeFile2(
|
|
3071
|
+
writeFile2(path16.join(projectDir, "LynxProvider.swift"), `
|
|
2921
3072
|
import Foundation
|
|
2922
3073
|
|
|
2923
3074
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2936,7 +3087,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2936
3087
|
}
|
|
2937
3088
|
}
|
|
2938
3089
|
`);
|
|
2939
|
-
writeFile2(
|
|
3090
|
+
writeFile2(path16.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2940
3091
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2941
3092
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2942
3093
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2976,7 +3127,7 @@ final class LynxInitProcessor {
|
|
|
2976
3127
|
}
|
|
2977
3128
|
`);
|
|
2978
3129
|
}
|
|
2979
|
-
writeFile2(
|
|
3130
|
+
writeFile2(path16.join(projectDir, bridgingHeader), `
|
|
2980
3131
|
#import <Lynx/LynxConfig.h>
|
|
2981
3132
|
#import <Lynx/LynxEnv.h>
|
|
2982
3133
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2985,7 +3136,7 @@ final class LynxInitProcessor {
|
|
|
2985
3136
|
#import <SDWebImage/SDWebImage.h>
|
|
2986
3137
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2987
3138
|
`);
|
|
2988
|
-
writeFile2(
|
|
3139
|
+
writeFile2(path16.join(projectDir, "Info.plist"), `
|
|
2989
3140
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2990
3141
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2991
3142
|
<plist version="1.0">
|
|
@@ -3043,21 +3194,21 @@ final class LynxInitProcessor {
|
|
|
3043
3194
|
</dict>
|
|
3044
3195
|
</plist>
|
|
3045
3196
|
`);
|
|
3046
|
-
const appIconDir =
|
|
3047
|
-
|
|
3197
|
+
const appIconDir = path16.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
3198
|
+
fs15.mkdirSync(appIconDir, { recursive: true });
|
|
3048
3199
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
3049
3200
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
3050
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");
|
|
3051
3202
|
} else {
|
|
3052
|
-
writeFile2(
|
|
3203
|
+
writeFile2(path16.join(appIconDir, "Contents.json"), `
|
|
3053
3204
|
{
|
|
3054
3205
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
3055
3206
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
3056
3207
|
}
|
|
3057
3208
|
`);
|
|
3058
3209
|
}
|
|
3059
|
-
|
|
3060
|
-
writeFile2(
|
|
3210
|
+
fs15.mkdirSync(xcodeprojDir, { recursive: true });
|
|
3211
|
+
writeFile2(path16.join(xcodeprojDir, "project.pbxproj"), `
|
|
3061
3212
|
// !$*UTF8*$!
|
|
3062
3213
|
{
|
|
3063
3214
|
archiveVersion = 1;
|
|
@@ -3343,8 +3494,8 @@ final class LynxInitProcessor {
|
|
|
3343
3494
|
var create_default2 = create2;
|
|
3344
3495
|
|
|
3345
3496
|
// src/ios/autolink.ts
|
|
3346
|
-
import
|
|
3347
|
-
import
|
|
3497
|
+
import fs17 from "fs";
|
|
3498
|
+
import path18 from "path";
|
|
3348
3499
|
import { execSync as execSync6 } from "child_process";
|
|
3349
3500
|
|
|
3350
3501
|
// src/common/hostNativeModulesManifest.ts
|
|
@@ -3355,8 +3506,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
|
|
|
3355
3506
|
}
|
|
3356
3507
|
|
|
3357
3508
|
// src/ios/syncHost.ts
|
|
3358
|
-
import
|
|
3359
|
-
import
|
|
3509
|
+
import fs16 from "fs";
|
|
3510
|
+
import path17 from "path";
|
|
3360
3511
|
import crypto from "crypto";
|
|
3361
3512
|
function deterministicUUID(seed) {
|
|
3362
3513
|
return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
|
|
@@ -3404,7 +3555,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3404
3555
|
`;
|
|
3405
3556
|
}
|
|
3406
3557
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3407
|
-
let content =
|
|
3558
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3408
3559
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3409
3560
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3410
3561
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3441,11 +3592,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3441
3592
|
);
|
|
3442
3593
|
content = content.replace(groupPattern, `$1
|
|
3443
3594
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3444
|
-
|
|
3595
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3445
3596
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3446
3597
|
}
|
|
3447
3598
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3448
|
-
let content =
|
|
3599
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3449
3600
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3450
3601
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3451
3602
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3470,11 +3621,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3470
3621
|
);
|
|
3471
3622
|
content = content.replace(groupPattern, `$1
|
|
3472
3623
|
${fileRefUUID} /* ${filename} */,`);
|
|
3473
|
-
|
|
3624
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3474
3625
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3475
3626
|
}
|
|
3476
3627
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3477
|
-
let content =
|
|
3628
|
+
let content = fs16.readFileSync(pbxprojPath, "utf8");
|
|
3478
3629
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3479
3630
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3480
3631
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3499,12 +3650,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3499
3650
|
);
|
|
3500
3651
|
content = content.replace(groupPattern, `$1
|
|
3501
3652
|
${fileRefUUID} /* ${filename} */,`);
|
|
3502
|
-
|
|
3653
|
+
fs16.writeFileSync(pbxprojPath, content, "utf8");
|
|
3503
3654
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3504
3655
|
}
|
|
3505
3656
|
function writeFile(filePath, content) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3657
|
+
fs16.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
3658
|
+
fs16.writeFileSync(filePath, content, "utf8");
|
|
3508
3659
|
}
|
|
3509
3660
|
function getAppDelegateSwift() {
|
|
3510
3661
|
return `import UIKit
|
|
@@ -3721,8 +3872,8 @@ class ViewController: UIViewController {
|
|
|
3721
3872
|
`;
|
|
3722
3873
|
}
|
|
3723
3874
|
function patchInfoPlist(infoPlistPath) {
|
|
3724
|
-
if (!
|
|
3725
|
-
let content =
|
|
3875
|
+
if (!fs16.existsSync(infoPlistPath)) return;
|
|
3876
|
+
let content = fs16.readFileSync(infoPlistPath, "utf8");
|
|
3726
3877
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3727
3878
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3728
3879
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3754,7 +3905,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3754
3905
|
</plist>`);
|
|
3755
3906
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3756
3907
|
}
|
|
3757
|
-
|
|
3908
|
+
fs16.writeFileSync(infoPlistPath, content, "utf8");
|
|
3758
3909
|
}
|
|
3759
3910
|
function getSimpleLynxProviderSwift() {
|
|
3760
3911
|
return `import Foundation
|
|
@@ -3779,9 +3930,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3779
3930
|
}
|
|
3780
3931
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3781
3932
|
if (devClientPkg) {
|
|
3782
|
-
const tplPath =
|
|
3783
|
-
if (
|
|
3784
|
-
let content =
|
|
3933
|
+
const tplPath = path17.join(devClientPkg, "ios", "templates", templateName);
|
|
3934
|
+
if (fs16.existsSync(tplPath)) {
|
|
3935
|
+
let content = fs16.readFileSync(tplPath, "utf8");
|
|
3785
3936
|
for (const [k, v] of Object.entries(vars)) {
|
|
3786
3937
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3787
3938
|
}
|
|
@@ -3799,19 +3950,19 @@ function syncHostIos(opts) {
|
|
|
3799
3950
|
if (!appName) {
|
|
3800
3951
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3801
3952
|
}
|
|
3802
|
-
const projectDir =
|
|
3803
|
-
const infoPlistPath =
|
|
3804
|
-
if (!
|
|
3953
|
+
const projectDir = path17.join(resolved.iosDir, appName);
|
|
3954
|
+
const infoPlistPath = path17.join(projectDir, "Info.plist");
|
|
3955
|
+
if (!fs16.existsSync(projectDir)) {
|
|
3805
3956
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3806
3957
|
}
|
|
3807
|
-
const pbxprojPath =
|
|
3808
|
-
const baseLprojDir =
|
|
3809
|
-
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");
|
|
3810
3961
|
patchInfoPlist(infoPlistPath);
|
|
3811
|
-
writeFile(
|
|
3812
|
-
writeFile(
|
|
3813
|
-
if (!
|
|
3814
|
-
|
|
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 });
|
|
3815
3966
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3816
3967
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3817
3968
|
}
|
|
@@ -3820,33 +3971,33 @@ function syncHostIos(opts) {
|
|
|
3820
3971
|
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3821
3972
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3822
3973
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3823
|
-
writeFile(
|
|
3824
|
-
writeFile(
|
|
3974
|
+
writeFile(path17.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3975
|
+
writeFile(path17.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3825
3976
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3826
3977
|
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3827
3978
|
if (devTPContent) {
|
|
3828
|
-
writeFile(
|
|
3979
|
+
writeFile(path17.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3829
3980
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3830
3981
|
}
|
|
3831
3982
|
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3832
3983
|
if (projectVCContent) {
|
|
3833
|
-
writeFile(
|
|
3984
|
+
writeFile(path17.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3834
3985
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3835
3986
|
}
|
|
3836
3987
|
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3837
3988
|
if (devCMContent) {
|
|
3838
|
-
writeFile(
|
|
3989
|
+
writeFile(path17.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3839
3990
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3840
3991
|
}
|
|
3841
3992
|
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3842
3993
|
if (qrContent) {
|
|
3843
|
-
writeFile(
|
|
3994
|
+
writeFile(path17.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3844
3995
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
3845
3996
|
}
|
|
3846
3997
|
console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
|
|
3847
3998
|
} else {
|
|
3848
|
-
writeFile(
|
|
3849
|
-
writeFile(
|
|
3999
|
+
writeFile(path17.join(projectDir, "ViewController.swift"), getViewControllerSwift());
|
|
4000
|
+
writeFile(path17.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3850
4001
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3851
4002
|
console.log("\u2705 Synced iOS host app controller files");
|
|
3852
4003
|
}
|
|
@@ -3854,7 +4005,7 @@ function syncHostIos(opts) {
|
|
|
3854
4005
|
var syncHost_default = syncHostIos;
|
|
3855
4006
|
|
|
3856
4007
|
// src/ios/autolink.ts
|
|
3857
|
-
var autolink2 = () => {
|
|
4008
|
+
var autolink2 = (syncHostOpts) => {
|
|
3858
4009
|
let resolved;
|
|
3859
4010
|
try {
|
|
3860
4011
|
resolved = resolveHostPaths();
|
|
@@ -3865,11 +4016,11 @@ var autolink2 = () => {
|
|
|
3865
4016
|
const projectRoot = resolved.projectRoot;
|
|
3866
4017
|
const iosProjectPath = resolved.iosDir;
|
|
3867
4018
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
3868
|
-
if (!
|
|
4019
|
+
if (!fs17.existsSync(filePath)) {
|
|
3869
4020
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
3870
4021
|
return;
|
|
3871
4022
|
}
|
|
3872
|
-
let fileContent =
|
|
4023
|
+
let fileContent = fs17.readFileSync(filePath, "utf8");
|
|
3873
4024
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3874
4025
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3875
4026
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3889,33 +4040,33 @@ ${replacementBlock}
|
|
|
3889
4040
|
`;
|
|
3890
4041
|
}
|
|
3891
4042
|
} else {
|
|
3892
|
-
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.`);
|
|
3893
4044
|
fileContent += `
|
|
3894
4045
|
${replacementBlock}
|
|
3895
4046
|
`;
|
|
3896
4047
|
}
|
|
3897
|
-
|
|
3898
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
4048
|
+
fs17.writeFileSync(filePath, fileContent, "utf8");
|
|
4049
|
+
console.log(`\u2705 Updated autolinked section in ${path18.basename(filePath)}`);
|
|
3899
4050
|
}
|
|
3900
4051
|
function resolvePodDirectory(pkg) {
|
|
3901
|
-
const configuredDir =
|
|
3902
|
-
if (
|
|
4052
|
+
const configuredDir = path18.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
|
|
4053
|
+
if (fs17.existsSync(configuredDir)) {
|
|
3903
4054
|
return configuredDir;
|
|
3904
4055
|
}
|
|
3905
|
-
const iosDir =
|
|
3906
|
-
if (
|
|
4056
|
+
const iosDir = path18.join(pkg.packagePath, "ios");
|
|
4057
|
+
if (fs17.existsSync(iosDir)) {
|
|
3907
4058
|
const stack = [iosDir];
|
|
3908
4059
|
while (stack.length > 0) {
|
|
3909
4060
|
const current = stack.pop();
|
|
3910
4061
|
try {
|
|
3911
|
-
const entries =
|
|
4062
|
+
const entries = fs17.readdirSync(current, { withFileTypes: true });
|
|
3912
4063
|
const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
|
|
3913
4064
|
if (podspec) {
|
|
3914
4065
|
return current;
|
|
3915
4066
|
}
|
|
3916
4067
|
for (const entry of entries) {
|
|
3917
4068
|
if (entry.isDirectory()) {
|
|
3918
|
-
stack.push(
|
|
4069
|
+
stack.push(path18.join(current, entry.name));
|
|
3919
4070
|
}
|
|
3920
4071
|
}
|
|
3921
4072
|
} catch {
|
|
@@ -3926,9 +4077,9 @@ ${replacementBlock}
|
|
|
3926
4077
|
}
|
|
3927
4078
|
function resolvePodName(pkg) {
|
|
3928
4079
|
const fullPodspecDir = resolvePodDirectory(pkg);
|
|
3929
|
-
if (
|
|
4080
|
+
if (fs17.existsSync(fullPodspecDir)) {
|
|
3930
4081
|
try {
|
|
3931
|
-
const files =
|
|
4082
|
+
const files = fs17.readdirSync(fullPodspecDir);
|
|
3932
4083
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
3933
4084
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
3934
4085
|
} catch {
|
|
@@ -3937,13 +4088,13 @@ ${replacementBlock}
|
|
|
3937
4088
|
return pkg.name.split("/").pop().replace(/-/g, "");
|
|
3938
4089
|
}
|
|
3939
4090
|
function updatePodfile(packages) {
|
|
3940
|
-
const podfilePath =
|
|
4091
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
3941
4092
|
let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
|
|
3942
4093
|
# Manual edits will be overwritten.`;
|
|
3943
4094
|
const iosPackages = packages.filter((p) => p.config.ios);
|
|
3944
4095
|
if (iosPackages.length > 0) {
|
|
3945
4096
|
iosPackages.forEach((pkg) => {
|
|
3946
|
-
const relativePath =
|
|
4097
|
+
const relativePath = path18.relative(iosProjectPath, resolvePodDirectory(pkg));
|
|
3947
4098
|
const podName = resolvePodName(pkg);
|
|
3948
4099
|
scriptContent += `
|
|
3949
4100
|
pod '${podName}', :path => '${relativePath}'`;
|
|
@@ -3955,9 +4106,9 @@ ${replacementBlock}
|
|
|
3955
4106
|
updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
|
|
3956
4107
|
}
|
|
3957
4108
|
function ensureXElementPod() {
|
|
3958
|
-
const podfilePath =
|
|
3959
|
-
if (!
|
|
3960
|
-
let content =
|
|
4109
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4110
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4111
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
3961
4112
|
if (content.includes("pod 'XElement'")) return;
|
|
3962
4113
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
3963
4114
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -3982,13 +4133,51 @@ ${replacementBlock}
|
|
|
3982
4133
|
`;
|
|
3983
4134
|
}
|
|
3984
4135
|
}
|
|
3985
|
-
|
|
4136
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
3986
4137
|
console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
|
|
3987
4138
|
}
|
|
4139
|
+
function ensureLynxDevToolPods(packages) {
|
|
4140
|
+
const hasDevClient = packages.some(
|
|
4141
|
+
(p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client"
|
|
4142
|
+
);
|
|
4143
|
+
if (!hasDevClient) return;
|
|
4144
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4145
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4146
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4147
|
+
if (content.includes("pod 'LynxDevtool'") || content.includes('pod "LynxDevtool"')) return;
|
|
4148
|
+
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
4149
|
+
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
4150
|
+
if (!content.includes("'Devtool'") && !content.includes('"Devtool"')) {
|
|
4151
|
+
content = content.replace(
|
|
4152
|
+
/(\s+'Http',\s*\n)(\s*\])/,
|
|
4153
|
+
"$1 'Devtool',\n$2"
|
|
4154
|
+
);
|
|
4155
|
+
}
|
|
4156
|
+
const devtoolLine = ` pod 'LynxDevtool', '${lynxVersion}'
|
|
4157
|
+
|
|
4158
|
+
`;
|
|
4159
|
+
if (content.includes("# GENERATED AUTOLINK DEPENDENCIES START")) {
|
|
4160
|
+
content = content.replace(/(# GENERATED AUTOLINK DEPENDENCIES START)/, `${devtoolLine}$1`);
|
|
4161
|
+
} else {
|
|
4162
|
+
const insertAfter = /pod\s+'LynxService'[^\n]*(?:\n\s*'[^']*',?\s*)*\]\s*/;
|
|
4163
|
+
const serviceMatch = content.match(insertAfter);
|
|
4164
|
+
if (serviceMatch) {
|
|
4165
|
+
const idx = serviceMatch.index + serviceMatch[0].length;
|
|
4166
|
+
content = content.slice(0, idx) + `
|
|
4167
|
+
pod 'LynxDevtool', '${lynxVersion}'` + content.slice(idx);
|
|
4168
|
+
} else {
|
|
4169
|
+
content += `
|
|
4170
|
+
pod 'LynxDevtool', '${lynxVersion}'
|
|
4171
|
+
`;
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4175
|
+
console.log(`\u2705 Added Lynx DevTool pods (v${lynxVersion}) to Podfile`);
|
|
4176
|
+
}
|
|
3988
4177
|
function ensureLynxPatchInPodfile() {
|
|
3989
|
-
const podfilePath =
|
|
3990
|
-
if (!
|
|
3991
|
-
let content =
|
|
4178
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4179
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4180
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
3992
4181
|
if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
|
|
3993
4182
|
const patch = `
|
|
3994
4183
|
Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
|
|
@@ -4000,13 +4189,13 @@ ${replacementBlock}
|
|
|
4000
4189
|
end`;
|
|
4001
4190
|
content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
|
|
4002
4191
|
$2`);
|
|
4003
|
-
|
|
4192
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4004
4193
|
console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
|
|
4005
4194
|
}
|
|
4006
4195
|
function ensurePodBuildSettings() {
|
|
4007
|
-
const podfilePath =
|
|
4008
|
-
if (!
|
|
4009
|
-
let content =
|
|
4196
|
+
const podfilePath = path18.join(iosProjectPath, "Podfile");
|
|
4197
|
+
if (!fs17.existsSync(podfilePath)) return;
|
|
4198
|
+
let content = fs17.readFileSync(podfilePath, "utf8");
|
|
4010
4199
|
let changed = false;
|
|
4011
4200
|
if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
|
|
4012
4201
|
content = content.replace(
|
|
@@ -4049,7 +4238,7 @@ $2`);
|
|
|
4049
4238
|
changed = true;
|
|
4050
4239
|
}
|
|
4051
4240
|
if (changed) {
|
|
4052
|
-
|
|
4241
|
+
fs17.writeFileSync(podfilePath, content, "utf8");
|
|
4053
4242
|
console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
|
|
4054
4243
|
}
|
|
4055
4244
|
}
|
|
@@ -4057,10 +4246,10 @@ $2`);
|
|
|
4057
4246
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4058
4247
|
const candidatePaths = [];
|
|
4059
4248
|
if (appNameFromConfig) {
|
|
4060
|
-
candidatePaths.push(
|
|
4249
|
+
candidatePaths.push(path18.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
4061
4250
|
}
|
|
4062
|
-
candidatePaths.push(
|
|
4063
|
-
const found = candidatePaths.find((p) =>
|
|
4251
|
+
candidatePaths.push(path18.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
4252
|
+
const found = candidatePaths.find((p) => fs17.existsSync(p));
|
|
4064
4253
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
4065
4254
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
4066
4255
|
const seenModules = /* @__PURE__ */ new Set();
|
|
@@ -4099,7 +4288,7 @@ $2`);
|
|
|
4099
4288
|
const podName = resolvePodName(pkg);
|
|
4100
4289
|
return `import ${podName}`;
|
|
4101
4290
|
}).join("\n");
|
|
4102
|
-
const fileContent =
|
|
4291
|
+
const fileContent = fs17.readFileSync(filePath, "utf8");
|
|
4103
4292
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
4104
4293
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
4105
4294
|
return;
|
|
@@ -4136,8 +4325,8 @@ ${after}`;
|
|
|
4136
4325
|
${fileContent}`;
|
|
4137
4326
|
}
|
|
4138
4327
|
}
|
|
4139
|
-
|
|
4140
|
-
console.log(`\u2705 Updated imports in ${
|
|
4328
|
+
fs17.writeFileSync(filePath, newContent, "utf8");
|
|
4329
|
+
console.log(`\u2705 Updated imports in ${path18.basename(filePath)}`);
|
|
4141
4330
|
}
|
|
4142
4331
|
updateImportsSection(lynxInitPath, importPackages);
|
|
4143
4332
|
if (importPackages.length === 0) {
|
|
@@ -4181,7 +4370,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4181
4370
|
} else {
|
|
4182
4371
|
devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
|
|
4183
4372
|
}
|
|
4184
|
-
if (
|
|
4373
|
+
if (fs17.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
|
|
4185
4374
|
updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
|
|
4186
4375
|
}
|
|
4187
4376
|
}
|
|
@@ -4189,13 +4378,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4189
4378
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4190
4379
|
const candidates = [];
|
|
4191
4380
|
if (appNameFromConfig) {
|
|
4192
|
-
candidates.push(
|
|
4381
|
+
candidates.push(path18.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
4193
4382
|
}
|
|
4194
|
-
candidates.push(
|
|
4195
|
-
return candidates.find((p) =>
|
|
4383
|
+
candidates.push(path18.join(iosProjectPath, "Info.plist"));
|
|
4384
|
+
return candidates.find((p) => fs17.existsSync(p)) ?? null;
|
|
4196
4385
|
}
|
|
4197
4386
|
function readPlistXml(plistPath) {
|
|
4198
|
-
return
|
|
4387
|
+
return fs17.readFileSync(plistPath, "utf8");
|
|
4199
4388
|
}
|
|
4200
4389
|
function syncInfoPlistPermissions(packages) {
|
|
4201
4390
|
const plistPath = findInfoPlist();
|
|
@@ -4226,7 +4415,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4226
4415
|
added++;
|
|
4227
4416
|
}
|
|
4228
4417
|
if (added > 0) {
|
|
4229
|
-
|
|
4418
|
+
fs17.writeFileSync(plistPath, plist, "utf8");
|
|
4230
4419
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
4231
4420
|
}
|
|
4232
4421
|
}
|
|
@@ -4273,16 +4462,16 @@ ${schemesXml}
|
|
|
4273
4462
|
$1`
|
|
4274
4463
|
);
|
|
4275
4464
|
}
|
|
4276
|
-
|
|
4465
|
+
fs17.writeFileSync(plistPath, plist, "utf8");
|
|
4277
4466
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
4278
4467
|
}
|
|
4279
4468
|
function runPodInstall(forcePath) {
|
|
4280
|
-
const podfilePath = forcePath ??
|
|
4281
|
-
if (!
|
|
4469
|
+
const podfilePath = forcePath ?? path18.join(iosProjectPath, "Podfile");
|
|
4470
|
+
if (!fs17.existsSync(podfilePath)) {
|
|
4282
4471
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
4283
4472
|
return;
|
|
4284
4473
|
}
|
|
4285
|
-
const cwd =
|
|
4474
|
+
const cwd = path18.dirname(podfilePath);
|
|
4286
4475
|
try {
|
|
4287
4476
|
console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
|
|
4288
4477
|
try {
|
|
@@ -4305,9 +4494,10 @@ $1`
|
|
|
4305
4494
|
} else {
|
|
4306
4495
|
console.log("\u2139\uFE0F No Tamer4Lynx native packages found.");
|
|
4307
4496
|
}
|
|
4308
|
-
syncHost_default();
|
|
4497
|
+
syncHost_default(syncHostOpts);
|
|
4309
4498
|
updatePodfile(packages);
|
|
4310
4499
|
ensureXElementPod();
|
|
4500
|
+
ensureLynxDevToolPods(discoverModules(projectRoot));
|
|
4311
4501
|
ensureLynxPatchInPodfile();
|
|
4312
4502
|
ensurePodBuildSettings();
|
|
4313
4503
|
updateLynxInitProcessor(packages);
|
|
@@ -4316,8 +4506,8 @@ $1`
|
|
|
4316
4506
|
syncInfoPlistUrlSchemes();
|
|
4317
4507
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4318
4508
|
if (appNameFromConfig) {
|
|
4319
|
-
const appPodfile =
|
|
4320
|
-
if (
|
|
4509
|
+
const appPodfile = path18.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
4510
|
+
if (fs17.existsSync(appPodfile)) {
|
|
4321
4511
|
runPodInstall(appPodfile);
|
|
4322
4512
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
4323
4513
|
return;
|
|
@@ -4332,13 +4522,13 @@ $1`
|
|
|
4332
4522
|
const appFolder = resolved.config.ios?.appName;
|
|
4333
4523
|
if (!hasDevClient || !appFolder) return;
|
|
4334
4524
|
const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
|
|
4335
|
-
const appDir =
|
|
4336
|
-
|
|
4337
|
-
const manifestPath =
|
|
4338
|
-
|
|
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");
|
|
4339
4529
|
console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
|
|
4340
|
-
const pbxprojPath =
|
|
4341
|
-
if (
|
|
4530
|
+
const pbxprojPath = path18.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
|
|
4531
|
+
if (fs17.existsSync(pbxprojPath)) {
|
|
4342
4532
|
addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4343
4533
|
}
|
|
4344
4534
|
}
|
|
@@ -4347,8 +4537,8 @@ $1`
|
|
|
4347
4537
|
var autolink_default2 = autolink2;
|
|
4348
4538
|
|
|
4349
4539
|
// src/ios/bundle.ts
|
|
4350
|
-
import
|
|
4351
|
-
import
|
|
4540
|
+
import fs18 from "fs";
|
|
4541
|
+
import path19 from "path";
|
|
4352
4542
|
import { execSync as execSync7 } from "child_process";
|
|
4353
4543
|
function bundleAndDeploy2(opts = {}) {
|
|
4354
4544
|
const release = opts.release === true || opts.production === true;
|
|
@@ -4366,20 +4556,19 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4366
4556
|
const includeDevClient = !release && !!devClientPkg;
|
|
4367
4557
|
const appName = resolved.config.ios.appName;
|
|
4368
4558
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
4369
|
-
const destinationDir =
|
|
4370
|
-
const destinationBundlePath =
|
|
4371
|
-
|
|
4372
|
-
autolink_default2();
|
|
4559
|
+
const destinationDir = path19.join(resolved.iosDir, appName);
|
|
4560
|
+
const destinationBundlePath = path19.join(destinationDir, resolved.lynxBundleFile);
|
|
4561
|
+
autolink_default2({ release, includeDevClient });
|
|
4373
4562
|
const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
|
|
4374
4563
|
if (iconPaths) {
|
|
4375
|
-
const appIconDir =
|
|
4564
|
+
const appIconDir = path19.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
4376
4565
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
4377
4566
|
console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
|
|
4378
4567
|
}
|
|
4379
4568
|
}
|
|
4380
4569
|
try {
|
|
4381
|
-
const lynxTsconfig =
|
|
4382
|
-
if (
|
|
4570
|
+
const lynxTsconfig = path19.join(resolved.lynxProjectDir, "tsconfig.json");
|
|
4571
|
+
if (fs18.existsSync(lynxTsconfig)) {
|
|
4383
4572
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
4384
4573
|
}
|
|
4385
4574
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -4390,40 +4579,40 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4390
4579
|
process.exit(1);
|
|
4391
4580
|
}
|
|
4392
4581
|
try {
|
|
4393
|
-
if (!
|
|
4582
|
+
if (!fs18.existsSync(sourceBundlePath)) {
|
|
4394
4583
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
4395
4584
|
process.exit(1);
|
|
4396
4585
|
}
|
|
4397
|
-
if (!
|
|
4586
|
+
if (!fs18.existsSync(destinationDir)) {
|
|
4398
4587
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
4399
4588
|
process.exit(1);
|
|
4400
4589
|
}
|
|
4401
|
-
const distDir =
|
|
4590
|
+
const distDir = path19.dirname(sourceBundlePath);
|
|
4402
4591
|
console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
|
|
4403
4592
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
4404
4593
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
4405
|
-
const pbxprojPath =
|
|
4406
|
-
if (
|
|
4594
|
+
const pbxprojPath = path19.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4595
|
+
if (fs18.existsSync(pbxprojPath)) {
|
|
4407
4596
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
4408
|
-
for (const entry of
|
|
4409
|
-
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;
|
|
4410
4599
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
4411
4600
|
}
|
|
4412
4601
|
}
|
|
4413
4602
|
if (includeDevClient && devClientPkg) {
|
|
4414
|
-
const devClientBundle =
|
|
4603
|
+
const devClientBundle = path19.join(destinationDir, "dev-client.lynx.bundle");
|
|
4415
4604
|
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
4416
4605
|
try {
|
|
4417
4606
|
execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
4418
4607
|
} catch {
|
|
4419
4608
|
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
4420
4609
|
}
|
|
4421
|
-
const builtBundle =
|
|
4422
|
-
if (
|
|
4423
|
-
|
|
4610
|
+
const builtBundle = path19.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
4611
|
+
if (fs18.existsSync(builtBundle)) {
|
|
4612
|
+
fs18.copyFileSync(builtBundle, devClientBundle);
|
|
4424
4613
|
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
4425
|
-
const pbxprojPath2 =
|
|
4426
|
-
if (
|
|
4614
|
+
const pbxprojPath2 = path19.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4615
|
+
if (fs18.existsSync(pbxprojPath2)) {
|
|
4427
4616
|
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
4428
4617
|
}
|
|
4429
4618
|
}
|
|
@@ -4437,9 +4626,10 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4437
4626
|
var bundle_default2 = bundleAndDeploy2;
|
|
4438
4627
|
|
|
4439
4628
|
// src/ios/build.ts
|
|
4440
|
-
import
|
|
4441
|
-
import
|
|
4629
|
+
import fs19 from "fs";
|
|
4630
|
+
import path20 from "path";
|
|
4442
4631
|
import os3 from "os";
|
|
4632
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
4443
4633
|
import { execSync as execSync8 } from "child_process";
|
|
4444
4634
|
function hostArch() {
|
|
4445
4635
|
return os3.arch() === "arm64" ? "arm64" : "x86_64";
|
|
@@ -4457,6 +4647,31 @@ function findBootedSimulator() {
|
|
|
4457
4647
|
}
|
|
4458
4648
|
return null;
|
|
4459
4649
|
}
|
|
4650
|
+
function findFirstConnectedIosDeviceUdid() {
|
|
4651
|
+
const jsonPath = path20.join(os3.tmpdir(), `t4l-devicectl-${randomBytes2(8).toString("hex")}.json`);
|
|
4652
|
+
try {
|
|
4653
|
+
execSync8(`xcrun devicectl list devices --json-output "${jsonPath}"`, {
|
|
4654
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4655
|
+
});
|
|
4656
|
+
if (!fs19.existsSync(jsonPath)) return null;
|
|
4657
|
+
const raw = fs19.readFileSync(jsonPath, "utf8");
|
|
4658
|
+
fs19.unlinkSync(jsonPath);
|
|
4659
|
+
const data = JSON.parse(raw);
|
|
4660
|
+
const devices = data.result?.devices ?? [];
|
|
4661
|
+
for (const d of devices) {
|
|
4662
|
+
const udid = d.hardwareProperties?.udid ?? d.identifier;
|
|
4663
|
+
if (typeof udid === "string" && udid.length >= 20) {
|
|
4664
|
+
return udid;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
} catch {
|
|
4668
|
+
try {
|
|
4669
|
+
if (fs19.existsSync(jsonPath)) fs19.unlinkSync(jsonPath);
|
|
4670
|
+
} catch {
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
return null;
|
|
4674
|
+
}
|
|
4460
4675
|
async function buildIpa(opts = {}) {
|
|
4461
4676
|
const resolved = resolveHostPaths();
|
|
4462
4677
|
if (!resolved.config.ios?.appName) {
|
|
@@ -4469,17 +4684,19 @@ async function buildIpa(opts = {}) {
|
|
|
4469
4684
|
const configuration = release ? "Release" : "Debug";
|
|
4470
4685
|
bundle_default2({ release, production: opts.production });
|
|
4471
4686
|
const scheme = appName;
|
|
4472
|
-
const workspacePath =
|
|
4473
|
-
const projectPath =
|
|
4474
|
-
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;
|
|
4475
4690
|
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
4476
|
-
const derivedDataPath =
|
|
4477
|
-
const
|
|
4478
|
-
const
|
|
4479
|
-
const
|
|
4691
|
+
const derivedDataPath = path20.join(iosDir, "build");
|
|
4692
|
+
const production = opts.production === true;
|
|
4693
|
+
const sdk = production ? "iphoneos" : opts.install ? "iphonesimulator" : "iphoneos";
|
|
4694
|
+
const signingArgs = production || opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
|
|
4695
|
+
const archFlag = opts.install && !production ? `-arch ${hostArch()} ` : "";
|
|
4480
4696
|
const extraSettings = [
|
|
4481
4697
|
"ONLY_ACTIVE_ARCH=YES",
|
|
4482
|
-
"CLANG_ENABLE_EXPLICIT_MODULES=NO"
|
|
4698
|
+
"CLANG_ENABLE_EXPLICIT_MODULES=NO",
|
|
4699
|
+
...configuration === "Debug" ? ["COMPILER_INDEX_STORE_ENABLE=NO"] : []
|
|
4483
4700
|
].join(" ");
|
|
4484
4701
|
console.log(`
|
|
4485
4702
|
\u{1F528} Building ${configuration} (${sdk})...`);
|
|
@@ -4489,38 +4706,77 @@ async function buildIpa(opts = {}) {
|
|
|
4489
4706
|
);
|
|
4490
4707
|
console.log(`\u2705 Build completed.`);
|
|
4491
4708
|
if (opts.install) {
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
console.log(`\u{
|
|
4512
|
-
execSync8(`xcrun
|
|
4513
|
-
|
|
4709
|
+
if (production) {
|
|
4710
|
+
const appPath = path20.join(
|
|
4711
|
+
derivedDataPath,
|
|
4712
|
+
"Build",
|
|
4713
|
+
"Products",
|
|
4714
|
+
`${configuration}-iphoneos`,
|
|
4715
|
+
`${appName}.app`
|
|
4716
|
+
);
|
|
4717
|
+
if (!fs19.existsSync(appPath)) {
|
|
4718
|
+
console.error(`\u274C Built app not found at: ${appPath}`);
|
|
4719
|
+
process.exit(1);
|
|
4720
|
+
}
|
|
4721
|
+
const udid = findFirstConnectedIosDeviceUdid();
|
|
4722
|
+
if (!udid) {
|
|
4723
|
+
console.error(
|
|
4724
|
+
"\u274C No connected iOS device found. Connect an iPhone/iPad with a trusted cable, unlock it, and ensure Developer Mode is on (iOS 16+). Requires Xcode 15+ (`xcrun devicectl`)."
|
|
4725
|
+
);
|
|
4726
|
+
process.exit(1);
|
|
4727
|
+
}
|
|
4728
|
+
console.log(`\u{1F4F2} Installing on device ${udid}...`);
|
|
4729
|
+
execSync8(`xcrun devicectl device install app --device "${udid}" "${appPath}"`, {
|
|
4730
|
+
stdio: "inherit"
|
|
4731
|
+
});
|
|
4732
|
+
if (bundleId) {
|
|
4733
|
+
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
4734
|
+
try {
|
|
4735
|
+
execSync8(
|
|
4736
|
+
`xcrun devicectl device process launch --device "${udid}" "${bundleId}"`,
|
|
4737
|
+
{ stdio: "inherit" }
|
|
4738
|
+
);
|
|
4739
|
+
console.log("\u2705 App launched.");
|
|
4740
|
+
} catch {
|
|
4741
|
+
console.log("\u2705 Installed. Launch manually on the device if auto-launch failed.");
|
|
4742
|
+
}
|
|
4743
|
+
} else {
|
|
4744
|
+
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
4745
|
+
}
|
|
4514
4746
|
} else {
|
|
4515
|
-
|
|
4747
|
+
const appGlob = path20.join(
|
|
4748
|
+
derivedDataPath,
|
|
4749
|
+
"Build",
|
|
4750
|
+
"Products",
|
|
4751
|
+
`${configuration}-iphonesimulator`,
|
|
4752
|
+
`${appName}.app`
|
|
4753
|
+
);
|
|
4754
|
+
if (!fs19.existsSync(appGlob)) {
|
|
4755
|
+
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
4756
|
+
process.exit(1);
|
|
4757
|
+
}
|
|
4758
|
+
const udid = findBootedSimulator();
|
|
4759
|
+
if (!udid) {
|
|
4760
|
+
console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
|
|
4761
|
+
process.exit(1);
|
|
4762
|
+
}
|
|
4763
|
+
console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
|
|
4764
|
+
execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
|
|
4765
|
+
if (bundleId) {
|
|
4766
|
+
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
4767
|
+
execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
|
|
4768
|
+
console.log("\u2705 App launched.");
|
|
4769
|
+
} else {
|
|
4770
|
+
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
4771
|
+
}
|
|
4516
4772
|
}
|
|
4517
4773
|
}
|
|
4518
4774
|
}
|
|
4519
4775
|
var build_default2 = buildIpa;
|
|
4520
4776
|
|
|
4521
4777
|
// src/common/init.tsx
|
|
4522
|
-
import
|
|
4523
|
-
import
|
|
4778
|
+
import fs20 from "fs";
|
|
4779
|
+
import path21 from "path";
|
|
4524
4780
|
import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
4525
4781
|
import { render, Text as Text9, Box as Box8 } from "ink";
|
|
4526
4782
|
|
|
@@ -4678,7 +4934,9 @@ function Wizard({ step, total, title, children }) {
|
|
|
4678
4934
|
import "react";
|
|
4679
4935
|
import { Box as Box7, Text as Text8 } from "ink";
|
|
4680
4936
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4937
|
+
var LOG_DISPLAY_MAX = 120;
|
|
4681
4938
|
function ServerDashboard({
|
|
4939
|
+
cliVersion,
|
|
4682
4940
|
projectName,
|
|
4683
4941
|
port,
|
|
4684
4942
|
lanIp,
|
|
@@ -4691,7 +4949,6 @@ function ServerDashboard({
|
|
|
4691
4949
|
buildError,
|
|
4692
4950
|
wsConnections,
|
|
4693
4951
|
logLines,
|
|
4694
|
-
showLogs,
|
|
4695
4952
|
qrLines,
|
|
4696
4953
|
phase,
|
|
4697
4954
|
startError
|
|
@@ -4710,6 +4967,10 @@ function ServerDashboard({
|
|
|
4710
4967
|
projectName,
|
|
4711
4968
|
")"
|
|
4712
4969
|
] }),
|
|
4970
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
4971
|
+
"t4l v",
|
|
4972
|
+
cliVersion
|
|
4973
|
+
] }),
|
|
4713
4974
|
verbose ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Logs: verbose (native + JS)" }) : null,
|
|
4714
4975
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", columnGap: 3, alignItems: "flex-start", children: [
|
|
4715
4976
|
qrLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
|
|
@@ -4746,7 +5007,7 @@ function ServerDashboard({
|
|
|
4746
5007
|
bonjour ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "mDNS: _tamer._tcp" }) : null,
|
|
4747
5008
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4748
5009
|
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Build" }),
|
|
4749
|
-
buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "
|
|
5010
|
+
buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "building\u2026" }) : buildPhase === "error" ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: buildError ?? "Build failed" }) : buildPhase === "success" ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "Lynx bundle ready" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "\u2014" })
|
|
4750
5011
|
] }),
|
|
4751
5012
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4752
5013
|
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Connections" }),
|
|
@@ -4759,15 +5020,15 @@ function ServerDashboard({
|
|
|
4759
5020
|
}
|
|
4760
5021
|
)
|
|
4761
5022
|
] }),
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
5023
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 c clear output \xB7 Ctrl+C or q quit" }) }),
|
|
5024
|
+
logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
5025
|
+
logLines.length > LOG_DISPLAY_MAX ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
5026
|
+
"\u2026 ",
|
|
5027
|
+
logLines.length - LOG_DISPLAY_MAX,
|
|
5028
|
+
" earlier lines omitted"
|
|
5029
|
+
] }) : null,
|
|
5030
|
+
logLines.slice(-LOG_DISPLAY_MAX).map((line, i) => /* @__PURE__ */ jsx8(Text8, { children: line }, i))
|
|
5031
|
+
] }) : null
|
|
4771
5032
|
] });
|
|
4772
5033
|
}
|
|
4773
5034
|
|
|
@@ -4833,22 +5094,22 @@ function InitWizard() {
|
|
|
4833
5094
|
paths: { androidDir: "android", iosDir: "ios" }
|
|
4834
5095
|
};
|
|
4835
5096
|
if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
|
|
4836
|
-
const configPath =
|
|
4837
|
-
|
|
5097
|
+
const configPath = path21.join(process.cwd(), "tamer.config.json");
|
|
5098
|
+
fs20.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4838
5099
|
const lines = [`Generated tamer.config.json at ${configPath}`];
|
|
4839
5100
|
const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
|
|
4840
5101
|
const tsconfigCandidates = lynxProject.trim() ? [
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
] : [
|
|
5102
|
+
path21.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
|
|
5103
|
+
path21.join(process.cwd(), "tsconfig.json")
|
|
5104
|
+
] : [path21.join(process.cwd(), "tsconfig.json")];
|
|
4844
5105
|
for (const tsconfigPath of tsconfigCandidates) {
|
|
4845
|
-
if (!
|
|
5106
|
+
if (!fs20.existsSync(tsconfigPath)) continue;
|
|
4846
5107
|
try {
|
|
4847
5108
|
if (fixTsconfigReferencesForBuild(tsconfigPath)) {
|
|
4848
|
-
lines.push(`Flattened ${
|
|
5109
|
+
lines.push(`Flattened ${path21.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
|
|
4849
5110
|
}
|
|
4850
5111
|
if (addTamerTypesInclude(tsconfigPath, tamerTypesInclude)) {
|
|
4851
|
-
lines.push(`Updated ${
|
|
5112
|
+
lines.push(`Updated ${path21.relative(process.cwd(), tsconfigPath)} for tamer types`);
|
|
4852
5113
|
}
|
|
4853
5114
|
break;
|
|
4854
5115
|
} catch (e) {
|
|
@@ -5008,8 +5269,8 @@ async function init() {
|
|
|
5008
5269
|
}
|
|
5009
5270
|
|
|
5010
5271
|
// src/common/create.ts
|
|
5011
|
-
import
|
|
5012
|
-
import
|
|
5272
|
+
import fs21 from "fs";
|
|
5273
|
+
import path22 from "path";
|
|
5013
5274
|
import readline from "readline";
|
|
5014
5275
|
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5015
5276
|
function ask(question) {
|
|
@@ -5071,13 +5332,13 @@ async function create3(opts) {
|
|
|
5071
5332
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
5072
5333
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
5073
5334
|
const cwd = process.cwd();
|
|
5074
|
-
const root =
|
|
5075
|
-
if (
|
|
5335
|
+
const root = path22.join(cwd, extName);
|
|
5336
|
+
if (fs21.existsSync(root)) {
|
|
5076
5337
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
5077
5338
|
rl.close();
|
|
5078
5339
|
process.exit(1);
|
|
5079
5340
|
}
|
|
5080
|
-
|
|
5341
|
+
fs21.mkdirSync(root, { recursive: true });
|
|
5081
5342
|
const lynxExt = {
|
|
5082
5343
|
platforms: {
|
|
5083
5344
|
android: {
|
|
@@ -5092,7 +5353,7 @@ async function create3(opts) {
|
|
|
5092
5353
|
web: {}
|
|
5093
5354
|
}
|
|
5094
5355
|
};
|
|
5095
|
-
|
|
5356
|
+
fs21.writeFileSync(path22.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
5096
5357
|
const pkg = {
|
|
5097
5358
|
name: extName,
|
|
5098
5359
|
version: "0.0.1",
|
|
@@ -5105,20 +5366,20 @@ async function create3(opts) {
|
|
|
5105
5366
|
engines: { node: ">=18" }
|
|
5106
5367
|
};
|
|
5107
5368
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5108
|
-
|
|
5369
|
+
fs21.writeFileSync(path22.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5109
5370
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5110
5371
|
const hasSrc = includeModule || includeElement || includeService;
|
|
5111
5372
|
if (hasSrc) {
|
|
5112
|
-
|
|
5373
|
+
fs21.mkdirSync(path22.join(root, "src"), { recursive: true });
|
|
5113
5374
|
}
|
|
5114
5375
|
if (includeModule) {
|
|
5115
|
-
|
|
5376
|
+
fs21.writeFileSync(path22.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5116
5377
|
export declare class ${simpleModuleName} {
|
|
5117
5378
|
// Add your module methods here
|
|
5118
5379
|
}
|
|
5119
5380
|
`);
|
|
5120
|
-
|
|
5121
|
-
|
|
5381
|
+
fs21.mkdirSync(path22.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
5382
|
+
fs21.writeFileSync(path22.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5122
5383
|
id("com.android.library")
|
|
5123
5384
|
id("org.jetbrains.kotlin.android")
|
|
5124
5385
|
}
|
|
@@ -5139,7 +5400,7 @@ dependencies {
|
|
|
5139
5400
|
implementation(libs.lynx.jssdk)
|
|
5140
5401
|
}
|
|
5141
5402
|
`);
|
|
5142
|
-
|
|
5403
|
+
fs21.writeFileSync(path22.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5143
5404
|
<manifest />
|
|
5144
5405
|
`);
|
|
5145
5406
|
const ktContent = `package ${packageName}
|
|
@@ -5156,8 +5417,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5156
5417
|
}
|
|
5157
5418
|
}
|
|
5158
5419
|
`;
|
|
5159
|
-
|
|
5160
|
-
|
|
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 });
|
|
5161
5422
|
const podspec = `Pod::Spec.new do |s|
|
|
5162
5423
|
s.name = '${extName}'
|
|
5163
5424
|
s.version = '0.0.1'
|
|
@@ -5171,7 +5432,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5171
5432
|
s.dependency 'Lynx'
|
|
5172
5433
|
end
|
|
5173
5434
|
`;
|
|
5174
|
-
|
|
5435
|
+
fs21.writeFileSync(path22.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5175
5436
|
const swiftContent = `import Foundation
|
|
5176
5437
|
|
|
5177
5438
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5180,18 +5441,18 @@ end
|
|
|
5180
5441
|
}
|
|
5181
5442
|
}
|
|
5182
5443
|
`;
|
|
5183
|
-
|
|
5444
|
+
fs21.writeFileSync(path22.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5184
5445
|
}
|
|
5185
5446
|
if (includeElement && !includeModule) {
|
|
5186
5447
|
const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
5187
|
-
|
|
5448
|
+
fs21.writeFileSync(path22.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
|
|
5188
5449
|
|
|
5189
5450
|
export const ${elementName}: FC = () => {
|
|
5190
5451
|
return null;
|
|
5191
5452
|
};
|
|
5192
5453
|
`);
|
|
5193
5454
|
}
|
|
5194
|
-
|
|
5455
|
+
fs21.writeFileSync(path22.join(root, "index.js"), `'use strict';
|
|
5195
5456
|
module.exports = {};
|
|
5196
5457
|
`);
|
|
5197
5458
|
const tsconfigCompiler = {
|
|
@@ -5204,11 +5465,11 @@ module.exports = {};
|
|
|
5204
5465
|
tsconfigCompiler.jsx = "preserve";
|
|
5205
5466
|
tsconfigCompiler.jsxImportSource = "@lynx-js/react";
|
|
5206
5467
|
}
|
|
5207
|
-
|
|
5468
|
+
fs21.writeFileSync(path22.join(root, "tsconfig.json"), JSON.stringify({
|
|
5208
5469
|
compilerOptions: tsconfigCompiler,
|
|
5209
5470
|
include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
|
|
5210
5471
|
}, null, 2));
|
|
5211
|
-
|
|
5472
|
+
fs21.writeFileSync(path22.join(root, "README.md"), `# ${extName}
|
|
5212
5473
|
|
|
5213
5474
|
Lynx extension for ${extName}.
|
|
5214
5475
|
|
|
@@ -5233,8 +5494,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5233
5494
|
var create_default3 = create3;
|
|
5234
5495
|
|
|
5235
5496
|
// src/common/codegen.ts
|
|
5236
|
-
import
|
|
5237
|
-
import
|
|
5497
|
+
import fs22 from "fs";
|
|
5498
|
+
import path23 from "path";
|
|
5238
5499
|
function codegen() {
|
|
5239
5500
|
const cwd = process.cwd();
|
|
5240
5501
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5242,9 +5503,9 @@ function codegen() {
|
|
|
5242
5503
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5243
5504
|
process.exit(1);
|
|
5244
5505
|
}
|
|
5245
|
-
const srcDir =
|
|
5246
|
-
const generatedDir =
|
|
5247
|
-
|
|
5506
|
+
const srcDir = path23.join(cwd, "src");
|
|
5507
|
+
const generatedDir = path23.join(cwd, "generated");
|
|
5508
|
+
fs22.mkdirSync(generatedDir, { recursive: true });
|
|
5248
5509
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5249
5510
|
const modules = extractLynxModules(dtsFiles);
|
|
5250
5511
|
if (modules.length === 0) {
|
|
@@ -5254,28 +5515,28 @@ function codegen() {
|
|
|
5254
5515
|
for (const mod of modules) {
|
|
5255
5516
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5256
5517
|
`;
|
|
5257
|
-
const outPath =
|
|
5258
|
-
|
|
5518
|
+
const outPath = path23.join(generatedDir, `${mod}.ts`);
|
|
5519
|
+
fs22.writeFileSync(outPath, tsContent);
|
|
5259
5520
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5260
5521
|
}
|
|
5261
5522
|
if (config.android) {
|
|
5262
|
-
const androidGenerated =
|
|
5263
|
-
|
|
5523
|
+
const androidGenerated = path23.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
5524
|
+
fs22.mkdirSync(androidGenerated, { recursive: true });
|
|
5264
5525
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5265
5526
|
}
|
|
5266
5527
|
if (config.ios) {
|
|
5267
|
-
const iosGenerated =
|
|
5268
|
-
|
|
5528
|
+
const iosGenerated = path23.join(cwd, "ios", "generated");
|
|
5529
|
+
fs22.mkdirSync(iosGenerated, { recursive: true });
|
|
5269
5530
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5270
5531
|
}
|
|
5271
5532
|
console.log("\u2728 Codegen complete.");
|
|
5272
5533
|
}
|
|
5273
5534
|
function findDtsFiles(dir) {
|
|
5274
5535
|
const result = [];
|
|
5275
|
-
if (!
|
|
5276
|
-
const entries =
|
|
5536
|
+
if (!fs22.existsSync(dir)) return result;
|
|
5537
|
+
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
5277
5538
|
for (const e of entries) {
|
|
5278
|
-
const full =
|
|
5539
|
+
const full = path23.join(dir, e.name);
|
|
5279
5540
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5280
5541
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5281
5542
|
}
|
|
@@ -5285,7 +5546,7 @@ function extractLynxModules(files) {
|
|
|
5285
5546
|
const modules = [];
|
|
5286
5547
|
const seen = /* @__PURE__ */ new Set();
|
|
5287
5548
|
for (const file of files) {
|
|
5288
|
-
const content =
|
|
5549
|
+
const content = fs22.readFileSync(file, "utf8");
|
|
5289
5550
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5290
5551
|
let m;
|
|
5291
5552
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5302,14 +5563,16 @@ var codegen_default = codegen;
|
|
|
5302
5563
|
// src/common/devServer.tsx
|
|
5303
5564
|
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
5304
5565
|
import { spawn } from "child_process";
|
|
5305
|
-
import
|
|
5566
|
+
import fs23 from "fs";
|
|
5306
5567
|
import http from "http";
|
|
5307
5568
|
import os4 from "os";
|
|
5308
|
-
import
|
|
5569
|
+
import path24 from "path";
|
|
5309
5570
|
import { render as render2, useInput, useApp } from "ink";
|
|
5310
|
-
import { WebSocketServer } from "ws";
|
|
5571
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
5311
5572
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
5312
5573
|
var DEFAULT_PORT = 3e3;
|
|
5574
|
+
var TAMER_CLI_VERSION = getCliVersion();
|
|
5575
|
+
var MAX_LOG_LINES = 800;
|
|
5313
5576
|
var STATIC_MIME = {
|
|
5314
5577
|
".png": "image/png",
|
|
5315
5578
|
".jpg": "image/jpeg",
|
|
@@ -5321,13 +5584,13 @@ var STATIC_MIME = {
|
|
|
5321
5584
|
".pdf": "application/pdf"
|
|
5322
5585
|
};
|
|
5323
5586
|
function sendFileFromDisk(res, absPath) {
|
|
5324
|
-
|
|
5587
|
+
fs23.readFile(absPath, (err, data) => {
|
|
5325
5588
|
if (err) {
|
|
5326
5589
|
res.writeHead(404);
|
|
5327
5590
|
res.end("Not found");
|
|
5328
5591
|
return;
|
|
5329
5592
|
}
|
|
5330
|
-
const ext =
|
|
5593
|
+
const ext = path24.extname(absPath).toLowerCase();
|
|
5331
5594
|
res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
|
|
5332
5595
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5333
5596
|
res.end(data);
|
|
@@ -5364,9 +5627,9 @@ function getLanIp() {
|
|
|
5364
5627
|
return "localhost";
|
|
5365
5628
|
}
|
|
5366
5629
|
function detectPackageManager(cwd) {
|
|
5367
|
-
const dir =
|
|
5368
|
-
if (
|
|
5369
|
-
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")))
|
|
5370
5633
|
return { cmd: "bun", args: ["run", "build"] };
|
|
5371
5634
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5372
5635
|
}
|
|
@@ -5383,7 +5646,6 @@ var initialUi = () => ({
|
|
|
5383
5646
|
buildPhase: "idle",
|
|
5384
5647
|
wsConnections: 0,
|
|
5385
5648
|
logLines: [],
|
|
5386
|
-
showLogs: false,
|
|
5387
5649
|
qrLines: []
|
|
5388
5650
|
});
|
|
5389
5651
|
function DevServerApp({ verbose }) {
|
|
@@ -5396,13 +5658,20 @@ function DevServerApp({ verbose }) {
|
|
|
5396
5658
|
const cleanupRef = useRef(null);
|
|
5397
5659
|
const rebuildRef = useRef(() => Promise.resolve());
|
|
5398
5660
|
const quitOnceRef = useRef(false);
|
|
5399
|
-
const
|
|
5400
|
-
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
5661
|
+
const appendLogLine = useCallback4((line) => {
|
|
5401
5662
|
setUi((prev) => ({
|
|
5402
5663
|
...prev,
|
|
5403
|
-
logLines: [...prev.logLines,
|
|
5664
|
+
logLines: [...prev.logLines, line].slice(-MAX_LOG_LINES)
|
|
5404
5665
|
}));
|
|
5405
5666
|
}, []);
|
|
5667
|
+
const appendLog = useCallback4(
|
|
5668
|
+
(chunk) => {
|
|
5669
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
5670
|
+
appendLogLine(line);
|
|
5671
|
+
}
|
|
5672
|
+
},
|
|
5673
|
+
[appendLogLine]
|
|
5674
|
+
);
|
|
5406
5675
|
const handleQuit = useCallback4(() => {
|
|
5407
5676
|
if (quitOnceRef.current) return;
|
|
5408
5677
|
quitOnceRef.current = true;
|
|
@@ -5410,7 +5679,7 @@ function DevServerApp({ verbose }) {
|
|
|
5410
5679
|
exit();
|
|
5411
5680
|
}, [exit]);
|
|
5412
5681
|
useInput((input, key) => {
|
|
5413
|
-
if (key.ctrl &&
|
|
5682
|
+
if (key.ctrl && input === "c") {
|
|
5414
5683
|
handleQuit();
|
|
5415
5684
|
return;
|
|
5416
5685
|
}
|
|
@@ -5422,8 +5691,9 @@ function DevServerApp({ verbose }) {
|
|
|
5422
5691
|
void rebuildRef.current();
|
|
5423
5692
|
return;
|
|
5424
5693
|
}
|
|
5425
|
-
if (input === "
|
|
5426
|
-
setUi((s) => ({ ...s,
|
|
5694
|
+
if (input === "c") {
|
|
5695
|
+
setUi((s) => ({ ...s, logLines: [] }));
|
|
5696
|
+
return;
|
|
5427
5697
|
}
|
|
5428
5698
|
});
|
|
5429
5699
|
useEffect3(() => {
|
|
@@ -5446,8 +5716,8 @@ function DevServerApp({ verbose }) {
|
|
|
5446
5716
|
try {
|
|
5447
5717
|
const resolved = resolveHostPaths();
|
|
5448
5718
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5449
|
-
const distDir =
|
|
5450
|
-
const projectName =
|
|
5719
|
+
const distDir = path24.dirname(lynxBundlePath);
|
|
5720
|
+
const projectName = path24.basename(lynxProjectDir);
|
|
5451
5721
|
const basePath = `/${projectName}`;
|
|
5452
5722
|
setUi((s) => ({ ...s, projectName, lynxBundleFile }));
|
|
5453
5723
|
const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
@@ -5457,18 +5727,18 @@ function DevServerApp({ verbose }) {
|
|
|
5457
5727
|
}
|
|
5458
5728
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5459
5729
|
let iconFilePath = null;
|
|
5460
|
-
if (iconPaths?.source &&
|
|
5730
|
+
if (iconPaths?.source && fs23.statSync(iconPaths.source).isFile()) {
|
|
5461
5731
|
iconFilePath = iconPaths.source;
|
|
5462
|
-
} else if (iconPaths?.androidAdaptiveForeground &&
|
|
5732
|
+
} else if (iconPaths?.androidAdaptiveForeground && fs23.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
|
|
5463
5733
|
iconFilePath = iconPaths.androidAdaptiveForeground;
|
|
5464
5734
|
} else if (iconPaths?.android) {
|
|
5465
|
-
const androidIcon =
|
|
5466
|
-
if (
|
|
5735
|
+
const androidIcon = path24.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
5736
|
+
if (fs23.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5467
5737
|
} else if (iconPaths?.ios) {
|
|
5468
|
-
const iosIcon =
|
|
5469
|
-
if (
|
|
5738
|
+
const iosIcon = path24.join(iconPaths.ios, "Icon-1024.png");
|
|
5739
|
+
if (fs23.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5470
5740
|
}
|
|
5471
|
-
const iconExt = iconFilePath ?
|
|
5741
|
+
const iconExt = iconFilePath ? path24.extname(iconFilePath) || ".png" : "";
|
|
5472
5742
|
const runBuild = () => {
|
|
5473
5743
|
return new Promise((resolve, reject) => {
|
|
5474
5744
|
const { cmd, args } = detectPackageManager(lynxProjectDir);
|
|
@@ -5477,19 +5747,15 @@ function DevServerApp({ verbose }) {
|
|
|
5477
5747
|
stdio: "pipe",
|
|
5478
5748
|
shell: process.platform === "win32"
|
|
5479
5749
|
});
|
|
5480
|
-
let
|
|
5481
|
-
buildProcess.stdout?.
|
|
5482
|
-
appendLog(d.toString());
|
|
5483
|
-
});
|
|
5750
|
+
let stderrRaw = "";
|
|
5751
|
+
buildProcess.stdout?.resume();
|
|
5484
5752
|
buildProcess.stderr?.on("data", (d) => {
|
|
5485
|
-
|
|
5486
|
-
stderr += t;
|
|
5487
|
-
appendLog(t);
|
|
5753
|
+
stderrRaw += d.toString();
|
|
5488
5754
|
});
|
|
5489
5755
|
buildProcess.on("close", (code) => {
|
|
5490
5756
|
buildProcess = null;
|
|
5491
5757
|
if (code === 0) resolve();
|
|
5492
|
-
else reject(new Error(
|
|
5758
|
+
else reject(new Error(stderrRaw.trim() || `Build exited ${code}`));
|
|
5493
5759
|
});
|
|
5494
5760
|
});
|
|
5495
5761
|
};
|
|
@@ -5554,7 +5820,7 @@ function DevServerApp({ verbose }) {
|
|
|
5554
5820
|
return;
|
|
5555
5821
|
}
|
|
5556
5822
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5557
|
-
|
|
5823
|
+
fs23.readFile(iconFilePath, (err, data) => {
|
|
5558
5824
|
if (err) {
|
|
5559
5825
|
res.writeHead(404);
|
|
5560
5826
|
res.end();
|
|
@@ -5580,20 +5846,20 @@ function DevServerApp({ verbose }) {
|
|
|
5580
5846
|
res.end();
|
|
5581
5847
|
return;
|
|
5582
5848
|
}
|
|
5583
|
-
const safe =
|
|
5584
|
-
if (
|
|
5849
|
+
const safe = path24.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
5850
|
+
if (path24.isAbsolute(safe) || safe.startsWith("..")) {
|
|
5585
5851
|
res.writeHead(403);
|
|
5586
5852
|
res.end();
|
|
5587
5853
|
return;
|
|
5588
5854
|
}
|
|
5589
|
-
const allowedRoot =
|
|
5590
|
-
const abs =
|
|
5591
|
-
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) {
|
|
5592
5858
|
res.writeHead(403);
|
|
5593
5859
|
res.end();
|
|
5594
5860
|
return;
|
|
5595
5861
|
}
|
|
5596
|
-
if (!
|
|
5862
|
+
if (!fs23.existsSync(abs) || !fs23.statSync(abs).isFile()) {
|
|
5597
5863
|
res.writeHead(404);
|
|
5598
5864
|
res.end("Not found");
|
|
5599
5865
|
return;
|
|
@@ -5607,14 +5873,14 @@ function DevServerApp({ verbose }) {
|
|
|
5607
5873
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5608
5874
|
}
|
|
5609
5875
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5610
|
-
const filePath =
|
|
5611
|
-
const distResolved =
|
|
5612
|
-
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) {
|
|
5613
5879
|
res.writeHead(403);
|
|
5614
5880
|
res.end();
|
|
5615
5881
|
return;
|
|
5616
5882
|
}
|
|
5617
|
-
|
|
5883
|
+
fs23.readFile(filePath, (err, data) => {
|
|
5618
5884
|
if (err) {
|
|
5619
5885
|
res.writeHead(404);
|
|
5620
5886
|
res.end("Not found");
|
|
@@ -5626,6 +5892,14 @@ function DevServerApp({ verbose }) {
|
|
|
5626
5892
|
});
|
|
5627
5893
|
});
|
|
5628
5894
|
const wssInst = new WebSocketServer({ noServer: true });
|
|
5895
|
+
const syncWsClientCount = () => {
|
|
5896
|
+
if (!alive) return;
|
|
5897
|
+
let n = 0;
|
|
5898
|
+
wssInst.clients.forEach((c) => {
|
|
5899
|
+
if (c.readyState === WebSocket.OPEN) n++;
|
|
5900
|
+
});
|
|
5901
|
+
setUi((s) => ({ ...s, wsConnections: n }));
|
|
5902
|
+
};
|
|
5629
5903
|
rebuildRef.current = async () => {
|
|
5630
5904
|
try {
|
|
5631
5905
|
await doBuild();
|
|
@@ -5647,12 +5921,15 @@ function DevServerApp({ verbose }) {
|
|
|
5647
5921
|
});
|
|
5648
5922
|
wssInst.on("connection", (ws, req) => {
|
|
5649
5923
|
const clientIp = req.socket.remoteAddress ?? "unknown";
|
|
5650
|
-
setUi((s) => ({ ...s, wsConnections: s.wsConnections + 1 }));
|
|
5651
5924
|
appendLog(`[WS] connected: ${clientIp}`);
|
|
5652
5925
|
ws.send(JSON.stringify({ type: "connected" }));
|
|
5926
|
+
syncWsClientCount();
|
|
5653
5927
|
ws.on("close", () => {
|
|
5654
|
-
setUi((s) => ({ ...s, wsConnections: Math.max(0, s.wsConnections - 1) }));
|
|
5655
5928
|
appendLog(`[WS] disconnected: ${clientIp}`);
|
|
5929
|
+
queueMicrotask(() => syncWsClientCount());
|
|
5930
|
+
});
|
|
5931
|
+
ws.on("error", () => {
|
|
5932
|
+
queueMicrotask(() => syncWsClientCount());
|
|
5656
5933
|
});
|
|
5657
5934
|
ws.on("message", (data) => {
|
|
5658
5935
|
try {
|
|
@@ -5675,10 +5952,10 @@ function DevServerApp({ verbose }) {
|
|
|
5675
5952
|
}
|
|
5676
5953
|
if (chokidar) {
|
|
5677
5954
|
const watchPaths = [
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
].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));
|
|
5682
5959
|
if (watchPaths.length > 0) {
|
|
5683
5960
|
const w = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5684
5961
|
w.on("change", async () => {
|
|
@@ -5758,10 +6035,11 @@ function DevServerApp({ verbose }) {
|
|
|
5758
6035
|
alive = false;
|
|
5759
6036
|
void cleanupRef.current?.();
|
|
5760
6037
|
};
|
|
5761
|
-
}, [appendLog, verbose]);
|
|
6038
|
+
}, [appendLog, appendLogLine, verbose]);
|
|
5762
6039
|
return /* @__PURE__ */ jsx10(
|
|
5763
6040
|
ServerDashboard,
|
|
5764
6041
|
{
|
|
6042
|
+
cliVersion: TAMER_CLI_VERSION,
|
|
5765
6043
|
projectName: ui.projectName,
|
|
5766
6044
|
port: ui.port,
|
|
5767
6045
|
lanIp: ui.lanIp,
|
|
@@ -5774,7 +6052,6 @@ function DevServerApp({ verbose }) {
|
|
|
5774
6052
|
buildError: ui.buildError,
|
|
5775
6053
|
wsConnections: ui.wsConnections,
|
|
5776
6054
|
logLines: ui.logLines,
|
|
5777
|
-
showLogs: ui.showLogs,
|
|
5778
6055
|
qrLines: ui.qrLines,
|
|
5779
6056
|
phase: ui.phase,
|
|
5780
6057
|
startError: ui.startError
|
|
@@ -5798,10 +6075,10 @@ async function start(opts) {
|
|
|
5798
6075
|
var start_default = start;
|
|
5799
6076
|
|
|
5800
6077
|
// src/common/injectHost.ts
|
|
5801
|
-
import
|
|
5802
|
-
import
|
|
6078
|
+
import fs24 from "fs";
|
|
6079
|
+
import path25 from "path";
|
|
5803
6080
|
function readAndSubstitute(templatePath, vars) {
|
|
5804
|
-
const raw =
|
|
6081
|
+
const raw = fs24.readFileSync(templatePath, "utf-8");
|
|
5805
6082
|
return Object.entries(vars).reduce(
|
|
5806
6083
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5807
6084
|
raw
|
|
@@ -5822,32 +6099,32 @@ async function injectHostAndroid(opts) {
|
|
|
5822
6099
|
process.exit(1);
|
|
5823
6100
|
}
|
|
5824
6101
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5825
|
-
const rootDir =
|
|
6102
|
+
const rootDir = path25.join(projectRoot, androidDir);
|
|
5826
6103
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5827
|
-
const javaDir =
|
|
5828
|
-
const kotlinDir =
|
|
5829
|
-
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)) {
|
|
5830
6107
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5831
6108
|
process.exit(1);
|
|
5832
6109
|
}
|
|
5833
|
-
const templateDir =
|
|
6110
|
+
const templateDir = path25.join(hostPkg, "android", "templates");
|
|
5834
6111
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5835
6112
|
const files = [
|
|
5836
|
-
{ src: "App.java", dst:
|
|
5837
|
-
{ src: "TemplateProvider.java", dst:
|
|
5838
|
-
{ 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") }
|
|
5839
6116
|
];
|
|
5840
6117
|
for (const { src, dst } of files) {
|
|
5841
|
-
const srcPath =
|
|
5842
|
-
if (!
|
|
5843
|
-
if (
|
|
5844
|
-
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)`);
|
|
5845
6122
|
continue;
|
|
5846
6123
|
}
|
|
5847
6124
|
const content = readAndSubstitute(srcPath, vars);
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
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)}`);
|
|
5851
6128
|
}
|
|
5852
6129
|
}
|
|
5853
6130
|
async function injectHostIos(opts) {
|
|
@@ -5865,13 +6142,13 @@ async function injectHostIos(opts) {
|
|
|
5865
6142
|
process.exit(1);
|
|
5866
6143
|
}
|
|
5867
6144
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5868
|
-
const rootDir =
|
|
5869
|
-
const projectDir =
|
|
5870
|
-
if (!
|
|
6145
|
+
const rootDir = path25.join(projectRoot, iosDir);
|
|
6146
|
+
const projectDir = path25.join(rootDir, appName);
|
|
6147
|
+
if (!fs24.existsSync(projectDir)) {
|
|
5871
6148
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5872
6149
|
process.exit(1);
|
|
5873
6150
|
}
|
|
5874
|
-
const templateDir =
|
|
6151
|
+
const templateDir = path25.join(hostPkg, "ios", "templates");
|
|
5875
6152
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5876
6153
|
const files = [
|
|
5877
6154
|
"AppDelegate.swift",
|
|
@@ -5881,22 +6158,22 @@ async function injectHostIos(opts) {
|
|
|
5881
6158
|
"LynxInitProcessor.swift"
|
|
5882
6159
|
];
|
|
5883
6160
|
for (const f of files) {
|
|
5884
|
-
const srcPath =
|
|
5885
|
-
const dstPath =
|
|
5886
|
-
if (!
|
|
5887
|
-
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) {
|
|
5888
6165
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5889
6166
|
continue;
|
|
5890
6167
|
}
|
|
5891
6168
|
const content = readAndSubstitute(srcPath, vars);
|
|
5892
|
-
|
|
6169
|
+
fs24.writeFileSync(dstPath, content);
|
|
5893
6170
|
console.log(`\u2705 Injected ${f}`);
|
|
5894
6171
|
}
|
|
5895
6172
|
}
|
|
5896
6173
|
|
|
5897
6174
|
// src/common/buildEmbeddable.ts
|
|
5898
|
-
import
|
|
5899
|
-
import
|
|
6175
|
+
import fs25 from "fs";
|
|
6176
|
+
import path26 from "path";
|
|
5900
6177
|
import { execSync as execSync9 } from "child_process";
|
|
5901
6178
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5902
6179
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
@@ -5973,14 +6250,14 @@ object LynxEmbeddable {
|
|
|
5973
6250
|
}
|
|
5974
6251
|
`;
|
|
5975
6252
|
function generateAndroidLibrary(outDir, androidDir, projectRoot, lynxBundlePath, lynxBundleFile, modules, abiFilters) {
|
|
5976
|
-
const libDir =
|
|
5977
|
-
const libSrcMain =
|
|
5978
|
-
const assetsDir =
|
|
5979
|
-
const kotlinDir =
|
|
5980
|
-
const generatedDir =
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
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 });
|
|
5984
6261
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5985
6262
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5986
6263
|
const settingsContent = `pluginManagement {
|
|
@@ -6000,7 +6277,7 @@ include(":lib")
|
|
|
6000
6277
|
${androidModules.map((p) => {
|
|
6001
6278
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
6002
6279
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
6003
|
-
const absPath =
|
|
6280
|
+
const absPath = path26.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
6004
6281
|
return `include(":${gradleName}")
|
|
6005
6282
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
6006
6283
|
}).join("\n")}
|
|
@@ -6049,10 +6326,10 @@ dependencies {
|
|
|
6049
6326
|
${libDeps}
|
|
6050
6327
|
}
|
|
6051
6328
|
`;
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
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"),
|
|
6056
6333
|
`plugins {
|
|
6057
6334
|
alias(libs.plugins.android.library) apply false
|
|
6058
6335
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -6060,26 +6337,26 @@ ${libDeps}
|
|
|
6060
6337
|
}
|
|
6061
6338
|
`
|
|
6062
6339
|
);
|
|
6063
|
-
|
|
6064
|
-
|
|
6340
|
+
fs25.writeFileSync(
|
|
6341
|
+
path26.join(androidDir, "gradle.properties"),
|
|
6065
6342
|
`org.gradle.jvmargs=-Xmx2048m
|
|
6066
6343
|
android.useAndroidX=true
|
|
6067
6344
|
kotlin.code.style=official
|
|
6068
6345
|
`
|
|
6069
6346
|
);
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6347
|
+
fs25.writeFileSync(path26.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
6348
|
+
fs25.writeFileSync(
|
|
6349
|
+
path26.join(libSrcMain, "AndroidManifest.xml"),
|
|
6073
6350
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
6074
6351
|
);
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
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"),
|
|
6079
6356
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
6080
6357
|
);
|
|
6081
|
-
|
|
6082
|
-
|
|
6358
|
+
fs25.writeFileSync(
|
|
6359
|
+
path26.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
6083
6360
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
6084
6361
|
);
|
|
6085
6362
|
}
|
|
@@ -6088,20 +6365,20 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6088
6365
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
6089
6366
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
6090
6367
|
execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
6091
|
-
if (!
|
|
6368
|
+
if (!fs25.existsSync(lynxBundlePath)) {
|
|
6092
6369
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
6093
6370
|
process.exit(1);
|
|
6094
6371
|
}
|
|
6095
|
-
const outDir =
|
|
6096
|
-
|
|
6097
|
-
const distDir =
|
|
6372
|
+
const outDir = path26.join(projectRoot, EMBEDDABLE_DIR);
|
|
6373
|
+
fs25.mkdirSync(outDir, { recursive: true });
|
|
6374
|
+
const distDir = path26.dirname(lynxBundlePath);
|
|
6098
6375
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
6099
6376
|
const modules = discoverModules(projectRoot);
|
|
6100
6377
|
const androidModules = modules.filter((m) => m.config.android);
|
|
6101
6378
|
const abiFilters = resolveAbiFilters(config);
|
|
6102
|
-
const androidDir =
|
|
6103
|
-
if (
|
|
6104
|
-
|
|
6379
|
+
const androidDir = path26.join(outDir, "android");
|
|
6380
|
+
if (fs25.existsSync(androidDir)) fs25.rmSync(androidDir, { recursive: true });
|
|
6381
|
+
fs25.mkdirSync(androidDir, { recursive: true });
|
|
6105
6382
|
generateAndroidLibrary(
|
|
6106
6383
|
outDir,
|
|
6107
6384
|
androidDir,
|
|
@@ -6111,23 +6388,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6111
6388
|
modules,
|
|
6112
6389
|
abiFilters
|
|
6113
6390
|
);
|
|
6114
|
-
const gradlewPath =
|
|
6391
|
+
const gradlewPath = path26.join(androidDir, "gradlew");
|
|
6115
6392
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
6116
6393
|
const existingGradleDirs = [
|
|
6117
|
-
|
|
6118
|
-
devAppDir ?
|
|
6394
|
+
path26.join(projectRoot, "android"),
|
|
6395
|
+
devAppDir ? path26.join(devAppDir, "android") : null
|
|
6119
6396
|
].filter(Boolean);
|
|
6120
6397
|
let hasWrapper = false;
|
|
6121
6398
|
for (const d of existingGradleDirs) {
|
|
6122
|
-
if (
|
|
6399
|
+
if (fs25.existsSync(path26.join(d, "gradlew"))) {
|
|
6123
6400
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
6124
|
-
const src =
|
|
6125
|
-
if (
|
|
6126
|
-
const dest =
|
|
6127
|
-
if (
|
|
6128
|
-
|
|
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 });
|
|
6129
6406
|
} else {
|
|
6130
|
-
|
|
6407
|
+
fs25.copyFileSync(src, dest);
|
|
6131
6408
|
}
|
|
6132
6409
|
}
|
|
6133
6410
|
}
|
|
@@ -6146,10 +6423,10 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6146
6423
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6147
6424
|
throw e;
|
|
6148
6425
|
}
|
|
6149
|
-
const aarSrc =
|
|
6150
|
-
const aarDest =
|
|
6151
|
-
if (
|
|
6152
|
-
|
|
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);
|
|
6153
6430
|
console.log(` - tamer-embeddable.aar`);
|
|
6154
6431
|
}
|
|
6155
6432
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6160,7 +6437,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6160
6437
|
// LynxEmbeddable.init(applicationContext)
|
|
6161
6438
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6162
6439
|
`;
|
|
6163
|
-
|
|
6440
|
+
fs25.writeFileSync(path26.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6164
6441
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6165
6442
|
const readme = `# Embeddable Lynx Bundle
|
|
6166
6443
|
|
|
@@ -6191,7 +6468,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6191
6468
|
|
|
6192
6469
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6193
6470
|
`;
|
|
6194
|
-
|
|
6471
|
+
fs25.writeFileSync(path26.join(outDir, "README.md"), readme);
|
|
6195
6472
|
console.log(`
|
|
6196
6473
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6197
6474
|
console.log(" - main.lynx.bundle");
|
|
@@ -6203,20 +6480,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6203
6480
|
console.log(" - README.md");
|
|
6204
6481
|
}
|
|
6205
6482
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6206
|
-
const iosDir =
|
|
6207
|
-
const podDir =
|
|
6208
|
-
const resourcesDir =
|
|
6209
|
-
|
|
6210
|
-
|
|
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));
|
|
6211
6488
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6212
6489
|
const podDeps = iosModules.map((p) => {
|
|
6213
6490
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6214
|
-
const podspecDir =
|
|
6215
|
-
if (!
|
|
6216
|
-
const files =
|
|
6491
|
+
const podspecDir = path26.join(p.packagePath, podspecPath);
|
|
6492
|
+
if (!fs25.existsSync(podspecDir)) return null;
|
|
6493
|
+
const files = fs25.readdirSync(podspecDir);
|
|
6217
6494
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6218
6495
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6219
|
-
const absPath =
|
|
6496
|
+
const absPath = path26.resolve(podspecDir);
|
|
6220
6497
|
return { podName, absPath };
|
|
6221
6498
|
}).filter(Boolean);
|
|
6222
6499
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6256,9 +6533,9 @@ end
|
|
|
6256
6533
|
});
|
|
6257
6534
|
const swiftImports = iosModules.map((p) => {
|
|
6258
6535
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6259
|
-
const podspecDir =
|
|
6260
|
-
if (!
|
|
6261
|
-
const files =
|
|
6536
|
+
const podspecDir = path26.join(p.packagePath, podspecPath);
|
|
6537
|
+
if (!fs25.existsSync(podspecDir)) return null;
|
|
6538
|
+
const files = fs25.readdirSync(podspecDir);
|
|
6262
6539
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6263
6540
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6264
6541
|
}).filter(Boolean);
|
|
@@ -6277,17 +6554,17 @@ ${regBlock}
|
|
|
6277
6554
|
}
|
|
6278
6555
|
}
|
|
6279
6556
|
`;
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
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);
|
|
6283
6560
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6284
6561
|
|
|
6285
6562
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6286
6563
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6287
6564
|
`;
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6565
|
+
fs25.writeFileSync(path26.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
6566
|
+
fs25.writeFileSync(
|
|
6567
|
+
path26.join(outDir, "snippet-ios.swift"),
|
|
6291
6568
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6292
6569
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6293
6570
|
`
|
|
@@ -6295,8 +6572,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6295
6572
|
}
|
|
6296
6573
|
|
|
6297
6574
|
// src/common/add.ts
|
|
6298
|
-
import
|
|
6299
|
-
import
|
|
6575
|
+
import fs26 from "fs";
|
|
6576
|
+
import path27 from "path";
|
|
6300
6577
|
import { execFile, execSync as execSync10 } from "child_process";
|
|
6301
6578
|
import { promisify } from "util";
|
|
6302
6579
|
import semver from "semver";
|
|
@@ -6311,21 +6588,15 @@ var CORE_PACKAGES = [
|
|
|
6311
6588
|
"@tamer4lynx/tamer-icons"
|
|
6312
6589
|
];
|
|
6313
6590
|
var DEV_STACK_PACKAGES = [
|
|
6314
|
-
"@tamer4lynx/jiggle",
|
|
6315
|
-
"@tamer4lynx/tamer-app-shell",
|
|
6316
|
-
"@tamer4lynx/tamer-biometric",
|
|
6317
6591
|
"@tamer4lynx/tamer-dev-app",
|
|
6318
6592
|
"@tamer4lynx/tamer-dev-client",
|
|
6319
|
-
"@tamer4lynx/tamer-
|
|
6593
|
+
"@tamer4lynx/tamer-app-shell",
|
|
6320
6594
|
"@tamer4lynx/tamer-icons",
|
|
6321
6595
|
"@tamer4lynx/tamer-insets",
|
|
6322
|
-
"@tamer4lynx/tamer-linking",
|
|
6323
6596
|
"@tamer4lynx/tamer-plugin",
|
|
6324
6597
|
"@tamer4lynx/tamer-router",
|
|
6325
6598
|
"@tamer4lynx/tamer-screen",
|
|
6326
|
-
"@tamer4lynx/tamer-
|
|
6327
|
-
"@tamer4lynx/tamer-system-ui",
|
|
6328
|
-
"@tamer4lynx/tamer-transports"
|
|
6599
|
+
"@tamer4lynx/tamer-system-ui"
|
|
6329
6600
|
];
|
|
6330
6601
|
var PACKAGE_ALIASES = {};
|
|
6331
6602
|
async function getHighestPublishedVersion(fullName) {
|
|
@@ -6353,9 +6624,9 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
6353
6624
|
return `${pkg}@prerelease`;
|
|
6354
6625
|
}
|
|
6355
6626
|
function detectPackageManager2(cwd) {
|
|
6356
|
-
const dir =
|
|
6357
|
-
if (
|
|
6358
|
-
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";
|
|
6359
6630
|
return "npm";
|
|
6360
6631
|
}
|
|
6361
6632
|
function runInstall(cwd, packages, pm) {
|
|
@@ -6407,13 +6678,13 @@ async function add(packages = []) {
|
|
|
6407
6678
|
// src/common/signing.tsx
|
|
6408
6679
|
import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
6409
6680
|
import { render as render3, Text as Text10, Box as Box9 } from "ink";
|
|
6410
|
-
import
|
|
6411
|
-
import
|
|
6681
|
+
import fs29 from "fs";
|
|
6682
|
+
import path30 from "path";
|
|
6412
6683
|
|
|
6413
6684
|
// src/common/androidKeystore.ts
|
|
6414
6685
|
import { execFileSync } from "child_process";
|
|
6415
|
-
import
|
|
6416
|
-
import
|
|
6686
|
+
import fs27 from "fs";
|
|
6687
|
+
import path28 from "path";
|
|
6417
6688
|
function normalizeJavaHome(raw) {
|
|
6418
6689
|
if (!raw) return void 0;
|
|
6419
6690
|
const t = raw.trim().replace(/^["']|["']$/g, "");
|
|
@@ -6426,7 +6697,7 @@ function discoverJavaHomeMacOs() {
|
|
|
6426
6697
|
encoding: "utf8",
|
|
6427
6698
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6428
6699
|
}).trim().split("\n")[0]?.trim();
|
|
6429
|
-
if (out &&
|
|
6700
|
+
if (out && fs27.existsSync(path28.join(out, "bin", "keytool"))) return out;
|
|
6430
6701
|
} catch {
|
|
6431
6702
|
}
|
|
6432
6703
|
return void 0;
|
|
@@ -6436,13 +6707,13 @@ function resolveKeytoolPath() {
|
|
|
6436
6707
|
const win = process.platform === "win32";
|
|
6437
6708
|
const keytoolName = win ? "keytool.exe" : "keytool";
|
|
6438
6709
|
if (jh) {
|
|
6439
|
-
const p =
|
|
6440
|
-
if (
|
|
6710
|
+
const p = path28.join(jh, "bin", keytoolName);
|
|
6711
|
+
if (fs27.existsSync(p)) return p;
|
|
6441
6712
|
}
|
|
6442
6713
|
const mac = discoverJavaHomeMacOs();
|
|
6443
6714
|
if (mac) {
|
|
6444
|
-
const p =
|
|
6445
|
-
if (
|
|
6715
|
+
const p = path28.join(mac, "bin", keytoolName);
|
|
6716
|
+
if (fs27.existsSync(p)) return p;
|
|
6446
6717
|
}
|
|
6447
6718
|
return "keytool";
|
|
6448
6719
|
}
|
|
@@ -6457,16 +6728,16 @@ function keytoolAvailable() {
|
|
|
6457
6728
|
};
|
|
6458
6729
|
if (tryRun("keytool")) return true;
|
|
6459
6730
|
const fromJavaHome = resolveKeytoolPath();
|
|
6460
|
-
if (fromJavaHome !== "keytool" &&
|
|
6731
|
+
if (fromJavaHome !== "keytool" && fs27.existsSync(fromJavaHome)) {
|
|
6461
6732
|
return tryRun(fromJavaHome);
|
|
6462
6733
|
}
|
|
6463
6734
|
return false;
|
|
6464
6735
|
}
|
|
6465
6736
|
function generateReleaseKeystore(opts) {
|
|
6466
6737
|
const keytool = resolveKeytoolPath();
|
|
6467
|
-
const dir =
|
|
6468
|
-
|
|
6469
|
-
if (
|
|
6738
|
+
const dir = path28.dirname(opts.keystoreAbsPath);
|
|
6739
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
6740
|
+
if (fs27.existsSync(opts.keystoreAbsPath)) {
|
|
6470
6741
|
throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
|
|
6471
6742
|
}
|
|
6472
6743
|
if (!opts.storePassword || !opts.keyPassword) {
|
|
@@ -6504,13 +6775,13 @@ function generateReleaseKeystore(opts) {
|
|
|
6504
6775
|
}
|
|
6505
6776
|
|
|
6506
6777
|
// src/common/appendEnvFile.ts
|
|
6507
|
-
import
|
|
6508
|
-
import
|
|
6778
|
+
import fs28 from "fs";
|
|
6779
|
+
import path29 from "path";
|
|
6509
6780
|
import { parse } from "dotenv";
|
|
6510
6781
|
function keysDefinedInFile(filePath) {
|
|
6511
|
-
if (!
|
|
6782
|
+
if (!fs28.existsSync(filePath)) return /* @__PURE__ */ new Set();
|
|
6512
6783
|
try {
|
|
6513
|
-
return new Set(Object.keys(parse(
|
|
6784
|
+
return new Set(Object.keys(parse(fs28.readFileSync(filePath, "utf8"))));
|
|
6514
6785
|
} catch {
|
|
6515
6786
|
return /* @__PURE__ */ new Set();
|
|
6516
6787
|
}
|
|
@@ -6525,11 +6796,11 @@ function formatEnvLine(key, value) {
|
|
|
6525
6796
|
function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
6526
6797
|
const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
|
|
6527
6798
|
if (entries.length === 0) return null;
|
|
6528
|
-
const envLocal =
|
|
6529
|
-
const envDefault =
|
|
6799
|
+
const envLocal = path29.join(projectRoot, ".env.local");
|
|
6800
|
+
const envDefault = path29.join(projectRoot, ".env");
|
|
6530
6801
|
let target;
|
|
6531
|
-
if (
|
|
6532
|
-
else if (
|
|
6802
|
+
if (fs28.existsSync(envLocal)) target = envLocal;
|
|
6803
|
+
else if (fs28.existsSync(envDefault)) target = envDefault;
|
|
6533
6804
|
else target = envLocal;
|
|
6534
6805
|
const existing = keysDefinedInFile(target);
|
|
6535
6806
|
const lines = [];
|
|
@@ -6541,20 +6812,20 @@ function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
|
6541
6812
|
}
|
|
6542
6813
|
if (lines.length === 0) {
|
|
6543
6814
|
return {
|
|
6544
|
-
file:
|
|
6815
|
+
file: path29.basename(target),
|
|
6545
6816
|
keys: [],
|
|
6546
6817
|
skippedAll: entries.length > 0
|
|
6547
6818
|
};
|
|
6548
6819
|
}
|
|
6549
6820
|
let prefix = "";
|
|
6550
|
-
if (
|
|
6551
|
-
const cur =
|
|
6821
|
+
if (fs28.existsSync(target)) {
|
|
6822
|
+
const cur = fs28.readFileSync(target, "utf8");
|
|
6552
6823
|
prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
|
|
6553
6824
|
`;
|
|
6554
6825
|
}
|
|
6555
6826
|
const block = lines.join("\n") + "\n";
|
|
6556
|
-
|
|
6557
|
-
return { file:
|
|
6827
|
+
fs28.writeFileSync(target, prefix + block, "utf8");
|
|
6828
|
+
return { file: path29.basename(target), keys: appendedKeys };
|
|
6558
6829
|
}
|
|
6559
6830
|
|
|
6560
6831
|
// src/common/signing.tsx
|
|
@@ -6660,7 +6931,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6660
6931
|
try {
|
|
6661
6932
|
const resolved = resolveHostPaths();
|
|
6662
6933
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6663
|
-
abs =
|
|
6934
|
+
abs = path30.isAbsolute(rel) ? rel : path30.join(resolved.projectRoot, rel);
|
|
6664
6935
|
const alias = state.android.keyAlias.trim() || "release";
|
|
6665
6936
|
const pw = state.android.genPassword;
|
|
6666
6937
|
const pkg = resolved.config.android?.packageName ?? "com.example.app";
|
|
@@ -6687,7 +6958,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6687
6958
|
}));
|
|
6688
6959
|
} catch (e) {
|
|
6689
6960
|
const msg = e.message;
|
|
6690
|
-
if (abs &&
|
|
6961
|
+
if (abs && fs29.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
|
|
6691
6962
|
if (cancelled || runId !== generateRunId.current) return;
|
|
6692
6963
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6693
6964
|
const alias = state.android.keyAlias.trim() || "release";
|
|
@@ -6727,11 +6998,11 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6727
6998
|
const saveConfig = async () => {
|
|
6728
6999
|
try {
|
|
6729
7000
|
const resolved = resolveHostPaths();
|
|
6730
|
-
const configPath =
|
|
7001
|
+
const configPath = path30.join(resolved.projectRoot, "tamer.config.json");
|
|
6731
7002
|
let config = {};
|
|
6732
7003
|
let androidEnvAppend = null;
|
|
6733
|
-
if (
|
|
6734
|
-
config = JSON.parse(
|
|
7004
|
+
if (fs29.existsSync(configPath)) {
|
|
7005
|
+
config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
|
|
6735
7006
|
}
|
|
6736
7007
|
if (state.platform === "android" || state.platform === "both") {
|
|
6737
7008
|
config.android = config.android || {};
|
|
@@ -6758,10 +7029,10 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6758
7029
|
...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
|
|
6759
7030
|
};
|
|
6760
7031
|
}
|
|
6761
|
-
|
|
6762
|
-
const gitignorePath =
|
|
6763
|
-
if (
|
|
6764
|
-
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");
|
|
6765
7036
|
const additions = [
|
|
6766
7037
|
".env.local",
|
|
6767
7038
|
"*.jks",
|
|
@@ -6774,7 +7045,7 @@ ${addition}
|
|
|
6774
7045
|
`;
|
|
6775
7046
|
}
|
|
6776
7047
|
}
|
|
6777
|
-
|
|
7048
|
+
fs29.writeFileSync(gitignorePath, gitignore);
|
|
6778
7049
|
}
|
|
6779
7050
|
setState((s) => ({
|
|
6780
7051
|
...s,
|
|
@@ -7032,13 +7303,13 @@ async function signing(platform) {
|
|
|
7032
7303
|
}
|
|
7033
7304
|
|
|
7034
7305
|
// src/common/productionSigning.ts
|
|
7035
|
-
import
|
|
7036
|
-
import
|
|
7306
|
+
import fs30 from "fs";
|
|
7307
|
+
import path31 from "path";
|
|
7037
7308
|
function isAndroidSigningConfigured(resolved) {
|
|
7038
7309
|
const signing2 = resolved.config.android?.signing;
|
|
7039
7310
|
const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
|
|
7040
|
-
const signingProps =
|
|
7041
|
-
const hasProps =
|
|
7311
|
+
const signingProps = path31.join(resolved.androidDir, "signing.properties");
|
|
7312
|
+
const hasProps = fs30.existsSync(signingProps);
|
|
7042
7313
|
return hasConfig || hasProps;
|
|
7043
7314
|
}
|
|
7044
7315
|
function isIosSigningConfigured(resolved) {
|
|
@@ -7068,14 +7339,7 @@ function assertProductionSigningReady(filter) {
|
|
|
7068
7339
|
}
|
|
7069
7340
|
|
|
7070
7341
|
// index.ts
|
|
7071
|
-
|
|
7072
|
-
const root = path30.dirname(fileURLToPath(import.meta.url));
|
|
7073
|
-
const here = path30.join(root, "package.json");
|
|
7074
|
-
const parent = path30.join(root, "..", "package.json");
|
|
7075
|
-
const pkgPath = fs29.existsSync(here) ? here : parent;
|
|
7076
|
-
return JSON.parse(fs29.readFileSync(pkgPath, "utf8")).version;
|
|
7077
|
-
}
|
|
7078
|
-
var version = readCliVersion();
|
|
7342
|
+
var version = getCliVersion();
|
|
7079
7343
|
function validateBuildMode(debug, release, production) {
|
|
7080
7344
|
const modes = [debug, release, production].filter(Boolean).length;
|
|
7081
7345
|
if (modes > 1) {
|
|
@@ -7114,7 +7378,10 @@ program.command("create <target>").description("Create a project or extension. T
|
|
|
7114
7378
|
console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
|
|
7115
7379
|
process.exit(1);
|
|
7116
7380
|
});
|
|
7117
|
-
program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option(
|
|
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(
|
|
7382
|
+
"-i, --install",
|
|
7383
|
+
"Install after build (iOS: simulator with -d; connected device with -p)"
|
|
7384
|
+
).option("-C, --clean", "Run Gradle clean before Android build (fixes stubborn caches)").action(async (platform, opts) => {
|
|
7118
7385
|
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7119
7386
|
const release = opts.release === true || opts.production === true;
|
|
7120
7387
|
const production = opts.production === true;
|
|
@@ -7127,7 +7394,7 @@ program.command("build [platform]").description("Build app. Platform: ios | andr
|
|
|
7127
7394
|
assertProductionSigningReady(p);
|
|
7128
7395
|
}
|
|
7129
7396
|
if (p === "android" || p === "all") {
|
|
7130
|
-
await build_default({ install: opts.install, release, production });
|
|
7397
|
+
await build_default({ install: opts.install, release, production, clean: opts.clean });
|
|
7131
7398
|
}
|
|
7132
7399
|
if (p === "ios" || p === "all") {
|
|
7133
7400
|
await build_default2({ install: opts.install, release, production });
|
|
@@ -7216,7 +7483,7 @@ program.command("signing [platform]").description("Configure Android and iOS sig
|
|
|
7216
7483
|
program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
|
|
7217
7484
|
codegen_default();
|
|
7218
7485
|
});
|
|
7219
|
-
program.command("android <subcommand>").description("(Legacy) Use: t4l <command> android. e.g. t4l create android").option("-d, --debug", "Create: host project. Bundle/build: debug with dev client.").option("-r, --release", "Create: dev-app project. Bundle/build: release without dev client.").option("-p, --production", "Bundle/build: production for app store (signed)").option("-i, --install", "Install after build").option("-e, --embeddable", "Build embeddable").option("-f, --force", "Force (inject)").action(async (subcommand, opts) => {
|
|
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) => {
|
|
7220
7487
|
const sub = subcommand?.toLowerCase();
|
|
7221
7488
|
if (sub === "create") {
|
|
7222
7489
|
if (opts.debug && opts.release) {
|
|
@@ -7242,7 +7509,12 @@ program.command("android <subcommand>").description("(Legacy) Use: t4l <command>
|
|
|
7242
7509
|
if (opts.embeddable) await buildEmbeddable({ release: true });
|
|
7243
7510
|
else {
|
|
7244
7511
|
if (opts.production === true) assertProductionSigningReady("android");
|
|
7245
|
-
await build_default({
|
|
7512
|
+
await build_default({
|
|
7513
|
+
install: opts.install,
|
|
7514
|
+
release,
|
|
7515
|
+
production: opts.production === true,
|
|
7516
|
+
clean: opts.clean
|
|
7517
|
+
});
|
|
7246
7518
|
}
|
|
7247
7519
|
return;
|
|
7248
7520
|
}
|
|
@@ -7291,10 +7563,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
7291
7563
|
process.exit(1);
|
|
7292
7564
|
});
|
|
7293
7565
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
7294
|
-
const configPath =
|
|
7566
|
+
const configPath = path32.join(process.cwd(), "tamer.config.json");
|
|
7295
7567
|
let config = {};
|
|
7296
|
-
if (
|
|
7297
|
-
config = JSON.parse(
|
|
7568
|
+
if (fs31.existsSync(configPath)) {
|
|
7569
|
+
config = JSON.parse(fs31.readFileSync(configPath, "utf8"));
|
|
7298
7570
|
}
|
|
7299
7571
|
if (config.autolink) {
|
|
7300
7572
|
delete config.autolink;
|
|
@@ -7303,7 +7575,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
7303
7575
|
config.autolink = true;
|
|
7304
7576
|
console.log("Autolink enabled in tamer.config.json");
|
|
7305
7577
|
}
|
|
7306
|
-
|
|
7578
|
+
fs31.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
7307
7579
|
console.log(`Updated ${configPath}`);
|
|
7308
7580
|
});
|
|
7309
7581
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|