@lotics/cli 0.36.0 → 0.36.2

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.
@@ -145,6 +145,20 @@ export function buildWrapperPage(args) {
145
145
  return completed.file;
146
146
  }
147
147
 
148
+ // openExternal — the wrapper page is the top frame (un-sandboxed), so it
149
+ // opens the link directly; NOT forwarded to the RPC endpoint (no server
150
+ // op). Re-validate the scheme; never trust the iframe's payload.
151
+ function handleOpenExternal(payload) {
152
+ var url = payload && payload.url;
153
+ if (typeof url !== "string") throw new Error("openExternal requires a url string");
154
+ var parsed = new URL(url);
155
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
156
+ throw new Error('openExternal: unsupported URL scheme "' + parsed.protocol + '"');
157
+ }
158
+ window.open(parsed.href, "_blank", "noopener,noreferrer");
159
+ return undefined;
160
+ }
161
+
148
162
  window.addEventListener("message", async function (event) {
149
163
  if (event.source !== iframe.contentWindow || event.origin !== VITE_ORIGIN) return;
150
164
  const msg = event.data;
@@ -153,6 +167,8 @@ export function buildWrapperPage(args) {
153
167
  try {
154
168
  const data = msg.op === "upload"
155
169
  ? await handleUpload(msg.payload)
170
+ : msg.op === "openExternal"
171
+ ? handleOpenExternal(msg.payload)
156
172
  : await rpc(msg.op, msg.payload);
157
173
  const ms = Math.round(performance.now() - startedAt);
158
174
  console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
package/dist/src/cli.js CHANGED
@@ -32257,6 +32257,7 @@ import { tmpdir } from "node:os";
32257
32257
  // src/starter_template.ts
32258
32258
  var STARTER_FALLBACK_UI_VERSION = "1.8.0";
32259
32259
  var STARTER_FALLBACK_SDK_VERSION = "0.11.0";
32260
+ var STARTER_REACT_NATIVE_VERSION = "0.85.3";
32260
32261
  function buildStarterTemplate(args) {
32261
32262
  const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
32262
32263
  const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
@@ -32288,7 +32289,7 @@ function buildStarterTemplate(args) {
32288
32289
  "lucide-react-native": "^0.562.0",
32289
32290
  react: "^19.0.0",
32290
32291
  "react-dom": "^19.0.0",
32291
- "react-native": "0.81.0",
32292
+ "react-native": STARTER_REACT_NATIVE_VERSION,
32292
32293
  "react-native-svg": "^15.0.0",
32293
32294
  "react-native-web": "^0.21.0"
32294
32295
  },
@@ -32366,6 +32367,11 @@ import react from "@vitejs/plugin-react";
32366
32367
  // is identical to RN-SVG when running under RN-Web.
32367
32368
  export default defineConfig({
32368
32369
  plugins: [react()],
32370
+ // RN libraries (react-native-web, @react-native-picker's UnimplementedView)
32371
+ // reference the \`__DEV__\` global that Metro injects but Vite does not \u2014
32372
+ // undefined \u21D2 the bundle throws at load and the iframe renders blank. Define
32373
+ // it (false = production RN behaviour) so dev and the deployed build both boot.
32374
+ define: { __DEV__: "false" },
32369
32375
  resolve: {
32370
32376
  alias: [
32371
32377
  {
@@ -32374,7 +32380,12 @@ export default defineConfig({
32374
32380
  },
32375
32381
  { find: "react-native", replacement: "react-native-web" },
32376
32382
  ],
32377
- extensions: [".web.tsx", ".web.ts", ".tsx", ".ts", ".jsx", ".js"],
32383
+ // \`.web.js\` resolves the web build of RN packages that ship \`X.js\` (native)
32384
+ // beside \`X.web.js\` (web) \u2014 e.g. @react-native-picker/picker, whose compiled
32385
+ // \`Picker.web.js\` renders a real <select>. Without it the extensionless
32386
+ // \`require("./Picker")\` picks native \`Picker.js\` and the standard Picker
32387
+ // renders nothing on web.
32388
+ extensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js"],
32378
32389
  // @lotics/ui ships source and is consumed across many subpath entries
32379
32390
  // (./card, ./metric, ./use_screen_size, \u2026). Without dedupe, Vite can
32380
32391
  // pre-bundle a subpath into its own chunk with a second React copy \u2014 a
@@ -32390,6 +32401,17 @@ export default defineConfig({
32390
32401
  // Pre-bundling forces Vite to convert it to an ESM shim with a default
32391
32402
  // export. Production build (rollup) handles it correctly without this.
32392
32403
  include: ["recharts", "es-toolkit", "es-toolkit/compat"],
32404
+ // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
32405
+ // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
32406
+ // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
32407
+ // here too \u2014 belt-and-suspenders with the top-level \`define\` above.
32408
+ // The optimizer pre-bundles with a SEPARATE esbuild resolver that ignores
32409
+ // \`resolve.extensions\`, so \`.web.js\` must be repeated here or a pre-bundled
32410
+ // RN dep still resolves \`./Picker\` to the native build.
32411
+ esbuildOptions: {
32412
+ define: { __DEV__: "false" },
32413
+ resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
32414
+ },
32393
32415
  },
32394
32416
  build: {
32395
32417
  outDir: "dist",
@@ -32880,6 +32902,20 @@ function buildWrapperPage(args) {
32880
32902
  return completed.file;
32881
32903
  }
32882
32904
 
32905
+ // openExternal \u2014 the wrapper page is the top frame (un-sandboxed), so it
32906
+ // opens the link directly; NOT forwarded to the RPC endpoint (no server
32907
+ // op). Re-validate the scheme; never trust the iframe's payload.
32908
+ function handleOpenExternal(payload) {
32909
+ var url = payload && payload.url;
32910
+ if (typeof url !== "string") throw new Error("openExternal requires a url string");
32911
+ var parsed = new URL(url);
32912
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
32913
+ throw new Error('openExternal: unsupported URL scheme "' + parsed.protocol + '"');
32914
+ }
32915
+ window.open(parsed.href, "_blank", "noopener,noreferrer");
32916
+ return undefined;
32917
+ }
32918
+
32883
32919
  window.addEventListener("message", async function (event) {
32884
32920
  if (event.source !== iframe.contentWindow || event.origin !== VITE_ORIGIN) return;
32885
32921
  const msg = event.data;
@@ -32888,6 +32924,8 @@ function buildWrapperPage(args) {
32888
32924
  try {
32889
32925
  const data = msg.op === "upload"
32890
32926
  ? await handleUpload(msg.payload)
32927
+ : msg.op === "openExternal"
32928
+ ? handleOpenExternal(msg.payload)
32891
32929
  : await rpc(msg.op, msg.payload);
32892
32930
  const ms = Math.round(performance.now() - startedAt);
32893
32931
  console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
@@ -41,6 +41,16 @@ export interface StarterFile {
41
41
  */
42
42
  export declare const STARTER_FALLBACK_UI_VERSION = "1.8.0";
43
43
  export declare const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
44
+ /**
45
+ * react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
46
+ * an app deep-typechecks `@lotics/ui`'s `.tsx` source against the SAME RN types
47
+ * the package is developed and CI'd against. A mismatch surfaces RN-version-
48
+ * sensitive type errors (e.g. `StyleSheet.absoluteFill`, which `frontend/` CI
49
+ * never sees because `packages/ui` is only typechecked through `frontend/`).
50
+ * RN is aliased to react-native-web at runtime, so this pin is effectively
51
+ * types-only.
52
+ */
53
+ export declare const STARTER_REACT_NATIVE_VERSION = "0.85.3";
44
54
  export declare function buildStarterTemplate(args: {
45
55
  app_name: string;
46
56
  app_id: string;
@@ -37,6 +37,16 @@
37
37
  */
38
38
  export const STARTER_FALLBACK_UI_VERSION = "1.8.0";
39
39
  export const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
40
+ /**
41
+ * react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
42
+ * an app deep-typechecks `@lotics/ui`'s `.tsx` source against the SAME RN types
43
+ * the package is developed and CI'd against. A mismatch surfaces RN-version-
44
+ * sensitive type errors (e.g. `StyleSheet.absoluteFill`, which `frontend/` CI
45
+ * never sees because `packages/ui` is only typechecked through `frontend/`).
46
+ * RN is aliased to react-native-web at runtime, so this pin is effectively
47
+ * types-only.
48
+ */
49
+ export const STARTER_REACT_NATIVE_VERSION = "0.85.3";
40
50
  export function buildStarterTemplate(args) {
41
51
  const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
42
52
  const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
@@ -72,7 +82,7 @@ export function buildStarterTemplate(args) {
72
82
  "lucide-react-native": "^0.562.0",
73
83
  react: "^19.0.0",
74
84
  "react-dom": "^19.0.0",
75
- "react-native": "0.81.0",
85
+ "react-native": STARTER_REACT_NATIVE_VERSION,
76
86
  "react-native-svg": "^15.0.0",
77
87
  "react-native-web": "^0.21.0",
78
88
  },
@@ -143,6 +153,11 @@ import react from "@vitejs/plugin-react";
143
153
  // is identical to RN-SVG when running under RN-Web.
144
154
  export default defineConfig({
145
155
  plugins: [react()],
156
+ // RN libraries (react-native-web, @react-native-picker's UnimplementedView)
157
+ // reference the \`__DEV__\` global that Metro injects but Vite does not —
158
+ // undefined ⇒ the bundle throws at load and the iframe renders blank. Define
159
+ // it (false = production RN behaviour) so dev and the deployed build both boot.
160
+ define: { __DEV__: "false" },
146
161
  resolve: {
147
162
  alias: [
148
163
  {
@@ -151,7 +166,12 @@ export default defineConfig({
151
166
  },
152
167
  { find: "react-native", replacement: "react-native-web" },
153
168
  ],
154
- extensions: [".web.tsx", ".web.ts", ".tsx", ".ts", ".jsx", ".js"],
169
+ // \`.web.js\` resolves the web build of RN packages that ship \`X.js\` (native)
170
+ // beside \`X.web.js\` (web) — e.g. @react-native-picker/picker, whose compiled
171
+ // \`Picker.web.js\` renders a real <select>. Without it the extensionless
172
+ // \`require("./Picker")\` picks native \`Picker.js\` and the standard Picker
173
+ // renders nothing on web.
174
+ extensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js"],
155
175
  // @lotics/ui ships source and is consumed across many subpath entries
156
176
  // (./card, ./metric, ./use_screen_size, …). Without dedupe, Vite can
157
177
  // pre-bundle a subpath into its own chunk with a second React copy — a
@@ -167,6 +187,17 @@ export default defineConfig({
167
187
  // Pre-bundling forces Vite to convert it to an ESM shim with a default
168
188
  // export. Production build (rollup) handles it correctly without this.
169
189
  include: ["recharts", "es-toolkit", "es-toolkit/compat"],
190
+ // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
191
+ // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
192
+ // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
193
+ // here too — belt-and-suspenders with the top-level \`define\` above.
194
+ // The optimizer pre-bundles with a SEPARATE esbuild resolver that ignores
195
+ // \`resolve.extensions\`, so \`.web.js\` must be repeated here or a pre-bundled
196
+ // RN dep still resolves \`./Picker\` to the native build.
197
+ esbuildOptions: {
198
+ define: { __DEV__: "false" },
199
+ resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
200
+ },
170
201
  },
171
202
  build: {
172
203
  outDir: "dist",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { buildStarterTemplate, STARTER_FALLBACK_SDK_VERSION, STARTER_FALLBACK_UI_VERSION, } from "./starter_template.js";
2
+ import { buildStarterTemplate, STARTER_FALLBACK_SDK_VERSION, STARTER_FALLBACK_UI_VERSION, STARTER_REACT_NATIVE_VERSION, } from "./starter_template.js";
3
3
  function fileNamed(files, path) {
4
4
  const f = files.find((f) => f.path === path);
5
5
  if (!f)
@@ -17,6 +17,23 @@ describe("buildStarterTemplate", () => {
17
17
  expect(config).toContain('"es-toolkit"');
18
18
  expect(config).toContain('"es-toolkit/compat"');
19
19
  });
20
+ test("vite.config.ts defines the RN __DEV__ global (else the bundle throws at load → blank iframe)", () => {
21
+ // `__DEV__` is a Metro-injected global that RN-ecosystem deps reference at
22
+ // module-eval time (e.g. @react-native-picker's UnimplementedView, pulled in
23
+ // via @lotics/ui's DatePicker). Vite doesn't define it; undefined ⇒ a runtime
24
+ // ReferenceError that no typecheck/lint/build catches. Pin it so it can't
25
+ // silently regress and re-blank every app that imports such a dep.
26
+ const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
27
+ // Top-level define (covers the prod build + most dev modules)...
28
+ expect(config).toMatch(/define:\s*\{\s*__DEV__:\s*"false"/);
29
+ // ...and the dep-optimizer's separate esbuild pass (covers pre-bundled
30
+ // deps under `lotics app dev`).
31
+ expect(config).toMatch(/esbuildOptions:\s*\{\s*define:\s*\{\s*__DEV__:\s*"false"/);
32
+ });
33
+ test("package.json pins react-native to the monorepo version (so apps typecheck @lotics/ui against the same RN types)", () => {
34
+ const pkg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "package.json"));
35
+ expect(pkg.dependencies["react-native"]).toBe(STARTER_REACT_NATIVE_VERSION);
36
+ });
20
37
  test("package.json uses caller-supplied versions for @lotics/ui and @lotics/app-sdk", () => {
21
38
  const pkg = JSON.parse(fileNamed(buildStarterTemplate({ ...baseArgs, ui_version: "^9.9.9", sdk_version: "^2.2.2" }), "package.json"));
22
39
  expect(pkg.dependencies["@lotics/ui"]).toBe("^9.9.9");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.36.0",
3
+ "version": "0.36.2",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "@lotics/xlsx": "*",
25
25
  "@types/node": "^22.15.21",
26
26
  "esbuild": "^0.25.0",
27
- "vitest": "^4.0.15"
27
+ "vitest": "^4.1.7"
28
28
  },
29
29
  "keywords": [
30
30
  "lotics",