@tamer4lynx/cli 0.0.7 → 0.0.9

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.
Files changed (3) hide show
  1. package/README.md +25 -29
  2. package/dist/index.js +265 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,17 +8,12 @@ Inspired by [Expo](https://expo.dev) and [Expo Go](https://expo.dev/go).
8
8
 
9
9
  ## Installation
10
10
 
11
- All Tamer packages are published under the `@tamer4lynx` scope on npm. Install the CLI globally:
11
+ All Tamer packages are published under the `@tamer4lynx` scope on npm. Install the CLI globally (use `@prerelease` for latest):
12
12
 
13
13
  ```bash
14
- npm i -g @tamer4lynx/cli
15
- ```
16
-
17
- With pnpm or Bun:
18
-
19
- ```bash
20
- pnpm add -g @tamer4lynx/cli
21
- bun add -g @tamer4lynx/cli
14
+ npm i -g @tamer4lynx/cli@prerelease
15
+ pnpm add -g @tamer4lynx/cli@prerelease
16
+ bun add -g @tamer4lynx/cli@prerelease
22
17
  ```
23
18
 
24
19
  Or from GitHub (run `npm uninstall -g @tamer4lynx/cli` first if switching):
@@ -175,7 +170,7 @@ Builds your app. Dev client (QR scan, HMR) is included when you use **debug** (`
175
170
  | Command | Flags | Description |
176
171
  |---------|-------|-------------|
177
172
  | `t4l add [packages...]` | — | Add @tamer4lynx packages. Future: version tracking (Expo-style). |
178
- | `t4l add-core` | — | Add core packages (app-shell, screen, router, insets, transports, text-input, system-ui, icons). |
173
+ | `t4l add-core` | — | Add core packages (app-shell, screen, router, insets, transports, input, system-ui, icons). |
179
174
  | `t4l start` | `-v, --verbose` | Dev server with HMR. `--verbose` shows native + JS logs. |
180
175
  | `t4l link` | `-i, --ios`, `-a, --android`, `-s, --silent` | Link modules. `--ios`/`--android` limit to one platform. `--silent` for CI/postinstall. |
181
176
  | `t4l autolink-toggle` | — | Toggle `autolink` in tamer.config.json (postinstall linking). |
@@ -266,29 +261,29 @@ Extensions are discovered via **lynx.ext.json** (RFC standard) or **tamer.json**
266
261
 
267
262
  ## Native Module References
268
263
 
269
- Install from npm and run `t4l link` after adding to your app:
264
+ Install from npm (use `@prerelease` for latest) and run `t4l link` after adding to your app. Also: `pnpm add @tamer4lynx/<pkg>@prerelease` | `bun add @tamer4lynx/<pkg>@prerelease`
270
265
 
271
266
  | Package | Install | Description |
272
267
  |---------|---------|-------------|
273
- | [@tamer4lynx/jiggle](https://www.npmjs.com/package/@tamer4lynx/jiggle) | `npm i @tamer4lynx/jiggle` | Vibration/haptic |
274
- | [@tamer4lynx/lynxwebsockets](https://www.npmjs.com/package/@tamer4lynx/lynxwebsockets) | `npm i @tamer4lynx/lynxwebsockets` | WebSocket native bridge |
275
- | [@tamer4lynx/tamer-host](https://www.npmjs.com/package/@tamer4lynx/tamer-host) | `npm i @tamer4lynx/tamer-host` | Production Lynx host templates |
276
- | [@tamer4lynx/tamer-dev-client](https://www.npmjs.com/package/@tamer4lynx/tamer-dev-client) | `npm i @tamer4lynx/tamer-dev-client` | Dev launcher UI (QR scan, HMR). Add to your app and build with `-d` for a dev build; `-r` omits it. |
268
+ | [@tamer4lynx/jiggle](https://www.npmjs.com/package/@tamer4lynx/jiggle) | `npm i @tamer4lynx/jiggle@prerelease` | Vibration/haptic |
269
+ | [@tamer4lynx/lynxwebsockets](https://www.npmjs.com/package/@tamer4lynx/lynxwebsockets) | `npm i @tamer4lynx/lynxwebsockets@prerelease` | WebSocket native bridge |
270
+ | [@tamer4lynx/tamer-host](https://www.npmjs.com/package/@tamer4lynx/tamer-host) | `npm i @tamer4lynx/tamer-host@prerelease` | Production Lynx host templates |
271
+ | [@tamer4lynx/tamer-dev-client](https://www.npmjs.com/package/@tamer4lynx/tamer-dev-client) | `npm i @tamer4lynx/tamer-dev-client@prerelease` | Dev launcher UI (QR scan, HMR). Add to your app and build with `-d` for a dev build; `-r` omits it. |
277
272
  | [@tamer4lynx/tamer-dev-app](https://www.npmjs.com/package/@tamer4lynx/tamer-dev-app) | workspace / npm | Standalone dev app (store build). Your app can use tamer-dev-client for dev builds instead. |
278
- | [@tamer4lynx/tamer-plugin](https://www.npmjs.com/package/@tamer4lynx/tamer-plugin) | `npm i @tamer4lynx/tamer-plugin` | Rsbuild plugin middleman |
279
- | [@tamer4lynx/tamer-router](https://www.npmjs.com/package/@tamer4lynx/tamer-router) | `npm i @tamer4lynx/tamer-router` | File-based routing, Stack/Tabs |
280
- | [@tamer4lynx/tamer-icons](https://www.npmjs.com/package/@tamer4lynx/tamer-icons) | `npm i @tamer4lynx/tamer-icons` | Icon fonts (Material, Font Awesome) |
281
- | [@tamer4lynx/tamer-insets](https://www.npmjs.com/package/@tamer4lynx/tamer-insets) | `npm i @tamer4lynx/tamer-insets` | System insets, keyboard state |
282
- | [@tamer4lynx/tamer-system-ui](https://www.npmjs.com/package/@tamer4lynx/tamer-system-ui) | `npm i @tamer4lynx/tamer-system-ui` | Status bar, navigation bar |
283
- | [@tamer4lynx/tamer-app-shell](https://www.npmjs.com/package/@tamer4lynx/tamer-app-shell) | `npm i @tamer4lynx/tamer-app-shell` | AppBar, TabBar, Content layout |
284
- | [@tamer4lynx/tamer-text-input](https://www.npmjs.com/package/@tamer4lynx/tamer-text-input) | `npm i @tamer4lynx/tamer-text-input` | React TextInput |
285
- | [@tamer4lynx/tamer-auth](https://www.npmjs.com/package/@tamer4lynx/tamer-auth) | `npm i @tamer4lynx/tamer-auth` | OAuth 2.0 / OIDC |
286
- | [@tamer4lynx/tamer-biometric](https://www.npmjs.com/package/@tamer4lynx/tamer-biometric) | `npm i @tamer4lynx/tamer-biometric` | Fingerprint, Face ID |
287
- | [@tamer4lynx/tamer-display-browser](https://www.npmjs.com/package/@tamer4lynx/tamer-display-browser) | `npm i @tamer4lynx/tamer-display-browser` | Open URLs in system browser |
288
- | [@tamer4lynx/tamer-linking](https://www.npmjs.com/package/@tamer4lynx/tamer-linking) | `npm i @tamer4lynx/tamer-linking` | Deep linking |
289
- | [@tamer4lynx/tamer-screen](https://www.npmjs.com/package/@tamer4lynx/tamer-screen) | `npm i @tamer4lynx/tamer-screen` | SafeArea, Screen, AvoidKeyboard |
290
- | [@tamer4lynx/tamer-secure-store](https://www.npmjs.com/package/@tamer4lynx/tamer-secure-store) | `npm i @tamer4lynx/tamer-secure-store` | Secure key-value storage |
291
- | [@tamer4lynx/tamer-transports](https://www.npmjs.com/package/@tamer4lynx/tamer-transports) | `npm i @tamer4lynx/tamer-transports` | Fetch, WebSocket, EventSource polyfills |
273
+ | [@tamer4lynx/tamer-plugin](https://www.npmjs.com/package/@tamer4lynx/tamer-plugin) | `npm i @tamer4lynx/tamer-plugin@prerelease` | Rsbuild plugin middleman |
274
+ | [@tamer4lynx/tamer-router](https://www.npmjs.com/package/@tamer4lynx/tamer-router) | `npm i @tamer4lynx/tamer-router@prerelease` | File-based routing, Stack/Tabs |
275
+ | [@tamer4lynx/tamer-icons](https://www.npmjs.com/package/@tamer4lynx/tamer-icons) | `npm i @tamer4lynx/tamer-icons@prerelease` | Icon fonts (Material, Font Awesome) |
276
+ | [@tamer4lynx/tamer-insets](https://www.npmjs.com/package/@tamer4lynx/tamer-insets) | `npm i @tamer4lynx/tamer-insets@prerelease` | System insets, keyboard state |
277
+ | [@tamer4lynx/tamer-system-ui](https://www.npmjs.com/package/@tamer4lynx/tamer-system-ui) | `npm i @tamer4lynx/tamer-system-ui@prerelease` | Status bar, navigation bar |
278
+ | [@tamer4lynx/tamer-app-shell](https://www.npmjs.com/package/@tamer4lynx/tamer-app-shell) | `npm i @tamer4lynx/tamer-app-shell@prerelease` | AppBar, TabBar, Content layout |
279
+ | [@tamer4lynx/tamer-text-input](https://www.npmjs.com/package/@tamer4lynx/tamer-text-input) | `npm i @tamer4lynx/tamer-text-input@prerelease` | React TextInput |
280
+ | [@tamer4lynx/tamer-auth](https://www.npmjs.com/package/@tamer4lynx/tamer-auth) | `npm i @tamer4lynx/tamer-auth@prerelease` | OAuth 2.0 / OIDC |
281
+ | [@tamer4lynx/tamer-biometric](https://www.npmjs.com/package/@tamer4lynx/tamer-biometric) | `npm i @tamer4lynx/tamer-biometric@prerelease` | Fingerprint, Face ID |
282
+ | [@tamer4lynx/tamer-display-browser](https://www.npmjs.com/package/@tamer4lynx/tamer-display-browser) | `npm i @tamer4lynx/tamer-display-browser@prerelease` | Open URLs in system browser |
283
+ | [@tamer4lynx/tamer-linking](https://www.npmjs.com/package/@tamer4lynx/tamer-linking) | `npm i @tamer4lynx/tamer-linking@prerelease` | Deep linking |
284
+ | [@tamer4lynx/tamer-screen](https://www.npmjs.com/package/@tamer4lynx/tamer-screen) | `npm i @tamer4lynx/tamer-screen@prerelease` | SafeArea, Screen, AvoidKeyboard |
285
+ | [@tamer4lynx/tamer-secure-store](https://www.npmjs.com/package/@tamer4lynx/tamer-secure-store) | `npm i @tamer4lynx/tamer-secure-store@prerelease` | Secure key-value storage |
286
+ | [@tamer4lynx/tamer-transports](https://www.npmjs.com/package/@tamer4lynx/tamer-transports) | `npm i @tamer4lynx/tamer-transports@prerelease` | Fetch, WebSocket, EventSource polyfills |
292
287
 
293
288
  The iOS autolinking feature runs `pod install` automatically.
294
289
 
@@ -305,6 +300,7 @@ Contributions are welcome! To develop on Tamer4Lynx:
305
300
  git clone https://github.com/tamer4lynx/tamer4lynx.git
306
301
  cd tamer4lynx
307
302
  npm install
303
+ # or: pnpm install | bun install
308
304
  ```
309
305
 
310
306
  Please feel free to submit issues or pull requests.
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import path24 from "path";
12
12
  import { program } from "commander";
13
13
 
14
14
  // package.json
15
- var version = "0.0.7";
15
+ var version = "0.0.9";
16
16
 
17
17
  // src/android/create.ts
18
18
  import fs3 from "fs";
@@ -679,6 +679,7 @@ ${reloadMethod}
679
679
  private fun buildLynxView(): LynxView {
680
680
  val viewBuilder = LynxViewBuilder()
681
681
  viewBuilder.setTemplateProvider(TemplateProvider(this))
682
+ GeneratedLynxExtensions.configureViewBuilder(viewBuilder)
682
683
  return viewBuilder.build(this)
683
684
  }
684
685
  }
@@ -844,6 +845,7 @@ ${devClientField} private var lynxView: LynxView? = null${!hasDevClient ? "\n
844
845
  private fun buildLynxView(): LynxView {
845
846
  val viewBuilder = LynxViewBuilder()
846
847
  viewBuilder.setTemplateProvider(TemplateProvider(this))
848
+ GeneratedLynxExtensions.configureViewBuilder(viewBuilder)
847
849
  return viewBuilder.build(this)
848
850
  }${standaloneLifecycle}${devClientCleanup}
849
851
  }
@@ -979,12 +981,12 @@ junit = "4.13.2"
979
981
  junitVersion = "1.1.5"
980
982
  espressoCore = "3.5.1"
981
983
  appcompat = "1.6.1"
982
- lynx = "3.3.1"
984
+ lynx = "3.6.0"
983
985
  material = "1.10.0"
984
986
  activity = "1.8.0"
985
987
  constraintlayout = "2.1.4"
986
988
  okhttp = "4.9.0"
987
- primjs = "2.12.0"
989
+ primjs = "3.6.1"
988
990
  zxing = "4.3.0"
989
991
 
990
992
  [libraries]
@@ -1007,6 +1009,8 @@ lynx-service-http = { module = "org.lynxsdk.lynx:lynx-service-http", version.ref
1007
1009
  lynx-service-image = { module = "org.lynxsdk.lynx:lynx-service-image", version.ref = "lynx" }
1008
1010
  lynx-service-log = { module = "org.lynxsdk.lynx:lynx-service-log", version.ref = "lynx" }
1009
1011
  lynx-trace = { module = "org.lynxsdk.lynx:lynx-trace", version.ref = "lynx" }
1012
+ lynx-xelement = { module = "org.lynxsdk.lynx:xelement", version.ref = "lynx" }
1013
+ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref = "lynx" }
1010
1014
  material = { group = "com.google.android.material", name = "material", version.ref = "material" }
1011
1015
  androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
1012
1016
  androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
@@ -1155,6 +1159,8 @@ dependencies {
1155
1159
  implementation(libs.animated.base)
1156
1160
  implementation(libs.lynx.service.log)
1157
1161
  implementation(libs.lynx.service.http)
1162
+ implementation(libs.lynx.xelement)
1163
+ implementation(libs.lynx.xelement.input)
1158
1164
  implementation(libs.okhttp)
1159
1165
  implementation(libs.zxing)
1160
1166
  kapt(libs.lynx.processor)
@@ -1578,12 +1584,18 @@ ${hostViewLines}
1578
1584
 
1579
1585
  import android.content.Context
1580
1586
  import com.lynx.tasm.LynxEnv
1587
+ import com.lynx.tasm.LynxViewBuilder
1588
+ import com.lynx.xelement.XElementBehaviors
1581
1589
  ${moduleImports}
1582
1590
  ${elementImports}
1583
1591
 
1584
1592
  object GeneratedLynxExtensions {
1585
1593
  fun register(context: Context) {
1586
1594
  ${allRegistrations}
1595
+ }
1596
+
1597
+ fun configureViewBuilder(viewBuilder: LynxViewBuilder) {
1598
+ viewBuilder.addBehaviors(XElementBehaviors().create())
1587
1599
  }${hostViewMethod}
1588
1600
  }
1589
1601
  `;
@@ -1850,6 +1862,7 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
1850
1862
  syncManifestPermissions(packages);
1851
1863
  syncDeepLinkIntentFilters();
1852
1864
  syncVersionCatalog(packages);
1865
+ ensureXElementDeps();
1853
1866
  ensureReleaseSigning();
1854
1867
  console.log("\u2728 Autolinking complete.");
1855
1868
  }
@@ -1900,6 +1913,40 @@ ${generateActivityLifecycleKotlin(packages, projectPackage)}`;
1900
1913
  console.log("\u2705 Synced version catalog (libs.versions.toml) for linked modules.");
1901
1914
  }
1902
1915
  }
