@lotics/cli 0.30.2 → 0.32.0
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/src/app_commands.js +43 -0
- package/dist/src/client.d.ts +8 -0
- package/dist/src/client.js +10 -0
- package/dist/src/starter_template.d.ts +16 -0
- package/dist/src/starter_template.js +25 -2
- package/dist/src/starter_template.test.d.ts +1 -0
- package/dist/src/starter_template.test.js +32 -0
- package/package.json +1 -1
package/dist/src/app_commands.js
CHANGED
|
@@ -19,6 +19,32 @@ import { buildStarterTemplate } from "./starter_template.js";
|
|
|
19
19
|
import { startDevServer, openBrowser } from "./dev/server.js";
|
|
20
20
|
import { generateAppWorkflowsDts } from "./generate_app_workflows_dts.js";
|
|
21
21
|
import { generateAppQueriesDts } from "./generate_app_queries_dts.js";
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the latest published version of a package from the npm registry.
|
|
24
|
+
* Returns null on any failure (network error, 404, malformed payload) so
|
|
25
|
+
* callers can fall back to a static pin rather than crashing `app create`.
|
|
26
|
+
*
|
|
27
|
+
* 1.5s timeout — npm registry is fast when reachable; the fallback is fine
|
|
28
|
+
* the rare times it isn't, and we don't want to block scaffold on a hang.
|
|
29
|
+
*/
|
|
30
|
+
async function fetchLatestNpmVersion(packageName) {
|
|
31
|
+
try {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
34
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
headers: { accept: "application/json" },
|
|
37
|
+
});
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
if (!response.ok)
|
|
40
|
+
return null;
|
|
41
|
+
const body = (await response.json());
|
|
42
|
+
return typeof body.version === "string" ? body.version : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
22
48
|
/** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
|
|
23
49
|
function runTar(args, cwd) {
|
|
24
50
|
return new Promise((resolve, reject) => {
|
|
@@ -66,6 +92,10 @@ function readAppMeta(projectDir) {
|
|
|
66
92
|
version_number: pkg.lotics.version_number ?? null,
|
|
67
93
|
workflows: pkg.lotics.workflows ?? {},
|
|
68
94
|
queries: pkg.lotics.queries ?? {},
|
|
95
|
+
// Left as `undefined` when absent so the deploy omits them and the
|
|
96
|
+
// App row's icon/theme is preserved.
|
|
97
|
+
icon: pkg.lotics.icon,
|
|
98
|
+
theme: pkg.lotics.theme,
|
|
69
99
|
};
|
|
70
100
|
}
|
|
71
101
|
function writeAppMeta(projectDir, meta) {
|
|
@@ -144,11 +174,20 @@ export async function appCreate(client, args) {
|
|
|
144
174
|
// Server-side row first — if this fails, no local files have been touched.
|
|
145
175
|
const app = await client.createApp({ name: args.name });
|
|
146
176
|
console.error(`Created app: ${app.name} (${app.id})`);
|
|
177
|
+
// Resolve latest versions in parallel; null falls back to the static pin
|
|
178
|
+
// baked into the starter template. Lookups time out at 1.5s so an offline
|
|
179
|
+
// create still succeeds (with the fallback) instead of hanging.
|
|
180
|
+
const [uiLatest, sdkLatest] = await Promise.all([
|
|
181
|
+
fetchLatestNpmVersion("@lotics/ui"),
|
|
182
|
+
fetchLatestNpmVersion("@lotics/app-sdk"),
|
|
183
|
+
]);
|
|
147
184
|
// Scaffold the starter into the target directory.
|
|
148
185
|
const files = buildStarterTemplate({
|
|
149
186
|
app_name: args.name,
|
|
150
187
|
app_id: app.id,
|
|
151
188
|
workspace_id: app.workspace_id,
|
|
189
|
+
ui_version: uiLatest ? `^${uiLatest}` : undefined,
|
|
190
|
+
sdk_version: sdkLatest ? `^${sdkLatest}` : undefined,
|
|
152
191
|
});
|
|
153
192
|
for (const file of files) {
|
|
154
193
|
const fullPath = path.join(targetPath, file.path);
|
|
@@ -293,6 +332,10 @@ export async function appDeploy(client, args) {
|
|
|
293
332
|
// Sync apps.queries from the manifest. Server validates each query
|
|
294
333
|
// template (parseQueryNode, table access, param coverage).
|
|
295
334
|
queries: meta.queries ?? {},
|
|
335
|
+
// Optional icon/theme overrides — only sent when the manifest
|
|
336
|
+
// declares them (undefined ⇒ omitted ⇒ App row left untouched).
|
|
337
|
+
icon: meta.icon,
|
|
338
|
+
theme: meta.theme,
|
|
296
339
|
// Opt into destructive workflow removal. Default false — the server
|
|
297
340
|
// rejects deploys whose manifest is missing aliases the App row has.
|
|
298
341
|
force_workflow_sync: args.forceWorkflowSync,
|
package/dist/src/client.d.ts
CHANGED
|
@@ -197,6 +197,14 @@ export declare class LoticsClient {
|
|
|
197
197
|
ast: unknown;
|
|
198
198
|
params?: Record<string, unknown>;
|
|
199
199
|
}>;
|
|
200
|
+
/**
|
|
201
|
+
* App icon (Lucide name) + theme `{ color }` from `package.json#lotics`.
|
|
202
|
+
* `undefined` ⇒ not sent ⇒ the deploy leaves the App row's icon/theme
|
|
203
|
+
* as-is. A present value is applied authoritatively. `icon` rides as a
|
|
204
|
+
* raw form field; `theme` is an object, so it's JSON-encoded.
|
|
205
|
+
*/
|
|
206
|
+
icon?: string;
|
|
207
|
+
theme?: Record<string, unknown>;
|
|
200
208
|
/**
|
|
201
209
|
* Opt into destructive workflow removal. When false/absent, the server
|
|
202
210
|
* rejects a deploy whose `workflows` map is missing aliases that exist
|
package/dist/src/client.js
CHANGED
|
@@ -216,6 +216,16 @@ export class LoticsClient {
|
|
|
216
216
|
// Always send queries — empty object clears any previously-declared
|
|
217
217
|
// named queries. Server validates each template.
|
|
218
218
|
formData.append("queries", JSON.stringify(args.queries ?? {}));
|
|
219
|
+
// icon/theme are optional overrides — only sent when the manifest
|
|
220
|
+
// declares them, so a deploy never clobbers a value set in the UI.
|
|
221
|
+
// `icon` is a plain string → raw field, like `message`. `theme` is an
|
|
222
|
+
// object → JSON-encoded, like `workflows`/`queries`.
|
|
223
|
+
if (args.icon !== undefined) {
|
|
224
|
+
formData.append("icon", args.icon);
|
|
225
|
+
}
|
|
226
|
+
if (args.theme !== undefined) {
|
|
227
|
+
formData.append("theme", JSON.stringify(args.theme));
|
|
228
|
+
}
|
|
219
229
|
// Only post the override flag when the user explicitly opts in. The
|
|
220
230
|
// server defaults to "honor the guard" — opt-in is loud, opt-out
|
|
221
231
|
// requires intent.
|
|
@@ -29,8 +29,24 @@ export interface StarterFile {
|
|
|
29
29
|
path: string;
|
|
30
30
|
content: string;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Pin defaults for `@lotics/ui` and `@lotics/app-sdk` in the scaffolded
|
|
34
|
+
* package.json. Used when `lotics app create` can't resolve the latest
|
|
35
|
+
* versions from npm (no network, registry blip) — keeps `app create`
|
|
36
|
+
* working offline at the cost of a stale starter pin.
|
|
37
|
+
*
|
|
38
|
+
* Bump these in tandem with new releases of either package. Production
|
|
39
|
+
* scaffolds resolve the live version via `fetchLatestNpmVersion` and only
|
|
40
|
+
* fall back here when the lookup fails.
|
|
41
|
+
*/
|
|
42
|
+
export declare const STARTER_FALLBACK_UI_VERSION = "1.8.0";
|
|
43
|
+
export declare const STARTER_FALLBACK_SDK_VERSION = "0.8.0";
|
|
32
44
|
export declare function buildStarterTemplate(args: {
|
|
33
45
|
app_name: string;
|
|
34
46
|
app_id: string;
|
|
35
47
|
workspace_id: string;
|
|
48
|
+
/** @lotics/ui version range, e.g. "^1.8.0". Defaults to the fallback pin. */
|
|
49
|
+
ui_version?: string;
|
|
50
|
+
/** @lotics/app-sdk version range, e.g. "^0.7.0". Defaults to the fallback pin. */
|
|
51
|
+
sdk_version?: string;
|
|
36
52
|
}): StarterFile[];
|
|
@@ -25,7 +25,21 @@
|
|
|
25
25
|
* - oxlint + vitest + tsc out of the box, plus a GitHub Actions CI
|
|
26
26
|
* workflow that runs all three on every PR.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Pin defaults for `@lotics/ui` and `@lotics/app-sdk` in the scaffolded
|
|
30
|
+
* package.json. Used when `lotics app create` can't resolve the latest
|
|
31
|
+
* versions from npm (no network, registry blip) — keeps `app create`
|
|
32
|
+
* working offline at the cost of a stale starter pin.
|
|
33
|
+
*
|
|
34
|
+
* Bump these in tandem with new releases of either package. Production
|
|
35
|
+
* scaffolds resolve the live version via `fetchLatestNpmVersion` and only
|
|
36
|
+
* fall back here when the lookup fails.
|
|
37
|
+
*/
|
|
38
|
+
export const STARTER_FALLBACK_UI_VERSION = "1.8.0";
|
|
39
|
+
export const STARTER_FALLBACK_SDK_VERSION = "0.8.0";
|
|
28
40
|
export function buildStarterTemplate(args) {
|
|
41
|
+
const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
|
|
42
|
+
const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
|
|
29
43
|
const sanitizedPkgName = args.app_name
|
|
30
44
|
.toLowerCase()
|
|
31
45
|
.replace(/[^a-z0-9-]/g, "-")
|
|
@@ -50,8 +64,8 @@ export function buildStarterTemplate(args) {
|
|
|
50
64
|
test: "vitest run",
|
|
51
65
|
},
|
|
52
66
|
dependencies: {
|
|
53
|
-
"@lotics/app-sdk":
|
|
54
|
-
"@lotics/ui":
|
|
67
|
+
"@lotics/app-sdk": sdkVersion,
|
|
68
|
+
"@lotics/ui": uiVersion,
|
|
55
69
|
"@react-native-picker/picker": "^2.7.0",
|
|
56
70
|
"expo-image": "~3.0.9",
|
|
57
71
|
"lucide-react": "^0.562.0",
|
|
@@ -145,6 +159,15 @@ export default defineConfig({
|
|
|
145
159
|
// Pin React (and RN-Web) to a single instance shared by every chunk.
|
|
146
160
|
dedupe: ["react", "react-dom", "react-native-web"],
|
|
147
161
|
},
|
|
162
|
+
optimizeDeps: {
|
|
163
|
+
// \`recharts\` (used by @lotics/ui chart_* + sparkline) imports
|
|
164
|
+
// \`es-toolkit/compat/get\` as a default-export CJS module. Vite's dev
|
|
165
|
+
// server treats \`compat/*\` as ESM and won't synthesize a default,
|
|
166
|
+
// so \`import get from "es-toolkit/compat/get"\` fails to resolve.
|
|
167
|
+
// Pre-bundling forces Vite to convert it to an ESM shim with a default
|
|
168
|
+
// export. Production build (rollup) handles it correctly without this.
|
|
169
|
+
include: ["recharts", "es-toolkit", "es-toolkit/compat"],
|
|
170
|
+
},
|
|
148
171
|
build: {
|
|
149
172
|
outDir: "dist",
|
|
150
173
|
sourcemap: true,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { buildStarterTemplate, STARTER_FALLBACK_SDK_VERSION, STARTER_FALLBACK_UI_VERSION, } from "./starter_template.js";
|
|
3
|
+
function fileNamed(files, path) {
|
|
4
|
+
const f = files.find((f) => f.path === path);
|
|
5
|
+
if (!f)
|
|
6
|
+
throw new Error(`Starter template missing file: ${path}`);
|
|
7
|
+
return f.content;
|
|
8
|
+
}
|
|
9
|
+
describe("buildStarterTemplate", () => {
|
|
10
|
+
const baseArgs = { app_name: "Demo", app_id: "app_test", workspace_id: "wsp_test" };
|
|
11
|
+
test("vite.config.ts pre-bundles recharts / es-toolkit for the dev server", () => {
|
|
12
|
+
const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
|
|
13
|
+
// Without this, `lotics app dev` crashes on `es-toolkit/compat/get` no
|
|
14
|
+
// default export when the app imports any @lotics/ui chart component.
|
|
15
|
+
expect(config).toMatch(/optimizeDeps:\s*\{/);
|
|
16
|
+
expect(config).toContain('"recharts"');
|
|
17
|
+
expect(config).toContain('"es-toolkit"');
|
|
18
|
+
expect(config).toContain('"es-toolkit/compat"');
|
|
19
|
+
});
|
|
20
|
+
test("package.json uses caller-supplied versions for @lotics/ui and @lotics/app-sdk", () => {
|
|
21
|
+
const pkg = JSON.parse(fileNamed(buildStarterTemplate({ ...baseArgs, ui_version: "^9.9.9", sdk_version: "^2.2.2" }), "package.json"));
|
|
22
|
+
expect(pkg.dependencies["@lotics/ui"]).toBe("^9.9.9");
|
|
23
|
+
expect(pkg.dependencies["@lotics/app-sdk"]).toBe("^2.2.2");
|
|
24
|
+
});
|
|
25
|
+
test("package.json falls back to the static pins when versions are omitted", () => {
|
|
26
|
+
// Used when `lotics app create` cannot reach the npm registry to resolve
|
|
27
|
+
// `latest`. Keeps `app create` working offline at the cost of a stale pin.
|
|
28
|
+
const pkg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "package.json"));
|
|
29
|
+
expect(pkg.dependencies["@lotics/ui"]).toBe(`^${STARTER_FALLBACK_UI_VERSION}`);
|
|
30
|
+
expect(pkg.dependencies["@lotics/app-sdk"]).toBe(`^${STARTER_FALLBACK_SDK_VERSION}`);
|
|
31
|
+
});
|
|
32
|
+
});
|