@tamer4lynx/cli 0.0.16 → 0.0.18
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 +963 -765
- package/package.json +2 -1
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 fs30 from "fs";
|
|
11
|
+
import path31 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 fs8 from "fs";
|
|
1585
|
+
import path8 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) {
|
|
@@ -1939,11 +2019,11 @@ var autolink = (opts) => {
|
|
|
1939
2019
|
const packageName = config.android.packageName;
|
|
1940
2020
|
const projectRoot = resolved.projectRoot;
|
|
1941
2021
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
1942
|
-
if (!
|
|
2022
|
+
if (!fs8.existsSync(filePath)) {
|
|
1943
2023
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
1944
2024
|
return;
|
|
1945
2025
|
}
|
|
1946
|
-
let fileContent =
|
|
2026
|
+
let fileContent = fs8.readFileSync(filePath, "utf8");
|
|
1947
2027
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1948
2028
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1949
2029
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -1953,16 +2033,16 @@ ${endMarker}`;
|
|
|
1953
2033
|
if (regex.test(fileContent)) {
|
|
1954
2034
|
fileContent = fileContent.replace(regex, replacementBlock);
|
|
1955
2035
|
} else {
|
|
1956
|
-
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${
|
|
2036
|
+
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path8.basename(filePath)}. Appending to the end of the file.`);
|
|
1957
2037
|
fileContent += `
|
|
1958
2038
|
${replacementBlock}
|
|
1959
2039
|
`;
|
|
1960
2040
|
}
|
|
1961
|
-
|
|
1962
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
2041
|
+
fs8.writeFileSync(filePath, fileContent);
|
|
2042
|
+
console.log(`\u2705 Updated autolinked section in ${path8.basename(filePath)}`);
|
|
1963
2043
|
}
|
|
1964
2044
|
function updateSettingsGradle(packages) {
|
|
1965
|
-
const settingsFilePath =
|
|
2045
|
+
const settingsFilePath = path8.join(appAndroidPath, "settings.gradle.kts");
|
|
1966
2046
|
let scriptContent = `// This section is automatically generated by Tamer4Lynx.
|
|
1967
2047
|
// Manual edits will be overwritten.`;
|
|
1968
2048
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
@@ -1970,7 +2050,7 @@ ${replacementBlock}
|
|
|
1970
2050
|
androidPackages.forEach((pkg) => {
|
|
1971
2051
|
const gradleProjectName = pkg.name.replace(/^@/, "").replace(/\//g, "_");
|
|
1972
2052
|
const sourceDir = pkg.config.android?.sourceDir || "android";
|
|
1973
|
-
const projectPath =
|
|
2053
|
+
const projectPath = path8.resolve(pkg.packagePath, sourceDir).replace(/\\/g, "/");
|
|
1974
2054
|
scriptContent += `
|
|
1975
2055
|
include(":${gradleProjectName}")`;
|
|
1976
2056
|
scriptContent += `
|
|
@@ -1983,7 +2063,7 @@ println("No native modules found by Tamer4Lynx autolinker.")`;
|
|
|
1983
2063
|
updateGeneratedSection(settingsFilePath, scriptContent.trim(), "// GENERATED AUTOLINK START", "// GENERATED AUTOLINK END");
|
|
1984
2064
|
}
|
|
1985
2065
|
function updateAppBuildGradle(packages) {
|
|
1986
|
-
const appBuildGradlePath =
|
|
2066
|
+
const appBuildGradlePath = path8.join(appAndroidPath, "app", "build.gradle.kts");
|
|
1987
2067
|
const androidPackages = packages.filter((p) => p.config.android);
|
|
1988
2068
|
const implementationLines = androidPackages.map((p) => {
|
|
1989
2069
|
const gradleProjectName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
@@ -2001,35 +2081,35 @@ ${implementationLines || " // No native dependencies found to link."}`;
|
|
|
2001
2081
|
}
|
|
2002
2082
|
function generateKotlinExtensionsFile(packages, projectPackage) {
|
|
2003
2083
|
const packagePath = projectPackage.replace(/\./g, "/");
|
|
2004
|
-
const generatedDir =
|
|
2005
|
-
const kotlinExtensionsPath =
|
|
2084
|
+
const generatedDir = path8.join(appAndroidPath, "app", "src", "main", "kotlin", packagePath, "generated");
|
|
2085
|
+
const kotlinExtensionsPath = path8.join(generatedDir, "GeneratedLynxExtensions.kt");
|
|
2006
2086
|
const content = `/**
|
|
2007
2087
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2008
2088
|
* Do not edit this file manually.
|
|
2009
2089
|
*/
|
|
2010
2090
|
${generateLynxExtensionsKotlin(packages, projectPackage)}`;
|
|
2011
|
-
|
|
2012
|
-
|
|
2091
|
+
fs8.mkdirSync(generatedDir, { recursive: true });
|
|
2092
|
+
fs8.writeFileSync(kotlinExtensionsPath, content.trimStart());
|
|
2013
2093
|
console.log(`\u2705 Generated Kotlin extensions at ${kotlinExtensionsPath}`);
|
|
2014
2094
|
}
|
|
2015
2095
|
function generateActivityLifecycleFile(packages, projectPackage) {
|
|
2016
2096
|
const packageKotlinPath = projectPackage.replace(/\./g, "/");
|
|
2017
|
-
const generatedDir =
|
|
2018
|
-
const outputPath =
|
|
2097
|
+
const generatedDir = path8.join(appAndroidPath, "app", "src", "main", "kotlin", packageKotlinPath, "generated");
|
|
2098
|
+
const outputPath = path8.join(generatedDir, "GeneratedActivityLifecycle.kt");
|
|
2019
2099
|
const content = `/**
|
|
2020
2100
|
* This file is generated by the Tamer4Lynx autolinker.
|
|
2021
2101
|
* Do not edit this file manually.
|
|
2022
2102
|
*/
|
|
2023
2103
|
${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
2024
|
-
|
|
2025
|
-
|
|
2104
|
+
fs8.mkdirSync(generatedDir, { recursive: true });
|
|
2105
|
+
fs8.writeFileSync(outputPath, content);
|
|
2026
2106
|
console.log(`\u2705 Generated activity lifecycle patches at ${outputPath}`);
|
|
2027
2107
|
}
|
|
2028
2108
|
function syncDeepLinkIntentFilters() {
|
|
2029
2109
|
const deepLinks = config.android?.deepLinks;
|
|
2030
2110
|
if (!deepLinks || deepLinks.length === 0) return;
|
|
2031
|
-
const manifestPath =
|
|
2032
|
-
if (!
|
|
2111
|
+
const manifestPath = path8.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2112
|
+
if (!fs8.existsSync(manifestPath)) return;
|
|
2033
2113
|
const intentFilters = deepLinks.map((link) => {
|
|
2034
2114
|
const dataAttrs = [
|
|
2035
2115
|
`android:scheme="${link.scheme}"`,
|
|
@@ -2054,9 +2134,9 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2054
2134
|
console.log("\u{1F50E} Finding Lynx extension packages (lynx.ext.json / tamer.json)...");
|
|
2055
2135
|
let packages = discoverModules(projectRoot).filter((p) => p.config.android);
|
|
2056
2136
|
const includeDevClient = opts?.includeDevClient === true;
|
|
2057
|
-
const devClientScoped =
|
|
2058
|
-
const devClientFlat =
|
|
2059
|
-
const devClientPath =
|
|
2137
|
+
const devClientScoped = path8.join(projectRoot, "node_modules", "@tamer4lynx", "tamer-dev-client");
|
|
2138
|
+
const devClientFlat = path8.join(projectRoot, "node_modules", "tamer-dev-client");
|
|
2139
|
+
const devClientPath = fs8.existsSync(path8.join(devClientScoped, "android")) ? devClientScoped : fs8.existsSync(path8.join(devClientFlat, "android")) ? devClientFlat : null;
|
|
2060
2140
|
const hasDevClient = packages.some((p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client");
|
|
2061
2141
|
if (includeDevClient && devClientPath && !hasDevClient) {
|
|
2062
2142
|
packages = [{
|
|
@@ -2083,8 +2163,8 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2083
2163
|
console.log("\u2728 Autolinking complete.");
|
|
2084
2164
|
}
|
|
2085
2165
|
function runGradleSync() {
|
|
2086
|
-
const gradlew =
|
|
2087
|
-
if (!
|
|
2166
|
+
const gradlew = path8.join(appAndroidPath, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2167
|
+
if (!fs8.existsSync(gradlew)) return;
|
|
2088
2168
|
try {
|
|
2089
2169
|
console.log("\u2139\uFE0F Running Gradle sync in android directory...");
|
|
2090
2170
|
execSync2(process.platform === "win32" ? "gradlew.bat projects" : "./gradlew projects", {
|
|
@@ -2098,14 +2178,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2098
2178
|
}
|
|
2099
2179
|
}
|
|
2100
2180
|
function syncVersionCatalog(packages) {
|
|
2101
|
-
const libsTomlPath =
|
|
2102
|
-
if (!
|
|
2181
|
+
const libsTomlPath = path8.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2182
|
+
if (!fs8.existsSync(libsTomlPath)) return;
|
|
2103
2183
|
const requiredAliases = /* @__PURE__ */ new Set();
|
|
2104
2184
|
const requiredPluginAliases = /* @__PURE__ */ new Set();
|
|
2105
2185
|
for (const pkg of packages) {
|
|
2106
|
-
const buildPath =
|
|
2107
|
-
if (!
|
|
2108
|
-
const content =
|
|
2186
|
+
const buildPath = path8.join(pkg.packagePath, pkg.config.android?.sourceDir || "android", "build.gradle.kts");
|
|
2187
|
+
if (!fs8.existsSync(buildPath)) continue;
|
|
2188
|
+
const content = fs8.readFileSync(buildPath, "utf8");
|
|
2109
2189
|
for (const m of content.matchAll(/libs\.([\w.]+)/g)) {
|
|
2110
2190
|
const alias = m[1];
|
|
2111
2191
|
if (alias && alias in REQUIRED_CATALOG_ENTRIES) requiredAliases.add(alias);
|
|
@@ -2116,7 +2196,7 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2116
2196
|
}
|
|
2117
2197
|
}
|
|
2118
2198
|
if (requiredAliases.size === 0 && requiredPluginAliases.size === 0) return;
|
|
2119
|
-
let toml =
|
|
2199
|
+
let toml = fs8.readFileSync(libsTomlPath, "utf8");
|
|
2120
2200
|
let updated = false;
|
|
2121
2201
|
for (const alias of requiredAliases) {
|
|
2122
2202
|
const entry = REQUIRED_CATALOG_ENTRIES[alias];
|
|
@@ -2140,14 +2220,14 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
|
|
|
2140
2220
|
updated = true;
|
|
2141
2221
|
}
|
|
2142
2222
|
if (updated) {
|
|
2143
|
-
|
|
2223
|
+
fs8.writeFileSync(libsTomlPath, toml);
|
|
2144
2224
|
console.log("\u2705 Synced version catalog (libs.versions.toml) for linked modules.");
|
|
2145
2225
|
}
|
|
2146
2226
|
}
|
|
2147
2227
|
function ensureXElementDeps() {
|
|
2148
|
-
const libsTomlPath =
|
|
2149
|
-
if (
|
|
2150
|
-
let toml =
|
|
2228
|
+
const libsTomlPath = path8.join(appAndroidPath, "gradle", "libs.versions.toml");
|
|
2229
|
+
if (fs8.existsSync(libsTomlPath)) {
|
|
2230
|
+
let toml = fs8.readFileSync(libsTomlPath, "utf8");
|
|
2151
2231
|
let updated = false;
|
|
2152
2232
|
if (!toml.includes("lynx-xelement =")) {
|
|
2153
2233
|
toml = toml.replace(
|
|
@@ -2159,13 +2239,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2159
2239
|
updated = true;
|
|
2160
2240
|
}
|
|
2161
2241
|
if (updated) {
|
|
2162
|
-
|
|
2242
|
+
fs8.writeFileSync(libsTomlPath, toml);
|
|
2163
2243
|
console.log("\u2705 Added XElement entries to version catalog.");
|
|
2164
2244
|
}
|
|
2165
2245
|
}
|
|
2166
|
-
const appBuildPath =
|
|
2167
|
-
if (
|
|
2168
|
-
let content =
|
|
2246
|
+
const appBuildPath = path8.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2247
|
+
if (fs8.existsSync(appBuildPath)) {
|
|
2248
|
+
let content = fs8.readFileSync(appBuildPath, "utf8");
|
|
2169
2249
|
if (!content.includes("lynx.xelement")) {
|
|
2170
2250
|
content = content.replace(
|
|
2171
2251
|
/(implementation\(libs\.lynx\.service\.http\))/,
|
|
@@ -2173,15 +2253,15 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2173
2253
|
implementation(libs.lynx.xelement)
|
|
2174
2254
|
implementation(libs.lynx.xelement.input)`
|
|
2175
2255
|
);
|
|
2176
|
-
|
|
2256
|
+
fs8.writeFileSync(appBuildPath, content);
|
|
2177
2257
|
console.log("\u2705 Added XElement dependencies to app build.gradle.kts.");
|
|
2178
2258
|
}
|
|
2179
2259
|
}
|
|
2180
2260
|
}
|
|
2181
2261
|
function ensureReleaseSigning() {
|
|
2182
|
-
const appBuildPath =
|
|
2183
|
-
if (!
|
|
2184
|
-
let content =
|
|
2262
|
+
const appBuildPath = path8.join(appAndroidPath, "app", "build.gradle.kts");
|
|
2263
|
+
if (!fs8.existsSync(appBuildPath)) return;
|
|
2264
|
+
let content = fs8.readFileSync(appBuildPath, "utf8");
|
|
2185
2265
|
if (content.includes('signingConfig = signingConfigs.getByName("debug")')) return;
|
|
2186
2266
|
const releaseBlock = /(release\s*\{)([\s\S]*?)(\n \}\s*\n(\s*\}|\s*compileOptions))/;
|
|
2187
2267
|
const match = content.match(releaseBlock);
|
|
@@ -2192,13 +2272,13 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2192
2272
|
signingConfig = signingConfigs.getByName("debug")
|
|
2193
2273
|
$3`
|
|
2194
2274
|
);
|
|
2195
|
-
|
|
2275
|
+
fs8.writeFileSync(appBuildPath, content);
|
|
2196
2276
|
console.log("\u2705 Set release signing to debug so installRelease works without a keystore.");
|
|
2197
2277
|
}
|
|
2198
2278
|
}
|
|
2199
2279
|
function syncManifestPermissions(packages) {
|
|
2200
|
-
const manifestPath =
|
|
2201
|
-
if (!
|
|
2280
|
+
const manifestPath = path8.join(appAndroidPath, "app", "src", "main", "AndroidManifest.xml");
|
|
2281
|
+
if (!fs8.existsSync(manifestPath)) return;
|
|
2202
2282
|
const allPermissions = /* @__PURE__ */ new Set();
|
|
2203
2283
|
for (const pkg of packages) {
|
|
2204
2284
|
const perms = pkg.config.android?.permissions;
|
|
@@ -2210,7 +2290,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2210
2290
|
}
|
|
2211
2291
|
}
|
|
2212
2292
|
if (allPermissions.size === 0) return;
|
|
2213
|
-
let manifest =
|
|
2293
|
+
let manifest = fs8.readFileSync(manifestPath, "utf8");
|
|
2214
2294
|
const existingMatch = [...manifest.matchAll(/<uses-permission android:name="(android\.permission\.\w+)"\s*\/>/g)];
|
|
2215
2295
|
const existing = new Set(existingMatch.map((m) => m[1]));
|
|
2216
2296
|
const toAdd = [...allPermissions].filter((p) => !existing.has(p));
|
|
@@ -2221,7 +2301,7 @@ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref
|
|
|
2221
2301
|
`${newLines}
|
|
2222
2302
|
$1$2`
|
|
2223
2303
|
);
|
|
2224
|
-
|
|
2304
|
+
fs8.writeFileSync(manifestPath, manifest);
|
|
2225
2305
|
console.log(`\u2705 Synced manifest permissions: ${toAdd.map((p) => p.split(".").pop()).join(", ")}`);
|
|
2226
2306
|
}
|
|
2227
2307
|
run();
|
|
@@ -2229,26 +2309,26 @@ $1$2`
|
|
|
2229
2309
|
var autolink_default = autolink;
|
|
2230
2310
|
|
|
2231
2311
|
// src/android/bundle.ts
|
|
2232
|
-
import
|
|
2233
|
-
import
|
|
2312
|
+
import fs12 from "fs";
|
|
2313
|
+
import path12 from "path";
|
|
2234
2314
|
import { execSync as execSync3 } from "child_process";
|
|
2235
2315
|
|
|
2236
2316
|
// src/common/copyDistAssets.ts
|
|
2237
|
-
import
|
|
2238
|
-
import
|
|
2317
|
+
import fs9 from "fs";
|
|
2318
|
+
import path9 from "path";
|
|
2239
2319
|
var SKIP = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
2240
2320
|
function copyDistAssets(distDir, destDir, bundleFile) {
|
|
2241
|
-
if (!
|
|
2242
|
-
for (const entry of
|
|
2321
|
+
if (!fs9.existsSync(distDir)) return;
|
|
2322
|
+
for (const entry of fs9.readdirSync(distDir)) {
|
|
2243
2323
|
if (SKIP.has(entry)) continue;
|
|
2244
|
-
const src =
|
|
2245
|
-
const dest =
|
|
2246
|
-
const stat =
|
|
2324
|
+
const src = path9.join(distDir, entry);
|
|
2325
|
+
const dest = path9.join(destDir, entry);
|
|
2326
|
+
const stat = fs9.statSync(src);
|
|
2247
2327
|
if (stat.isDirectory()) {
|
|
2248
|
-
|
|
2328
|
+
fs9.mkdirSync(dest, { recursive: true });
|
|
2249
2329
|
copyDistAssets(src, dest, bundleFile);
|
|
2250
2330
|
} else {
|
|
2251
|
-
|
|
2331
|
+
fs9.copyFileSync(src, dest);
|
|
2252
2332
|
if (entry !== bundleFile) {
|
|
2253
2333
|
console.log(`\u2728 Copied asset: ${entry}`);
|
|
2254
2334
|
}
|
|
@@ -2257,8 +2337,8 @@ function copyDistAssets(distDir, destDir, bundleFile) {
|
|
|
2257
2337
|
}
|
|
2258
2338
|
|
|
2259
2339
|
// src/common/tsconfigUtils.ts
|
|
2260
|
-
import
|
|
2261
|
-
import
|
|
2340
|
+
import fs10 from "fs";
|
|
2341
|
+
import path10 from "path";
|
|
2262
2342
|
function stripJsonCommentsAndTrailingCommas(str) {
|
|
2263
2343
|
return str.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/,\s*([\]}])/g, "$1");
|
|
2264
2344
|
}
|
|
@@ -2270,19 +2350,19 @@ function parseTsconfigJson(raw) {
|
|
|
2270
2350
|
}
|
|
2271
2351
|
}
|
|
2272
2352
|
function readTsconfig(filePath) {
|
|
2273
|
-
if (!
|
|
2353
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
2274
2354
|
try {
|
|
2275
|
-
return parseTsconfigJson(
|
|
2355
|
+
return parseTsconfigJson(fs10.readFileSync(filePath, "utf-8"));
|
|
2276
2356
|
} catch {
|
|
2277
2357
|
return null;
|
|
2278
2358
|
}
|
|
2279
2359
|
}
|
|
2280
2360
|
function resolveExtends(tsconfig, dir) {
|
|
2281
2361
|
if (!tsconfig.extends) return tsconfig;
|
|
2282
|
-
const basePath =
|
|
2362
|
+
const basePath = path10.resolve(dir, tsconfig.extends);
|
|
2283
2363
|
const base = readTsconfig(basePath);
|
|
2284
2364
|
if (!base) return tsconfig;
|
|
2285
|
-
const baseDir =
|
|
2365
|
+
const baseDir = path10.dirname(basePath);
|
|
2286
2366
|
const resolved = resolveExtends(base, baseDir);
|
|
2287
2367
|
return {
|
|
2288
2368
|
...resolved,
|
|
@@ -2291,13 +2371,13 @@ function resolveExtends(tsconfig, dir) {
|
|
|
2291
2371
|
};
|
|
2292
2372
|
}
|
|
2293
2373
|
function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
2294
|
-
const dir =
|
|
2374
|
+
const dir = path10.dirname(tsconfigPath);
|
|
2295
2375
|
const tsconfig = readTsconfig(tsconfigPath);
|
|
2296
2376
|
if (!tsconfig?.references?.length) return false;
|
|
2297
2377
|
const refsWithNoEmit = [];
|
|
2298
2378
|
for (const ref of tsconfig.references) {
|
|
2299
|
-
const refPath =
|
|
2300
|
-
const refConfigPath = refPath.endsWith(".json") ? refPath :
|
|
2379
|
+
const refPath = path10.resolve(dir, ref.path);
|
|
2380
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path10.join(refPath, "tsconfig.json");
|
|
2301
2381
|
const refConfig = readTsconfig(refConfigPath);
|
|
2302
2382
|
if (refConfig?.compilerOptions?.noEmit === true) {
|
|
2303
2383
|
refsWithNoEmit.push(refConfigPath);
|
|
@@ -2312,16 +2392,16 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2312
2392
|
const includes = [];
|
|
2313
2393
|
const compilerOpts = { ...tsconfig.compilerOptions };
|
|
2314
2394
|
for (const ref of tsconfig.references) {
|
|
2315
|
-
const refPath =
|
|
2316
|
-
const refConfigPath = refPath.endsWith(".json") ? refPath :
|
|
2395
|
+
const refPath = path10.resolve(dir, ref.path);
|
|
2396
|
+
const refConfigPath = refPath.endsWith(".json") ? refPath : path10.join(refPath, "tsconfig.json");
|
|
2317
2397
|
const refConfig = readTsconfig(refConfigPath);
|
|
2318
2398
|
if (!refConfig) continue;
|
|
2319
|
-
const refDir =
|
|
2399
|
+
const refDir = path10.dirname(refConfigPath);
|
|
2320
2400
|
const resolved = resolveExtends(refConfig, refDir);
|
|
2321
2401
|
const inc = resolved.include;
|
|
2322
2402
|
if (inc) {
|
|
2323
2403
|
const arr = Array.isArray(inc) ? inc : [inc];
|
|
2324
|
-
const baseDir =
|
|
2404
|
+
const baseDir = path10.relative(dir, refDir);
|
|
2325
2405
|
for (const p of arr) {
|
|
2326
2406
|
const clean = typeof p === "string" && p.startsWith("./") ? p.slice(2) : p;
|
|
2327
2407
|
includes.push(!baseDir || baseDir === "." ? clean : `${baseDir}/${clean}`);
|
|
@@ -2339,7 +2419,7 @@ function fixTsconfigReferencesForBuild(tsconfigPath) {
|
|
|
2339
2419
|
if (includes.length > 0) merged.include = [...new Set(includes)];
|
|
2340
2420
|
compilerOpts.noEmit = true;
|
|
2341
2421
|
merged.compilerOptions = compilerOpts;
|
|
2342
|
-
|
|
2422
|
+
fs10.writeFileSync(tsconfigPath, JSON.stringify(merged, null, 2));
|
|
2343
2423
|
return true;
|
|
2344
2424
|
}
|
|
2345
2425
|
function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
@@ -2350,23 +2430,23 @@ function addTamerTypesInclude(tsconfigPath, tamerTypesInclude) {
|
|
|
2350
2430
|
if (arr.some((p) => (typeof p === "string" ? p : "").includes("tamer-"))) return false;
|
|
2351
2431
|
arr.push(tamerTypesInclude);
|
|
2352
2432
|
tsconfig.include = arr;
|
|
2353
|
-
|
|
2433
|
+
fs10.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
2354
2434
|
return true;
|
|
2355
2435
|
}
|
|
2356
2436
|
|
|
2357
2437
|
// src/android/syncDevClient.ts
|
|
2358
|
-
import
|
|
2359
|
-
import
|
|
2438
|
+
import fs11 from "fs";
|
|
2439
|
+
import path11 from "path";
|
|
2360
2440
|
function readAndSubstituteTemplate2(templatePath, vars) {
|
|
2361
|
-
const raw =
|
|
2441
|
+
const raw = fs11.readFileSync(templatePath, "utf-8");
|
|
2362
2442
|
return Object.entries(vars).reduce(
|
|
2363
2443
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2364
2444
|
raw
|
|
2365
2445
|
);
|
|
2366
2446
|
}
|
|
2367
2447
|
function patchAppLogService(appPath) {
|
|
2368
|
-
if (!
|
|
2369
|
-
const raw =
|
|
2448
|
+
if (!fs11.existsSync(appPath)) return;
|
|
2449
|
+
const raw = fs11.readFileSync(appPath, "utf-8");
|
|
2370
2450
|
const patched = raw.replace(
|
|
2371
2451
|
/private void initLynxService\(\)\s*\{[\s\S]*?\n\s*}\s*\n\s*private void initFresco\(\)/,
|
|
2372
2452
|
`private void initLynxService() {
|
|
@@ -2385,7 +2465,7 @@ function patchAppLogService(appPath) {
|
|
|
2385
2465
|
private void initFresco()`
|
|
2386
2466
|
);
|
|
2387
2467
|
if (patched !== raw) {
|
|
2388
|
-
|
|
2468
|
+
fs11.writeFileSync(appPath, patched);
|
|
2389
2469
|
}
|
|
2390
2470
|
}
|
|
2391
2471
|
async function syncDevClient(opts) {
|
|
@@ -2400,9 +2480,9 @@ async function syncDevClient(opts) {
|
|
|
2400
2480
|
const packageName = config.android?.packageName;
|
|
2401
2481
|
const appName = config.android?.appName;
|
|
2402
2482
|
const packagePath = packageName.replace(/\./g, "/");
|
|
2403
|
-
const javaDir =
|
|
2404
|
-
const kotlinDir =
|
|
2405
|
-
if (!
|
|
2483
|
+
const javaDir = path11.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
2484
|
+
const kotlinDir = path11.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
2485
|
+
if (!fs11.existsSync(javaDir) || !fs11.existsSync(kotlinDir)) {
|
|
2406
2486
|
console.error("\u274C Android project not found. Run `tamer android create` first.");
|
|
2407
2487
|
process.exit(1);
|
|
2408
2488
|
}
|
|
@@ -2418,14 +2498,14 @@ async function syncDevClient(opts) {
|
|
|
2418
2498
|
const [templateProviderSource] = await Promise.all([
|
|
2419
2499
|
fetchAndPatchTemplateProvider(vars)
|
|
2420
2500
|
]);
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
patchAppLogService(
|
|
2424
|
-
const appDir =
|
|
2425
|
-
const mainDir =
|
|
2426
|
-
const manifestPath =
|
|
2501
|
+
fs11.writeFileSync(path11.join(javaDir, "TemplateProvider.java"), templateProviderSource);
|
|
2502
|
+
fs11.writeFileSync(path11.join(kotlinDir, "MainActivity.kt"), getStandaloneMainActivity(vars));
|
|
2503
|
+
patchAppLogService(path11.join(javaDir, "App.java"));
|
|
2504
|
+
const appDir = path11.join(rootDir, "app");
|
|
2505
|
+
const mainDir = path11.join(appDir, "src", "main");
|
|
2506
|
+
const manifestPath = path11.join(mainDir, "AndroidManifest.xml");
|
|
2427
2507
|
if (hasDevClient) {
|
|
2428
|
-
const templateDir =
|
|
2508
|
+
const templateDir = path11.join(devClientPkg, "android", "templates");
|
|
2429
2509
|
const templateVars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
2430
2510
|
const devClientFiles = [
|
|
2431
2511
|
"DevClientManager.kt",
|
|
@@ -2434,13 +2514,13 @@ async function syncDevClient(opts) {
|
|
|
2434
2514
|
"PortraitCaptureActivity.kt"
|
|
2435
2515
|
];
|
|
2436
2516
|
for (const f of devClientFiles) {
|
|
2437
|
-
const src =
|
|
2438
|
-
if (
|
|
2517
|
+
const src = path11.join(templateDir, f);
|
|
2518
|
+
if (fs11.existsSync(src)) {
|
|
2439
2519
|
const content = readAndSubstituteTemplate2(src, templateVars);
|
|
2440
|
-
|
|
2520
|
+
fs11.writeFileSync(path11.join(kotlinDir, f), content);
|
|
2441
2521
|
}
|
|
2442
2522
|
}
|
|
2443
|
-
let manifest =
|
|
2523
|
+
let manifest = fs11.readFileSync(manifestPath, "utf-8");
|
|
2444
2524
|
const projectActivityEntry = ' <activity android:name=".ProjectActivity" android:exported="false" android:taskAffinity="" android:launchMode="singleTask" android:documentLaunchMode="always" android:windowSoftInputMode="adjustResize" />';
|
|
2445
2525
|
const portraitCaptureEntry = ' <activity android:name=".PortraitCaptureActivity" android:screenOrientation="portrait" android:stateNotNeeded="true" android:theme="@style/zxing_CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden" />';
|
|
2446
2526
|
if (!manifest.includes("ProjectActivity")) {
|
|
@@ -2462,16 +2542,16 @@ $1$2`);
|
|
|
2462
2542
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2463
2543
|
);
|
|
2464
2544
|
}
|
|
2465
|
-
|
|
2545
|
+
fs11.writeFileSync(manifestPath, manifest);
|
|
2466
2546
|
console.log("\u2705 Synced dev client (TemplateProvider, MainActivity, ProjectActivity, DevClientManager)");
|
|
2467
2547
|
} else {
|
|
2468
2548
|
for (const f of ["DevClientManager.kt", "DevServerPrefs.kt", "ProjectActivity.kt", "PortraitCaptureActivity.kt", "DevLauncherActivity.kt"]) {
|
|
2469
2549
|
try {
|
|
2470
|
-
|
|
2550
|
+
fs11.rmSync(path11.join(kotlinDir, f));
|
|
2471
2551
|
} catch {
|
|
2472
2552
|
}
|
|
2473
2553
|
}
|
|
2474
|
-
let manifest =
|
|
2554
|
+
let manifest = fs11.readFileSync(manifestPath, "utf-8");
|
|
2475
2555
|
manifest = manifest.replace(/\s*<activity android:name="\.ProjectActivity"[^\/]*\/>\n?/g, "");
|
|
2476
2556
|
manifest = manifest.replace(/\s*<activity android:name="\.PortraitCaptureActivity"[^\/]*\/>\n?/g, "");
|
|
2477
2557
|
const mainActivityTag = manifest.match(/<activity[^>]*android:name="\.MainActivity"[^>]*>/);
|
|
@@ -2481,7 +2561,7 @@ $1$2`);
|
|
|
2481
2561
|
'$1 android:windowSoftInputMode="adjustResize"$2'
|
|
2482
2562
|
);
|
|
2483
2563
|
}
|
|
2484
|
-
|
|
2564
|
+
fs11.writeFileSync(manifestPath, manifest);
|
|
2485
2565
|
console.log("\u2705 Synced (dev client disabled - use -d for debug build with dev client)");
|
|
2486
2566
|
}
|
|
2487
2567
|
}
|
|
@@ -2505,15 +2585,15 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2505
2585
|
await syncDevClient_default({ includeDevClient });
|
|
2506
2586
|
const iconPaths = resolveIconPaths(projectRoot, resolved.config);
|
|
2507
2587
|
if (iconPaths) {
|
|
2508
|
-
const resDir =
|
|
2588
|
+
const resDir = path12.join(resolved.androidAppDir, "src", "main", "res");
|
|
2509
2589
|
if (applyAndroidLauncherIcons(resDir, iconPaths)) {
|
|
2510
2590
|
console.log("\u2705 Synced Android launcher icon(s) from tamer.config.json");
|
|
2511
|
-
ensureAndroidManifestLauncherIcon(
|
|
2591
|
+
ensureAndroidManifestLauncherIcon(path12.join(resolved.androidAppDir, "src", "main", "AndroidManifest.xml"));
|
|
2512
2592
|
}
|
|
2513
2593
|
}
|
|
2514
2594
|
try {
|
|
2515
|
-
const lynxTsconfig =
|
|
2516
|
-
if (
|
|
2595
|
+
const lynxTsconfig = path12.join(lynxProjectDir, "tsconfig.json");
|
|
2596
|
+
if (fs12.existsSync(lynxTsconfig)) {
|
|
2517
2597
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
2518
2598
|
}
|
|
2519
2599
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -2523,8 +2603,8 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2523
2603
|
console.error("\u274C Build process failed.");
|
|
2524
2604
|
process.exit(1);
|
|
2525
2605
|
}
|
|
2526
|
-
if (includeDevClient && devClientBundlePath && !
|
|
2527
|
-
const devClientDir =
|
|
2606
|
+
if (includeDevClient && devClientBundlePath && !fs12.existsSync(devClientBundlePath)) {
|
|
2607
|
+
const devClientDir = path12.dirname(path12.dirname(devClientBundlePath));
|
|
2528
2608
|
try {
|
|
2529
2609
|
console.log("\u{1F4E6} Building dev launcher (tamer-dev-client)...");
|
|
2530
2610
|
execSync3("npm run build", { stdio: "inherit", cwd: devClientDir });
|
|
@@ -2535,22 +2615,22 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2535
2615
|
}
|
|
2536
2616
|
}
|
|
2537
2617
|
try {
|
|
2538
|
-
|
|
2618
|
+
fs12.mkdirSync(destinationDir, { recursive: true });
|
|
2539
2619
|
if (release) {
|
|
2540
|
-
const devClientAsset =
|
|
2541
|
-
if (
|
|
2542
|
-
|
|
2620
|
+
const devClientAsset = path12.join(destinationDir, "dev-client.lynx.bundle");
|
|
2621
|
+
if (fs12.existsSync(devClientAsset)) {
|
|
2622
|
+
fs12.rmSync(devClientAsset);
|
|
2543
2623
|
console.log(`\u2728 Removed dev-client.lynx.bundle from assets (production build)`);
|
|
2544
2624
|
}
|
|
2545
|
-
} else if (includeDevClient && devClientBundlePath &&
|
|
2546
|
-
|
|
2625
|
+
} else if (includeDevClient && devClientBundlePath && fs12.existsSync(devClientBundlePath)) {
|
|
2626
|
+
fs12.copyFileSync(devClientBundlePath, path12.join(destinationDir, "dev-client.lynx.bundle"));
|
|
2547
2627
|
console.log(`\u2728 Copied dev-client.lynx.bundle to assets`);
|
|
2548
2628
|
}
|
|
2549
|
-
if (!
|
|
2629
|
+
if (!fs12.existsSync(lynxBundlePath)) {
|
|
2550
2630
|
console.error(`\u274C Build output not found at: ${lynxBundlePath}`);
|
|
2551
2631
|
process.exit(1);
|
|
2552
2632
|
}
|
|
2553
|
-
const distDir =
|
|
2633
|
+
const distDir = path12.dirname(lynxBundlePath);
|
|
2554
2634
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
2555
2635
|
console.log(`\u2728 Copied ${resolved.lynxBundleFile} to assets`);
|
|
2556
2636
|
} catch (error) {
|
|
@@ -2561,7 +2641,7 @@ async function bundleAndDeploy(opts = {}) {
|
|
|
2561
2641
|
var bundle_default = bundleAndDeploy;
|
|
2562
2642
|
|
|
2563
2643
|
// src/android/build.ts
|
|
2564
|
-
import
|
|
2644
|
+
import path13 from "path";
|
|
2565
2645
|
import { execSync as execSync4 } from "child_process";
|
|
2566
2646
|
async function buildApk(opts = {}) {
|
|
2567
2647
|
let resolved;
|
|
@@ -2573,7 +2653,7 @@ async function buildApk(opts = {}) {
|
|
|
2573
2653
|
const release = opts.release === true || opts.production === true;
|
|
2574
2654
|
await bundle_default({ release, production: opts.production });
|
|
2575
2655
|
const androidDir = resolved.androidDir;
|
|
2576
|
-
const gradlew =
|
|
2656
|
+
const gradlew = path13.join(androidDir, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
2577
2657
|
const variant = release ? "Release" : "Debug";
|
|
2578
2658
|
const task = opts.install ? `install${variant}` : `assemble${variant}`;
|
|
2579
2659
|
console.log(`
|
|
@@ -2598,13 +2678,13 @@ async function buildApk(opts = {}) {
|
|
|
2598
2678
|
var build_default = buildApk;
|
|
2599
2679
|
|
|
2600
2680
|
// src/ios/create.ts
|
|
2601
|
-
import
|
|
2602
|
-
import
|
|
2681
|
+
import fs14 from "fs";
|
|
2682
|
+
import path15 from "path";
|
|
2603
2683
|
|
|
2604
2684
|
// src/ios/getPod.ts
|
|
2605
2685
|
import { execSync as execSync5 } from "child_process";
|
|
2606
|
-
import
|
|
2607
|
-
import
|
|
2686
|
+
import fs13 from "fs";
|
|
2687
|
+
import path14 from "path";
|
|
2608
2688
|
function isCocoaPodsInstalled() {
|
|
2609
2689
|
try {
|
|
2610
2690
|
execSync5("command -v pod >/dev/null 2>&1");
|
|
@@ -2626,8 +2706,8 @@ async function setupCocoaPods(rootDir) {
|
|
|
2626
2706
|
}
|
|
2627
2707
|
try {
|
|
2628
2708
|
console.log("\u{1F4E6} CocoaPods is installed. Proceeding with dependency installation...");
|
|
2629
|
-
const podfilePath =
|
|
2630
|
-
if (!
|
|
2709
|
+
const podfilePath = path14.join(rootDir, "Podfile");
|
|
2710
|
+
if (!fs13.existsSync(podfilePath)) {
|
|
2631
2711
|
throw new Error(`Podfile not found at ${podfilePath}`);
|
|
2632
2712
|
}
|
|
2633
2713
|
console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
|
|
@@ -2653,7 +2733,7 @@ async function setupCocoaPods(rootDir) {
|
|
|
2653
2733
|
// src/ios/create.ts
|
|
2654
2734
|
import { randomBytes } from "crypto";
|
|
2655
2735
|
function readAndSubstituteTemplate3(templatePath, vars) {
|
|
2656
|
-
const raw =
|
|
2736
|
+
const raw = fs14.readFileSync(templatePath, "utf-8");
|
|
2657
2737
|
return Object.entries(vars).reduce(
|
|
2658
2738
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
2659
2739
|
raw
|
|
@@ -2676,17 +2756,17 @@ var create2 = () => {
|
|
|
2676
2756
|
process.exit(1);
|
|
2677
2757
|
}
|
|
2678
2758
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
2679
|
-
const rootDir =
|
|
2680
|
-
const projectDir =
|
|
2681
|
-
const xcodeprojDir =
|
|
2759
|
+
const rootDir = path15.join(process.cwd(), iosDir);
|
|
2760
|
+
const projectDir = path15.join(rootDir, appName);
|
|
2761
|
+
const xcodeprojDir = path15.join(rootDir, `${appName}.xcodeproj`);
|
|
2682
2762
|
const bridgingHeader = `${appName}-Bridging-Header.h`;
|
|
2683
2763
|
function writeFile2(filePath, content) {
|
|
2684
|
-
|
|
2685
|
-
|
|
2764
|
+
fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
|
|
2765
|
+
fs14.writeFileSync(filePath, content.trimStart(), "utf8");
|
|
2686
2766
|
}
|
|
2687
|
-
if (
|
|
2767
|
+
if (fs14.existsSync(rootDir)) {
|
|
2688
2768
|
console.log(`\u{1F9F9} Removing existing directory: ${rootDir}`);
|
|
2689
|
-
|
|
2769
|
+
fs14.rmSync(rootDir, { recursive: true, force: true });
|
|
2690
2770
|
}
|
|
2691
2771
|
console.log(`\u{1F680} Creating a new Tamer4Lynx project in: ${rootDir}`);
|
|
2692
2772
|
const ids = {
|
|
@@ -2722,9 +2802,11 @@ var create2 = () => {
|
|
|
2722
2802
|
targetDebugConfig: generateId(),
|
|
2723
2803
|
targetReleaseConfig: generateId()
|
|
2724
2804
|
};
|
|
2725
|
-
writeFile2(
|
|
2805
|
+
writeFile2(path15.join(rootDir, "Podfile"), `
|
|
2726
2806
|
source 'https://cdn.cocoapods.org/'
|
|
2727
2807
|
|
|
2808
|
+
install! 'cocoapods', :incremental_installation => true, :generate_multiple_pod_projects => true
|
|
2809
|
+
|
|
2728
2810
|
platform :ios, '13.0'
|
|
2729
2811
|
|
|
2730
2812
|
target '${appName}' do
|
|
@@ -2807,15 +2889,15 @@ end
|
|
|
2807
2889
|
const hostPkg = findTamerHostPackage(process.cwd());
|
|
2808
2890
|
const templateVars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
2809
2891
|
if (hostPkg) {
|
|
2810
|
-
const templateDir =
|
|
2892
|
+
const templateDir = path15.join(hostPkg, "ios", "templates");
|
|
2811
2893
|
for (const f of ["AppDelegate.swift", "SceneDelegate.swift", "ViewController.swift", "LynxProvider.swift", "LynxInitProcessor.swift"]) {
|
|
2812
|
-
const srcPath =
|
|
2813
|
-
if (
|
|
2814
|
-
writeFile2(
|
|
2894
|
+
const srcPath = path15.join(templateDir, f);
|
|
2895
|
+
if (fs14.existsSync(srcPath)) {
|
|
2896
|
+
writeFile2(path15.join(projectDir, f), readAndSubstituteTemplate3(srcPath, templateVars));
|
|
2815
2897
|
}
|
|
2816
2898
|
}
|
|
2817
2899
|
} else {
|
|
2818
|
-
writeFile2(
|
|
2900
|
+
writeFile2(path15.join(projectDir, "AppDelegate.swift"), `
|
|
2819
2901
|
import UIKit
|
|
2820
2902
|
|
|
2821
2903
|
@UIApplicationMain
|
|
@@ -2830,7 +2912,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
2830
2912
|
}
|
|
2831
2913
|
}
|
|
2832
2914
|
`);
|
|
2833
|
-
writeFile2(
|
|
2915
|
+
writeFile2(path15.join(projectDir, "SceneDelegate.swift"), `
|
|
2834
2916
|
import UIKit
|
|
2835
2917
|
|
|
2836
2918
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -2844,7 +2926,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
2844
2926
|
}
|
|
2845
2927
|
}
|
|
2846
2928
|
`);
|
|
2847
|
-
writeFile2(
|
|
2929
|
+
writeFile2(path15.join(projectDir, "ViewController.swift"), `
|
|
2848
2930
|
import UIKit
|
|
2849
2931
|
import Lynx
|
|
2850
2932
|
import tamerinsets
|
|
@@ -2917,7 +2999,7 @@ class ViewController: UIViewController {
|
|
|
2917
2999
|
}
|
|
2918
3000
|
}
|
|
2919
3001
|
`);
|
|
2920
|
-
writeFile2(
|
|
3002
|
+
writeFile2(path15.join(projectDir, "LynxProvider.swift"), `
|
|
2921
3003
|
import Foundation
|
|
2922
3004
|
|
|
2923
3005
|
class LynxProvider: NSObject, LynxTemplateProvider {
|
|
@@ -2936,7 +3018,7 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
2936
3018
|
}
|
|
2937
3019
|
}
|
|
2938
3020
|
`);
|
|
2939
|
-
writeFile2(
|
|
3021
|
+
writeFile2(path15.join(projectDir, "LynxInitProcessor.swift"), `
|
|
2940
3022
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2941
3023
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
2942
3024
|
// LICENSE file in the root directory of this source tree.
|
|
@@ -2976,7 +3058,7 @@ final class LynxInitProcessor {
|
|
|
2976
3058
|
}
|
|
2977
3059
|
`);
|
|
2978
3060
|
}
|
|
2979
|
-
writeFile2(
|
|
3061
|
+
writeFile2(path15.join(projectDir, bridgingHeader), `
|
|
2980
3062
|
#import <Lynx/LynxConfig.h>
|
|
2981
3063
|
#import <Lynx/LynxEnv.h>
|
|
2982
3064
|
#import <Lynx/LynxTemplateProvider.h>
|
|
@@ -2985,7 +3067,7 @@ final class LynxInitProcessor {
|
|
|
2985
3067
|
#import <SDWebImage/SDWebImage.h>
|
|
2986
3068
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
|
2987
3069
|
`);
|
|
2988
|
-
writeFile2(
|
|
3070
|
+
writeFile2(path15.join(projectDir, "Info.plist"), `
|
|
2989
3071
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2990
3072
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2991
3073
|
<plist version="1.0">
|
|
@@ -3043,21 +3125,21 @@ final class LynxInitProcessor {
|
|
|
3043
3125
|
</dict>
|
|
3044
3126
|
</plist>
|
|
3045
3127
|
`);
|
|
3046
|
-
const appIconDir =
|
|
3047
|
-
|
|
3128
|
+
const appIconDir = path15.join(projectDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
3129
|
+
fs14.mkdirSync(appIconDir, { recursive: true });
|
|
3048
3130
|
const iconPaths = resolveIconPaths(process.cwd(), config);
|
|
3049
3131
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
3050
3132
|
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
3133
|
} else {
|
|
3052
|
-
writeFile2(
|
|
3134
|
+
writeFile2(path15.join(appIconDir, "Contents.json"), `
|
|
3053
3135
|
{
|
|
3054
3136
|
"images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ],
|
|
3055
3137
|
"info" : { "author" : "xcode", "version" : 1 }
|
|
3056
3138
|
}
|
|
3057
3139
|
`);
|
|
3058
3140
|
}
|
|
3059
|
-
|
|
3060
|
-
writeFile2(
|
|
3141
|
+
fs14.mkdirSync(xcodeprojDir, { recursive: true });
|
|
3142
|
+
writeFile2(path15.join(xcodeprojDir, "project.pbxproj"), `
|
|
3061
3143
|
// !$*UTF8*$!
|
|
3062
3144
|
{
|
|
3063
3145
|
archiveVersion = 1;
|
|
@@ -3343,8 +3425,8 @@ final class LynxInitProcessor {
|
|
|
3343
3425
|
var create_default2 = create2;
|
|
3344
3426
|
|
|
3345
3427
|
// src/ios/autolink.ts
|
|
3346
|
-
import
|
|
3347
|
-
import
|
|
3428
|
+
import fs16 from "fs";
|
|
3429
|
+
import path17 from "path";
|
|
3348
3430
|
import { execSync as execSync6 } from "child_process";
|
|
3349
3431
|
|
|
3350
3432
|
// src/common/hostNativeModulesManifest.ts
|
|
@@ -3355,8 +3437,8 @@ function buildHostNativeModulesManifestJson(moduleClassNames) {
|
|
|
3355
3437
|
}
|
|
3356
3438
|
|
|
3357
3439
|
// src/ios/syncHost.ts
|
|
3358
|
-
import
|
|
3359
|
-
import
|
|
3440
|
+
import fs15 from "fs";
|
|
3441
|
+
import path16 from "path";
|
|
3360
3442
|
import crypto from "crypto";
|
|
3361
3443
|
function deterministicUUID(seed) {
|
|
3362
3444
|
return crypto.createHash("sha256").update(seed).digest("hex").substring(0, 24).toUpperCase();
|
|
@@ -3404,7 +3486,7 @@ function getLaunchScreenStoryboard() {
|
|
|
3404
3486
|
`;
|
|
3405
3487
|
}
|
|
3406
3488
|
function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
3407
|
-
let content =
|
|
3489
|
+
let content = fs15.readFileSync(pbxprojPath, "utf8");
|
|
3408
3490
|
if (content.includes("LaunchScreen.storyboard")) return;
|
|
3409
3491
|
const baseFileRefUUID = deterministicUUID(`launchScreenBase:${appName}`);
|
|
3410
3492
|
const variantGroupUUID = deterministicUUID(`launchScreenGroup:${appName}`);
|
|
@@ -3441,11 +3523,11 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
|
|
|
3441
3523
|
);
|
|
3442
3524
|
content = content.replace(groupPattern, `$1
|
|
3443
3525
|
${variantGroupUUID} /* LaunchScreen.storyboard */,`);
|
|
3444
|
-
|
|
3526
|
+
fs15.writeFileSync(pbxprojPath, content, "utf8");
|
|
3445
3527
|
console.log("\u2705 Registered LaunchScreen.storyboard in Xcode project");
|
|
3446
3528
|
}
|
|
3447
3529
|
function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3448
|
-
let content =
|
|
3530
|
+
let content = fs15.readFileSync(pbxprojPath, "utf8");
|
|
3449
3531
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3450
3532
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3451
3533
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3470,11 +3552,11 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3470
3552
|
);
|
|
3471
3553
|
content = content.replace(groupPattern, `$1
|
|
3472
3554
|
${fileRefUUID} /* ${filename} */,`);
|
|
3473
|
-
|
|
3555
|
+
fs15.writeFileSync(pbxprojPath, content, "utf8");
|
|
3474
3556
|
console.log(`\u2705 Registered ${filename} in Xcode project sources`);
|
|
3475
3557
|
}
|
|
3476
3558
|
function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
3477
|
-
let content =
|
|
3559
|
+
let content = fs15.readFileSync(pbxprojPath, "utf8");
|
|
3478
3560
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3479
3561
|
if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
|
|
3480
3562
|
const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
|
|
@@ -3499,12 +3581,12 @@ function addResourceToXcodeProject(pbxprojPath, appName, filename) {
|
|
|
3499
3581
|
);
|
|
3500
3582
|
content = content.replace(groupPattern, `$1
|
|
3501
3583
|
${fileRefUUID} /* ${filename} */,`);
|
|
3502
|
-
|
|
3584
|
+
fs15.writeFileSync(pbxprojPath, content, "utf8");
|
|
3503
3585
|
console.log(`\u2705 Registered ${filename} in Xcode project resources`);
|
|
3504
3586
|
}
|
|
3505
3587
|
function writeFile(filePath, content) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3588
|
+
fs15.mkdirSync(path16.dirname(filePath), { recursive: true });
|
|
3589
|
+
fs15.writeFileSync(filePath, content, "utf8");
|
|
3508
3590
|
}
|
|
3509
3591
|
function getAppDelegateSwift() {
|
|
3510
3592
|
return `import UIKit
|
|
@@ -3721,8 +3803,8 @@ class ViewController: UIViewController {
|
|
|
3721
3803
|
`;
|
|
3722
3804
|
}
|
|
3723
3805
|
function patchInfoPlist(infoPlistPath) {
|
|
3724
|
-
if (!
|
|
3725
|
-
let content =
|
|
3806
|
+
if (!fs15.existsSync(infoPlistPath)) return;
|
|
3807
|
+
let content = fs15.readFileSync(infoPlistPath, "utf8");
|
|
3726
3808
|
content = content.replace(/\s*<key>UIMainStoryboardFile<\/key>\s*<string>[^<]*<\/string>/g, "");
|
|
3727
3809
|
if (!content.includes("UILaunchStoryboardName")) {
|
|
3728
3810
|
content = content.replace("</dict>\n</plist>", ` <key>UILaunchStoryboardName</key>
|
|
@@ -3754,7 +3836,7 @@ function patchInfoPlist(infoPlistPath) {
|
|
|
3754
3836
|
</plist>`);
|
|
3755
3837
|
console.log("\u2705 Added UIApplicationSceneManifest to Info.plist");
|
|
3756
3838
|
}
|
|
3757
|
-
|
|
3839
|
+
fs15.writeFileSync(infoPlistPath, content, "utf8");
|
|
3758
3840
|
}
|
|
3759
3841
|
function getSimpleLynxProviderSwift() {
|
|
3760
3842
|
return `import Foundation
|
|
@@ -3779,9 +3861,9 @@ class LynxProvider: NSObject, LynxTemplateProvider {
|
|
|
3779
3861
|
}
|
|
3780
3862
|
function readTemplateOrFallback(devClientPkg, templateName, fallback, vars = {}) {
|
|
3781
3863
|
if (devClientPkg) {
|
|
3782
|
-
const tplPath =
|
|
3783
|
-
if (
|
|
3784
|
-
let content =
|
|
3864
|
+
const tplPath = path16.join(devClientPkg, "ios", "templates", templateName);
|
|
3865
|
+
if (fs15.existsSync(tplPath)) {
|
|
3866
|
+
let content = fs15.readFileSync(tplPath, "utf8");
|
|
3785
3867
|
for (const [k, v] of Object.entries(vars)) {
|
|
3786
3868
|
content = content.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
3787
3869
|
}
|
|
@@ -3799,19 +3881,19 @@ function syncHostIos(opts) {
|
|
|
3799
3881
|
if (!appName) {
|
|
3800
3882
|
throw new Error('"ios.appName" must be defined in tamer.config.json');
|
|
3801
3883
|
}
|
|
3802
|
-
const projectDir =
|
|
3803
|
-
const infoPlistPath =
|
|
3804
|
-
if (!
|
|
3884
|
+
const projectDir = path16.join(resolved.iosDir, appName);
|
|
3885
|
+
const infoPlistPath = path16.join(projectDir, "Info.plist");
|
|
3886
|
+
if (!fs15.existsSync(projectDir)) {
|
|
3805
3887
|
throw new Error(`iOS project not found at ${projectDir}. Run \`tamer ios create\` first.`);
|
|
3806
3888
|
}
|
|
3807
|
-
const pbxprojPath =
|
|
3808
|
-
const baseLprojDir =
|
|
3809
|
-
const launchScreenPath =
|
|
3889
|
+
const pbxprojPath = path16.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
3890
|
+
const baseLprojDir = path16.join(projectDir, "Base.lproj");
|
|
3891
|
+
const launchScreenPath = path16.join(baseLprojDir, "LaunchScreen.storyboard");
|
|
3810
3892
|
patchInfoPlist(infoPlistPath);
|
|
3811
|
-
writeFile(
|
|
3812
|
-
writeFile(
|
|
3813
|
-
if (!
|
|
3814
|
-
|
|
3893
|
+
writeFile(path16.join(projectDir, "AppDelegate.swift"), getAppDelegateSwift());
|
|
3894
|
+
writeFile(path16.join(projectDir, "SceneDelegate.swift"), getSceneDelegateSwift());
|
|
3895
|
+
if (!fs15.existsSync(launchScreenPath)) {
|
|
3896
|
+
fs15.mkdirSync(baseLprojDir, { recursive: true });
|
|
3815
3897
|
writeFile(launchScreenPath, getLaunchScreenStoryboard());
|
|
3816
3898
|
addLaunchScreenToXcodeProject(pbxprojPath, appName);
|
|
3817
3899
|
}
|
|
@@ -3820,33 +3902,33 @@ function syncHostIos(opts) {
|
|
|
3820
3902
|
const devClientPkg2 = findDevClientPackage(resolved.projectRoot);
|
|
3821
3903
|
const segment = resolved.lynxProjectDir.split("/").filter(Boolean).pop() ?? "";
|
|
3822
3904
|
const tplVars = { PROJECT_BUNDLE_SEGMENT: segment };
|
|
3823
|
-
writeFile(
|
|
3824
|
-
writeFile(
|
|
3905
|
+
writeFile(path16.join(projectDir, "ViewController.swift"), getDevViewControllerSwift());
|
|
3906
|
+
writeFile(path16.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3825
3907
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3826
3908
|
const devTPContent = readTemplateOrFallback(devClientPkg2, "DevTemplateProvider.swift", "", tplVars);
|
|
3827
3909
|
if (devTPContent) {
|
|
3828
|
-
writeFile(
|
|
3910
|
+
writeFile(path16.join(projectDir, "DevTemplateProvider.swift"), devTPContent);
|
|
3829
3911
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevTemplateProvider.swift");
|
|
3830
3912
|
}
|
|
3831
3913
|
const projectVCContent = readTemplateOrFallback(devClientPkg2, "ProjectViewController.swift", "", tplVars);
|
|
3832
3914
|
if (projectVCContent) {
|
|
3833
|
-
writeFile(
|
|
3915
|
+
writeFile(path16.join(projectDir, "ProjectViewController.swift"), projectVCContent);
|
|
3834
3916
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "ProjectViewController.swift");
|
|
3835
3917
|
}
|
|
3836
3918
|
const devCMContent = readTemplateOrFallback(devClientPkg2, "DevClientManager.swift", "", tplVars);
|
|
3837
3919
|
if (devCMContent) {
|
|
3838
|
-
writeFile(
|
|
3920
|
+
writeFile(path16.join(projectDir, "DevClientManager.swift"), devCMContent);
|
|
3839
3921
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "DevClientManager.swift");
|
|
3840
3922
|
}
|
|
3841
3923
|
const qrContent = readTemplateOrFallback(devClientPkg2, "QRScannerViewController.swift", "", tplVars);
|
|
3842
3924
|
if (qrContent) {
|
|
3843
|
-
writeFile(
|
|
3925
|
+
writeFile(path16.join(projectDir, "QRScannerViewController.swift"), qrContent);
|
|
3844
3926
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "QRScannerViewController.swift");
|
|
3845
3927
|
}
|
|
3846
3928
|
console.log("\u2705 Synced iOS host app (embedded dev mode) \u2014 ViewController, DevTemplateProvider, ProjectViewController, DevClientManager, QRScannerViewController");
|
|
3847
3929
|
} else {
|
|
3848
|
-
writeFile(
|
|
3849
|
-
writeFile(
|
|
3930
|
+
writeFile(path16.join(projectDir, "ViewController.swift"), getViewControllerSwift());
|
|
3931
|
+
writeFile(path16.join(projectDir, "LynxProvider.swift"), getSimpleLynxProviderSwift());
|
|
3850
3932
|
addSwiftSourceToXcodeProject(pbxprojPath, appName, "LynxProvider.swift");
|
|
3851
3933
|
console.log("\u2705 Synced iOS host app controller files");
|
|
3852
3934
|
}
|
|
@@ -3854,7 +3936,7 @@ function syncHostIos(opts) {
|
|
|
3854
3936
|
var syncHost_default = syncHostIos;
|
|
3855
3937
|
|
|
3856
3938
|
// src/ios/autolink.ts
|
|
3857
|
-
var autolink2 = () => {
|
|
3939
|
+
var autolink2 = (syncHostOpts) => {
|
|
3858
3940
|
let resolved;
|
|
3859
3941
|
try {
|
|
3860
3942
|
resolved = resolveHostPaths();
|
|
@@ -3865,11 +3947,11 @@ var autolink2 = () => {
|
|
|
3865
3947
|
const projectRoot = resolved.projectRoot;
|
|
3866
3948
|
const iosProjectPath = resolved.iosDir;
|
|
3867
3949
|
function updateGeneratedSection(filePath, newContent, startMarker, endMarker) {
|
|
3868
|
-
if (!
|
|
3950
|
+
if (!fs16.existsSync(filePath)) {
|
|
3869
3951
|
console.warn(`\u26A0\uFE0F File not found, skipping update: ${filePath}`);
|
|
3870
3952
|
return;
|
|
3871
3953
|
}
|
|
3872
|
-
let fileContent =
|
|
3954
|
+
let fileContent = fs16.readFileSync(filePath, "utf8");
|
|
3873
3955
|
const escapedStartMarker = startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3874
3956
|
const escapedEndMarker = endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3875
3957
|
const regex = new RegExp(`${escapedStartMarker}[\\s\\S]*?${escapedEndMarker}`, "g");
|
|
@@ -3889,33 +3971,33 @@ ${replacementBlock}
|
|
|
3889
3971
|
`;
|
|
3890
3972
|
}
|
|
3891
3973
|
} else {
|
|
3892
|
-
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${
|
|
3974
|
+
console.warn(`\u26A0\uFE0F Could not find autolink markers in ${path17.basename(filePath)}. Appending to the end of the file.`);
|
|
3893
3975
|
fileContent += `
|
|
3894
3976
|
${replacementBlock}
|
|
3895
3977
|
`;
|
|
3896
3978
|
}
|
|
3897
|
-
|
|
3898
|
-
console.log(`\u2705 Updated autolinked section in ${
|
|
3979
|
+
fs16.writeFileSync(filePath, fileContent, "utf8");
|
|
3980
|
+
console.log(`\u2705 Updated autolinked section in ${path17.basename(filePath)}`);
|
|
3899
3981
|
}
|
|
3900
3982
|
function resolvePodDirectory(pkg) {
|
|
3901
|
-
const configuredDir =
|
|
3902
|
-
if (
|
|
3983
|
+
const configuredDir = path17.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
|
|
3984
|
+
if (fs16.existsSync(configuredDir)) {
|
|
3903
3985
|
return configuredDir;
|
|
3904
3986
|
}
|
|
3905
|
-
const iosDir =
|
|
3906
|
-
if (
|
|
3987
|
+
const iosDir = path17.join(pkg.packagePath, "ios");
|
|
3988
|
+
if (fs16.existsSync(iosDir)) {
|
|
3907
3989
|
const stack = [iosDir];
|
|
3908
3990
|
while (stack.length > 0) {
|
|
3909
3991
|
const current = stack.pop();
|
|
3910
3992
|
try {
|
|
3911
|
-
const entries =
|
|
3993
|
+
const entries = fs16.readdirSync(current, { withFileTypes: true });
|
|
3912
3994
|
const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
|
|
3913
3995
|
if (podspec) {
|
|
3914
3996
|
return current;
|
|
3915
3997
|
}
|
|
3916
3998
|
for (const entry of entries) {
|
|
3917
3999
|
if (entry.isDirectory()) {
|
|
3918
|
-
stack.push(
|
|
4000
|
+
stack.push(path17.join(current, entry.name));
|
|
3919
4001
|
}
|
|
3920
4002
|
}
|
|
3921
4003
|
} catch {
|
|
@@ -3926,9 +4008,9 @@ ${replacementBlock}
|
|
|
3926
4008
|
}
|
|
3927
4009
|
function resolvePodName(pkg) {
|
|
3928
4010
|
const fullPodspecDir = resolvePodDirectory(pkg);
|
|
3929
|
-
if (
|
|
4011
|
+
if (fs16.existsSync(fullPodspecDir)) {
|
|
3930
4012
|
try {
|
|
3931
|
-
const files =
|
|
4013
|
+
const files = fs16.readdirSync(fullPodspecDir);
|
|
3932
4014
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
3933
4015
|
if (podspecFile) return podspecFile.replace(".podspec", "");
|
|
3934
4016
|
} catch {
|
|
@@ -3937,13 +4019,13 @@ ${replacementBlock}
|
|
|
3937
4019
|
return pkg.name.split("/").pop().replace(/-/g, "");
|
|
3938
4020
|
}
|
|
3939
4021
|
function updatePodfile(packages) {
|
|
3940
|
-
const podfilePath =
|
|
4022
|
+
const podfilePath = path17.join(iosProjectPath, "Podfile");
|
|
3941
4023
|
let scriptContent = ` # This section is automatically generated by Tamer4Lynx.
|
|
3942
4024
|
# Manual edits will be overwritten.`;
|
|
3943
4025
|
const iosPackages = packages.filter((p) => p.config.ios);
|
|
3944
4026
|
if (iosPackages.length > 0) {
|
|
3945
4027
|
iosPackages.forEach((pkg) => {
|
|
3946
|
-
const relativePath =
|
|
4028
|
+
const relativePath = path17.relative(iosProjectPath, resolvePodDirectory(pkg));
|
|
3947
4029
|
const podName = resolvePodName(pkg);
|
|
3948
4030
|
scriptContent += `
|
|
3949
4031
|
pod '${podName}', :path => '${relativePath}'`;
|
|
@@ -3955,9 +4037,9 @@ ${replacementBlock}
|
|
|
3955
4037
|
updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
|
|
3956
4038
|
}
|
|
3957
4039
|
function ensureXElementPod() {
|
|
3958
|
-
const podfilePath =
|
|
3959
|
-
if (!
|
|
3960
|
-
let content =
|
|
4040
|
+
const podfilePath = path17.join(iosProjectPath, "Podfile");
|
|
4041
|
+
if (!fs16.existsSync(podfilePath)) return;
|
|
4042
|
+
let content = fs16.readFileSync(podfilePath, "utf8");
|
|
3961
4043
|
if (content.includes("pod 'XElement'")) return;
|
|
3962
4044
|
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
3963
4045
|
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
@@ -3982,13 +4064,51 @@ ${replacementBlock}
|
|
|
3982
4064
|
`;
|
|
3983
4065
|
}
|
|
3984
4066
|
}
|
|
3985
|
-
|
|
4067
|
+
fs16.writeFileSync(podfilePath, content, "utf8");
|
|
3986
4068
|
console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
|
|
3987
4069
|
}
|
|
4070
|
+
function ensureLynxDevToolPods(packages) {
|
|
4071
|
+
const hasDevClient = packages.some(
|
|
4072
|
+
(p) => p.name === "@tamer4lynx/tamer-dev-client" || p.name === "tamer-dev-client"
|
|
4073
|
+
);
|
|
4074
|
+
if (!hasDevClient) return;
|
|
4075
|
+
const podfilePath = path17.join(iosProjectPath, "Podfile");
|
|
4076
|
+
if (!fs16.existsSync(podfilePath)) return;
|
|
4077
|
+
let content = fs16.readFileSync(podfilePath, "utf8");
|
|
4078
|
+
if (content.includes("pod 'LynxDevtool'") || content.includes('pod "LynxDevtool"')) return;
|
|
4079
|
+
const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
|
|
4080
|
+
const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
|
|
4081
|
+
if (!content.includes("'Devtool'") && !content.includes('"Devtool"')) {
|
|
4082
|
+
content = content.replace(
|
|
4083
|
+
/(\s+'Http',\s*\n)(\s*\])/,
|
|
4084
|
+
"$1 'Devtool',\n$2"
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
const devtoolLine = ` pod 'LynxDevtool', '${lynxVersion}'
|
|
4088
|
+
|
|
4089
|
+
`;
|
|
4090
|
+
if (content.includes("# GENERATED AUTOLINK DEPENDENCIES START")) {
|
|
4091
|
+
content = content.replace(/(# GENERATED AUTOLINK DEPENDENCIES START)/, `${devtoolLine}$1`);
|
|
4092
|
+
} else {
|
|
4093
|
+
const insertAfter = /pod\s+'LynxService'[^\n]*(?:\n\s*'[^']*',?\s*)*\]\s*/;
|
|
4094
|
+
const serviceMatch = content.match(insertAfter);
|
|
4095
|
+
if (serviceMatch) {
|
|
4096
|
+
const idx = serviceMatch.index + serviceMatch[0].length;
|
|
4097
|
+
content = content.slice(0, idx) + `
|
|
4098
|
+
pod 'LynxDevtool', '${lynxVersion}'` + content.slice(idx);
|
|
4099
|
+
} else {
|
|
4100
|
+
content += `
|
|
4101
|
+
pod 'LynxDevtool', '${lynxVersion}'
|
|
4102
|
+
`;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
fs16.writeFileSync(podfilePath, content, "utf8");
|
|
4106
|
+
console.log(`\u2705 Added Lynx DevTool pods (v${lynxVersion}) to Podfile`);
|
|
4107
|
+
}
|
|
3988
4108
|
function ensureLynxPatchInPodfile() {
|
|
3989
|
-
const podfilePath =
|
|
3990
|
-
if (!
|
|
3991
|
-
let content =
|
|
4109
|
+
const podfilePath = path17.join(iosProjectPath, "Podfile");
|
|
4110
|
+
if (!fs16.existsSync(podfilePath)) return;
|
|
4111
|
+
let content = fs16.readFileSync(podfilePath, "utf8");
|
|
3992
4112
|
if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
|
|
3993
4113
|
const patch = `
|
|
3994
4114
|
Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
|
|
@@ -4000,13 +4120,13 @@ ${replacementBlock}
|
|
|
4000
4120
|
end`;
|
|
4001
4121
|
content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
|
|
4002
4122
|
$2`);
|
|
4003
|
-
|
|
4123
|
+
fs16.writeFileSync(podfilePath, content, "utf8");
|
|
4004
4124
|
console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
|
|
4005
4125
|
}
|
|
4006
4126
|
function ensurePodBuildSettings() {
|
|
4007
|
-
const podfilePath =
|
|
4008
|
-
if (!
|
|
4009
|
-
let content =
|
|
4127
|
+
const podfilePath = path17.join(iosProjectPath, "Podfile");
|
|
4128
|
+
if (!fs16.existsSync(podfilePath)) return;
|
|
4129
|
+
let content = fs16.readFileSync(podfilePath, "utf8");
|
|
4010
4130
|
let changed = false;
|
|
4011
4131
|
if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
|
|
4012
4132
|
content = content.replace(
|
|
@@ -4049,7 +4169,7 @@ $2`);
|
|
|
4049
4169
|
changed = true;
|
|
4050
4170
|
}
|
|
4051
4171
|
if (changed) {
|
|
4052
|
-
|
|
4172
|
+
fs16.writeFileSync(podfilePath, content, "utf8");
|
|
4053
4173
|
console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
|
|
4054
4174
|
}
|
|
4055
4175
|
}
|
|
@@ -4057,10 +4177,10 @@ $2`);
|
|
|
4057
4177
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4058
4178
|
const candidatePaths = [];
|
|
4059
4179
|
if (appNameFromConfig) {
|
|
4060
|
-
candidatePaths.push(
|
|
4180
|
+
candidatePaths.push(path17.join(iosProjectPath, appNameFromConfig, "LynxInitProcessor.swift"));
|
|
4061
4181
|
}
|
|
4062
|
-
candidatePaths.push(
|
|
4063
|
-
const found = candidatePaths.find((p) =>
|
|
4182
|
+
candidatePaths.push(path17.join(iosProjectPath, "LynxInitProcessor.swift"));
|
|
4183
|
+
const found = candidatePaths.find((p) => fs16.existsSync(p));
|
|
4064
4184
|
const lynxInitPath = found ?? candidatePaths[0];
|
|
4065
4185
|
const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
|
|
4066
4186
|
const seenModules = /* @__PURE__ */ new Set();
|
|
@@ -4099,7 +4219,7 @@ $2`);
|
|
|
4099
4219
|
const podName = resolvePodName(pkg);
|
|
4100
4220
|
return `import ${podName}`;
|
|
4101
4221
|
}).join("\n");
|
|
4102
|
-
const fileContent =
|
|
4222
|
+
const fileContent = fs16.readFileSync(filePath, "utf8");
|
|
4103
4223
|
if (fileContent.indexOf(startMarker) !== -1) {
|
|
4104
4224
|
updateGeneratedSection(filePath, imports, startMarker, endMarker);
|
|
4105
4225
|
return;
|
|
@@ -4136,8 +4256,8 @@ ${after}`;
|
|
|
4136
4256
|
${fileContent}`;
|
|
4137
4257
|
}
|
|
4138
4258
|
}
|
|
4139
|
-
|
|
4140
|
-
console.log(`\u2705 Updated imports in ${
|
|
4259
|
+
fs16.writeFileSync(filePath, newContent, "utf8");
|
|
4260
|
+
console.log(`\u2705 Updated imports in ${path17.basename(filePath)}`);
|
|
4141
4261
|
}
|
|
4142
4262
|
updateImportsSection(lynxInitPath, importPackages);
|
|
4143
4263
|
if (importPackages.length === 0) {
|
|
@@ -4181,7 +4301,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4181
4301
|
} else {
|
|
4182
4302
|
devClientSupportedBody = " // @tamer4lynx/tamer-dev-client not linked";
|
|
4183
4303
|
}
|
|
4184
|
-
if (
|
|
4304
|
+
if (fs16.readFileSync(lynxInitPath, "utf8").includes("GENERATED DEV_CLIENT_SUPPORTED START")) {
|
|
4185
4305
|
updateGeneratedSection(lynxInitPath, devClientSupportedBody, "// GENERATED DEV_CLIENT_SUPPORTED START", "// GENERATED DEV_CLIENT_SUPPORTED END");
|
|
4186
4306
|
}
|
|
4187
4307
|
}
|
|
@@ -4189,13 +4309,13 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4189
4309
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4190
4310
|
const candidates = [];
|
|
4191
4311
|
if (appNameFromConfig) {
|
|
4192
|
-
candidates.push(
|
|
4312
|
+
candidates.push(path17.join(iosProjectPath, appNameFromConfig, "Info.plist"));
|
|
4193
4313
|
}
|
|
4194
|
-
candidates.push(
|
|
4195
|
-
return candidates.find((p) =>
|
|
4314
|
+
candidates.push(path17.join(iosProjectPath, "Info.plist"));
|
|
4315
|
+
return candidates.find((p) => fs16.existsSync(p)) ?? null;
|
|
4196
4316
|
}
|
|
4197
4317
|
function readPlistXml(plistPath) {
|
|
4198
|
-
return
|
|
4318
|
+
return fs16.readFileSync(plistPath, "utf8");
|
|
4199
4319
|
}
|
|
4200
4320
|
function syncInfoPlistPermissions(packages) {
|
|
4201
4321
|
const plistPath = findInfoPlist();
|
|
@@ -4226,7 +4346,7 @@ ${androidNames.map((n) => ` "${n.replace(/\\/g, "\\\\").replace(/"/g,
|
|
|
4226
4346
|
added++;
|
|
4227
4347
|
}
|
|
4228
4348
|
if (added > 0) {
|
|
4229
|
-
|
|
4349
|
+
fs16.writeFileSync(plistPath, plist, "utf8");
|
|
4230
4350
|
console.log(`\u2705 Synced ${added} Info.plist permission description(s)`);
|
|
4231
4351
|
}
|
|
4232
4352
|
}
|
|
@@ -4273,16 +4393,16 @@ ${schemesXml}
|
|
|
4273
4393
|
$1`
|
|
4274
4394
|
);
|
|
4275
4395
|
}
|
|
4276
|
-
|
|
4396
|
+
fs16.writeFileSync(plistPath, plist, "utf8");
|
|
4277
4397
|
console.log(`\u2705 Synced ${urlSchemes.length} iOS URL scheme(s) into Info.plist`);
|
|
4278
4398
|
}
|
|
4279
4399
|
function runPodInstall(forcePath) {
|
|
4280
|
-
const podfilePath = forcePath ??
|
|
4281
|
-
if (!
|
|
4400
|
+
const podfilePath = forcePath ?? path17.join(iosProjectPath, "Podfile");
|
|
4401
|
+
if (!fs16.existsSync(podfilePath)) {
|
|
4282
4402
|
console.log("\u2139\uFE0F No Podfile found in ios directory; skipping `pod install`.");
|
|
4283
4403
|
return;
|
|
4284
4404
|
}
|
|
4285
|
-
const cwd =
|
|
4405
|
+
const cwd = path17.dirname(podfilePath);
|
|
4286
4406
|
try {
|
|
4287
4407
|
console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
|
|
4288
4408
|
try {
|
|
@@ -4305,9 +4425,10 @@ $1`
|
|
|
4305
4425
|
} else {
|
|
4306
4426
|
console.log("\u2139\uFE0F No Tamer4Lynx native packages found.");
|
|
4307
4427
|
}
|
|
4308
|
-
syncHost_default();
|
|
4428
|
+
syncHost_default(syncHostOpts);
|
|
4309
4429
|
updatePodfile(packages);
|
|
4310
4430
|
ensureXElementPod();
|
|
4431
|
+
ensureLynxDevToolPods(discoverModules(projectRoot));
|
|
4311
4432
|
ensureLynxPatchInPodfile();
|
|
4312
4433
|
ensurePodBuildSettings();
|
|
4313
4434
|
updateLynxInitProcessor(packages);
|
|
@@ -4316,8 +4437,8 @@ $1`
|
|
|
4316
4437
|
syncInfoPlistUrlSchemes();
|
|
4317
4438
|
const appNameFromConfig = resolved.config.ios?.appName;
|
|
4318
4439
|
if (appNameFromConfig) {
|
|
4319
|
-
const appPodfile =
|
|
4320
|
-
if (
|
|
4440
|
+
const appPodfile = path17.join(iosProjectPath, appNameFromConfig, "Podfile");
|
|
4441
|
+
if (fs16.existsSync(appPodfile)) {
|
|
4321
4442
|
runPodInstall(appPodfile);
|
|
4322
4443
|
console.log("\u2728 Autolinking complete for iOS.");
|
|
4323
4444
|
return;
|
|
@@ -4332,13 +4453,13 @@ $1`
|
|
|
4332
4453
|
const appFolder = resolved.config.ios?.appName;
|
|
4333
4454
|
if (!hasDevClient || !appFolder) return;
|
|
4334
4455
|
const androidNames = getDedupedAndroidModuleClassNames(allPkgs);
|
|
4335
|
-
const appDir =
|
|
4336
|
-
|
|
4337
|
-
const manifestPath =
|
|
4338
|
-
|
|
4456
|
+
const appDir = path17.join(iosProjectPath, appFolder);
|
|
4457
|
+
fs16.mkdirSync(appDir, { recursive: true });
|
|
4458
|
+
const manifestPath = path17.join(appDir, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4459
|
+
fs16.writeFileSync(manifestPath, buildHostNativeModulesManifestJson(androidNames), "utf8");
|
|
4339
4460
|
console.log(`\u2705 Wrote ${TAMER_HOST_NATIVE_MODULES_FILENAME} (native module ids for dev-client checks)`);
|
|
4340
|
-
const pbxprojPath =
|
|
4341
|
-
if (
|
|
4461
|
+
const pbxprojPath = path17.join(iosProjectPath, `${appFolder}.xcodeproj`, "project.pbxproj");
|
|
4462
|
+
if (fs16.existsSync(pbxprojPath)) {
|
|
4342
4463
|
addResourceToXcodeProject(pbxprojPath, appFolder, TAMER_HOST_NATIVE_MODULES_FILENAME);
|
|
4343
4464
|
}
|
|
4344
4465
|
}
|
|
@@ -4347,8 +4468,8 @@ $1`
|
|
|
4347
4468
|
var autolink_default2 = autolink2;
|
|
4348
4469
|
|
|
4349
4470
|
// src/ios/bundle.ts
|
|
4350
|
-
import
|
|
4351
|
-
import
|
|
4471
|
+
import fs17 from "fs";
|
|
4472
|
+
import path18 from "path";
|
|
4352
4473
|
import { execSync as execSync7 } from "child_process";
|
|
4353
4474
|
function bundleAndDeploy2(opts = {}) {
|
|
4354
4475
|
const release = opts.release === true || opts.production === true;
|
|
@@ -4366,20 +4487,19 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4366
4487
|
const includeDevClient = !release && !!devClientPkg;
|
|
4367
4488
|
const appName = resolved.config.ios.appName;
|
|
4368
4489
|
const sourceBundlePath = resolved.lynxBundlePath;
|
|
4369
|
-
const destinationDir =
|
|
4370
|
-
const destinationBundlePath =
|
|
4371
|
-
|
|
4372
|
-
autolink_default2();
|
|
4490
|
+
const destinationDir = path18.join(resolved.iosDir, appName);
|
|
4491
|
+
const destinationBundlePath = path18.join(destinationDir, resolved.lynxBundleFile);
|
|
4492
|
+
autolink_default2({ release, includeDevClient });
|
|
4373
4493
|
const iconPaths = resolveIconPaths(resolved.projectRoot, resolved.config);
|
|
4374
4494
|
if (iconPaths) {
|
|
4375
|
-
const appIconDir =
|
|
4495
|
+
const appIconDir = path18.join(destinationDir, "Assets.xcassets", "AppIcon.appiconset");
|
|
4376
4496
|
if (applyIosAppIconAssets(appIconDir, iconPaths)) {
|
|
4377
4497
|
console.log("\u2705 Synced iOS AppIcon from tamer.config.json");
|
|
4378
4498
|
}
|
|
4379
4499
|
}
|
|
4380
4500
|
try {
|
|
4381
|
-
const lynxTsconfig =
|
|
4382
|
-
if (
|
|
4501
|
+
const lynxTsconfig = path18.join(resolved.lynxProjectDir, "tsconfig.json");
|
|
4502
|
+
if (fs17.existsSync(lynxTsconfig)) {
|
|
4383
4503
|
fixTsconfigReferencesForBuild(lynxTsconfig);
|
|
4384
4504
|
}
|
|
4385
4505
|
console.log("\u{1F4E6} Building Lynx bundle...");
|
|
@@ -4390,40 +4510,40 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4390
4510
|
process.exit(1);
|
|
4391
4511
|
}
|
|
4392
4512
|
try {
|
|
4393
|
-
if (!
|
|
4513
|
+
if (!fs17.existsSync(sourceBundlePath)) {
|
|
4394
4514
|
console.error(`\u274C Build output not found at: ${sourceBundlePath}`);
|
|
4395
4515
|
process.exit(1);
|
|
4396
4516
|
}
|
|
4397
|
-
if (!
|
|
4517
|
+
if (!fs17.existsSync(destinationDir)) {
|
|
4398
4518
|
console.error(`Destination directory not found at: ${destinationDir}`);
|
|
4399
4519
|
process.exit(1);
|
|
4400
4520
|
}
|
|
4401
|
-
const distDir =
|
|
4521
|
+
const distDir = path18.dirname(sourceBundlePath);
|
|
4402
4522
|
console.log(`\u{1F69A} Copying bundle and assets to iOS project...`);
|
|
4403
4523
|
copyDistAssets(distDir, destinationDir, resolved.lynxBundleFile);
|
|
4404
4524
|
console.log(`\u2728 Successfully copied bundle to: ${destinationBundlePath}`);
|
|
4405
|
-
const pbxprojPath =
|
|
4406
|
-
if (
|
|
4525
|
+
const pbxprojPath = path18.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4526
|
+
if (fs17.existsSync(pbxprojPath)) {
|
|
4407
4527
|
const skip = /* @__PURE__ */ new Set([".rspeedy", "stats.json"]);
|
|
4408
|
-
for (const entry of
|
|
4409
|
-
if (skip.has(entry) ||
|
|
4528
|
+
for (const entry of fs17.readdirSync(distDir)) {
|
|
4529
|
+
if (skip.has(entry) || fs17.statSync(path18.join(distDir, entry)).isDirectory()) continue;
|
|
4410
4530
|
addResourceToXcodeProject(pbxprojPath, appName, entry);
|
|
4411
4531
|
}
|
|
4412
4532
|
}
|
|
4413
4533
|
if (includeDevClient && devClientPkg) {
|
|
4414
|
-
const devClientBundle =
|
|
4534
|
+
const devClientBundle = path18.join(destinationDir, "dev-client.lynx.bundle");
|
|
4415
4535
|
console.log("\u{1F4E6} Building dev-client bundle...");
|
|
4416
4536
|
try {
|
|
4417
4537
|
execSync7("npm run build", { stdio: "inherit", cwd: devClientPkg });
|
|
4418
4538
|
} catch {
|
|
4419
4539
|
console.warn("\u26A0\uFE0F dev-client build failed; skipping dev-client bundle");
|
|
4420
4540
|
}
|
|
4421
|
-
const builtBundle =
|
|
4422
|
-
if (
|
|
4423
|
-
|
|
4541
|
+
const builtBundle = path18.join(devClientPkg, "dist", "dev-client.lynx.bundle");
|
|
4542
|
+
if (fs17.existsSync(builtBundle)) {
|
|
4543
|
+
fs17.copyFileSync(builtBundle, devClientBundle);
|
|
4424
4544
|
console.log("\u2728 Copied dev-client.lynx.bundle to iOS project");
|
|
4425
|
-
const pbxprojPath2 =
|
|
4426
|
-
if (
|
|
4545
|
+
const pbxprojPath2 = path18.join(resolved.iosDir, `${appName}.xcodeproj`, "project.pbxproj");
|
|
4546
|
+
if (fs17.existsSync(pbxprojPath2)) {
|
|
4427
4547
|
addResourceToXcodeProject(pbxprojPath2, appName, "dev-client.lynx.bundle");
|
|
4428
4548
|
}
|
|
4429
4549
|
}
|
|
@@ -4437,9 +4557,10 @@ function bundleAndDeploy2(opts = {}) {
|
|
|
4437
4557
|
var bundle_default2 = bundleAndDeploy2;
|
|
4438
4558
|
|
|
4439
4559
|
// src/ios/build.ts
|
|
4440
|
-
import
|
|
4441
|
-
import
|
|
4560
|
+
import fs18 from "fs";
|
|
4561
|
+
import path19 from "path";
|
|
4442
4562
|
import os3 from "os";
|
|
4563
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
4443
4564
|
import { execSync as execSync8 } from "child_process";
|
|
4444
4565
|
function hostArch() {
|
|
4445
4566
|
return os3.arch() === "arm64" ? "arm64" : "x86_64";
|
|
@@ -4457,6 +4578,31 @@ function findBootedSimulator() {
|
|
|
4457
4578
|
}
|
|
4458
4579
|
return null;
|
|
4459
4580
|
}
|
|
4581
|
+
function findFirstConnectedIosDeviceUdid() {
|
|
4582
|
+
const jsonPath = path19.join(os3.tmpdir(), `t4l-devicectl-${randomBytes2(8).toString("hex")}.json`);
|
|
4583
|
+
try {
|
|
4584
|
+
execSync8(`xcrun devicectl list devices --json-output "${jsonPath}"`, {
|
|
4585
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4586
|
+
});
|
|
4587
|
+
if (!fs18.existsSync(jsonPath)) return null;
|
|
4588
|
+
const raw = fs18.readFileSync(jsonPath, "utf8");
|
|
4589
|
+
fs18.unlinkSync(jsonPath);
|
|
4590
|
+
const data = JSON.parse(raw);
|
|
4591
|
+
const devices = data.result?.devices ?? [];
|
|
4592
|
+
for (const d of devices) {
|
|
4593
|
+
const udid = d.hardwareProperties?.udid ?? d.identifier;
|
|
4594
|
+
if (typeof udid === "string" && udid.length >= 20) {
|
|
4595
|
+
return udid;
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
} catch {
|
|
4599
|
+
try {
|
|
4600
|
+
if (fs18.existsSync(jsonPath)) fs18.unlinkSync(jsonPath);
|
|
4601
|
+
} catch {
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
return null;
|
|
4605
|
+
}
|
|
4460
4606
|
async function buildIpa(opts = {}) {
|
|
4461
4607
|
const resolved = resolveHostPaths();
|
|
4462
4608
|
if (!resolved.config.ios?.appName) {
|
|
@@ -4469,17 +4615,19 @@ async function buildIpa(opts = {}) {
|
|
|
4469
4615
|
const configuration = release ? "Release" : "Debug";
|
|
4470
4616
|
bundle_default2({ release, production: opts.production });
|
|
4471
4617
|
const scheme = appName;
|
|
4472
|
-
const workspacePath =
|
|
4473
|
-
const projectPath =
|
|
4474
|
-
const xcproject =
|
|
4618
|
+
const workspacePath = path19.join(iosDir, `${appName}.xcworkspace`);
|
|
4619
|
+
const projectPath = path19.join(iosDir, `${appName}.xcodeproj`);
|
|
4620
|
+
const xcproject = fs18.existsSync(workspacePath) ? workspacePath : projectPath;
|
|
4475
4621
|
const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
|
|
4476
|
-
const derivedDataPath =
|
|
4477
|
-
const
|
|
4478
|
-
const
|
|
4479
|
-
const
|
|
4622
|
+
const derivedDataPath = path19.join(iosDir, "build");
|
|
4623
|
+
const production = opts.production === true;
|
|
4624
|
+
const sdk = production ? "iphoneos" : opts.install ? "iphonesimulator" : "iphoneos";
|
|
4625
|
+
const signingArgs = production || opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
|
|
4626
|
+
const archFlag = opts.install && !production ? `-arch ${hostArch()} ` : "";
|
|
4480
4627
|
const extraSettings = [
|
|
4481
4628
|
"ONLY_ACTIVE_ARCH=YES",
|
|
4482
|
-
"CLANG_ENABLE_EXPLICIT_MODULES=NO"
|
|
4629
|
+
"CLANG_ENABLE_EXPLICIT_MODULES=NO",
|
|
4630
|
+
...configuration === "Debug" ? ["COMPILER_INDEX_STORE_ENABLE=NO"] : []
|
|
4483
4631
|
].join(" ");
|
|
4484
4632
|
console.log(`
|
|
4485
4633
|
\u{1F528} Building ${configuration} (${sdk})...`);
|
|
@@ -4489,38 +4637,77 @@ async function buildIpa(opts = {}) {
|
|
|
4489
4637
|
);
|
|
4490
4638
|
console.log(`\u2705 Build completed.`);
|
|
4491
4639
|
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
|
-
|
|
4640
|
+
if (production) {
|
|
4641
|
+
const appPath = path19.join(
|
|
4642
|
+
derivedDataPath,
|
|
4643
|
+
"Build",
|
|
4644
|
+
"Products",
|
|
4645
|
+
`${configuration}-iphoneos`,
|
|
4646
|
+
`${appName}.app`
|
|
4647
|
+
);
|
|
4648
|
+
if (!fs18.existsSync(appPath)) {
|
|
4649
|
+
console.error(`\u274C Built app not found at: ${appPath}`);
|
|
4650
|
+
process.exit(1);
|
|
4651
|
+
}
|
|
4652
|
+
const udid = findFirstConnectedIosDeviceUdid();
|
|
4653
|
+
if (!udid) {
|
|
4654
|
+
console.error(
|
|
4655
|
+
"\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`)."
|
|
4656
|
+
);
|
|
4657
|
+
process.exit(1);
|
|
4658
|
+
}
|
|
4659
|
+
console.log(`\u{1F4F2} Installing on device ${udid}...`);
|
|
4660
|
+
execSync8(`xcrun devicectl device install app --device "${udid}" "${appPath}"`, {
|
|
4661
|
+
stdio: "inherit"
|
|
4662
|
+
});
|
|
4663
|
+
if (bundleId) {
|
|
4664
|
+
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
4665
|
+
try {
|
|
4666
|
+
execSync8(
|
|
4667
|
+
`xcrun devicectl device process launch --device "${udid}" "${bundleId}"`,
|
|
4668
|
+
{ stdio: "inherit" }
|
|
4669
|
+
);
|
|
4670
|
+
console.log("\u2705 App launched.");
|
|
4671
|
+
} catch {
|
|
4672
|
+
console.log("\u2705 Installed. Launch manually on the device if auto-launch failed.");
|
|
4673
|
+
}
|
|
4674
|
+
} else {
|
|
4675
|
+
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
4676
|
+
}
|
|
4514
4677
|
} else {
|
|
4515
|
-
|
|
4678
|
+
const appGlob = path19.join(
|
|
4679
|
+
derivedDataPath,
|
|
4680
|
+
"Build",
|
|
4681
|
+
"Products",
|
|
4682
|
+
`${configuration}-iphonesimulator`,
|
|
4683
|
+
`${appName}.app`
|
|
4684
|
+
);
|
|
4685
|
+
if (!fs18.existsSync(appGlob)) {
|
|
4686
|
+
console.error(`\u274C Built app not found at: ${appGlob}`);
|
|
4687
|
+
process.exit(1);
|
|
4688
|
+
}
|
|
4689
|
+
const udid = findBootedSimulator();
|
|
4690
|
+
if (!udid) {
|
|
4691
|
+
console.error("\u274C No booted simulator found. Start one with: xcrun simctl boot <udid>");
|
|
4692
|
+
process.exit(1);
|
|
4693
|
+
}
|
|
4694
|
+
console.log(`\u{1F4F2} Installing on simulator ${udid}...`);
|
|
4695
|
+
execSync8(`xcrun simctl install "${udid}" "${appGlob}"`, { stdio: "inherit" });
|
|
4696
|
+
if (bundleId) {
|
|
4697
|
+
console.log(`\u{1F680} Launching ${bundleId}...`);
|
|
4698
|
+
execSync8(`xcrun simctl launch "${udid}" "${bundleId}"`, { stdio: "inherit" });
|
|
4699
|
+
console.log("\u2705 App launched.");
|
|
4700
|
+
} else {
|
|
4701
|
+
console.log('\u2705 App installed. (Set "ios.bundleId" in tamer.config.json to auto-launch.)');
|
|
4702
|
+
}
|
|
4516
4703
|
}
|
|
4517
4704
|
}
|
|
4518
4705
|
}
|
|
4519
4706
|
var build_default2 = buildIpa;
|
|
4520
4707
|
|
|
4521
4708
|
// src/common/init.tsx
|
|
4522
|
-
import
|
|
4523
|
-
import
|
|
4709
|
+
import fs19 from "fs";
|
|
4710
|
+
import path20 from "path";
|
|
4524
4711
|
import { useState as useState4, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
4525
4712
|
import { render, Text as Text9, Box as Box8 } from "ink";
|
|
4526
4713
|
|
|
@@ -4678,7 +4865,9 @@ function Wizard({ step, total, title, children }) {
|
|
|
4678
4865
|
import "react";
|
|
4679
4866
|
import { Box as Box7, Text as Text8 } from "ink";
|
|
4680
4867
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4868
|
+
var LOG_DISPLAY_MAX = 120;
|
|
4681
4869
|
function ServerDashboard({
|
|
4870
|
+
cliVersion,
|
|
4682
4871
|
projectName,
|
|
4683
4872
|
port,
|
|
4684
4873
|
lanIp,
|
|
@@ -4691,7 +4880,6 @@ function ServerDashboard({
|
|
|
4691
4880
|
buildError,
|
|
4692
4881
|
wsConnections,
|
|
4693
4882
|
logLines,
|
|
4694
|
-
showLogs,
|
|
4695
4883
|
qrLines,
|
|
4696
4884
|
phase,
|
|
4697
4885
|
startError
|
|
@@ -4710,6 +4898,10 @@ function ServerDashboard({
|
|
|
4710
4898
|
projectName,
|
|
4711
4899
|
")"
|
|
4712
4900
|
] }),
|
|
4901
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
4902
|
+
"t4l v",
|
|
4903
|
+
cliVersion
|
|
4904
|
+
] }),
|
|
4713
4905
|
verbose ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Logs: verbose (native + JS)" }) : null,
|
|
4714
4906
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "row", columnGap: 3, alignItems: "flex-start", children: [
|
|
4715
4907
|
qrLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", flexShrink: 0, children: [
|
|
@@ -4746,7 +4938,7 @@ function ServerDashboard({
|
|
|
4746
4938
|
bonjour ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "mDNS: _tamer._tcp" }) : null,
|
|
4747
4939
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4748
4940
|
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Build" }),
|
|
4749
|
-
buildPhase === "building" ? /* @__PURE__ */ jsx8(TuiSpinner, { label: "
|
|
4941
|
+
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
4942
|
] }),
|
|
4751
4943
|
/* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4752
4944
|
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Connections" }),
|
|
@@ -4759,15 +4951,15 @@ function ServerDashboard({
|
|
|
4759
4951
|
}
|
|
4760
4952
|
)
|
|
4761
4953
|
] }),
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4954
|
+
/* @__PURE__ */ jsx8(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "r rebuild \xB7 c clear output \xB7 Ctrl+C or q quit" }) }),
|
|
4955
|
+
logLines.length > 0 ? /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
4956
|
+
logLines.length > LOG_DISPLAY_MAX ? /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
4957
|
+
"\u2026 ",
|
|
4958
|
+
logLines.length - LOG_DISPLAY_MAX,
|
|
4959
|
+
" earlier lines omitted"
|
|
4960
|
+
] }) : null,
|
|
4961
|
+
logLines.slice(-LOG_DISPLAY_MAX).map((line, i) => /* @__PURE__ */ jsx8(Text8, { children: line }, i))
|
|
4962
|
+
] }) : null
|
|
4771
4963
|
] });
|
|
4772
4964
|
}
|
|
4773
4965
|
|
|
@@ -4833,22 +5025,22 @@ function InitWizard() {
|
|
|
4833
5025
|
paths: { androidDir: "android", iosDir: "ios" }
|
|
4834
5026
|
};
|
|
4835
5027
|
if (lynxProject.trim()) config.lynxProject = lynxProject.trim();
|
|
4836
|
-
const configPath =
|
|
4837
|
-
|
|
5028
|
+
const configPath = path20.join(process.cwd(), "tamer.config.json");
|
|
5029
|
+
fs19.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4838
5030
|
const lines = [`Generated tamer.config.json at ${configPath}`];
|
|
4839
5031
|
const tamerTypesInclude = "node_modules/@tamer4lynx/tamer-*/src/**/*.d.ts";
|
|
4840
5032
|
const tsconfigCandidates = lynxProject.trim() ? [
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
] : [
|
|
5033
|
+
path20.join(process.cwd(), lynxProject.trim(), "tsconfig.json"),
|
|
5034
|
+
path20.join(process.cwd(), "tsconfig.json")
|
|
5035
|
+
] : [path20.join(process.cwd(), "tsconfig.json")];
|
|
4844
5036
|
for (const tsconfigPath of tsconfigCandidates) {
|
|
4845
|
-
if (!
|
|
5037
|
+
if (!fs19.existsSync(tsconfigPath)) continue;
|
|
4846
5038
|
try {
|
|
4847
5039
|
if (fixTsconfigReferencesForBuild(tsconfigPath)) {
|
|
4848
|
-
lines.push(`Flattened ${
|
|
5040
|
+
lines.push(`Flattened ${path20.relative(process.cwd(), tsconfigPath)} (fixed TS6310)`);
|
|
4849
5041
|
}
|
|
4850
5042
|
if (addTamerTypesInclude(tsconfigPath, tamerTypesInclude)) {
|
|
4851
|
-
lines.push(`Updated ${
|
|
5043
|
+
lines.push(`Updated ${path20.relative(process.cwd(), tsconfigPath)} for tamer types`);
|
|
4852
5044
|
}
|
|
4853
5045
|
break;
|
|
4854
5046
|
} catch (e) {
|
|
@@ -5008,8 +5200,8 @@ async function init() {
|
|
|
5008
5200
|
}
|
|
5009
5201
|
|
|
5010
5202
|
// src/common/create.ts
|
|
5011
|
-
import
|
|
5012
|
-
import
|
|
5203
|
+
import fs20 from "fs";
|
|
5204
|
+
import path21 from "path";
|
|
5013
5205
|
import readline from "readline";
|
|
5014
5206
|
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
5015
5207
|
function ask(question) {
|
|
@@ -5071,13 +5263,13 @@ async function create3(opts) {
|
|
|
5071
5263
|
const simpleModuleName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Module";
|
|
5072
5264
|
const fullModuleClassName = `${packageName}.${simpleModuleName}`;
|
|
5073
5265
|
const cwd = process.cwd();
|
|
5074
|
-
const root =
|
|
5075
|
-
if (
|
|
5266
|
+
const root = path21.join(cwd, extName);
|
|
5267
|
+
if (fs20.existsSync(root)) {
|
|
5076
5268
|
console.error(`\u274C Directory ${extName} already exists.`);
|
|
5077
5269
|
rl.close();
|
|
5078
5270
|
process.exit(1);
|
|
5079
5271
|
}
|
|
5080
|
-
|
|
5272
|
+
fs20.mkdirSync(root, { recursive: true });
|
|
5081
5273
|
const lynxExt = {
|
|
5082
5274
|
platforms: {
|
|
5083
5275
|
android: {
|
|
@@ -5092,7 +5284,7 @@ async function create3(opts) {
|
|
|
5092
5284
|
web: {}
|
|
5093
5285
|
}
|
|
5094
5286
|
};
|
|
5095
|
-
|
|
5287
|
+
fs20.writeFileSync(path21.join(root, "lynx.ext.json"), JSON.stringify(lynxExt, null, 2));
|
|
5096
5288
|
const pkg = {
|
|
5097
5289
|
name: extName,
|
|
5098
5290
|
version: "0.0.1",
|
|
@@ -5105,20 +5297,20 @@ async function create3(opts) {
|
|
|
5105
5297
|
engines: { node: ">=18" }
|
|
5106
5298
|
};
|
|
5107
5299
|
if (includeModule) pkg.types = "src/index.d.ts";
|
|
5108
|
-
|
|
5300
|
+
fs20.writeFileSync(path21.join(root, "package.json"), JSON.stringify(pkg, null, 2));
|
|
5109
5301
|
const pkgPath = packageName.replace(/\./g, "/");
|
|
5110
5302
|
const hasSrc = includeModule || includeElement || includeService;
|
|
5111
5303
|
if (hasSrc) {
|
|
5112
|
-
|
|
5304
|
+
fs20.mkdirSync(path21.join(root, "src"), { recursive: true });
|
|
5113
5305
|
}
|
|
5114
5306
|
if (includeModule) {
|
|
5115
|
-
|
|
5307
|
+
fs20.writeFileSync(path21.join(root, "src", "index.d.ts"), `/** @lynxmodule */
|
|
5116
5308
|
export declare class ${simpleModuleName} {
|
|
5117
5309
|
// Add your module methods here
|
|
5118
5310
|
}
|
|
5119
5311
|
`);
|
|
5120
|
-
|
|
5121
|
-
|
|
5312
|
+
fs20.mkdirSync(path21.join(root, "android", "src", "main", "kotlin", pkgPath), { recursive: true });
|
|
5313
|
+
fs20.writeFileSync(path21.join(root, "android", "build.gradle.kts"), `plugins {
|
|
5122
5314
|
id("com.android.library")
|
|
5123
5315
|
id("org.jetbrains.kotlin.android")
|
|
5124
5316
|
}
|
|
@@ -5139,7 +5331,7 @@ dependencies {
|
|
|
5139
5331
|
implementation(libs.lynx.jssdk)
|
|
5140
5332
|
}
|
|
5141
5333
|
`);
|
|
5142
|
-
|
|
5334
|
+
fs20.writeFileSync(path21.join(root, "android", "src", "main", "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
5143
5335
|
<manifest />
|
|
5144
5336
|
`);
|
|
5145
5337
|
const ktContent = `package ${packageName}
|
|
@@ -5156,8 +5348,8 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5156
5348
|
}
|
|
5157
5349
|
}
|
|
5158
5350
|
`;
|
|
5159
|
-
|
|
5160
|
-
|
|
5351
|
+
fs20.writeFileSync(path21.join(root, "android", "src", "main", "kotlin", pkgPath, `${simpleModuleName}.kt`), ktContent);
|
|
5352
|
+
fs20.mkdirSync(path21.join(root, "ios", extName, extName, "Classes"), { recursive: true });
|
|
5161
5353
|
const podspec = `Pod::Spec.new do |s|
|
|
5162
5354
|
s.name = '${extName}'
|
|
5163
5355
|
s.version = '0.0.1'
|
|
@@ -5171,7 +5363,7 @@ class ${simpleModuleName}(context: Context) : LynxModule(context) {
|
|
|
5171
5363
|
s.dependency 'Lynx'
|
|
5172
5364
|
end
|
|
5173
5365
|
`;
|
|
5174
|
-
|
|
5366
|
+
fs20.writeFileSync(path21.join(root, "ios", extName, `${extName}.podspec`), podspec);
|
|
5175
5367
|
const swiftContent = `import Foundation
|
|
5176
5368
|
|
|
5177
5369
|
@objc public class ${simpleModuleName}: NSObject {
|
|
@@ -5180,18 +5372,18 @@ end
|
|
|
5180
5372
|
}
|
|
5181
5373
|
}
|
|
5182
5374
|
`;
|
|
5183
|
-
|
|
5375
|
+
fs20.writeFileSync(path21.join(root, "ios", extName, extName, "Classes", `${simpleModuleName}.swift`), swiftContent);
|
|
5184
5376
|
}
|
|
5185
5377
|
if (includeElement && !includeModule) {
|
|
5186
5378
|
const elementName = extName.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
5187
|
-
|
|
5379
|
+
fs20.writeFileSync(path21.join(root, "src", "index.tsx"), `import type { FC } from '@lynx-js/react';
|
|
5188
5380
|
|
|
5189
5381
|
export const ${elementName}: FC = () => {
|
|
5190
5382
|
return null;
|
|
5191
5383
|
};
|
|
5192
5384
|
`);
|
|
5193
5385
|
}
|
|
5194
|
-
|
|
5386
|
+
fs20.writeFileSync(path21.join(root, "index.js"), `'use strict';
|
|
5195
5387
|
module.exports = {};
|
|
5196
5388
|
`);
|
|
5197
5389
|
const tsconfigCompiler = {
|
|
@@ -5204,11 +5396,11 @@ module.exports = {};
|
|
|
5204
5396
|
tsconfigCompiler.jsx = "preserve";
|
|
5205
5397
|
tsconfigCompiler.jsxImportSource = "@lynx-js/react";
|
|
5206
5398
|
}
|
|
5207
|
-
|
|
5399
|
+
fs20.writeFileSync(path21.join(root, "tsconfig.json"), JSON.stringify({
|
|
5208
5400
|
compilerOptions: tsconfigCompiler,
|
|
5209
5401
|
include: includeElement ? ["src", "src/**/*.tsx"] : ["src"]
|
|
5210
5402
|
}, null, 2));
|
|
5211
|
-
|
|
5403
|
+
fs20.writeFileSync(path21.join(root, "README.md"), `# ${extName}
|
|
5212
5404
|
|
|
5213
5405
|
Lynx extension for ${extName}.
|
|
5214
5406
|
|
|
@@ -5233,8 +5425,8 @@ This package uses \`lynx.ext.json\` (RFC-compliant) for autolinking.
|
|
|
5233
5425
|
var create_default3 = create3;
|
|
5234
5426
|
|
|
5235
5427
|
// src/common/codegen.ts
|
|
5236
|
-
import
|
|
5237
|
-
import
|
|
5428
|
+
import fs21 from "fs";
|
|
5429
|
+
import path22 from "path";
|
|
5238
5430
|
function codegen() {
|
|
5239
5431
|
const cwd = process.cwd();
|
|
5240
5432
|
const config = loadExtensionConfig(cwd);
|
|
@@ -5242,9 +5434,9 @@ function codegen() {
|
|
|
5242
5434
|
console.error("\u274C No lynx.ext.json or tamer.json found. Run from an extension package root.");
|
|
5243
5435
|
process.exit(1);
|
|
5244
5436
|
}
|
|
5245
|
-
const srcDir =
|
|
5246
|
-
const generatedDir =
|
|
5247
|
-
|
|
5437
|
+
const srcDir = path22.join(cwd, "src");
|
|
5438
|
+
const generatedDir = path22.join(cwd, "generated");
|
|
5439
|
+
fs21.mkdirSync(generatedDir, { recursive: true });
|
|
5248
5440
|
const dtsFiles = findDtsFiles(srcDir);
|
|
5249
5441
|
const modules = extractLynxModules(dtsFiles);
|
|
5250
5442
|
if (modules.length === 0) {
|
|
@@ -5254,28 +5446,28 @@ function codegen() {
|
|
|
5254
5446
|
for (const mod of modules) {
|
|
5255
5447
|
const tsContent = `export type { ${mod} } from '../src/index.js';
|
|
5256
5448
|
`;
|
|
5257
|
-
const outPath =
|
|
5258
|
-
|
|
5449
|
+
const outPath = path22.join(generatedDir, `${mod}.ts`);
|
|
5450
|
+
fs21.writeFileSync(outPath, tsContent);
|
|
5259
5451
|
console.log(`\u2705 Generated ${outPath}`);
|
|
5260
5452
|
}
|
|
5261
5453
|
if (config.android) {
|
|
5262
|
-
const androidGenerated =
|
|
5263
|
-
|
|
5454
|
+
const androidGenerated = path22.join(cwd, "android", "src", "main", "kotlin", config.android.moduleClassName.replace(/\./g, "/").replace(/[^/]+$/, ""), "generated");
|
|
5455
|
+
fs21.mkdirSync(androidGenerated, { recursive: true });
|
|
5264
5456
|
console.log(`\u2139\uFE0F Android generated dir: ${androidGenerated} (spec generation coming soon)`);
|
|
5265
5457
|
}
|
|
5266
5458
|
if (config.ios) {
|
|
5267
|
-
const iosGenerated =
|
|
5268
|
-
|
|
5459
|
+
const iosGenerated = path22.join(cwd, "ios", "generated");
|
|
5460
|
+
fs21.mkdirSync(iosGenerated, { recursive: true });
|
|
5269
5461
|
console.log(`\u2139\uFE0F iOS generated dir: ${iosGenerated} (spec generation coming soon)`);
|
|
5270
5462
|
}
|
|
5271
5463
|
console.log("\u2728 Codegen complete.");
|
|
5272
5464
|
}
|
|
5273
5465
|
function findDtsFiles(dir) {
|
|
5274
5466
|
const result = [];
|
|
5275
|
-
if (!
|
|
5276
|
-
const entries =
|
|
5467
|
+
if (!fs21.existsSync(dir)) return result;
|
|
5468
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
5277
5469
|
for (const e of entries) {
|
|
5278
|
-
const full =
|
|
5470
|
+
const full = path22.join(dir, e.name);
|
|
5279
5471
|
if (e.isDirectory()) result.push(...findDtsFiles(full));
|
|
5280
5472
|
else if (e.name.endsWith(".d.ts")) result.push(full);
|
|
5281
5473
|
}
|
|
@@ -5285,7 +5477,7 @@ function extractLynxModules(files) {
|
|
|
5285
5477
|
const modules = [];
|
|
5286
5478
|
const seen = /* @__PURE__ */ new Set();
|
|
5287
5479
|
for (const file of files) {
|
|
5288
|
-
const content =
|
|
5480
|
+
const content = fs21.readFileSync(file, "utf8");
|
|
5289
5481
|
const regex = /\/\*\*\s*@lynxmodule\s*\*\/\s*export\s+declare\s+class\s+(\w+)/g;
|
|
5290
5482
|
let m;
|
|
5291
5483
|
while ((m = regex.exec(content)) !== null) {
|
|
@@ -5302,14 +5494,16 @@ var codegen_default = codegen;
|
|
|
5302
5494
|
// src/common/devServer.tsx
|
|
5303
5495
|
import { useState as useState5, useEffect as useEffect3, useRef, useCallback as useCallback4 } from "react";
|
|
5304
5496
|
import { spawn } from "child_process";
|
|
5305
|
-
import
|
|
5497
|
+
import fs22 from "fs";
|
|
5306
5498
|
import http from "http";
|
|
5307
5499
|
import os4 from "os";
|
|
5308
|
-
import
|
|
5500
|
+
import path23 from "path";
|
|
5309
5501
|
import { render as render2, useInput, useApp } from "ink";
|
|
5310
|
-
import { WebSocketServer } from "ws";
|
|
5502
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
5311
5503
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
5312
5504
|
var DEFAULT_PORT = 3e3;
|
|
5505
|
+
var TAMER_CLI_VERSION = getCliVersion();
|
|
5506
|
+
var MAX_LOG_LINES = 800;
|
|
5313
5507
|
var STATIC_MIME = {
|
|
5314
5508
|
".png": "image/png",
|
|
5315
5509
|
".jpg": "image/jpeg",
|
|
@@ -5321,13 +5515,13 @@ var STATIC_MIME = {
|
|
|
5321
5515
|
".pdf": "application/pdf"
|
|
5322
5516
|
};
|
|
5323
5517
|
function sendFileFromDisk(res, absPath) {
|
|
5324
|
-
|
|
5518
|
+
fs22.readFile(absPath, (err, data) => {
|
|
5325
5519
|
if (err) {
|
|
5326
5520
|
res.writeHead(404);
|
|
5327
5521
|
res.end("Not found");
|
|
5328
5522
|
return;
|
|
5329
5523
|
}
|
|
5330
|
-
const ext =
|
|
5524
|
+
const ext = path23.extname(absPath).toLowerCase();
|
|
5331
5525
|
res.setHeader("Content-Type", STATIC_MIME[ext] ?? "application/octet-stream");
|
|
5332
5526
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5333
5527
|
res.end(data);
|
|
@@ -5364,9 +5558,9 @@ function getLanIp() {
|
|
|
5364
5558
|
return "localhost";
|
|
5365
5559
|
}
|
|
5366
5560
|
function detectPackageManager(cwd) {
|
|
5367
|
-
const dir =
|
|
5368
|
-
if (
|
|
5369
|
-
if (
|
|
5561
|
+
const dir = path23.resolve(cwd);
|
|
5562
|
+
if (fs22.existsSync(path23.join(dir, "pnpm-lock.yaml"))) return { cmd: "pnpm", args: ["run", "build"] };
|
|
5563
|
+
if (fs22.existsSync(path23.join(dir, "bun.lockb")) || fs22.existsSync(path23.join(dir, "bun.lock")))
|
|
5370
5564
|
return { cmd: "bun", args: ["run", "build"] };
|
|
5371
5565
|
return { cmd: "npm", args: ["run", "build"] };
|
|
5372
5566
|
}
|
|
@@ -5383,7 +5577,6 @@ var initialUi = () => ({
|
|
|
5383
5577
|
buildPhase: "idle",
|
|
5384
5578
|
wsConnections: 0,
|
|
5385
5579
|
logLines: [],
|
|
5386
|
-
showLogs: false,
|
|
5387
5580
|
qrLines: []
|
|
5388
5581
|
});
|
|
5389
5582
|
function DevServerApp({ verbose }) {
|
|
@@ -5396,13 +5589,20 @@ function DevServerApp({ verbose }) {
|
|
|
5396
5589
|
const cleanupRef = useRef(null);
|
|
5397
5590
|
const rebuildRef = useRef(() => Promise.resolve());
|
|
5398
5591
|
const quitOnceRef = useRef(false);
|
|
5399
|
-
const
|
|
5400
|
-
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
5592
|
+
const appendLogLine = useCallback4((line) => {
|
|
5401
5593
|
setUi((prev) => ({
|
|
5402
5594
|
...prev,
|
|
5403
|
-
logLines: [...prev.logLines,
|
|
5595
|
+
logLines: [...prev.logLines, line].slice(-MAX_LOG_LINES)
|
|
5404
5596
|
}));
|
|
5405
5597
|
}, []);
|
|
5598
|
+
const appendLog = useCallback4(
|
|
5599
|
+
(chunk) => {
|
|
5600
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
5601
|
+
appendLogLine(line);
|
|
5602
|
+
}
|
|
5603
|
+
},
|
|
5604
|
+
[appendLogLine]
|
|
5605
|
+
);
|
|
5406
5606
|
const handleQuit = useCallback4(() => {
|
|
5407
5607
|
if (quitOnceRef.current) return;
|
|
5408
5608
|
quitOnceRef.current = true;
|
|
@@ -5410,7 +5610,7 @@ function DevServerApp({ verbose }) {
|
|
|
5410
5610
|
exit();
|
|
5411
5611
|
}, [exit]);
|
|
5412
5612
|
useInput((input, key) => {
|
|
5413
|
-
if (key.ctrl &&
|
|
5613
|
+
if (key.ctrl && input === "c") {
|
|
5414
5614
|
handleQuit();
|
|
5415
5615
|
return;
|
|
5416
5616
|
}
|
|
@@ -5422,8 +5622,9 @@ function DevServerApp({ verbose }) {
|
|
|
5422
5622
|
void rebuildRef.current();
|
|
5423
5623
|
return;
|
|
5424
5624
|
}
|
|
5425
|
-
if (input === "
|
|
5426
|
-
setUi((s) => ({ ...s,
|
|
5625
|
+
if (input === "c") {
|
|
5626
|
+
setUi((s) => ({ ...s, logLines: [] }));
|
|
5627
|
+
return;
|
|
5427
5628
|
}
|
|
5428
5629
|
});
|
|
5429
5630
|
useEffect3(() => {
|
|
@@ -5446,8 +5647,8 @@ function DevServerApp({ verbose }) {
|
|
|
5446
5647
|
try {
|
|
5447
5648
|
const resolved = resolveHostPaths();
|
|
5448
5649
|
const { projectRoot, lynxProjectDir, lynxBundlePath, lynxBundleFile, config } = resolved;
|
|
5449
|
-
const distDir =
|
|
5450
|
-
const projectName =
|
|
5650
|
+
const distDir = path23.dirname(lynxBundlePath);
|
|
5651
|
+
const projectName = path23.basename(lynxProjectDir);
|
|
5451
5652
|
const basePath = `/${projectName}`;
|
|
5452
5653
|
setUi((s) => ({ ...s, projectName, lynxBundleFile }));
|
|
5453
5654
|
const preferredPort = config.devServer?.port ?? config.devServer?.httpPort ?? DEFAULT_PORT;
|
|
@@ -5457,18 +5658,18 @@ function DevServerApp({ verbose }) {
|
|
|
5457
5658
|
}
|
|
5458
5659
|
const iconPaths = resolveIconPaths(projectRoot, config);
|
|
5459
5660
|
let iconFilePath = null;
|
|
5460
|
-
if (iconPaths?.source &&
|
|
5661
|
+
if (iconPaths?.source && fs22.statSync(iconPaths.source).isFile()) {
|
|
5461
5662
|
iconFilePath = iconPaths.source;
|
|
5462
|
-
} else if (iconPaths?.androidAdaptiveForeground &&
|
|
5663
|
+
} else if (iconPaths?.androidAdaptiveForeground && fs22.statSync(iconPaths.androidAdaptiveForeground).isFile()) {
|
|
5463
5664
|
iconFilePath = iconPaths.androidAdaptiveForeground;
|
|
5464
5665
|
} else if (iconPaths?.android) {
|
|
5465
|
-
const androidIcon =
|
|
5466
|
-
if (
|
|
5666
|
+
const androidIcon = path23.join(iconPaths.android, "mipmap-xxxhdpi", "ic_launcher.png");
|
|
5667
|
+
if (fs22.existsSync(androidIcon)) iconFilePath = androidIcon;
|
|
5467
5668
|
} else if (iconPaths?.ios) {
|
|
5468
|
-
const iosIcon =
|
|
5469
|
-
if (
|
|
5669
|
+
const iosIcon = path23.join(iconPaths.ios, "Icon-1024.png");
|
|
5670
|
+
if (fs22.existsSync(iosIcon)) iconFilePath = iosIcon;
|
|
5470
5671
|
}
|
|
5471
|
-
const iconExt = iconFilePath ?
|
|
5672
|
+
const iconExt = iconFilePath ? path23.extname(iconFilePath) || ".png" : "";
|
|
5472
5673
|
const runBuild = () => {
|
|
5473
5674
|
return new Promise((resolve, reject) => {
|
|
5474
5675
|
const { cmd, args } = detectPackageManager(lynxProjectDir);
|
|
@@ -5477,19 +5678,15 @@ function DevServerApp({ verbose }) {
|
|
|
5477
5678
|
stdio: "pipe",
|
|
5478
5679
|
shell: process.platform === "win32"
|
|
5479
5680
|
});
|
|
5480
|
-
let
|
|
5481
|
-
buildProcess.stdout?.
|
|
5482
|
-
appendLog(d.toString());
|
|
5483
|
-
});
|
|
5681
|
+
let stderrRaw = "";
|
|
5682
|
+
buildProcess.stdout?.resume();
|
|
5484
5683
|
buildProcess.stderr?.on("data", (d) => {
|
|
5485
|
-
|
|
5486
|
-
stderr += t;
|
|
5487
|
-
appendLog(t);
|
|
5684
|
+
stderrRaw += d.toString();
|
|
5488
5685
|
});
|
|
5489
5686
|
buildProcess.on("close", (code) => {
|
|
5490
5687
|
buildProcess = null;
|
|
5491
5688
|
if (code === 0) resolve();
|
|
5492
|
-
else reject(new Error(
|
|
5689
|
+
else reject(new Error(stderrRaw.trim() || `Build exited ${code}`));
|
|
5493
5690
|
});
|
|
5494
5691
|
});
|
|
5495
5692
|
};
|
|
@@ -5554,7 +5751,7 @@ function DevServerApp({ verbose }) {
|
|
|
5554
5751
|
return;
|
|
5555
5752
|
}
|
|
5556
5753
|
if (iconFilePath && (reqPath === `${basePath}/icon` || reqPath === `${basePath}/icon${iconExt}`)) {
|
|
5557
|
-
|
|
5754
|
+
fs22.readFile(iconFilePath, (err, data) => {
|
|
5558
5755
|
if (err) {
|
|
5559
5756
|
res.writeHead(404);
|
|
5560
5757
|
res.end();
|
|
@@ -5580,20 +5777,20 @@ function DevServerApp({ verbose }) {
|
|
|
5580
5777
|
res.end();
|
|
5581
5778
|
return;
|
|
5582
5779
|
}
|
|
5583
|
-
const safe =
|
|
5584
|
-
if (
|
|
5780
|
+
const safe = path23.normalize(rel).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
5781
|
+
if (path23.isAbsolute(safe) || safe.startsWith("..")) {
|
|
5585
5782
|
res.writeHead(403);
|
|
5586
5783
|
res.end();
|
|
5587
5784
|
return;
|
|
5588
5785
|
}
|
|
5589
|
-
const allowedRoot =
|
|
5590
|
-
const abs =
|
|
5591
|
-
if (!abs.startsWith(allowedRoot +
|
|
5786
|
+
const allowedRoot = path23.resolve(lynxProjectDir, rootSub);
|
|
5787
|
+
const abs = path23.resolve(allowedRoot, safe);
|
|
5788
|
+
if (!abs.startsWith(allowedRoot + path23.sep) && abs !== allowedRoot) {
|
|
5592
5789
|
res.writeHead(403);
|
|
5593
5790
|
res.end();
|
|
5594
5791
|
return;
|
|
5595
5792
|
}
|
|
5596
|
-
if (!
|
|
5793
|
+
if (!fs22.existsSync(abs) || !fs22.statSync(abs).isFile()) {
|
|
5597
5794
|
res.writeHead(404);
|
|
5598
5795
|
res.end("Not found");
|
|
5599
5796
|
return;
|
|
@@ -5607,14 +5804,14 @@ function DevServerApp({ verbose }) {
|
|
|
5607
5804
|
reqPath = basePath + (reqPath.startsWith("/") ? reqPath : "/" + reqPath);
|
|
5608
5805
|
}
|
|
5609
5806
|
const relPath = reqPath.replace(basePath, "").replace(/^\//, "") || lynxBundleFile;
|
|
5610
|
-
const filePath =
|
|
5611
|
-
const distResolved =
|
|
5612
|
-
if (!filePath.startsWith(distResolved +
|
|
5807
|
+
const filePath = path23.resolve(distDir, relPath);
|
|
5808
|
+
const distResolved = path23.resolve(distDir);
|
|
5809
|
+
if (!filePath.startsWith(distResolved + path23.sep) && filePath !== distResolved) {
|
|
5613
5810
|
res.writeHead(403);
|
|
5614
5811
|
res.end();
|
|
5615
5812
|
return;
|
|
5616
5813
|
}
|
|
5617
|
-
|
|
5814
|
+
fs22.readFile(filePath, (err, data) => {
|
|
5618
5815
|
if (err) {
|
|
5619
5816
|
res.writeHead(404);
|
|
5620
5817
|
res.end("Not found");
|
|
@@ -5626,6 +5823,14 @@ function DevServerApp({ verbose }) {
|
|
|
5626
5823
|
});
|
|
5627
5824
|
});
|
|
5628
5825
|
const wssInst = new WebSocketServer({ noServer: true });
|
|
5826
|
+
const syncWsClientCount = () => {
|
|
5827
|
+
if (!alive) return;
|
|
5828
|
+
let n = 0;
|
|
5829
|
+
wssInst.clients.forEach((c) => {
|
|
5830
|
+
if (c.readyState === WebSocket.OPEN) n++;
|
|
5831
|
+
});
|
|
5832
|
+
setUi((s) => ({ ...s, wsConnections: n }));
|
|
5833
|
+
};
|
|
5629
5834
|
rebuildRef.current = async () => {
|
|
5630
5835
|
try {
|
|
5631
5836
|
await doBuild();
|
|
@@ -5647,12 +5852,15 @@ function DevServerApp({ verbose }) {
|
|
|
5647
5852
|
});
|
|
5648
5853
|
wssInst.on("connection", (ws, req) => {
|
|
5649
5854
|
const clientIp = req.socket.remoteAddress ?? "unknown";
|
|
5650
|
-
setUi((s) => ({ ...s, wsConnections: s.wsConnections + 1 }));
|
|
5651
5855
|
appendLog(`[WS] connected: ${clientIp}`);
|
|
5652
5856
|
ws.send(JSON.stringify({ type: "connected" }));
|
|
5857
|
+
syncWsClientCount();
|
|
5653
5858
|
ws.on("close", () => {
|
|
5654
|
-
setUi((s) => ({ ...s, wsConnections: Math.max(0, s.wsConnections - 1) }));
|
|
5655
5859
|
appendLog(`[WS] disconnected: ${clientIp}`);
|
|
5860
|
+
queueMicrotask(() => syncWsClientCount());
|
|
5861
|
+
});
|
|
5862
|
+
ws.on("error", () => {
|
|
5863
|
+
queueMicrotask(() => syncWsClientCount());
|
|
5656
5864
|
});
|
|
5657
5865
|
ws.on("message", (data) => {
|
|
5658
5866
|
try {
|
|
@@ -5675,10 +5883,10 @@ function DevServerApp({ verbose }) {
|
|
|
5675
5883
|
}
|
|
5676
5884
|
if (chokidar) {
|
|
5677
5885
|
const watchPaths = [
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
].filter((p) =>
|
|
5886
|
+
path23.join(lynxProjectDir, "src"),
|
|
5887
|
+
path23.join(lynxProjectDir, "lynx.config.ts"),
|
|
5888
|
+
path23.join(lynxProjectDir, "lynx.config.js")
|
|
5889
|
+
].filter((p) => fs22.existsSync(p));
|
|
5682
5890
|
if (watchPaths.length > 0) {
|
|
5683
5891
|
const w = chokidar.watch(watchPaths, { ignoreInitial: true });
|
|
5684
5892
|
w.on("change", async () => {
|
|
@@ -5758,10 +5966,11 @@ function DevServerApp({ verbose }) {
|
|
|
5758
5966
|
alive = false;
|
|
5759
5967
|
void cleanupRef.current?.();
|
|
5760
5968
|
};
|
|
5761
|
-
}, [appendLog, verbose]);
|
|
5969
|
+
}, [appendLog, appendLogLine, verbose]);
|
|
5762
5970
|
return /* @__PURE__ */ jsx10(
|
|
5763
5971
|
ServerDashboard,
|
|
5764
5972
|
{
|
|
5973
|
+
cliVersion: TAMER_CLI_VERSION,
|
|
5765
5974
|
projectName: ui.projectName,
|
|
5766
5975
|
port: ui.port,
|
|
5767
5976
|
lanIp: ui.lanIp,
|
|
@@ -5774,7 +5983,6 @@ function DevServerApp({ verbose }) {
|
|
|
5774
5983
|
buildError: ui.buildError,
|
|
5775
5984
|
wsConnections: ui.wsConnections,
|
|
5776
5985
|
logLines: ui.logLines,
|
|
5777
|
-
showLogs: ui.showLogs,
|
|
5778
5986
|
qrLines: ui.qrLines,
|
|
5779
5987
|
phase: ui.phase,
|
|
5780
5988
|
startError: ui.startError
|
|
@@ -5798,10 +6006,10 @@ async function start(opts) {
|
|
|
5798
6006
|
var start_default = start;
|
|
5799
6007
|
|
|
5800
6008
|
// src/common/injectHost.ts
|
|
5801
|
-
import
|
|
5802
|
-
import
|
|
6009
|
+
import fs23 from "fs";
|
|
6010
|
+
import path24 from "path";
|
|
5803
6011
|
function readAndSubstitute(templatePath, vars) {
|
|
5804
|
-
const raw =
|
|
6012
|
+
const raw = fs23.readFileSync(templatePath, "utf-8");
|
|
5805
6013
|
return Object.entries(vars).reduce(
|
|
5806
6014
|
(s, [k, v]) => s.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v),
|
|
5807
6015
|
raw
|
|
@@ -5822,32 +6030,32 @@ async function injectHostAndroid(opts) {
|
|
|
5822
6030
|
process.exit(1);
|
|
5823
6031
|
}
|
|
5824
6032
|
const androidDir = config.paths?.androidDir ?? "android";
|
|
5825
|
-
const rootDir =
|
|
6033
|
+
const rootDir = path24.join(projectRoot, androidDir);
|
|
5826
6034
|
const packagePath = packageName.replace(/\./g, "/");
|
|
5827
|
-
const javaDir =
|
|
5828
|
-
const kotlinDir =
|
|
5829
|
-
if (!
|
|
6035
|
+
const javaDir = path24.join(rootDir, "app", "src", "main", "java", packagePath);
|
|
6036
|
+
const kotlinDir = path24.join(rootDir, "app", "src", "main", "kotlin", packagePath);
|
|
6037
|
+
if (!fs23.existsSync(javaDir) || !fs23.existsSync(kotlinDir)) {
|
|
5830
6038
|
console.error("\u274C Android project not found. Run `t4l android create` first or ensure android/ exists.");
|
|
5831
6039
|
process.exit(1);
|
|
5832
6040
|
}
|
|
5833
|
-
const templateDir =
|
|
6041
|
+
const templateDir = path24.join(hostPkg, "android", "templates");
|
|
5834
6042
|
const vars = { PACKAGE_NAME: packageName, APP_NAME: appName };
|
|
5835
6043
|
const files = [
|
|
5836
|
-
{ src: "App.java", dst:
|
|
5837
|
-
{ src: "TemplateProvider.java", dst:
|
|
5838
|
-
{ src: "MainActivity.kt", dst:
|
|
6044
|
+
{ src: "App.java", dst: path24.join(javaDir, "App.java") },
|
|
6045
|
+
{ src: "TemplateProvider.java", dst: path24.join(javaDir, "TemplateProvider.java") },
|
|
6046
|
+
{ src: "MainActivity.kt", dst: path24.join(kotlinDir, "MainActivity.kt") }
|
|
5839
6047
|
];
|
|
5840
6048
|
for (const { src, dst } of files) {
|
|
5841
|
-
const srcPath =
|
|
5842
|
-
if (!
|
|
5843
|
-
if (
|
|
5844
|
-
console.log(`\u23ED\uFE0F Skipping ${
|
|
6049
|
+
const srcPath = path24.join(templateDir, src);
|
|
6050
|
+
if (!fs23.existsSync(srcPath)) continue;
|
|
6051
|
+
if (fs23.existsSync(dst) && !opts?.force) {
|
|
6052
|
+
console.log(`\u23ED\uFE0F Skipping ${path24.basename(dst)} (use --force to overwrite)`);
|
|
5845
6053
|
continue;
|
|
5846
6054
|
}
|
|
5847
6055
|
const content = readAndSubstitute(srcPath, vars);
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
console.log(`\u2705 Injected ${
|
|
6056
|
+
fs23.mkdirSync(path24.dirname(dst), { recursive: true });
|
|
6057
|
+
fs23.writeFileSync(dst, content);
|
|
6058
|
+
console.log(`\u2705 Injected ${path24.basename(dst)}`);
|
|
5851
6059
|
}
|
|
5852
6060
|
}
|
|
5853
6061
|
async function injectHostIos(opts) {
|
|
@@ -5865,13 +6073,13 @@ async function injectHostIos(opts) {
|
|
|
5865
6073
|
process.exit(1);
|
|
5866
6074
|
}
|
|
5867
6075
|
const iosDir = config.paths?.iosDir ?? "ios";
|
|
5868
|
-
const rootDir =
|
|
5869
|
-
const projectDir =
|
|
5870
|
-
if (!
|
|
6076
|
+
const rootDir = path24.join(projectRoot, iosDir);
|
|
6077
|
+
const projectDir = path24.join(rootDir, appName);
|
|
6078
|
+
if (!fs23.existsSync(projectDir)) {
|
|
5871
6079
|
console.error("\u274C iOS project not found. Run `t4l ios create` first or ensure ios/ exists.");
|
|
5872
6080
|
process.exit(1);
|
|
5873
6081
|
}
|
|
5874
|
-
const templateDir =
|
|
6082
|
+
const templateDir = path24.join(hostPkg, "ios", "templates");
|
|
5875
6083
|
const vars = { PACKAGE_NAME: bundleId, APP_NAME: appName, BUNDLE_ID: bundleId };
|
|
5876
6084
|
const files = [
|
|
5877
6085
|
"AppDelegate.swift",
|
|
@@ -5881,22 +6089,22 @@ async function injectHostIos(opts) {
|
|
|
5881
6089
|
"LynxInitProcessor.swift"
|
|
5882
6090
|
];
|
|
5883
6091
|
for (const f of files) {
|
|
5884
|
-
const srcPath =
|
|
5885
|
-
const dstPath =
|
|
5886
|
-
if (!
|
|
5887
|
-
if (
|
|
6092
|
+
const srcPath = path24.join(templateDir, f);
|
|
6093
|
+
const dstPath = path24.join(projectDir, f);
|
|
6094
|
+
if (!fs23.existsSync(srcPath)) continue;
|
|
6095
|
+
if (fs23.existsSync(dstPath) && !opts?.force) {
|
|
5888
6096
|
console.log(`\u23ED\uFE0F Skipping ${f} (use --force to overwrite)`);
|
|
5889
6097
|
continue;
|
|
5890
6098
|
}
|
|
5891
6099
|
const content = readAndSubstitute(srcPath, vars);
|
|
5892
|
-
|
|
6100
|
+
fs23.writeFileSync(dstPath, content);
|
|
5893
6101
|
console.log(`\u2705 Injected ${f}`);
|
|
5894
6102
|
}
|
|
5895
6103
|
}
|
|
5896
6104
|
|
|
5897
6105
|
// src/common/buildEmbeddable.ts
|
|
5898
|
-
import
|
|
5899
|
-
import
|
|
6106
|
+
import fs24 from "fs";
|
|
6107
|
+
import path25 from "path";
|
|
5900
6108
|
import { execSync as execSync9 } from "child_process";
|
|
5901
6109
|
var EMBEDDABLE_DIR = "embeddable";
|
|
5902
6110
|
var LIB_PACKAGE = "com.tamer.embeddable";
|
|
@@ -5973,14 +6181,14 @@ object LynxEmbeddable {
|
|
|
5973
6181
|
}
|
|
5974
6182
|
`;
|
|
5975
6183
|
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
|
-
|
|
6184
|
+
const libDir = path25.join(androidDir, "lib");
|
|
6185
|
+
const libSrcMain = path25.join(libDir, "src", "main");
|
|
6186
|
+
const assetsDir = path25.join(libSrcMain, "assets");
|
|
6187
|
+
const kotlinDir = path25.join(libSrcMain, "kotlin", LIB_PACKAGE.replace(/\./g, "/"));
|
|
6188
|
+
const generatedDir = path25.join(kotlinDir, "generated");
|
|
6189
|
+
fs24.mkdirSync(path25.join(androidDir, "gradle"), { recursive: true });
|
|
6190
|
+
fs24.mkdirSync(generatedDir, { recursive: true });
|
|
6191
|
+
fs24.mkdirSync(assetsDir, { recursive: true });
|
|
5984
6192
|
const androidModules = modules.filter((m) => m.config.android);
|
|
5985
6193
|
const abiList = abiFilters.map((a) => `"${a}"`).join(", ");
|
|
5986
6194
|
const settingsContent = `pluginManagement {
|
|
@@ -6000,7 +6208,7 @@ include(":lib")
|
|
|
6000
6208
|
${androidModules.map((p) => {
|
|
6001
6209
|
const gradleName = p.name.replace(/^@/, "").replace(/\//g, "_");
|
|
6002
6210
|
const sourceDir = p.config.android?.sourceDir || "android";
|
|
6003
|
-
const absPath =
|
|
6211
|
+
const absPath = path25.join(p.packagePath, sourceDir).replace(/\\/g, "/");
|
|
6004
6212
|
return `include(":${gradleName}")
|
|
6005
6213
|
project(":${gradleName}").projectDir = file("${absPath}")`;
|
|
6006
6214
|
}).join("\n")}
|
|
@@ -6049,10 +6257,10 @@ dependencies {
|
|
|
6049
6257
|
${libDeps}
|
|
6050
6258
|
}
|
|
6051
6259
|
`;
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6260
|
+
fs24.writeFileSync(path25.join(androidDir, "gradle", "libs.versions.toml"), LIBS_VERSIONS_TOML);
|
|
6261
|
+
fs24.writeFileSync(path25.join(androidDir, "settings.gradle.kts"), settingsContent);
|
|
6262
|
+
fs24.writeFileSync(
|
|
6263
|
+
path25.join(androidDir, "build.gradle.kts"),
|
|
6056
6264
|
`plugins {
|
|
6057
6265
|
alias(libs.plugins.android.library) apply false
|
|
6058
6266
|
alias(libs.plugins.kotlin.android) apply false
|
|
@@ -6060,26 +6268,26 @@ ${libDeps}
|
|
|
6060
6268
|
}
|
|
6061
6269
|
`
|
|
6062
6270
|
);
|
|
6063
|
-
|
|
6064
|
-
|
|
6271
|
+
fs24.writeFileSync(
|
|
6272
|
+
path25.join(androidDir, "gradle.properties"),
|
|
6065
6273
|
`org.gradle.jvmargs=-Xmx2048m
|
|
6066
6274
|
android.useAndroidX=true
|
|
6067
6275
|
kotlin.code.style=official
|
|
6068
6276
|
`
|
|
6069
6277
|
);
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6278
|
+
fs24.writeFileSync(path25.join(libDir, "build.gradle.kts"), libBuildContent);
|
|
6279
|
+
fs24.writeFileSync(
|
|
6280
|
+
path25.join(libSrcMain, "AndroidManifest.xml"),
|
|
6073
6281
|
'<?xml version="1.0" encoding="utf-8"?>\n<manifest />'
|
|
6074
6282
|
);
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6283
|
+
fs24.copyFileSync(lynxBundlePath, path25.join(assetsDir, lynxBundleFile));
|
|
6284
|
+
fs24.writeFileSync(path25.join(kotlinDir, "LynxEmbeddable.kt"), LYNX_EMBEDDABLE_KT);
|
|
6285
|
+
fs24.writeFileSync(
|
|
6286
|
+
path25.join(generatedDir, "GeneratedLynxExtensions.kt"),
|
|
6079
6287
|
generateLynxExtensionsKotlin(modules, LIB_PACKAGE)
|
|
6080
6288
|
);
|
|
6081
|
-
|
|
6082
|
-
|
|
6289
|
+
fs24.writeFileSync(
|
|
6290
|
+
path25.join(generatedDir, "GeneratedActivityLifecycle.kt"),
|
|
6083
6291
|
generateActivityLifecycleKotlin(modules, LIB_PACKAGE)
|
|
6084
6292
|
);
|
|
6085
6293
|
}
|
|
@@ -6088,20 +6296,20 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6088
6296
|
const { lynxProjectDir, lynxBundlePath, lynxBundleFile, projectRoot, config } = resolved;
|
|
6089
6297
|
console.log("\u{1F4E6} Building Lynx project (release)...");
|
|
6090
6298
|
execSync9("npm run build", { stdio: "inherit", cwd: lynxProjectDir });
|
|
6091
|
-
if (!
|
|
6299
|
+
if (!fs24.existsSync(lynxBundlePath)) {
|
|
6092
6300
|
console.error(`\u274C Bundle not found at ${lynxBundlePath}`);
|
|
6093
6301
|
process.exit(1);
|
|
6094
6302
|
}
|
|
6095
|
-
const outDir =
|
|
6096
|
-
|
|
6097
|
-
const distDir =
|
|
6303
|
+
const outDir = path25.join(projectRoot, EMBEDDABLE_DIR);
|
|
6304
|
+
fs24.mkdirSync(outDir, { recursive: true });
|
|
6305
|
+
const distDir = path25.dirname(lynxBundlePath);
|
|
6098
6306
|
copyDistAssets(distDir, outDir, lynxBundleFile);
|
|
6099
6307
|
const modules = discoverModules(projectRoot);
|
|
6100
6308
|
const androidModules = modules.filter((m) => m.config.android);
|
|
6101
6309
|
const abiFilters = resolveAbiFilters(config);
|
|
6102
|
-
const androidDir =
|
|
6103
|
-
if (
|
|
6104
|
-
|
|
6310
|
+
const androidDir = path25.join(outDir, "android");
|
|
6311
|
+
if (fs24.existsSync(androidDir)) fs24.rmSync(androidDir, { recursive: true });
|
|
6312
|
+
fs24.mkdirSync(androidDir, { recursive: true });
|
|
6105
6313
|
generateAndroidLibrary(
|
|
6106
6314
|
outDir,
|
|
6107
6315
|
androidDir,
|
|
@@ -6111,23 +6319,23 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6111
6319
|
modules,
|
|
6112
6320
|
abiFilters
|
|
6113
6321
|
);
|
|
6114
|
-
const gradlewPath =
|
|
6322
|
+
const gradlewPath = path25.join(androidDir, "gradlew");
|
|
6115
6323
|
const devAppDir = findDevAppPackage(projectRoot);
|
|
6116
6324
|
const existingGradleDirs = [
|
|
6117
|
-
|
|
6118
|
-
devAppDir ?
|
|
6325
|
+
path25.join(projectRoot, "android"),
|
|
6326
|
+
devAppDir ? path25.join(devAppDir, "android") : null
|
|
6119
6327
|
].filter(Boolean);
|
|
6120
6328
|
let hasWrapper = false;
|
|
6121
6329
|
for (const d of existingGradleDirs) {
|
|
6122
|
-
if (
|
|
6330
|
+
if (fs24.existsSync(path25.join(d, "gradlew"))) {
|
|
6123
6331
|
for (const name of ["gradlew", "gradlew.bat", "gradle"]) {
|
|
6124
|
-
const src =
|
|
6125
|
-
if (
|
|
6126
|
-
const dest =
|
|
6127
|
-
if (
|
|
6128
|
-
|
|
6332
|
+
const src = path25.join(d, name);
|
|
6333
|
+
if (fs24.existsSync(src)) {
|
|
6334
|
+
const dest = path25.join(androidDir, name);
|
|
6335
|
+
if (fs24.statSync(src).isDirectory()) {
|
|
6336
|
+
fs24.cpSync(src, dest, { recursive: true });
|
|
6129
6337
|
} else {
|
|
6130
|
-
|
|
6338
|
+
fs24.copyFileSync(src, dest);
|
|
6131
6339
|
}
|
|
6132
6340
|
}
|
|
6133
6341
|
}
|
|
@@ -6146,10 +6354,10 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6146
6354
|
console.error("\u274C Android AAR build failed. Run manually: cd embeddable/android && ./gradlew :lib:assembleRelease");
|
|
6147
6355
|
throw e;
|
|
6148
6356
|
}
|
|
6149
|
-
const aarSrc =
|
|
6150
|
-
const aarDest =
|
|
6151
|
-
if (
|
|
6152
|
-
|
|
6357
|
+
const aarSrc = path25.join(androidDir, "lib", "build", "outputs", "aar", "lib-release.aar");
|
|
6358
|
+
const aarDest = path25.join(outDir, "tamer-embeddable.aar");
|
|
6359
|
+
if (fs24.existsSync(aarSrc)) {
|
|
6360
|
+
fs24.copyFileSync(aarSrc, aarDest);
|
|
6153
6361
|
console.log(` - tamer-embeddable.aar`);
|
|
6154
6362
|
}
|
|
6155
6363
|
const snippetAndroid = `// Add to your app's build.gradle:
|
|
@@ -6160,7 +6368,7 @@ async function buildEmbeddable(opts = {}) {
|
|
|
6160
6368
|
// LynxEmbeddable.init(applicationContext)
|
|
6161
6369
|
// val lynxView = LynxEmbeddable.buildLynxView(containerViewGroup)
|
|
6162
6370
|
`;
|
|
6163
|
-
|
|
6371
|
+
fs24.writeFileSync(path25.join(outDir, "snippet-android.kt"), snippetAndroid);
|
|
6164
6372
|
generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules);
|
|
6165
6373
|
const readme = `# Embeddable Lynx Bundle
|
|
6166
6374
|
|
|
@@ -6191,7 +6399,7 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6191
6399
|
|
|
6192
6400
|
- [Embedding LynxView](https://lynxjs.org/guide/embed-lynx-to-native)
|
|
6193
6401
|
`;
|
|
6194
|
-
|
|
6402
|
+
fs24.writeFileSync(path25.join(outDir, "README.md"), readme);
|
|
6195
6403
|
console.log(`
|
|
6196
6404
|
\u2705 Embeddable output at ${outDir}/`);
|
|
6197
6405
|
console.log(" - main.lynx.bundle");
|
|
@@ -6203,20 +6411,20 @@ Add the \`Podfile.snippet\` entries to your Podfile (inside your app target), th
|
|
|
6203
6411
|
console.log(" - README.md");
|
|
6204
6412
|
}
|
|
6205
6413
|
function generateIosPod(outDir, projectRoot, lynxBundlePath, lynxBundleFile, modules) {
|
|
6206
|
-
const iosDir =
|
|
6207
|
-
const podDir =
|
|
6208
|
-
const resourcesDir =
|
|
6209
|
-
|
|
6210
|
-
|
|
6414
|
+
const iosDir = path25.join(outDir, "ios");
|
|
6415
|
+
const podDir = path25.join(iosDir, "TamerEmbeddable");
|
|
6416
|
+
const resourcesDir = path25.join(podDir, "Resources");
|
|
6417
|
+
fs24.mkdirSync(resourcesDir, { recursive: true });
|
|
6418
|
+
fs24.copyFileSync(lynxBundlePath, path25.join(resourcesDir, lynxBundleFile));
|
|
6211
6419
|
const iosModules = modules.filter((m) => m.config.ios);
|
|
6212
6420
|
const podDeps = iosModules.map((p) => {
|
|
6213
6421
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6214
|
-
const podspecDir =
|
|
6215
|
-
if (!
|
|
6216
|
-
const files =
|
|
6422
|
+
const podspecDir = path25.join(p.packagePath, podspecPath);
|
|
6423
|
+
if (!fs24.existsSync(podspecDir)) return null;
|
|
6424
|
+
const files = fs24.readdirSync(podspecDir);
|
|
6217
6425
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6218
6426
|
const podName = podspecFile ? podspecFile.replace(".podspec", "") : p.name.split("/").pop().replace(/-/g, "");
|
|
6219
|
-
const absPath =
|
|
6427
|
+
const absPath = path25.resolve(podspecDir);
|
|
6220
6428
|
return { podName, absPath };
|
|
6221
6429
|
}).filter(Boolean);
|
|
6222
6430
|
const podDepLines = podDeps.map((d) => ` s.dependency '${d.podName}'`).join("\n");
|
|
@@ -6256,9 +6464,9 @@ end
|
|
|
6256
6464
|
});
|
|
6257
6465
|
const swiftImports = iosModules.map((p) => {
|
|
6258
6466
|
const podspecPath = p.config.ios?.podspecPath || ".";
|
|
6259
|
-
const podspecDir =
|
|
6260
|
-
if (!
|
|
6261
|
-
const files =
|
|
6467
|
+
const podspecDir = path25.join(p.packagePath, podspecPath);
|
|
6468
|
+
if (!fs24.existsSync(podspecDir)) return null;
|
|
6469
|
+
const files = fs24.readdirSync(podspecDir);
|
|
6262
6470
|
const podspecFile = files.find((f) => f.endsWith(".podspec"));
|
|
6263
6471
|
return podspecFile ? podspecFile.replace(".podspec", "") : null;
|
|
6264
6472
|
}).filter(Boolean);
|
|
@@ -6277,17 +6485,17 @@ ${regBlock}
|
|
|
6277
6485
|
}
|
|
6278
6486
|
}
|
|
6279
6487
|
`;
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
const absIosDir =
|
|
6488
|
+
fs24.writeFileSync(path25.join(iosDir, "TamerEmbeddable.podspec"), podspecContent);
|
|
6489
|
+
fs24.writeFileSync(path25.join(podDir, "LynxEmbeddable.swift"), lynxEmbeddableSwift);
|
|
6490
|
+
const absIosDir = path25.resolve(iosDir);
|
|
6283
6491
|
const podfileSnippet = `# Paste into your app target in Podfile:
|
|
6284
6492
|
|
|
6285
6493
|
pod 'TamerEmbeddable', :path => '${absIosDir}'
|
|
6286
6494
|
${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
6287
6495
|
`;
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6496
|
+
fs24.writeFileSync(path25.join(iosDir, "Podfile.snippet"), podfileSnippet);
|
|
6497
|
+
fs24.writeFileSync(
|
|
6498
|
+
path25.join(outDir, "snippet-ios.swift"),
|
|
6291
6499
|
`// Add LynxEmbeddable.initEnvironment() in your AppDelegate/SceneDelegate before presenting LynxView.
|
|
6292
6500
|
// Then create LynxView with your bundle URL (main.lynx.bundle is in the pod resources).
|
|
6293
6501
|
`
|
|
@@ -6295,8 +6503,8 @@ ${podDeps.map((d) => `pod '${d.podName}', :path => '${d.absPath}'`).join("\n")}
|
|
|
6295
6503
|
}
|
|
6296
6504
|
|
|
6297
6505
|
// src/common/add.ts
|
|
6298
|
-
import
|
|
6299
|
-
import
|
|
6506
|
+
import fs25 from "fs";
|
|
6507
|
+
import path26 from "path";
|
|
6300
6508
|
import { execFile, execSync as execSync10 } from "child_process";
|
|
6301
6509
|
import { promisify } from "util";
|
|
6302
6510
|
import semver from "semver";
|
|
@@ -6311,21 +6519,15 @@ var CORE_PACKAGES = [
|
|
|
6311
6519
|
"@tamer4lynx/tamer-icons"
|
|
6312
6520
|
];
|
|
6313
6521
|
var DEV_STACK_PACKAGES = [
|
|
6314
|
-
"@tamer4lynx/jiggle",
|
|
6315
|
-
"@tamer4lynx/tamer-app-shell",
|
|
6316
|
-
"@tamer4lynx/tamer-biometric",
|
|
6317
6522
|
"@tamer4lynx/tamer-dev-app",
|
|
6318
6523
|
"@tamer4lynx/tamer-dev-client",
|
|
6319
|
-
"@tamer4lynx/tamer-
|
|
6524
|
+
"@tamer4lynx/tamer-app-shell",
|
|
6320
6525
|
"@tamer4lynx/tamer-icons",
|
|
6321
6526
|
"@tamer4lynx/tamer-insets",
|
|
6322
|
-
"@tamer4lynx/tamer-linking",
|
|
6323
6527
|
"@tamer4lynx/tamer-plugin",
|
|
6324
6528
|
"@tamer4lynx/tamer-router",
|
|
6325
6529
|
"@tamer4lynx/tamer-screen",
|
|
6326
|
-
"@tamer4lynx/tamer-
|
|
6327
|
-
"@tamer4lynx/tamer-system-ui",
|
|
6328
|
-
"@tamer4lynx/tamer-transports"
|
|
6530
|
+
"@tamer4lynx/tamer-system-ui"
|
|
6329
6531
|
];
|
|
6330
6532
|
var PACKAGE_ALIASES = {};
|
|
6331
6533
|
async function getHighestPublishedVersion(fullName) {
|
|
@@ -6353,9 +6555,9 @@ async function normalizeTamerInstallSpec(pkg) {
|
|
|
6353
6555
|
return `${pkg}@prerelease`;
|
|
6354
6556
|
}
|
|
6355
6557
|
function detectPackageManager2(cwd) {
|
|
6356
|
-
const dir =
|
|
6357
|
-
if (
|
|
6358
|
-
if (
|
|
6558
|
+
const dir = path26.resolve(cwd);
|
|
6559
|
+
if (fs25.existsSync(path26.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
6560
|
+
if (fs25.existsSync(path26.join(dir, "bun.lockb"))) return "bun";
|
|
6359
6561
|
return "npm";
|
|
6360
6562
|
}
|
|
6361
6563
|
function runInstall(cwd, packages, pm) {
|
|
@@ -6407,13 +6609,13 @@ async function add(packages = []) {
|
|
|
6407
6609
|
// src/common/signing.tsx
|
|
6408
6610
|
import { useState as useState6, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
6409
6611
|
import { render as render3, Text as Text10, Box as Box9 } from "ink";
|
|
6410
|
-
import
|
|
6411
|
-
import
|
|
6612
|
+
import fs28 from "fs";
|
|
6613
|
+
import path29 from "path";
|
|
6412
6614
|
|
|
6413
6615
|
// src/common/androidKeystore.ts
|
|
6414
6616
|
import { execFileSync } from "child_process";
|
|
6415
|
-
import
|
|
6416
|
-
import
|
|
6617
|
+
import fs26 from "fs";
|
|
6618
|
+
import path27 from "path";
|
|
6417
6619
|
function normalizeJavaHome(raw) {
|
|
6418
6620
|
if (!raw) return void 0;
|
|
6419
6621
|
const t = raw.trim().replace(/^["']|["']$/g, "");
|
|
@@ -6426,7 +6628,7 @@ function discoverJavaHomeMacOs() {
|
|
|
6426
6628
|
encoding: "utf8",
|
|
6427
6629
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6428
6630
|
}).trim().split("\n")[0]?.trim();
|
|
6429
|
-
if (out &&
|
|
6631
|
+
if (out && fs26.existsSync(path27.join(out, "bin", "keytool"))) return out;
|
|
6430
6632
|
} catch {
|
|
6431
6633
|
}
|
|
6432
6634
|
return void 0;
|
|
@@ -6436,13 +6638,13 @@ function resolveKeytoolPath() {
|
|
|
6436
6638
|
const win = process.platform === "win32";
|
|
6437
6639
|
const keytoolName = win ? "keytool.exe" : "keytool";
|
|
6438
6640
|
if (jh) {
|
|
6439
|
-
const p =
|
|
6440
|
-
if (
|
|
6641
|
+
const p = path27.join(jh, "bin", keytoolName);
|
|
6642
|
+
if (fs26.existsSync(p)) return p;
|
|
6441
6643
|
}
|
|
6442
6644
|
const mac = discoverJavaHomeMacOs();
|
|
6443
6645
|
if (mac) {
|
|
6444
|
-
const p =
|
|
6445
|
-
if (
|
|
6646
|
+
const p = path27.join(mac, "bin", keytoolName);
|
|
6647
|
+
if (fs26.existsSync(p)) return p;
|
|
6446
6648
|
}
|
|
6447
6649
|
return "keytool";
|
|
6448
6650
|
}
|
|
@@ -6457,16 +6659,16 @@ function keytoolAvailable() {
|
|
|
6457
6659
|
};
|
|
6458
6660
|
if (tryRun("keytool")) return true;
|
|
6459
6661
|
const fromJavaHome = resolveKeytoolPath();
|
|
6460
|
-
if (fromJavaHome !== "keytool" &&
|
|
6662
|
+
if (fromJavaHome !== "keytool" && fs26.existsSync(fromJavaHome)) {
|
|
6461
6663
|
return tryRun(fromJavaHome);
|
|
6462
6664
|
}
|
|
6463
6665
|
return false;
|
|
6464
6666
|
}
|
|
6465
6667
|
function generateReleaseKeystore(opts) {
|
|
6466
6668
|
const keytool = resolveKeytoolPath();
|
|
6467
|
-
const dir =
|
|
6468
|
-
|
|
6469
|
-
if (
|
|
6669
|
+
const dir = path27.dirname(opts.keystoreAbsPath);
|
|
6670
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
6671
|
+
if (fs26.existsSync(opts.keystoreAbsPath)) {
|
|
6470
6672
|
throw new Error(`Keystore already exists: ${opts.keystoreAbsPath}`);
|
|
6471
6673
|
}
|
|
6472
6674
|
if (!opts.storePassword || !opts.keyPassword) {
|
|
@@ -6504,13 +6706,13 @@ function generateReleaseKeystore(opts) {
|
|
|
6504
6706
|
}
|
|
6505
6707
|
|
|
6506
6708
|
// src/common/appendEnvFile.ts
|
|
6507
|
-
import
|
|
6508
|
-
import
|
|
6709
|
+
import fs27 from "fs";
|
|
6710
|
+
import path28 from "path";
|
|
6509
6711
|
import { parse } from "dotenv";
|
|
6510
6712
|
function keysDefinedInFile(filePath) {
|
|
6511
|
-
if (!
|
|
6713
|
+
if (!fs27.existsSync(filePath)) return /* @__PURE__ */ new Set();
|
|
6512
6714
|
try {
|
|
6513
|
-
return new Set(Object.keys(parse(
|
|
6715
|
+
return new Set(Object.keys(parse(fs27.readFileSync(filePath, "utf8"))));
|
|
6514
6716
|
} catch {
|
|
6515
6717
|
return /* @__PURE__ */ new Set();
|
|
6516
6718
|
}
|
|
@@ -6525,11 +6727,11 @@ function formatEnvLine(key, value) {
|
|
|
6525
6727
|
function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
6526
6728
|
const entries = Object.entries(vars).filter(([, v]) => v !== void 0 && v !== "");
|
|
6527
6729
|
if (entries.length === 0) return null;
|
|
6528
|
-
const envLocal =
|
|
6529
|
-
const envDefault =
|
|
6730
|
+
const envLocal = path28.join(projectRoot, ".env.local");
|
|
6731
|
+
const envDefault = path28.join(projectRoot, ".env");
|
|
6530
6732
|
let target;
|
|
6531
|
-
if (
|
|
6532
|
-
else if (
|
|
6733
|
+
if (fs27.existsSync(envLocal)) target = envLocal;
|
|
6734
|
+
else if (fs27.existsSync(envDefault)) target = envDefault;
|
|
6533
6735
|
else target = envLocal;
|
|
6534
6736
|
const existing = keysDefinedInFile(target);
|
|
6535
6737
|
const lines = [];
|
|
@@ -6541,20 +6743,20 @@ function appendEnvVarsIfMissing(projectRoot, vars) {
|
|
|
6541
6743
|
}
|
|
6542
6744
|
if (lines.length === 0) {
|
|
6543
6745
|
return {
|
|
6544
|
-
file:
|
|
6746
|
+
file: path28.basename(target),
|
|
6545
6747
|
keys: [],
|
|
6546
6748
|
skippedAll: entries.length > 0
|
|
6547
6749
|
};
|
|
6548
6750
|
}
|
|
6549
6751
|
let prefix = "";
|
|
6550
|
-
if (
|
|
6551
|
-
const cur =
|
|
6752
|
+
if (fs27.existsSync(target)) {
|
|
6753
|
+
const cur = fs27.readFileSync(target, "utf8");
|
|
6552
6754
|
prefix = cur.length === 0 ? "" : cur.endsWith("\n") ? cur : `${cur}
|
|
6553
6755
|
`;
|
|
6554
6756
|
}
|
|
6555
6757
|
const block = lines.join("\n") + "\n";
|
|
6556
|
-
|
|
6557
|
-
return { file:
|
|
6758
|
+
fs27.writeFileSync(target, prefix + block, "utf8");
|
|
6759
|
+
return { file: path28.basename(target), keys: appendedKeys };
|
|
6558
6760
|
}
|
|
6559
6761
|
|
|
6560
6762
|
// src/common/signing.tsx
|
|
@@ -6660,7 +6862,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6660
6862
|
try {
|
|
6661
6863
|
const resolved = resolveHostPaths();
|
|
6662
6864
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6663
|
-
abs =
|
|
6865
|
+
abs = path29.isAbsolute(rel) ? rel : path29.join(resolved.projectRoot, rel);
|
|
6664
6866
|
const alias = state.android.keyAlias.trim() || "release";
|
|
6665
6867
|
const pw = state.android.genPassword;
|
|
6666
6868
|
const pkg = resolved.config.android?.packageName ?? "com.example.app";
|
|
@@ -6687,7 +6889,7 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6687
6889
|
}));
|
|
6688
6890
|
} catch (e) {
|
|
6689
6891
|
const msg = e.message;
|
|
6690
|
-
if (abs &&
|
|
6892
|
+
if (abs && fs28.existsSync(abs) && (msg.includes("already exists") || msg.includes("Keystore already exists"))) {
|
|
6691
6893
|
if (cancelled || runId !== generateRunId.current) return;
|
|
6692
6894
|
const rel = state.android.genKeystorePath.trim() || "android/release.keystore";
|
|
6693
6895
|
const alias = state.android.keyAlias.trim() || "release";
|
|
@@ -6727,11 +6929,11 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6727
6929
|
const saveConfig = async () => {
|
|
6728
6930
|
try {
|
|
6729
6931
|
const resolved = resolveHostPaths();
|
|
6730
|
-
const configPath =
|
|
6932
|
+
const configPath = path29.join(resolved.projectRoot, "tamer.config.json");
|
|
6731
6933
|
let config = {};
|
|
6732
6934
|
let androidEnvAppend = null;
|
|
6733
|
-
if (
|
|
6734
|
-
config = JSON.parse(
|
|
6935
|
+
if (fs28.existsSync(configPath)) {
|
|
6936
|
+
config = JSON.parse(fs28.readFileSync(configPath, "utf8"));
|
|
6735
6937
|
}
|
|
6736
6938
|
if (state.platform === "android" || state.platform === "both") {
|
|
6737
6939
|
config.android = config.android || {};
|
|
@@ -6758,10 +6960,10 @@ function SigningWizard({ platform: initialPlatform }) {
|
|
|
6758
6960
|
...state.ios.provisioningProfileSpecifier && { provisioningProfileSpecifier: state.ios.provisioningProfileSpecifier }
|
|
6759
6961
|
};
|
|
6760
6962
|
}
|
|
6761
|
-
|
|
6762
|
-
const gitignorePath =
|
|
6763
|
-
if (
|
|
6764
|
-
let gitignore =
|
|
6963
|
+
fs28.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
6964
|
+
const gitignorePath = path29.join(resolved.projectRoot, ".gitignore");
|
|
6965
|
+
if (fs28.existsSync(gitignorePath)) {
|
|
6966
|
+
let gitignore = fs28.readFileSync(gitignorePath, "utf8");
|
|
6765
6967
|
const additions = [
|
|
6766
6968
|
".env.local",
|
|
6767
6969
|
"*.jks",
|
|
@@ -6774,7 +6976,7 @@ ${addition}
|
|
|
6774
6976
|
`;
|
|
6775
6977
|
}
|
|
6776
6978
|
}
|
|
6777
|
-
|
|
6979
|
+
fs28.writeFileSync(gitignorePath, gitignore);
|
|
6778
6980
|
}
|
|
6779
6981
|
setState((s) => ({
|
|
6780
6982
|
...s,
|
|
@@ -7032,13 +7234,13 @@ async function signing(platform) {
|
|
|
7032
7234
|
}
|
|
7033
7235
|
|
|
7034
7236
|
// src/common/productionSigning.ts
|
|
7035
|
-
import
|
|
7036
|
-
import
|
|
7237
|
+
import fs29 from "fs";
|
|
7238
|
+
import path30 from "path";
|
|
7037
7239
|
function isAndroidSigningConfigured(resolved) {
|
|
7038
7240
|
const signing2 = resolved.config.android?.signing;
|
|
7039
7241
|
const hasConfig = Boolean(signing2?.keystoreFile?.trim() && signing2?.keyAlias?.trim());
|
|
7040
|
-
const signingProps =
|
|
7041
|
-
const hasProps =
|
|
7242
|
+
const signingProps = path30.join(resolved.androidDir, "signing.properties");
|
|
7243
|
+
const hasProps = fs29.existsSync(signingProps);
|
|
7042
7244
|
return hasConfig || hasProps;
|
|
7043
7245
|
}
|
|
7044
7246
|
function isIosSigningConfigured(resolved) {
|
|
@@ -7068,14 +7270,7 @@ function assertProductionSigningReady(filter) {
|
|
|
7068
7270
|
}
|
|
7069
7271
|
|
|
7070
7272
|
// 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();
|
|
7273
|
+
var version = getCliVersion();
|
|
7079
7274
|
function validateBuildMode(debug, release, production) {
|
|
7080
7275
|
const modes = [debug, release, production].filter(Boolean).length;
|
|
7081
7276
|
if (modes > 1) {
|
|
@@ -7114,7 +7309,10 @@ program.command("create <target>").description("Create a project or extension. T
|
|
|
7114
7309
|
console.error(`Invalid create target: ${target}. Use ios | android | module | element | service | combo`);
|
|
7115
7310
|
process.exit(1);
|
|
7116
7311
|
});
|
|
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(
|
|
7312
|
+
program.command("build [platform]").description("Build app. Platform: ios | android (default: both)").option("-e, --embeddable", "Output embeddable bundle + code for existing apps. Use with --release.").option("-d, --debug", "Debug build with dev client embedded (default)").option("-r, --release", "Release build without dev client (unsigned)").option("-p, --production", "Production build for app store (signed)").option(
|
|
7313
|
+
"-i, --install",
|
|
7314
|
+
"Install after build (iOS: simulator with -d; connected device with -p)"
|
|
7315
|
+
).action(async (platform, opts) => {
|
|
7118
7316
|
validateBuildMode(opts.debug, opts.release, opts.production);
|
|
7119
7317
|
const release = opts.release === true || opts.production === true;
|
|
7120
7318
|
const production = opts.production === true;
|
|
@@ -7291,10 +7489,10 @@ program.command("ios <subcommand>").description("(Legacy) Use: t4l <command> ios
|
|
|
7291
7489
|
process.exit(1);
|
|
7292
7490
|
});
|
|
7293
7491
|
program.command("autolink-toggle").alias("autolink").description("Toggle autolink on/off in tamer.config.json (controls postinstall linking)").action(async () => {
|
|
7294
|
-
const configPath =
|
|
7492
|
+
const configPath = path31.join(process.cwd(), "tamer.config.json");
|
|
7295
7493
|
let config = {};
|
|
7296
|
-
if (
|
|
7297
|
-
config = JSON.parse(
|
|
7494
|
+
if (fs30.existsSync(configPath)) {
|
|
7495
|
+
config = JSON.parse(fs30.readFileSync(configPath, "utf8"));
|
|
7298
7496
|
}
|
|
7299
7497
|
if (config.autolink) {
|
|
7300
7498
|
delete config.autolink;
|
|
@@ -7303,7 +7501,7 @@ program.command("autolink-toggle").alias("autolink").description("Toggle autolin
|
|
|
7303
7501
|
config.autolink = true;
|
|
7304
7502
|
console.log("Autolink enabled in tamer.config.json");
|
|
7305
7503
|
}
|
|
7306
|
-
|
|
7504
|
+
fs30.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
7307
7505
|
console.log(`Updated ${configPath}`);
|
|
7308
7506
|
});
|
|
7309
7507
|
if (process.argv.length <= 2 || process.argv.length === 3 && process.argv[2] === "init") {
|