@lotics/cli 0.54.0 → 0.54.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.
- package/dist/dev/server.js +4 -1
- package/dist/dev/wrapper_page.d.ts +3 -0
- package/dist/dev/wrapper_page.js +53 -0
- package/dist/src/cli.js +74 -5
- package/dist/starter_template.d.ts +5 -3
- package/dist/starter_template.js +26 -6
- package/package.json +1 -1
package/dist/dev/server.js
CHANGED
|
@@ -86,7 +86,10 @@ export async function startDevServer(args) {
|
|
|
86
86
|
// ── HTTP server ────────────────────────────────────────────────────────
|
|
87
87
|
const server = http.createServer(async (req, res) => {
|
|
88
88
|
const url = req.url ?? "/";
|
|
89
|
-
|
|
89
|
+
// Route by pathname — `useUrlState` writes a query string onto the wrapper
|
|
90
|
+
// URL, so a refresh / shared link requests `/?…`; the page must still serve.
|
|
91
|
+
const pathname = url.split("?")[0];
|
|
92
|
+
if (req.method === "GET" && (pathname === "/" || pathname === "/index.html")) {
|
|
90
93
|
res.writeHead(200, {
|
|
91
94
|
"Content-Type": "text/html; charset=utf-8",
|
|
92
95
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
* wrapper → iframe: { id, type: "result", data } | { id, type: "error", message }
|
|
25
25
|
* streaming (op "agentRun"): { id, type: "stream-chunk", chunk } * → { id, type: "stream-end" };
|
|
26
26
|
* the iframe aborts with { id, type: "abort" }.
|
|
27
|
+
* url-state (useUrlState): the iframe reads/writes the wrapper's address bar
|
|
28
|
+
* via the urlState.get/set ops; the wrapper pushes { type: "url-state",
|
|
29
|
+
* params } (no id) on back/forward.
|
|
27
30
|
*/
|
|
28
31
|
export interface WrapperPageArgs {
|
|
29
32
|
app_name: string;
|
package/dist/dev/wrapper_page.js
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
* wrapper → iframe: { id, type: "result", data } | { id, type: "error", message }
|
|
25
25
|
* streaming (op "agentRun"): { id, type: "stream-chunk", chunk } * → { id, type: "stream-end" };
|
|
26
26
|
* the iframe aborts with { id, type: "abort" }.
|
|
27
|
+
* url-state (useUrlState): the iframe reads/writes the wrapper's address bar
|
|
28
|
+
* via the urlState.get/set ops; the wrapper pushes { type: "url-state",
|
|
29
|
+
* params } (no id) on back/forward.
|
|
27
30
|
*/
|
|
28
31
|
export function buildWrapperPage(args) {
|
|
29
32
|
const { app_name, app_id, workspace_id, vite_url, api_url } = args;
|
|
@@ -165,6 +168,42 @@ export function buildWrapperPage(args) {
|
|
|
165
168
|
return undefined;
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
// urlState.get/set — useUrlState keeps app view-state (filters, search) in
|
|
172
|
+
// THIS wrapper page's address bar so it survives refresh and is shareable,
|
|
173
|
+
// mirroring the production host. Handled locally (the URL is the store; no
|
|
174
|
+
// API hop). The wrapper page is its own top-level page, so its query string
|
|
175
|
+
// is entirely the app's. Repeated keys become arrays. Parse/merge MUST
|
|
176
|
+
// match @lotics/app-sdk parseSearch/serializeMerge and the production host
|
|
177
|
+
// (inline JS here — no module system to share the trivial copy).
|
|
178
|
+
function readUrlParams() {
|
|
179
|
+
const sp = new URLSearchParams(window.location.search);
|
|
180
|
+
const out = {};
|
|
181
|
+
sp.forEach(function (_value, key) {
|
|
182
|
+
if (key in out) return;
|
|
183
|
+
const all = sp.getAll(key);
|
|
184
|
+
out[key] = all.length > 1 ? all : all[0];
|
|
185
|
+
});
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function handleUrlStateSet(payload) {
|
|
189
|
+
const params = (payload && payload.params) || {};
|
|
190
|
+
const sp = new URLSearchParams(window.location.search);
|
|
191
|
+
Object.keys(params).forEach(function (key) {
|
|
192
|
+
sp.delete(key);
|
|
193
|
+
const value = params[key];
|
|
194
|
+
if (value === undefined || value === null) return;
|
|
195
|
+
if (Array.isArray(value)) value.forEach(function (item) { sp.append(key, item); });
|
|
196
|
+
else sp.append(key, value);
|
|
197
|
+
});
|
|
198
|
+
const qs = sp.toString();
|
|
199
|
+
const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
|
|
200
|
+
// pushState/replaceState don't fire popstate, so no echo — the app
|
|
201
|
+
// updated itself optimistically on write.
|
|
202
|
+
if (payload && payload.push) window.history.pushState(null, "", url);
|
|
203
|
+
else window.history.replaceState(null, "", url);
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
168
207
|
// Streaming agent runs (op "agentRun"): the response is a stream, so it
|
|
169
208
|
// can't use rpc()'s single JSON round-trip. POST /_agent_run, read the SSE
|
|
170
209
|
// body, and forward chunks to the iframe as the SDK's bridged streaming
|
|
@@ -226,6 +265,10 @@ export function buildWrapperPage(args) {
|
|
|
226
265
|
? await handleUpload(msg.payload)
|
|
227
266
|
: msg.op === "openExternal"
|
|
228
267
|
? handleOpenExternal(msg.payload)
|
|
268
|
+
: msg.op === "urlState.get"
|
|
269
|
+
? readUrlParams()
|
|
270
|
+
: msg.op === "urlState.set"
|
|
271
|
+
? handleUrlStateSet(msg.payload)
|
|
229
272
|
: await rpc(msg.op, msg.payload);
|
|
230
273
|
const ms = Math.round(performance.now() - startedAt);
|
|
231
274
|
console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
|
|
@@ -242,6 +285,16 @@ export function buildWrapperPage(args) {
|
|
|
242
285
|
);
|
|
243
286
|
}
|
|
244
287
|
});
|
|
288
|
+
|
|
289
|
+
// Push the URL back into the app on back/forward so useUrlState
|
|
290
|
+
// re-hydrates (the app's own writes use replace/pushState — no popstate —
|
|
291
|
+
// so there's no echo). Matches the production host.
|
|
292
|
+
window.addEventListener("popstate", function () {
|
|
293
|
+
iframe.contentWindow.postMessage(
|
|
294
|
+
{ type: "url-state", params: readUrlParams() },
|
|
295
|
+
VITE_ORIGIN
|
|
296
|
+
);
|
|
297
|
+
});
|
|
245
298
|
})();
|
|
246
299
|
</script>
|
|
247
300
|
</body>
|
package/dist/src/cli.js
CHANGED
|
@@ -30241,7 +30241,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
30241
30241
|
import { tmpdir } from "node:os";
|
|
30242
30242
|
|
|
30243
30243
|
// src/starter_template.ts
|
|
30244
|
-
var STARTER_FALLBACK_UI_VERSION = "
|
|
30244
|
+
var STARTER_FALLBACK_UI_VERSION = "6.1.0";
|
|
30245
30245
|
var STARTER_FALLBACK_SDK_VERSION = "0.11.0";
|
|
30246
30246
|
var STARTER_REACT_NATIVE_VERSION = "0.85.3";
|
|
30247
30247
|
function buildStarterTemplate(args) {
|
|
@@ -30409,8 +30409,18 @@ export default defineConfig({
|
|
|
30409
30409
|
// otherwise serves them without a synthesized default export ("does not
|
|
30410
30410
|
// provide an export named 'default'"), blanking the iframe. (Production/rollup
|
|
30411
30411
|
// resolves the interop already, so this is dev-only.)
|
|
30412
|
+
//
|
|
30413
|
+
// \`react-markdown\` (@lotics/ui's Markdown renderer, reached by AgentRun and
|
|
30414
|
+
// any markdown surface) pulls a transitive CJS dep, \`style-to-js\`, whose
|
|
30415
|
+
// default export the dev optimizer won't synthesize unbundled ("does not
|
|
30416
|
+
// provide an export named 'default'") \u2014 blanking the iframe the moment a
|
|
30417
|
+
// markdown component mounts. Pre-bundling react-markdown folds the whole
|
|
30418
|
+
// subtree (incl. style-to-js) into one interop'd chunk; remark-gfm (imported
|
|
30419
|
+
// alongside it) is pre-bundled for the same consistency. Both are regular
|
|
30420
|
+
// deps of @lotics/ui (hoisted), so they need no package.json entry here.
|
|
30412
30421
|
include: [
|
|
30413
30422
|
"react-native-svg",
|
|
30423
|
+
"react-markdown", "remark-gfm",
|
|
30414
30424
|
"react-native-web", "@react-native/normalize-colors",
|
|
30415
30425
|
"inline-style-prefixer/lib/createPrefixer",
|
|
30416
30426
|
"inline-style-prefixer/lib/plugins/crossFade",
|
|
@@ -30533,15 +30543,23 @@ export default defineConfig({
|
|
|
30533
30543
|
content: `import "@lotics/ui/index.css";
|
|
30534
30544
|
import "@lotics/ui/fonts.css";
|
|
30535
30545
|
import { PortalHost } from "@lotics/ui/portal";
|
|
30546
|
+
import { LoticsLocaleProvider, vi } from "@lotics/ui/locale";
|
|
30536
30547
|
import { mount } from "@lotics/app-sdk";
|
|
30537
30548
|
import App from "./App";
|
|
30538
30549
|
|
|
30539
30550
|
// PortalHost is the render target for @lotics/ui overlays (Popover, Tooltip,
|
|
30540
30551
|
// Dialog). Without it, Portal renders nothing \u2014 keep it wrapping the app.
|
|
30552
|
+
//
|
|
30553
|
+
// LoticsLocaleProvider supplies the kit's own strings (Pagination, sort
|
|
30554
|
+
// headers, select-all, the Drawer's record nav\u2026) in one language \u2014 Vietnamese
|
|
30555
|
+
// by default, since Lotics apps are Vietnamese. Swap to \`en\` (or your own
|
|
30556
|
+
// LoticsLocale pack) for another language; remove it to fall back to English.
|
|
30541
30557
|
mount(
|
|
30542
|
-
<
|
|
30543
|
-
<
|
|
30544
|
-
|
|
30558
|
+
<LoticsLocaleProvider locale={vi}>
|
|
30559
|
+
<PortalHost>
|
|
30560
|
+
<App />
|
|
30561
|
+
</PortalHost>
|
|
30562
|
+
</LoticsLocaleProvider>,
|
|
30545
30563
|
);
|
|
30546
30564
|
`
|
|
30547
30565
|
},
|
|
@@ -31060,6 +31078,42 @@ function buildWrapperPage(args) {
|
|
|
31060
31078
|
return undefined;
|
|
31061
31079
|
}
|
|
31062
31080
|
|
|
31081
|
+
// urlState.get/set \u2014 useUrlState keeps app view-state (filters, search) in
|
|
31082
|
+
// THIS wrapper page's address bar so it survives refresh and is shareable,
|
|
31083
|
+
// mirroring the production host. Handled locally (the URL is the store; no
|
|
31084
|
+
// API hop). The wrapper page is its own top-level page, so its query string
|
|
31085
|
+
// is entirely the app's. Repeated keys become arrays. Parse/merge MUST
|
|
31086
|
+
// match @lotics/app-sdk parseSearch/serializeMerge and the production host
|
|
31087
|
+
// (inline JS here \u2014 no module system to share the trivial copy).
|
|
31088
|
+
function readUrlParams() {
|
|
31089
|
+
const sp = new URLSearchParams(window.location.search);
|
|
31090
|
+
const out = {};
|
|
31091
|
+
sp.forEach(function (_value, key) {
|
|
31092
|
+
if (key in out) return;
|
|
31093
|
+
const all = sp.getAll(key);
|
|
31094
|
+
out[key] = all.length > 1 ? all : all[0];
|
|
31095
|
+
});
|
|
31096
|
+
return out;
|
|
31097
|
+
}
|
|
31098
|
+
function handleUrlStateSet(payload) {
|
|
31099
|
+
const params = (payload && payload.params) || {};
|
|
31100
|
+
const sp = new URLSearchParams(window.location.search);
|
|
31101
|
+
Object.keys(params).forEach(function (key) {
|
|
31102
|
+
sp.delete(key);
|
|
31103
|
+
const value = params[key];
|
|
31104
|
+
if (value === undefined || value === null) return;
|
|
31105
|
+
if (Array.isArray(value)) value.forEach(function (item) { sp.append(key, item); });
|
|
31106
|
+
else sp.append(key, value);
|
|
31107
|
+
});
|
|
31108
|
+
const qs = sp.toString();
|
|
31109
|
+
const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
|
|
31110
|
+
// pushState/replaceState don't fire popstate, so no echo \u2014 the app
|
|
31111
|
+
// updated itself optimistically on write.
|
|
31112
|
+
if (payload && payload.push) window.history.pushState(null, "", url);
|
|
31113
|
+
else window.history.replaceState(null, "", url);
|
|
31114
|
+
return undefined;
|
|
31115
|
+
}
|
|
31116
|
+
|
|
31063
31117
|
// Streaming agent runs (op "agentRun"): the response is a stream, so it
|
|
31064
31118
|
// can't use rpc()'s single JSON round-trip. POST /_agent_run, read the SSE
|
|
31065
31119
|
// body, and forward chunks to the iframe as the SDK's bridged streaming
|
|
@@ -31121,6 +31175,10 @@ function buildWrapperPage(args) {
|
|
|
31121
31175
|
? await handleUpload(msg.payload)
|
|
31122
31176
|
: msg.op === "openExternal"
|
|
31123
31177
|
? handleOpenExternal(msg.payload)
|
|
31178
|
+
: msg.op === "urlState.get"
|
|
31179
|
+
? readUrlParams()
|
|
31180
|
+
: msg.op === "urlState.set"
|
|
31181
|
+
? handleUrlStateSet(msg.payload)
|
|
31124
31182
|
: await rpc(msg.op, msg.payload);
|
|
31125
31183
|
const ms = Math.round(performance.now() - startedAt);
|
|
31126
31184
|
console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
|
|
@@ -31137,6 +31195,16 @@ function buildWrapperPage(args) {
|
|
|
31137
31195
|
);
|
|
31138
31196
|
}
|
|
31139
31197
|
});
|
|
31198
|
+
|
|
31199
|
+
// Push the URL back into the app on back/forward so useUrlState
|
|
31200
|
+
// re-hydrates (the app's own writes use replace/pushState \u2014 no popstate \u2014
|
|
31201
|
+
// so there's no echo). Matches the production host.
|
|
31202
|
+
window.addEventListener("popstate", function () {
|
|
31203
|
+
iframe.contentWindow.postMessage(
|
|
31204
|
+
{ type: "url-state", params: readUrlParams() },
|
|
31205
|
+
VITE_ORIGIN
|
|
31206
|
+
);
|
|
31207
|
+
});
|
|
31140
31208
|
})();
|
|
31141
31209
|
</script>
|
|
31142
31210
|
</body>
|
|
@@ -31195,7 +31263,8 @@ async function startDevServer(args) {
|
|
|
31195
31263
|
process.once("exit", killViteOnExit);
|
|
31196
31264
|
const server = http.createServer(async (req, res) => {
|
|
31197
31265
|
const url = req.url ?? "/";
|
|
31198
|
-
|
|
31266
|
+
const pathname = url.split("?")[0];
|
|
31267
|
+
if (req.method === "GET" && (pathname === "/" || pathname === "/index.html")) {
|
|
31199
31268
|
res.writeHead(200, {
|
|
31200
31269
|
"Content-Type": "text/html; charset=utf-8",
|
|
31201
31270
|
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* Conventions (locked decisions):
|
|
12
12
|
* - Vite + React + TypeScript (strict mode).
|
|
13
13
|
* - Entry: src/App.tsx with `export default`. main.tsx wires
|
|
14
|
-
* `mount(<PortalHost><App/></PortalHost>)` —
|
|
15
|
-
*
|
|
14
|
+
* `mount(<LoticsLocaleProvider locale={vi}><PortalHost><App/></PortalHost></LoticsLocaleProvider>)` —
|
|
15
|
+
* LoticsLocaleProvider sets the kit's strings to Vietnamese (Lotics apps are
|
|
16
|
+
* Vietnamese; swap to `en` for English); PortalHost is the render target
|
|
17
|
+
* @lotics/ui overlays (Popover/Tooltip/Dialog) need — and imports
|
|
16
18
|
* @lotics/ui/index.css (base style reset) + @lotics/ui/fonts.css
|
|
17
19
|
* (path-independent Inter @font-face bundle — Text renders unstyled without it).
|
|
18
20
|
* - Vite default `base: "/"` so emitted asset URLs are absolute; the render
|
|
@@ -39,7 +41,7 @@ export interface StarterFile {
|
|
|
39
41
|
* scaffolds resolve the live version via `fetchLatestNpmVersion` and only
|
|
40
42
|
* fall back here when the lookup fails.
|
|
41
43
|
*/
|
|
42
|
-
export declare const STARTER_FALLBACK_UI_VERSION = "
|
|
44
|
+
export declare const STARTER_FALLBACK_UI_VERSION = "6.1.0";
|
|
43
45
|
export declare const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
|
|
44
46
|
/**
|
|
45
47
|
* react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
|
package/dist/starter_template.js
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* Conventions (locked decisions):
|
|
12
12
|
* - Vite + React + TypeScript (strict mode).
|
|
13
13
|
* - Entry: src/App.tsx with `export default`. main.tsx wires
|
|
14
|
-
* `mount(<PortalHost><App/></PortalHost>)` —
|
|
15
|
-
*
|
|
14
|
+
* `mount(<LoticsLocaleProvider locale={vi}><PortalHost><App/></PortalHost></LoticsLocaleProvider>)` —
|
|
15
|
+
* LoticsLocaleProvider sets the kit's strings to Vietnamese (Lotics apps are
|
|
16
|
+
* Vietnamese; swap to `en` for English); PortalHost is the render target
|
|
17
|
+
* @lotics/ui overlays (Popover/Tooltip/Dialog) need — and imports
|
|
16
18
|
* @lotics/ui/index.css (base style reset) + @lotics/ui/fonts.css
|
|
17
19
|
* (path-independent Inter @font-face bundle — Text renders unstyled without it).
|
|
18
20
|
* - Vite default `base: "/"` so emitted asset URLs are absolute; the render
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
* scaffolds resolve the live version via `fetchLatestNpmVersion` and only
|
|
36
38
|
* fall back here when the lookup fails.
|
|
37
39
|
*/
|
|
38
|
-
export const STARTER_FALLBACK_UI_VERSION = "
|
|
40
|
+
export const STARTER_FALLBACK_UI_VERSION = "6.1.0";
|
|
39
41
|
export const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
|
|
40
42
|
/**
|
|
41
43
|
* react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
|
|
@@ -209,8 +211,18 @@ export default defineConfig({
|
|
|
209
211
|
// otherwise serves them without a synthesized default export ("does not
|
|
210
212
|
// provide an export named 'default'"), blanking the iframe. (Production/rollup
|
|
211
213
|
// resolves the interop already, so this is dev-only.)
|
|
214
|
+
//
|
|
215
|
+
// \`react-markdown\` (@lotics/ui's Markdown renderer, reached by AgentRun and
|
|
216
|
+
// any markdown surface) pulls a transitive CJS dep, \`style-to-js\`, whose
|
|
217
|
+
// default export the dev optimizer won't synthesize unbundled ("does not
|
|
218
|
+
// provide an export named 'default'") — blanking the iframe the moment a
|
|
219
|
+
// markdown component mounts. Pre-bundling react-markdown folds the whole
|
|
220
|
+
// subtree (incl. style-to-js) into one interop'd chunk; remark-gfm (imported
|
|
221
|
+
// alongside it) is pre-bundled for the same consistency. Both are regular
|
|
222
|
+
// deps of @lotics/ui (hoisted), so they need no package.json entry here.
|
|
212
223
|
include: [
|
|
213
224
|
"react-native-svg",
|
|
225
|
+
"react-markdown", "remark-gfm",
|
|
214
226
|
"react-native-web", "@react-native/normalize-colors",
|
|
215
227
|
"inline-style-prefixer/lib/createPrefixer",
|
|
216
228
|
"inline-style-prefixer/lib/plugins/crossFade",
|
|
@@ -333,15 +345,23 @@ export default defineConfig({
|
|
|
333
345
|
content: `import "@lotics/ui/index.css";
|
|
334
346
|
import "@lotics/ui/fonts.css";
|
|
335
347
|
import { PortalHost } from "@lotics/ui/portal";
|
|
348
|
+
import { LoticsLocaleProvider, vi } from "@lotics/ui/locale";
|
|
336
349
|
import { mount } from "@lotics/app-sdk";
|
|
337
350
|
import App from "./App";
|
|
338
351
|
|
|
339
352
|
// PortalHost is the render target for @lotics/ui overlays (Popover, Tooltip,
|
|
340
353
|
// Dialog). Without it, Portal renders nothing — keep it wrapping the app.
|
|
354
|
+
//
|
|
355
|
+
// LoticsLocaleProvider supplies the kit's own strings (Pagination, sort
|
|
356
|
+
// headers, select-all, the Drawer's record nav…) in one language — Vietnamese
|
|
357
|
+
// by default, since Lotics apps are Vietnamese. Swap to \`en\` (or your own
|
|
358
|
+
// LoticsLocale pack) for another language; remove it to fall back to English.
|
|
341
359
|
mount(
|
|
342
|
-
<
|
|
343
|
-
<
|
|
344
|
-
|
|
360
|
+
<LoticsLocaleProvider locale={vi}>
|
|
361
|
+
<PortalHost>
|
|
362
|
+
<App />
|
|
363
|
+
</PortalHost>
|
|
364
|
+
</LoticsLocaleProvider>,
|
|
345
365
|
);
|
|
346
366
|
`,
|
|
347
367
|
},
|