@lotics/cli 0.88.1 → 0.90.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/cli.js +213 -118
- package/dist/src/client.d.ts +8 -0
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -30643,8 +30643,40 @@ import { tmpdir } from "node:os";
|
|
|
30643
30643
|
|
|
30644
30644
|
// src/starter_template.ts
|
|
30645
30645
|
var STARTER_FALLBACK_UI_VERSION = "6.1.0";
|
|
30646
|
-
var STARTER_FALLBACK_SDK_VERSION = "0.
|
|
30646
|
+
var STARTER_FALLBACK_SDK_VERSION = "0.52.0";
|
|
30647
30647
|
var STARTER_REACT_NATIVE_VERSION = "0.85.3";
|
|
30648
|
+
var VITEST_SETUP_FILENAME = "vitest.setup.ts";
|
|
30649
|
+
var VITEST_SETUP_CONTENT = `import { vi } from "vitest";
|
|
30650
|
+
|
|
30651
|
+
// A package-linked app's generated .lotics/app_fields.ts resolves F/OPT/ROLE
|
|
30652
|
+
// from the installation's live binding at module load (\`await getAppBinding()\`,
|
|
30653
|
+
// a network call over the host bridge). Under vitest/jsdom there is no host, so
|
|
30654
|
+
// that call throws while a test is still COLLECTING \u2014 failing every test that
|
|
30655
|
+
// imports the app graph, even pure logic tests. getAppBinding is the network
|
|
30656
|
+
// boundary; mock it (and only it) so the module graph evaluates in tests.
|
|
30657
|
+
//
|
|
30658
|
+
// The stub returns an ECHO binding: any alias resolves to a synthetic,
|
|
30659
|
+
// self-identifying id (fld:test:<entity>.<field>, opt:test:\u2026, grp:test:\u2026). Echo,
|
|
30660
|
+
// not empty maps \u2014 app_fields's bound() throws on a missing key, so empty maps
|
|
30661
|
+
// would just re-break collection; and a ':test:' id can never be confused with a
|
|
30662
|
+
// real fld_\u2026/opt_\u2026/grp_\u2026 id, so a test that depends on a concrete binding fails
|
|
30663
|
+
// loud instead of silently reading undefined.
|
|
30664
|
+
vi.mock("@lotics/app-sdk", async (importOriginal) => {
|
|
30665
|
+
const actual = await importOriginal<typeof import("@lotics/app-sdk")>();
|
|
30666
|
+
const echoIds = (prefix: string): Record<string, string> =>
|
|
30667
|
+
new Proxy(
|
|
30668
|
+
{},
|
|
30669
|
+
{
|
|
30670
|
+
get: (_target, key) => (typeof key === "string" ? \`\${prefix}:test:\${key}\` : undefined),
|
|
30671
|
+
},
|
|
30672
|
+
) as Record<string, string>;
|
|
30673
|
+
return {
|
|
30674
|
+
...actual,
|
|
30675
|
+
getAppBinding: () =>
|
|
30676
|
+
Promise.resolve({ fields: echoIds("fld"), options: echoIds("opt"), roles: echoIds("grp") }),
|
|
30677
|
+
};
|
|
30678
|
+
});
|
|
30679
|
+
`;
|
|
30648
30680
|
function buildStarterTemplate(args) {
|
|
30649
30681
|
const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
|
|
30650
30682
|
const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
|
|
@@ -30761,8 +30793,32 @@ function buildStarterTemplate(args) {
|
|
|
30761
30793
|
path: "vite.config.ts",
|
|
30762
30794
|
content: `/// <reference types="vitest" />
|
|
30763
30795
|
import { defineConfig } from "vite";
|
|
30796
|
+
import type { Plugin } from "vite";
|
|
30764
30797
|
import react from "@vitejs/plugin-react";
|
|
30765
30798
|
|
|
30799
|
+
// @lotics/ui/icon.tsx deep-imports \`lucide-react-native/dist/esm/icons/<name>\`.
|
|
30800
|
+
// lucide-react-native's \`exports\` map lists only "." and "./icons", so a strict
|
|
30801
|
+
// resolver (the sandbox's vite dev optimizer) blocks the deep path with
|
|
30802
|
+
// \`Missing "./dist/esm/icons/<name>" specifier\`. lucide-react ships the same
|
|
30803
|
+
// per-icon files with NO exports map, so redirect there. A \`resolveId\` PLUGIN
|
|
30804
|
+
// (not \`resolve.alias\`) is required: the dep-optimizer SCAN runs plugin
|
|
30805
|
+
// resolveId hooks but does NOT apply regex \`resolve.alias\`, so the alias alone
|
|
30806
|
+
// fixes the production build but leaves the dev preview broken.
|
|
30807
|
+
function lucideIconsWebAlias(): Plugin {
|
|
30808
|
+
return {
|
|
30809
|
+
name: "lucide-icons-web-alias",
|
|
30810
|
+
enforce: "pre",
|
|
30811
|
+
async resolveId(source, importer, options) {
|
|
30812
|
+
const m = /^lucide-react-native\\/dist\\/esm\\/icons\\/(.+)$/.exec(source);
|
|
30813
|
+
if (!m) return null;
|
|
30814
|
+
return this.resolve(\`lucide-react/dist/esm/icons/\${m[1]}\`, importer, {
|
|
30815
|
+
...options,
|
|
30816
|
+
skipSelf: true,
|
|
30817
|
+
});
|
|
30818
|
+
},
|
|
30819
|
+
};
|
|
30820
|
+
}
|
|
30821
|
+
|
|
30766
30822
|
// Vite default base (/) emits absolute asset URLs in index.html. Lotics's
|
|
30767
30823
|
// render endpoint rewrites those to /v1/apps/{id}/asset/... so the bundle
|
|
30768
30824
|
// loads via the platform's asset proxy. Don't change \`base\` unless you
|
|
@@ -30772,14 +30828,8 @@ import react from "@vitejs/plugin-react";
|
|
|
30772
30828
|
// (View, Text, Pressable, StyleSheet, etc.) render in a pure-web environment.
|
|
30773
30829
|
// .web.tsx is prioritized in resolve.extensions so per-target variants
|
|
30774
30830
|
// (avatar.web.tsx, wave_avatar.web.tsx) win over the native .tsx file.
|
|
30775
|
-
//
|
|
30776
|
-
// @lotics/ui/icon.tsx imports from \`lucide-react-native/dist/esm/icons/<name>\`
|
|
30777
|
-
// \u2014 deep paths that lucide-react-native's package \`exports\` map blocks under
|
|
30778
|
-
// Vite's strict resolution. lucide-react has the same per-icon files with no
|
|
30779
|
-
// exports map, so we alias the deep path to use it. Web rendering of <svg>
|
|
30780
|
-
// is identical to RN-SVG when running under RN-Web.
|
|
30781
30831
|
export default defineConfig({
|
|
30782
|
-
plugins: [react()],
|
|
30832
|
+
plugins: [lucideIconsWebAlias(), react()],
|
|
30783
30833
|
// RN libraries reference globals Metro injects but Vite does not \u2014 undefined
|
|
30784
30834
|
// \u21D2 the bundle throws. \`__DEV__\` (react-native-web, @react-native-picker's
|
|
30785
30835
|
// UnimplementedView): throws at load, blank iframe. \`global\` (rn-web
|
|
@@ -30790,13 +30840,7 @@ export default defineConfig({
|
|
|
30790
30840
|
// is safe. Define both so dev and the deployed build behave identically.
|
|
30791
30841
|
define: { __DEV__: "false", global: "globalThis" },
|
|
30792
30842
|
resolve: {
|
|
30793
|
-
alias: [
|
|
30794
|
-
{
|
|
30795
|
-
find: /^lucide-react-native\\/dist\\/esm\\/icons\\/(.+)$/,
|
|
30796
|
-
replacement: "lucide-react/dist/esm/icons/$1",
|
|
30797
|
-
},
|
|
30798
|
-
{ find: "react-native", replacement: "react-native-web" },
|
|
30799
|
-
],
|
|
30843
|
+
alias: [{ find: "react-native", replacement: "react-native-web" }],
|
|
30800
30844
|
// \`.web.js\` resolves the web build of RN packages that ship \`X.js\` (native)
|
|
30801
30845
|
// beside \`X.web.js\` (web) \u2014 e.g. @react-native-picker/picker, whose compiled
|
|
30802
30846
|
// \`Picker.web.js\` renders a real <select>. Without it the extensionless
|
|
@@ -30875,6 +30919,12 @@ export default defineConfig({
|
|
|
30875
30919
|
// iframe loads modules from api.lotics.ai which already permits null
|
|
30876
30920
|
// origin via CORS.
|
|
30877
30921
|
cors: { origin: "*" },
|
|
30922
|
+
// Chat-authoring previews reach this dev server through the sandbox proxy
|
|
30923
|
+
// at \`5173-<id>-<token>.lotics-sandbox.app\`. Vite \u22655.4.12 rejects HMR
|
|
30924
|
+
// WebSocket upgrades whose Origin doesn't match a trusted host (its
|
|
30925
|
+
// cross-site WS hijack fix) \u2014 without this entry the proxied preview page
|
|
30926
|
+
// loads over HTTP but HMR never connects, so edits stop live-updating.
|
|
30927
|
+
allowedHosts: [".lotics-sandbox.app"],
|
|
30878
30928
|
// @lotics/ui/fonts.css references the API's /iframe/fonts/*.woff2 files
|
|
30879
30929
|
// by root-relative URL. A deployed app is served from the API origin so
|
|
30880
30930
|
// they resolve directly; under \`lotics app dev\` the app runs on
|
|
@@ -30883,6 +30933,11 @@ export default defineConfig({
|
|
|
30883
30933
|
},
|
|
30884
30934
|
test: {
|
|
30885
30935
|
environment: "jsdom",
|
|
30936
|
+
// Stub @lotics/app-sdk's getAppBinding (the network boundary) before any test
|
|
30937
|
+
// collects. A package-linked app's generated .lotics/app_fields.ts awaits it
|
|
30938
|
+
// at module load; without the stub that network call throws under jsdom and
|
|
30939
|
+
// every test that imports the app graph fails to collect. See vitest.setup.ts.
|
|
30940
|
+
setupFiles: ["./${VITEST_SETUP_FILENAME}"],
|
|
30886
30941
|
// RN packages ship Flow (\`import typeof\`) in their native source, reached
|
|
30887
30942
|
// transitively by RN-Web components (pickers, calendars, anything touching
|
|
30888
30943
|
// Animated). Vitest's web optimizer is OFF by default and ignores the
|
|
@@ -31013,6 +31068,7 @@ mount(
|
|
|
31013
31068
|
import { View } from "react-native";
|
|
31014
31069
|
import { useNavigate, useParams } from "react-router-dom";
|
|
31015
31070
|
import { AppRouter } from "@lotics/app-sdk/router";
|
|
31071
|
+
import { useAiContext } from "@lotics/app-sdk";
|
|
31016
31072
|
import { Card } from "@lotics/ui/card";
|
|
31017
31073
|
import { Text } from "@lotics/ui/text";
|
|
31018
31074
|
import { Button } from "@lotics/ui/button";
|
|
@@ -31077,6 +31133,16 @@ function ItemDetailScreen() {
|
|
|
31077
31133
|
const { id } = useParams();
|
|
31078
31134
|
const navigate = useNavigate();
|
|
31079
31135
|
const item = ITEMS.find((i) => i.id === id);
|
|
31136
|
+
// Tell the member's ambient chat agent what this screen is showing, so a
|
|
31137
|
+
// question like "summarise this" has context. This is PUSH-ONLY: a snapshot of
|
|
31138
|
+
// what the app rendered to the member, never a channel for chat to pull app
|
|
31139
|
+
// data \u2014 the text lands in the prompt as labeled data (not instructions) and
|
|
31140
|
+
// any record refs would act only within the member's own IAM. Pass null to
|
|
31141
|
+
// clear the slot (here: nothing to describe when the item isn't found).
|
|
31142
|
+
useAiContext(
|
|
31143
|
+
"item_detail",
|
|
31144
|
+
item ? { description: \`Viewing item "\${item.name}".\`, data: { id: item.id } } : null,
|
|
31145
|
+
);
|
|
31080
31146
|
return (
|
|
31081
31147
|
<Screen>
|
|
31082
31148
|
{/* An in-app Back control; navigate(-1) walks the history (the browser Back
|
|
@@ -31166,9 +31232,9 @@ each alias lives in \`package.json#lotics.workflows.<alias>\`.
|
|
|
31166
31232
|
path: "src/lucide-react-native.d.ts",
|
|
31167
31233
|
content: `// Type shim for lucide-react-native's deep-icon imports
|
|
31168
31234
|
// (\`lucide-react-native/dist/esm/icons/<name>\`). The package ships JS-only
|
|
31169
|
-
// files without per-file .d.ts. The
|
|
31170
|
-
// these to lucide-react at
|
|
31171
|
-
// TypeScript compile-time gap.
|
|
31235
|
+
// files without per-file .d.ts. The lucideIconsWebAlias() plugin in
|
|
31236
|
+
// vite.config.ts redirects these to lucide-react at resolve time; this
|
|
31237
|
+
// declaration covers the TypeScript compile-time gap.
|
|
31172
31238
|
declare module "lucide-react-native/dist/esm/icons/*" {
|
|
31173
31239
|
import type { ComponentType } from "react";
|
|
31174
31240
|
const Icon: ComponentType<{
|
|
@@ -31248,6 +31314,10 @@ describe("App", () => {
|
|
|
31248
31314
|
});
|
|
31249
31315
|
`
|
|
31250
31316
|
},
|
|
31317
|
+
{
|
|
31318
|
+
path: VITEST_SETUP_FILENAME,
|
|
31319
|
+
content: VITEST_SETUP_CONTENT
|
|
31320
|
+
},
|
|
31251
31321
|
{
|
|
31252
31322
|
path: ".github/workflows/ci.yml",
|
|
31253
31323
|
content: `name: CI
|
|
@@ -32225,48 +32295,10 @@ function openBrowser(url) {
|
|
|
32225
32295
|
child.unref();
|
|
32226
32296
|
}
|
|
32227
32297
|
|
|
32228
|
-
// src/
|
|
32229
|
-
var
|
|
32230
|
-
|
|
32231
|
-
|
|
32232
|
-
// This file gives \`useWorkflow("alias")\` a typed input parameter at call
|
|
32233
|
-
// sites by augmenting the @lotics/app-sdk \`AppWorkflows\` interface.
|
|
32234
|
-
|
|
32235
|
-
import "@lotics/app-sdk";
|
|
32236
|
-
`;
|
|
32237
|
-
function generateAppWorkflowsDts(workflows) {
|
|
32238
|
-
const entries = Object.entries(workflows ?? {});
|
|
32239
|
-
if (entries.length === 0) {
|
|
32240
|
-
return `${HEADER}
|
|
32241
|
-
// No workflows declared in package.json#lotics.workflows.
|
|
32242
|
-
// Add an entry to enable typed useWorkflow<"alias"> at call sites.
|
|
32243
|
-
declare module "@lotics/app-sdk" {
|
|
32244
|
-
interface AppWorkflows {}
|
|
32245
|
-
}
|
|
32246
|
-
`;
|
|
32247
|
-
}
|
|
32248
|
-
entries.sort(([a], [b]) => a.localeCompare(b));
|
|
32249
|
-
const inputLines = [];
|
|
32250
|
-
const resultLines = [];
|
|
32251
|
-
for (const [alias, declaration] of entries) {
|
|
32252
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true }) : "Record<string, unknown>";
|
|
32253
|
-
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
32254
|
-
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
32255
|
-
if (declaration.outputs) {
|
|
32256
|
-
resultLines.push(` ${aliasKey}: ${objectFieldsToType(declaration.outputs)};`);
|
|
32257
|
-
}
|
|
32258
|
-
}
|
|
32259
|
-
const resultsBlock = resultLines.length > 0 ? `
|
|
32260
|
-
interface AppWorkflowResults {
|
|
32261
|
-
${resultLines.join("\n")}
|
|
32262
|
-
}` : "";
|
|
32263
|
-
return `${HEADER}
|
|
32264
|
-
declare module "@lotics/app-sdk" {
|
|
32265
|
-
interface AppWorkflows {
|
|
32266
|
-
${inputLines.join("\n")}
|
|
32267
|
-
}${resultsBlock}
|
|
32268
|
-
}
|
|
32269
|
-
`;
|
|
32298
|
+
// ../shared/src/app_dts.ts
|
|
32299
|
+
var IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
32300
|
+
function isValidIdentifier(name) {
|
|
32301
|
+
return IDENTIFIER_REGEX.test(name);
|
|
32270
32302
|
}
|
|
32271
32303
|
function inputsToType(inputs, opts) {
|
|
32272
32304
|
const nullableOptional = opts?.nullableOptional === true;
|
|
@@ -32380,28 +32412,56 @@ function outputDeclToTsType(decl) {
|
|
|
32380
32412
|
return "unknown";
|
|
32381
32413
|
}
|
|
32382
32414
|
}
|
|
32383
|
-
var
|
|
32384
|
-
|
|
32385
|
-
|
|
32386
|
-
|
|
32415
|
+
var QUERIES_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
32416
|
+
// DO NOT EDIT \u2014 regenerated from package.json#lotics.queries.
|
|
32417
|
+
//
|
|
32418
|
+
// This file gives \`useQuery("alias", params)\` typed params at call sites by
|
|
32419
|
+
// augmenting the @lotics/app-sdk \`AppQueries\` interface.
|
|
32387
32420
|
|
|
32388
|
-
|
|
32389
|
-
|
|
32390
|
-
|
|
32421
|
+
import "@lotics/app-sdk";
|
|
32422
|
+
`;
|
|
32423
|
+
function generateAppQueriesDts(queries) {
|
|
32424
|
+
const entries = Object.entries(queries ?? {});
|
|
32425
|
+
if (entries.length === 0) {
|
|
32426
|
+
return `${QUERIES_HEADER}
|
|
32427
|
+
// No queries declared in package.json#lotics.queries.
|
|
32428
|
+
// Add an entry to enable typed useQuery("alias", params) at call sites.
|
|
32429
|
+
declare module "@lotics/app-sdk" {
|
|
32430
|
+
interface AppQueries {}
|
|
32431
|
+
}
|
|
32432
|
+
`;
|
|
32433
|
+
}
|
|
32434
|
+
entries.sort(([a], [b]) => a.localeCompare(b));
|
|
32435
|
+
const lines = [];
|
|
32436
|
+
for (const [alias, declaration] of entries) {
|
|
32437
|
+
const valueType = inputsToType(declaration.params ?? {});
|
|
32438
|
+
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
32439
|
+
lines.push(` ${aliasKey}: ${valueType};`);
|
|
32440
|
+
}
|
|
32441
|
+
return `${QUERIES_HEADER}
|
|
32442
|
+
declare module "@lotics/app-sdk" {
|
|
32443
|
+
interface AppQueries {
|
|
32444
|
+
${lines.join("\n")}
|
|
32445
|
+
}
|
|
32446
|
+
}
|
|
32447
|
+
`;
|
|
32448
|
+
}
|
|
32449
|
+
var WORKFLOWS_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
32450
|
+
// DO NOT EDIT \u2014 regenerated from package.json#lotics.workflows.
|
|
32391
32451
|
//
|
|
32392
|
-
// This file gives \`
|
|
32393
|
-
// augmenting the @lotics/app-sdk \`
|
|
32452
|
+
// This file gives \`useWorkflow("alias")\` a typed input parameter at call
|
|
32453
|
+
// sites by augmenting the @lotics/app-sdk \`AppWorkflows\` interface.
|
|
32394
32454
|
|
|
32395
32455
|
import "@lotics/app-sdk";
|
|
32396
32456
|
`;
|
|
32397
|
-
function
|
|
32398
|
-
const entries = Object.entries(
|
|
32457
|
+
function generateAppWorkflowsDts(workflows) {
|
|
32458
|
+
const entries = Object.entries(workflows ?? {});
|
|
32399
32459
|
if (entries.length === 0) {
|
|
32400
|
-
return `${
|
|
32401
|
-
// No
|
|
32402
|
-
// Add an entry to enable typed
|
|
32460
|
+
return `${WORKFLOWS_HEADER}
|
|
32461
|
+
// No workflows declared in package.json#lotics.workflows.
|
|
32462
|
+
// Add an entry to enable typed useWorkflow<"alias"> at call sites.
|
|
32403
32463
|
declare module "@lotics/app-sdk" {
|
|
32404
|
-
interface
|
|
32464
|
+
interface AppWorkflows {}
|
|
32405
32465
|
}
|
|
32406
32466
|
`;
|
|
32407
32467
|
}
|
|
@@ -32409,7 +32469,7 @@ declare module "@lotics/app-sdk" {
|
|
|
32409
32469
|
const inputLines = [];
|
|
32410
32470
|
const resultLines = [];
|
|
32411
32471
|
for (const [alias, declaration] of entries) {
|
|
32412
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs) : "Record<string, unknown>";
|
|
32472
|
+
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true }) : "Record<string, unknown>";
|
|
32413
32473
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
32414
32474
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
32415
32475
|
if (declaration.outputs) {
|
|
@@ -32417,53 +32477,78 @@ declare module "@lotics/app-sdk" {
|
|
|
32417
32477
|
}
|
|
32418
32478
|
}
|
|
32419
32479
|
const resultsBlock = resultLines.length > 0 ? `
|
|
32420
|
-
interface
|
|
32480
|
+
interface AppWorkflowResults {
|
|
32421
32481
|
${resultLines.join("\n")}
|
|
32422
32482
|
}` : "";
|
|
32423
|
-
return `${
|
|
32483
|
+
return `${WORKFLOWS_HEADER}
|
|
32424
32484
|
declare module "@lotics/app-sdk" {
|
|
32425
|
-
interface
|
|
32485
|
+
interface AppWorkflows {
|
|
32426
32486
|
${inputLines.join("\n")}
|
|
32427
32487
|
}${resultsBlock}
|
|
32428
32488
|
}
|
|
32429
32489
|
`;
|
|
32430
32490
|
}
|
|
32431
|
-
|
|
32432
|
-
//
|
|
32433
|
-
var HEADER3 = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
32434
|
-
// DO NOT EDIT \u2014 regenerated from package.json#lotics.queries.
|
|
32491
|
+
var AGENTS_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
32492
|
+
// DO NOT EDIT \u2014 regenerated from package.json#lotics.agents.
|
|
32435
32493
|
//
|
|
32436
|
-
// This file gives \`
|
|
32437
|
-
// augmenting the @lotics/app-sdk \`
|
|
32494
|
+
// This file gives \`useAgentRun("alias")\` typed input + output at call sites by
|
|
32495
|
+
// augmenting the @lotics/app-sdk \`AppAgents\` / \`AppAgentResults\` interfaces.
|
|
32438
32496
|
|
|
32439
32497
|
import "@lotics/app-sdk";
|
|
32440
32498
|
`;
|
|
32441
|
-
function
|
|
32442
|
-
const entries = Object.entries(
|
|
32499
|
+
function generateAppAgentsDts(agents) {
|
|
32500
|
+
const entries = Object.entries(agents ?? {});
|
|
32443
32501
|
if (entries.length === 0) {
|
|
32444
|
-
return `${
|
|
32445
|
-
// No
|
|
32446
|
-
// Add an entry to enable typed
|
|
32502
|
+
return `${AGENTS_HEADER}
|
|
32503
|
+
// No agents declared in package.json#lotics.agents.
|
|
32504
|
+
// Add an entry to enable typed useAgentRun<"alias"> at call sites.
|
|
32447
32505
|
declare module "@lotics/app-sdk" {
|
|
32448
|
-
interface
|
|
32506
|
+
interface AppAgents {}
|
|
32449
32507
|
}
|
|
32450
32508
|
`;
|
|
32451
32509
|
}
|
|
32452
32510
|
entries.sort(([a], [b]) => a.localeCompare(b));
|
|
32453
|
-
const
|
|
32511
|
+
const inputLines = [];
|
|
32512
|
+
const resultLines = [];
|
|
32454
32513
|
for (const [alias, declaration] of entries) {
|
|
32455
|
-
const valueType = inputsToType(declaration.
|
|
32514
|
+
const valueType = declaration.inputs ? inputsToType(declaration.inputs) : "Record<string, unknown>";
|
|
32456
32515
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
32457
|
-
|
|
32516
|
+
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
32517
|
+
if (declaration.outputs) {
|
|
32518
|
+
resultLines.push(` ${aliasKey}: ${objectFieldsToType(declaration.outputs)};`);
|
|
32519
|
+
}
|
|
32458
32520
|
}
|
|
32459
|
-
|
|
32521
|
+
const resultsBlock = resultLines.length > 0 ? `
|
|
32522
|
+
interface AppAgentResults {
|
|
32523
|
+
${resultLines.join("\n")}
|
|
32524
|
+
}` : "";
|
|
32525
|
+
return `${AGENTS_HEADER}
|
|
32460
32526
|
declare module "@lotics/app-sdk" {
|
|
32461
|
-
interface
|
|
32462
|
-
${
|
|
32463
|
-
}
|
|
32527
|
+
interface AppAgents {
|
|
32528
|
+
${inputLines.join("\n")}
|
|
32529
|
+
}${resultsBlock}
|
|
32464
32530
|
}
|
|
32465
32531
|
`;
|
|
32466
32532
|
}
|
|
32533
|
+
var CAPABILITY_GATED_CALLS = {
|
|
32534
|
+
comments: ["useComments", "createComment", "updateComment", "deleteComment"]
|
|
32535
|
+
};
|
|
32536
|
+
function undeclaredCapabilities(sourceText, declared) {
|
|
32537
|
+
const used = [];
|
|
32538
|
+
for (const [capability, calls] of Object.entries(CAPABILITY_GATED_CALLS)) {
|
|
32539
|
+
const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(sourceText));
|
|
32540
|
+
if (isCalled && declared?.[capability] !== true) used.push(capability);
|
|
32541
|
+
}
|
|
32542
|
+
return used;
|
|
32543
|
+
}
|
|
32544
|
+
function unboundAliases(declared, bound) {
|
|
32545
|
+
const boundWorkflows = new Set(bound.workflows ?? []);
|
|
32546
|
+
const boundAgents = new Set(bound.agents ?? []);
|
|
32547
|
+
return {
|
|
32548
|
+
workflows: [...declared.workflows ?? []].filter((a) => !boundWorkflows.has(a)),
|
|
32549
|
+
agents: [...declared.agents ?? []].filter((a) => !boundAgents.has(a))
|
|
32550
|
+
};
|
|
32551
|
+
}
|
|
32467
32552
|
|
|
32468
32553
|
// ../shared/src/app_query_ast.ts
|
|
32469
32554
|
function collectQueryTableIds(node) {
|
|
@@ -32504,7 +32589,7 @@ function walk(node, visit) {
|
|
|
32504
32589
|
}
|
|
32505
32590
|
|
|
32506
32591
|
// src/generate_app_fields.ts
|
|
32507
|
-
var
|
|
32592
|
+
var HEADER = `// Auto-generated by 'lotics app codegen' (and app pull/dev/deploy).
|
|
32508
32593
|
// DO NOT EDIT \u2014 regenerated from the workspace schema.
|
|
32509
32594
|
//
|
|
32510
32595
|
// Runtime field + option ids addressed by stable display-name aliases:
|
|
@@ -32598,7 +32683,7 @@ ${tableBlocks.join("\n")}
|
|
|
32598
32683
|
}
|
|
32599
32684
|
function generateAppFields(tables) {
|
|
32600
32685
|
if (tables.length === 0) {
|
|
32601
|
-
return `${
|
|
32686
|
+
return `${HEADER}
|
|
32602
32687
|
export const F = {} as const;
|
|
32603
32688
|
|
|
32604
32689
|
export const OPT = {} as const;
|
|
@@ -32610,7 +32695,7 @@ export type AppOptions = typeof OPT;
|
|
|
32610
32695
|
`;
|
|
32611
32696
|
}
|
|
32612
32697
|
const aliased = aliasTables(tables);
|
|
32613
|
-
return `${
|
|
32698
|
+
return `${HEADER}
|
|
32614
32699
|
${emitFieldMap(aliased)}
|
|
32615
32700
|
|
|
32616
32701
|
${emitOptionMap(aliased)}
|
|
@@ -32623,7 +32708,7 @@ export type AppOptions = typeof OPT;
|
|
|
32623
32708
|
}
|
|
32624
32709
|
|
|
32625
32710
|
// src/generate_package_fields.ts
|
|
32626
|
-
var
|
|
32711
|
+
var HEADER2 = `// Auto-generated by 'lotics app codegen' (linked/published app; also app pull/dev/deploy).
|
|
32627
32712
|
// DO NOT EDIT \u2014 regenerated from the installation's live binding.
|
|
32628
32713
|
//
|
|
32629
32714
|
// A package installation resolves F/OPT/ROLE at MODULE LOAD from its binding
|
|
@@ -32723,7 +32808,7 @@ ${lines.join("\n")}
|
|
|
32723
32808
|
}
|
|
32724
32809
|
function generatePackageAppFields(binding) {
|
|
32725
32810
|
const { entities, roles } = parseBinding(binding);
|
|
32726
|
-
return `${
|
|
32811
|
+
return `${HEADER2}
|
|
32727
32812
|
${emitFieldMap2(entities)}
|
|
32728
32813
|
|
|
32729
32814
|
${emitOptionMap2(entities)}
|
|
@@ -33070,8 +33155,29 @@ function writeBindingAppFields(projectDir, binding) {
|
|
|
33070
33155
|
fs4.mkdirSync(dotLotics, { recursive: true });
|
|
33071
33156
|
const file = path5.join(dotLotics, "app_fields.ts");
|
|
33072
33157
|
fs4.writeFileSync(file, generatePackageAppFields(binding));
|
|
33158
|
+
ensureAppVitestSetup(projectDir);
|
|
33073
33159
|
return file;
|
|
33074
33160
|
}
|
|
33161
|
+
function ensureAppVitestSetup(projectDir) {
|
|
33162
|
+
const setupPath = path5.join(projectDir, VITEST_SETUP_FILENAME);
|
|
33163
|
+
if (!fs4.existsSync(setupPath)) {
|
|
33164
|
+
fs4.writeFileSync(setupPath, VITEST_SETUP_CONTENT);
|
|
33165
|
+
console.error(`Wrote ${VITEST_SETUP_FILENAME} (stubs getAppBinding so \`npm test\` collects).`);
|
|
33166
|
+
} else if (!fs4.readFileSync(setupPath, "utf-8").includes("getAppBinding")) {
|
|
33167
|
+
console.error(
|
|
33168
|
+
`\u26A0 ${VITEST_SETUP_FILENAME} exists but does not stub getAppBinding \u2014 \`npm test\` will fail to collect any test that imports the app graph (the binding-form .lotics/app_fields.ts awaits a network call). Add the stub to it:
|
|
33169
|
+
${VITEST_SETUP_CONTENT}`
|
|
33170
|
+
);
|
|
33171
|
+
}
|
|
33172
|
+
const viteConfigPath = path5.join(projectDir, "vite.config.ts");
|
|
33173
|
+
if (!fs4.existsSync(viteConfigPath)) return;
|
|
33174
|
+
const viteConfig = fs4.readFileSync(viteConfigPath, "utf-8");
|
|
33175
|
+
if (/setupFiles\s*:/.test(viteConfig) && viteConfig.includes(VITEST_SETUP_FILENAME)) return;
|
|
33176
|
+
console.error(
|
|
33177
|
+
`\u26A0 vite.config.ts does not wire ${VITEST_SETUP_FILENAME} \u2014 \`npm test\` will fail to collect any test that imports the app graph (the binding-form .lotics/app_fields.ts awaits a network call). Add it to the \`test\` block:
|
|
33178
|
+
setupFiles: ["./${VITEST_SETUP_FILENAME}"],`
|
|
33179
|
+
);
|
|
33180
|
+
}
|
|
33075
33181
|
async function appCodegen(args) {
|
|
33076
33182
|
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
33077
33183
|
const meta = readAppMeta(projectDir);
|
|
@@ -33299,17 +33405,6 @@ Ready. Next steps:`);
|
|
|
33299
33405
|
console.error(` # edit src/App.tsx`);
|
|
33300
33406
|
console.error(` lotics app deploy`);
|
|
33301
33407
|
}
|
|
33302
|
-
var CAPABILITY_GATED_CALLS = {
|
|
33303
|
-
comments: ["useComments", "createComment", "updateComment", "deleteComment"]
|
|
33304
|
-
};
|
|
33305
|
-
function undeclaredCapabilities(sourceText, declared) {
|
|
33306
|
-
const used = [];
|
|
33307
|
-
for (const [capability, calls] of Object.entries(CAPABILITY_GATED_CALLS)) {
|
|
33308
|
-
const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(sourceText));
|
|
33309
|
-
if (isCalled && declared?.[capability] !== true) used.push(capability);
|
|
33310
|
-
}
|
|
33311
|
-
return used;
|
|
33312
|
-
}
|
|
33313
33408
|
function readAppSourceText(projectDir) {
|
|
33314
33409
|
const srcDir = path5.join(projectDir, "src");
|
|
33315
33410
|
if (!fs4.existsSync(srcDir)) return "";
|
|
@@ -33430,10 +33525,10 @@ function warnIfUnbranded(app) {
|
|
|
33430
33525
|
);
|
|
33431
33526
|
}
|
|
33432
33527
|
function warnIfUnboundAliases(app, declaredWorkflows, declaredAgents) {
|
|
33433
|
-
const
|
|
33434
|
-
|
|
33435
|
-
|
|
33436
|
-
|
|
33528
|
+
const { workflows: unboundWorkflows, agents: unboundAgents } = unboundAliases(
|
|
33529
|
+
{ workflows: Object.keys(declaredWorkflows), agents: Object.keys(declaredAgents) },
|
|
33530
|
+
{ workflows: Object.keys(app.workflows ?? {}), agents: Object.keys(app.agents ?? {}) }
|
|
33531
|
+
);
|
|
33437
33532
|
if (unboundWorkflows.length === 0 && unboundAgents.length === 0) return;
|
|
33438
33533
|
const lines = [
|
|
33439
33534
|
"\n\u26A0 The manifest declares aliases that are NOT bound on the server. Deploy ships code +",
|
package/dist/src/client.d.ts
CHANGED
|
@@ -539,6 +539,14 @@ export declare class LoticsClient {
|
|
|
539
539
|
}>;
|
|
540
540
|
/** `knowledge_expects` names the package's agents route to but it does not own (advisory). */
|
|
541
541
|
missing_expected_docs: string[];
|
|
542
|
+
/** Orphan duplicate link fields (unbound links between binding-covered tables). Always empty on an origin. */
|
|
543
|
+
orphan_duplicate_links: Array<{
|
|
544
|
+
field_id: string;
|
|
545
|
+
field_name: string;
|
|
546
|
+
table_id: string;
|
|
547
|
+
table_name: string;
|
|
548
|
+
target_table_id: string;
|
|
549
|
+
}>;
|
|
542
550
|
}>;
|
|
543
551
|
/**
|
|
544
552
|
* Preview a release — the dry run behind `opctl app release`. Runs the
|