1916
+ function ensureXElementDeps() {
1917
+ const libsTomlPath = path6.join(appAndroidPath, "gradle", "libs.versions.toml");
1918
+ if (fs6.existsSync(libsTomlPath)) {
1919
+ let toml = fs6.readFileSync(libsTomlPath, "utf8");
1920
+ let updated = false;
1921
+ if (!toml.includes("lynx-xelement =")) {
1922
+ toml = toml.replace(
1923
+ /(lynx-trace\s*=\s*\{[^\n]*\n)/,
1924
+ `$1lynx-xelement = { module = "org.lynxsdk.lynx:xelement", version.ref = "lynx" }
1925
+ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref = "lynx" }
1926
+ `
1927
+ );
1928
+ updated = true;
1929
+ }
1930
+ if (updated) {
1931
+ fs6.writeFileSync(libsTomlPath, toml);
1932
+ console.log("\u2705 Added XElement entries to version catalog.");
1933
+ }
1934
+ }
1935
+ const appBuildPath = path6.join(appAndroidPath, "app", "build.gradle.kts");
1936
+ if (fs6.existsSync(appBuildPath)) {
1937
+ let content = fs6.readFileSync(appBuildPath, "utf8");
1938
+ if (!content.includes("lynx.xelement")) {
1939
+ content = content.replace(
1940
+ /(implementation\(libs\.lynx\.service\.http\))/,
1941
+ `$1
1942
+ implementation(libs.lynx.xelement)
1943
+ implementation(libs.lynx.xelement.input)`
1944
+ );
1945
+ fs6.writeFileSync(appBuildPath, content);
1946
+ console.log("\u2705 Added XElement dependencies to app build.gradle.kts.");
1947
+ }
1948
+ }
1949
+ }
1903
1950
  function ensureReleaseSigning() {
1904
1951
  const appBuildPath = path6.join(appAndroidPath, "app", "build.gradle.kts");
1905
1952
  if (!fs6.existsSync(appBuildPath)) return;
@@ -2247,10 +2294,18 @@ async function setupCocoaPods(rootDir) {
2247
2294
  throw new Error(`Podfile not found at ${podfilePath}`);
2248
2295
  }
2249
2296
  console.log(`\u{1F680} Executing pod install in: ${rootDir}`);
2250
- execSync4(`pod install`, {
2251
- cwd: rootDir,
2252
- stdio: "inherit"
2253
- });
2297
+ try {
2298
+ execSync4("pod install", {
2299
+ cwd: rootDir,
2300
+ stdio: "inherit"
2301
+ });
2302
+ } catch {
2303
+ console.log("\u2139\uFE0F Retrying CocoaPods install with repo update...");
2304
+ execSync4("pod install --repo-update", {
2305
+ cwd: rootDir,
2306
+ stdio: "inherit"
2307
+ });
2308
+ }
2254
2309
  console.log("\u2705 CocoaPods dependencies installed successfully.");
2255
2310
  } catch (err) {
2256
2311
  console.error("\u274C Failed to install CocoaPods dependencies.", err.message);
@@ -2350,6 +2405,8 @@ target '${appName}' do
2350
2405
  pod 'SDWebImage','5.15.5'
2351
2406
  pod 'SDWebImageWebPCoder', '0.11.0'
2352
2407
 
2408
+ pod 'XElement', '3.6.0'
2409
+
2353
2410
  # GENERATED AUTOLINK DEPENDENCIES START
2354
2411
  # This section is automatically generated by Tamer4Lynx.
2355
2412
  # Manual edits will be overwritten.
@@ -2361,6 +2418,8 @@ post_install do |installer|
2361
2418
  target.build_configurations.each do |config|
2362
2419
  config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
2363
2420
  config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
2421
+ config.build_settings['CLANG_ENABLE_EXPLICIT_MODULES'] = 'NO'
2422
+ config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
2364
2423
  end
2365
2424
 
2366
2425
  if target.name == 'Lynx'
@@ -2371,10 +2430,12 @@ post_install do |installer|
2371
2430
  '-Wno-error=vla-extension',
2372
2431
  '-Wno-deprecated-declarations',
2373
2432
  '-Wno-deprecated',
2433
+ '-Wno-deprecated-implementations',
2374
2434
  '-Wno-macro-redefined',
2375
2435
  '-Wno-enum-compare',
2376
2436
  '-Wno-enum-compare-conditional',
2377
- '-Wno-enum-conversion'
2437
+ '-Wno-enum-conversion',
2438
+ '-Wno-error'
2378
2439
  ].join(' ')
2379
2440
 
2380
2441
  config.build_settings['OTHER_CPLUSPLUSFLAGS'] = "$(inherited) #{flags}"
@@ -2385,6 +2446,19 @@ post_install do |installer|
2385
2446
  end
2386
2447
  end
2387
2448
  end
2449
+ Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', 'Lynx', '*.xcconfig')).each do |xcconfig_path|
2450
+ next unless File.file?(xcconfig_path)
2451
+ content = File.read(xcconfig_path)
2452
+ next unless content.include?('-Werror')
2453
+ File.write(xcconfig_path, content.gsub('-Werror', ''))
2454
+ end
2455
+ Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
2456
+ next unless File.file?(lynx_source)
2457
+ content = File.read(lynx_source)
2458
+ next unless content.match?(/\\btypeof\\(/)
2459
+ File.chmod(0644, lynx_source) rescue nil
2460
+ File.write(lynx_source, content.gsub(/\\btypeof\\(/, '__typeof__('))
2461
+ end
2388
2462
  end
2389
2463
  `);
2390
2464
  const hostPkg = findTamerHostPackage(process.cwd());
@@ -2987,9 +3061,35 @@ ${replacementBlock}
2987
3061
  fs12.writeFileSync(filePath, fileContent, "utf8");
2988
3062
  console.log(`\u2705 Updated autolinked section in ${path13.basename(filePath)}`);
2989
3063
  }
3064
+ function resolvePodDirectory(pkg) {
3065
+ const configuredDir = path13.join(pkg.packagePath, pkg.config.ios?.podspecPath || ".");
3066
+ if (fs12.existsSync(configuredDir)) {
3067
+ return configuredDir;
3068
+ }
3069
+ const iosDir = path13.join(pkg.packagePath, "ios");
3070
+ if (fs12.existsSync(iosDir)) {
3071
+ const stack = [iosDir];
3072
+ while (stack.length > 0) {
3073
+ const current = stack.pop();
3074
+ try {
3075
+ const entries = fs12.readdirSync(current, { withFileTypes: true });
3076
+ const podspec = entries.find((entry) => entry.isFile() && entry.name.endsWith(".podspec"));
3077
+ if (podspec) {
3078
+ return current;
3079
+ }
3080
+ for (const entry of entries) {
3081
+ if (entry.isDirectory()) {
3082
+ stack.push(path13.join(current, entry.name));
3083
+ }
3084
+ }
3085
+ } catch {
3086
+ }
3087
+ }
3088
+ }
3089
+ return configuredDir;
3090
+ }
2990
3091
  function resolvePodName(pkg) {
2991
- const podspecDir = pkg.config.ios?.podspecPath || ".";
2992
- const fullPodspecDir = path13.join(pkg.packagePath, podspecDir);
3092
+ const fullPodspecDir = resolvePodDirectory(pkg);
2993
3093
  if (fs12.existsSync(fullPodspecDir)) {
2994
3094
  try {
2995
3095
  const files = fs12.readdirSync(fullPodspecDir);
@@ -3007,8 +3107,7 @@ ${replacementBlock}
3007
3107
  const iosPackages = packages.filter((p) => p.config.ios);
3008
3108
  if (iosPackages.length > 0) {
3009
3109
  iosPackages.forEach((pkg) => {
3010
- const podspecPath = pkg.config.ios?.podspecPath || ".";
3011
- const relativePath = path13.relative(iosProjectPath, path13.join(pkg.packagePath, podspecPath));
3110
+ const relativePath = path13.relative(iosProjectPath, resolvePodDirectory(pkg));
3012
3111
  const podName = resolvePodName(pkg);
3013
3112
  scriptContent += `
3014
3113
  pod '${podName}', :path => '${relativePath}'`;
@@ -3019,6 +3118,83 @@ ${replacementBlock}
3019
3118
  }
3020
3119
  updateGeneratedSection(podfilePath, scriptContent.trim(), "# GENERATED AUTOLINK DEPENDENCIES START", "# GENERATED AUTOLINK DEPENDENCIES END");
3021
3120
  }
3121
+ function ensureXElementPod() {
3122
+ const podfilePath = path13.join(iosProjectPath, "Podfile");
3123
+ if (!fs12.existsSync(podfilePath)) return;
3124
+ let content = fs12.readFileSync(podfilePath, "utf8");
3125
+ if (content.includes("pod 'XElement'")) return;
3126
+ const lynxVersionMatch = content.match(/pod\s+'Lynx',\s*'([^']+)'/);
3127
+ const lynxVersion = lynxVersionMatch?.[1] ?? "3.6.0";
3128
+ const xelementLine = `
3129
+ pod 'XElement', '${lynxVersion}'`;
3130
+ const insertAfter = /pod\s+'LynxService'[^\n]*(?:\n\s*'[^']*',?\s*)*/;
3131
+ const serviceMatch = content.match(insertAfter);
3132
+ if (serviceMatch) {
3133
+ const idx = serviceMatch.index + serviceMatch[0].length;
3134
+ content = content.slice(0, idx) + xelementLine + content.slice(idx);
3135
+ } else {
3136
+ content = content.replace(
3137
+ /(# GENERATED AUTOLINK DEPENDENCIES START)/,
3138
+ `pod 'XElement', '${lynxVersion}'
3139
+
3140
+ $1`
3141
+ );
3142
+ }
3143
+ fs12.writeFileSync(podfilePath, content, "utf8");
3144
+ console.log(`\u2705 Added XElement pod (v${lynxVersion}) to Podfile`);
3145
+ }
3146
+ function ensureLynxPatchInPodfile() {
3147
+ const podfilePath = path13.join(iosProjectPath, "Podfile");
3148
+ if (!fs12.existsSync(podfilePath)) return;
3149
+ let content = fs12.readFileSync(podfilePath, "utf8");
3150
+ if (content.includes("content.gsub(/\\btypeof\\(/, '__typeof__(')")) return;
3151
+ const patch = `
3152
+ Dir.glob(File.join(installer.sandbox.root, 'Lynx/platform/darwin/**/*.{m,mm}')).each do |lynx_source|
3153
+ next unless File.file?(lynx_source)
3154
+ content = File.read(lynx_source)
3155
+ next unless content.match?(/\\btypeof\\(/)
3156
+ File.chmod(0644, lynx_source) rescue nil
3157
+ File.write(lynx_source, content.gsub(/\\btypeof\\(/, '__typeof__('))
3158
+ end`;
3159
+ content = content.replace(/(\n end\s*\n)(end\s*)$/, `$1${patch}
3160
+ $2`);
3161
+ fs12.writeFileSync(podfilePath, content, "utf8");
3162
+ console.log("\u2705 Added Lynx typeof patch to Podfile post_install.");
3163
+ }
3164
+ function ensurePodBuildSettings() {
3165
+ const podfilePath = path13.join(iosProjectPath, "Podfile");
3166
+ if (!fs12.existsSync(podfilePath)) return;
3167
+ let content = fs12.readFileSync(podfilePath, "utf8");
3168
+ let changed = false;
3169
+ if (!content.includes("CLANG_ENABLE_EXPLICIT_MODULES")) {
3170
+ content = content.replace(
3171
+ /config\.build_settings\['IPHONEOS_DEPLOYMENT_TARGET'\]\s*=\s*'[^']*'/,
3172
+ `$&
3173
+ config.build_settings['CLANG_ENABLE_EXPLICIT_MODULES'] = 'NO'
3174
+ config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'`
3175
+ );
3176
+ changed = true;
3177
+ }
3178
+ if (!content.includes("gsub('-Werror'")) {
3179
+ const xcconfigStrip = `
3180
+ Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', 'Lynx', '*.xcconfig')).each do |xcconfig_path|
3181
+ next unless File.file?(xcconfig_path)
3182
+ content = File.read(xcconfig_path)
3183
+ next unless content.include?('-Werror')
3184
+ File.write(xcconfig_path, content.gsub('-Werror', ''))
3185
+ end`;
3186
+ content = content.replace(
3187
+ /(Dir\.glob.*?Lynx\/platform\/darwin)/s,
3188
+ `${xcconfigStrip}
3189
+ $1`
3190
+ );
3191
+ changed = true;
3192
+ }
3193
+ if (changed) {
3194
+ fs12.writeFileSync(podfilePath, content, "utf8");
3195
+ console.log("\u2705 Added Xcode compatibility build settings to Podfile post_install.");
3196
+ }
3197
+ }
3022
3198
  function updateLynxInitProcessor(packages) {
3023
3199
  const appNameFromConfig = resolved.config.ios?.appName;
3024
3200
  const candidatePaths = [];
@@ -3029,6 +3205,30 @@ ${replacementBlock}
3029
3205
  const found = candidatePaths.find((p) => fs12.existsSync(p));
3030
3206
  const lynxInitPath = found ?? candidatePaths[0];
3031
3207
  const iosPackages = packages.filter((p) => getIosModuleClassNames(p.config.ios).length > 0 || Object.keys(getIosElements(p.config.ios)).length > 0);
3208
+ const seenModules = /* @__PURE__ */ new Set();
3209
+ const seenElements = /* @__PURE__ */ new Set();
3210
+ const packagesWithContributions = /* @__PURE__ */ new Set();
3211
+ for (const pkg of iosPackages) {
3212
+ let hasUnique = false;
3213
+ for (const cls of getIosModuleClassNames(pkg.config.ios)) {
3214
+ if (!seenModules.has(cls)) {
3215
+ seenModules.add(cls);
3216
+ hasUnique = true;
3217
+ } else {
3218
+ console.warn(`\u26A0\uFE0F Skipping duplicate module "${cls}" from ${pkg.name} (already registered by another package)`);
3219
+ }
3220
+ }
3221
+ for (const tag of Object.keys(getIosElements(pkg.config.ios))) {
3222
+ if (!seenElements.has(tag)) {
3223
+ seenElements.add(tag);
3224
+ hasUnique = true;
3225
+ } else {
3226
+ console.warn(`\u26A0\uFE0F Skipping duplicate element "${tag}" from ${pkg.name} (already registered by another package)`);
3227
+ }
3228
+ }
3229
+ if (hasUnique) packagesWithContributions.add(pkg);
3230
+ }
3231
+ const importPackages = iosPackages.filter((p) => packagesWithContributions.has(p));
3032
3232
  function updateImportsSection(filePath, pkgs) {
3033
3233
  const startMarker = "// GENERATED IMPORTS START";
3034
3234
  const endMarker = "// GENERATED IMPORTS END";
@@ -3081,19 +3281,29 @@ ${fileContent}`;
3081
3281
  fs12.writeFileSync(filePath, newContent, "utf8");
3082
3282
  console.log(`\u2705 Updated imports in ${path13.basename(filePath)}`);
3083
3283
  }
3084
- updateImportsSection(lynxInitPath, iosPackages);
3085
- if (iosPackages.length === 0) {
3284
+ updateImportsSection(lynxInitPath, importPackages);
3285
+ if (importPackages.length === 0) {
3086
3286
  const placeholder = " // No native modules found by Tamer4Lynx autolinker.";
3087
3287
  updateGeneratedSection(lynxInitPath, placeholder, "// GENERATED AUTOLINK START", "// GENERATED AUTOLINK END");
3088
3288
  return;
3089
3289
  }
3090
- const blocks = iosPackages.flatMap((pkg) => {
3290
+ const seenModules2 = /* @__PURE__ */ new Set();
3291
+ const seenElements2 = /* @__PURE__ */ new Set();
3292
+ const blocks = importPackages.flatMap((pkg) => {
3091
3293
  const classNames = getIosModuleClassNames(pkg.config.ios);
3092
- const moduleBlocks = classNames.map((classNameRaw) => [
3294
+ const moduleBlocks = classNames.filter((cls) => {
3295
+ if (seenModules2.has(cls)) return false;
3296
+ seenModules2.add(cls);
3297
+ return true;
3298
+ }).map((classNameRaw) => [
3093
3299
  ` // Register module from package: ${pkg.name}`,
3094
3300
  ` globalConfig.register(${classNameRaw}.self)`
3095
3301
  ].join("\n"));
3096
- const elementBlocks = Object.entries(getIosElements(pkg.config.ios)).map(([tagName, classNameRaw]) => [
3302
+ const elementBlocks = Object.entries(getIosElements(pkg.config.ios)).filter(([tagName]) => {
3303
+ if (seenElements2.has(tagName)) return false;
3304
+ seenElements2.add(tagName);
3305
+ return true;
3306
+ }).map(([tagName, classNameRaw]) => [
3097
3307
  ` // Register element from package: ${pkg.name}`,
3098
3308
  ` globalConfig.registerUI(${classNameRaw}.self, withName: "${tagName}")`
3099
3309
  ].join("\n"));
@@ -3202,7 +3412,12 @@ $1`
3202
3412
  const cwd = path13.dirname(podfilePath);
3203
3413
  try {
3204
3414
  console.log(`\u2139\uFE0F Running \`pod install\` in ${cwd}...`);
3205
- execSync5("pod install", { cwd, stdio: "inherit" });
3415
+ try {
3416
+ execSync5("pod install", { cwd, stdio: "inherit" });
3417
+ } catch {
3418
+ console.log("\u2139\uFE0F Retrying `pod install` with repo update...");
3419
+ execSync5("pod install --repo-update", { cwd, stdio: "inherit" });
3420
+ }
3206
3421
  console.log("\u2705 `pod install` completed successfully.");
3207
3422
  } catch (e) {
3208
3423
  console.warn(`\u26A0\uFE0F 'pod install' failed: ${e.message}`);
@@ -3218,6 +3433,9 @@ $1`
3218
3433
  console.log("\u2139\uFE0F No Tamer4Lynx native packages found.");
3219
3434
  }
3220
3435
  updatePodfile(packages);
3436
+ ensureXElementPod();
3437
+ ensureLynxPatchInPodfile();
3438
+ ensurePodBuildSettings();
3221
3439
  updateLynxInitProcessor(packages);
3222
3440
  syncInfoPlistPermissions(packages);
3223
3441
  syncInfoPlistUrlSchemes();
@@ -3335,7 +3553,7 @@ function addLaunchScreenToXcodeProject(pbxprojPath, appName) {
3335
3553
  function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
3336
3554
  let content = fs13.readFileSync(pbxprojPath, "utf8");
3337
3555
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3338
- if (new RegExp(`path = ${escaped};`).test(content)) return;
3556
+ if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
3339
3557
  const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
3340
3558
  const buildFileUUID = deterministicUUID(`buildFile:${appName}:${filename}`);
3341
3559
  content = content.replace(
@@ -3364,7 +3582,7 @@ function addSwiftSourceToXcodeProject(pbxprojPath, appName, filename) {
3364
3582
  function addResourceToXcodeProject(pbxprojPath, appName, filename) {
3365
3583
  let content = fs13.readFileSync(pbxprojPath, "utf8");
3366
3584
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3367
- if (new RegExp(`path = ${escaped};`).test(content)) return;
3585
+ if (new RegExp(`path = "?${escaped}"?;`).test(content)) return;
3368
3586
  const fileRefUUID = deterministicUUID(`fileRef:${appName}:${filename}`);
3369
3587
  const buildFileUUID = deterministicUUID(`buildFile:${appName}:${filename}`);
3370
3588
  content = content.replace(
@@ -3813,7 +4031,11 @@ var bundle_default2 = bundleAndDeploy2;
3813
4031
  // src/ios/build.ts
3814
4032
  import fs15 from "fs";
3815
4033
  import path16 from "path";
4034
+ import os3 from "os";
3816
4035
  import { execSync as execSync7 } from "child_process";
4036
+ function hostArch() {
4037
+ return os3.arch() === "arm64" ? "arm64" : "x86_64";
4038
+ }
3817
4039
  function findBootedSimulator() {
3818
4040
  try {
3819
4041
  const out = execSync7("xcrun simctl list devices --json", { encoding: "utf8" });
@@ -3844,10 +4066,16 @@ async function buildIpa(opts = {}) {
3844
4066
  const flag = xcproject.endsWith(".xcworkspace") ? "-workspace" : "-project";
3845
4067
  const derivedDataPath = path16.join(iosDir, "build");
3846
4068
  const sdk = opts.install ? "iphonesimulator" : "iphoneos";
4069
+ const signingArgs = opts.install ? "" : " CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO";
4070
+ const archFlag = opts.install ? `-arch ${hostArch()} ` : "";
4071
+ const extraSettings = [
4072
+ "ONLY_ACTIVE_ARCH=YES",
4073
+ "CLANG_ENABLE_EXPLICIT_MODULES=NO"
4074
+ ].join(" ");
3847
4075
  console.log(`
3848
4076
  \u{1F528} Building ${configuration} (${sdk})...`);
3849
4077
  execSync7(
3850
- `xcodebuild ${flag} "${xcproject}" -scheme "${scheme}" -configuration ${configuration} -sdk ${sdk} -derivedDataPath "${derivedDataPath}"`,
4078
+ `xcodebuild ${flag} "${xcproject}" -scheme "${scheme}" -configuration ${configuration} -sdk ${sdk} ${archFlag}-derivedDataPath "${derivedDataPath}" ${extraSettings}${signingArgs}`,
3851
4079
  { stdio: "inherit", cwd: iosDir }
3852
4080
  );
3853
4081
  console.log(`\u2705 Build completed.`);
@@ -4184,12 +4412,12 @@ var codegen_default = codegen;
4184
4412
  import { spawn } from "child_process";
4185
4413
  import fs19 from "fs";
4186
4414
  import http from "http";
4187
- import os3 from "os";
4415
+ import os4 from "os";
4188
4416
  import path20 from "path";
4189
4417
  import { WebSocketServer } from "ws";
4190
4418
  var DEFAULT_PORT = 3e3;
4191
4419
  function getLanIp() {
4192
- const nets = os3.networkInterfaces();
4420
+ const nets = os4.networkInterfaces();
4193
4421
  for (const name of Object.keys(nets)) {
4194
4422
  const addrs = nets[name];
4195
4423
  if (!addrs) continue;
@@ -4549,15 +4777,17 @@ var LIB_PACKAGE = "com.tamer.embeddable";
4549
4777
  var GRADLE_VERSION = "8.14.2";
4550
4778
  var LIBS_VERSIONS_TOML = `[versions]
4551
4779
  agp = "8.9.1"
4552
- lynx = "3.3.1"
4780
+ lynx = "3.6.0"
4553
4781
  kotlin = "2.0.21"
4554
- primjs = "2.12.0"
4782
+ primjs = "3.6.1"
4555
4783
 
4556
4784
  [libraries]
4557
4785
  lynx = { module = "org.lynxsdk.lynx:lynx", version.ref = "lynx" }
4558
4786
  lynx-jssdk = { module = "org.lynxsdk.lynx:lynx-jssdk", version.ref = "lynx" }
4559
4787
  lynx-processor = { module = "org.lynxsdk.lynx:lynx-processor", version.ref = "lynx" }
4560
4788
  lynx-trace = { module = "org.lynxsdk.lynx:lynx-trace", version.ref = "lynx" }
4789
+ lynx-xelement = { module = "org.lynxsdk.lynx:xelement", version.ref = "lynx" }
4790
+ lynx-xelement-input = { module = "org.lynxsdk.lynx:xelement-input", version.ref = "lynx" }
4561
4791
  lynx-service-http = { module = "org.lynxsdk.lynx:lynx-service-http", version.ref = "lynx" }
4562
4792
  lynx-service-image = { module = "org.lynxsdk.lynx:lynx-service-image", version.ref = "lynx" }
4563
4793
  lynx-service-log = { module = "org.lynxsdk.lynx:lynx-service-log", version.ref = "lynx" }
@@ -4677,6 +4907,8 @@ dependencies {
4677
4907
  implementation(libs.lynx)
4678
4908
  implementation(libs.lynx.jssdk)
4679
4909
  implementation(libs.lynx.trace)
4910
+ implementation(libs.lynx.xelement)
4911
+ implementation(libs.lynx.xelement.input)
4680
4912
  implementation(libs.primjs)
4681
4913
  implementation(libs.lynx.service.image)
4682
4914
  implementation(libs.lynx.service.http)
@@ -4950,6 +5182,9 @@ var CORE_PACKAGES = [
4950
5182
  "@tamer4lynx/tamer-system-ui",
4951
5183
  "@tamer4lynx/tamer-icons"
4952
5184
  ];
5185
+ var PACKAGE_ALIASES = {
5186
+ input: "@tamer4lynx/tamer-text-input"
5187
+ };
4953
5188
  function detectPackageManager(cwd) {
4954
5189
  const dir = path23.resolve(cwd);
4955
5190
  if (fs22.existsSync(path23.join(dir, "pnpm-lock.yaml"))) return "pnpm";
@@ -4979,9 +5214,10 @@ function add(packages = []) {
4979
5214
  }
4980
5215
  const { lynxProjectDir } = resolveHostPaths();
4981
5216
  const pm = detectPackageManager(lynxProjectDir);
4982
- const normalized = list.map(
4983
- (p) => p.startsWith("@") ? p : `@tamer4lynx/${p}`
4984
- );
5217
+ const normalized = list.map((p) => {
5218
+ if (p.startsWith("@")) return p;
5219
+ return PACKAGE_ALIASES[p] ?? `@tamer4lynx/${p}`;
5220
+ });
4985
5221
  console.log(`Adding ${normalized.join(", ")} to ${lynxProjectDir} (using ${pm})...`);
4986
5222
  runInstall(lynxProjectDir, normalized, pm);
4987
5223
  console.log("\u2705 Packages installed. Run `t4l link` to link native modules.");
@@ -5104,7 +5340,7 @@ program.command("build-dev-app").option("-p, --platform <platform>", "Platform:
5104
5340
  }
5105
5341
  });
5106
5342
  program.command("add [packages...]").description("Add @tamer4lynx packages to the Lynx project. Future: will track versions for compatibility (Expo-style).").action((packages) => add(packages));
5107
- program.command("add-core").description("Add core packages (app-shell, screen, router, insets, transports, text-input, system-ui, icons)").action(() => addCore());
5343
+ program.command("add-core").description("Add core packages (app-shell, screen, router, insets, transports, input/text-input, system-ui, icons)").action(() => addCore());
5108
5344
  program.command("create").description("Create a new Lynx extension project (RFC-compliant)").action(() => create_default3());
5109
5345
  program.command("codegen").description("Generate code from @lynxmodule declarations").action(() => {
5110
5346
  codegen_default();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamer4lynx/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "description": "A CLI tool for managing LynxJS native modules.",