@lotics/cli 0.88.0 → 0.88.1

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 (87) hide show
  1. package/dist/render_page.js +100 -100
  2. package/dist/src/cli.js +100 -100
  3. package/dist/{client.d.ts → src/client.d.ts} +1 -1
  4. package/dist/src/package_content_types.d.ts +70 -0
  5. package/dist/src/package_content_types.js +12 -0
  6. package/package.json +2 -2
  7. package/dist/app_commands.d.ts +0 -401
  8. package/dist/app_commands.js +0 -1520
  9. package/dist/app_commands.test.d.ts +0 -1
  10. package/dist/app_commands.test.js +0 -898
  11. package/dist/app_workflow_check.d.ts +0 -77
  12. package/dist/app_workflow_check.js +0 -169
  13. package/dist/app_workflow_check.test.d.ts +0 -1
  14. package/dist/app_workflow_check.test.js +0 -166
  15. package/dist/args.d.ts +0 -42
  16. package/dist/args.js +0 -121
  17. package/dist/args.test.d.ts +0 -1
  18. package/dist/args.test.js +0 -88
  19. package/dist/child_env.d.ts +0 -13
  20. package/dist/child_env.js +0 -24
  21. package/dist/cli.d.ts +0 -2
  22. package/dist/cli.js +0 -1095
  23. package/dist/cli_dispatch.test.d.ts +0 -1
  24. package/dist/cli_dispatch.test.js +0 -89
  25. package/dist/client.test.d.ts +0 -1
  26. package/dist/client.test.js +0 -47
  27. package/dist/config.d.ts +0 -124
  28. package/dist/config.js +0 -315
  29. package/dist/config.test.d.ts +0 -1
  30. package/dist/config.test.js +0 -319
  31. package/dist/dev/file_relay.d.ts +0 -39
  32. package/dist/dev/file_relay.js +0 -87
  33. package/dist/dev/file_relay.test.d.ts +0 -1
  34. package/dist/dev/file_relay.test.js +0 -87
  35. package/dist/dev/rpc_handler.d.ts +0 -21
  36. package/dist/dev/rpc_handler.js +0 -166
  37. package/dist/dev/rpc_handler.test.d.ts +0 -1
  38. package/dist/dev/rpc_handler.test.js +0 -107
  39. package/dist/dev/server.d.ts +0 -75
  40. package/dist/dev/server.js +0 -443
  41. package/dist/dev/upload_relay.d.ts +0 -36
  42. package/dist/dev/upload_relay.js +0 -61
  43. package/dist/dev/upload_relay.test.d.ts +0 -1
  44. package/dist/dev/upload_relay.test.js +0 -76
  45. package/dist/dev/wrapper_page.d.ts +0 -40
  46. package/dist/dev/wrapper_page.js +0 -335
  47. package/dist/docx.d.ts +0 -2
  48. package/dist/docx.js +0 -341
  49. package/dist/docx.test.d.ts +0 -1
  50. package/dist/docx.test.js +0 -145
  51. package/dist/file_command_io.d.ts +0 -12
  52. package/dist/file_command_io.js +0 -34
  53. package/dist/generate_app_agents_dts.d.ts +0 -16
  54. package/dist/generate_app_agents_dts.js +0 -62
  55. package/dist/generate_app_agents_dts.test.d.ts +0 -1
  56. package/dist/generate_app_agents_dts.test.js +0 -45
  57. package/dist/generate_app_fields.d.ts +0 -56
  58. package/dist/generate_app_fields.js +0 -148
  59. package/dist/generate_app_fields.test.d.ts +0 -1
  60. package/dist/generate_app_fields.test.js +0 -108
  61. package/dist/generate_app_queries_dts.d.ts +0 -21
  62. package/dist/generate_app_queries_dts.js +0 -57
  63. package/dist/generate_app_workflows_dts.d.ts +0 -39
  64. package/dist/generate_app_workflows_dts.js +0 -229
  65. package/dist/generate_app_workflows_dts.test.d.ts +0 -1
  66. package/dist/generate_app_workflows_dts.test.js +0 -109
  67. package/dist/generate_package_fields.d.ts +0 -49
  68. package/dist/generate_package_fields.js +0 -157
  69. package/dist/generate_package_fields.test.d.ts +0 -1
  70. package/dist/generate_package_fields.test.js +0 -59
  71. package/dist/inputs.d.ts +0 -38
  72. package/dist/inputs.js +0 -50
  73. package/dist/inputs.test.d.ts +0 -1
  74. package/dist/inputs.test.js +0 -89
  75. package/dist/preview.d.ts +0 -3
  76. package/dist/preview.js +0 -233
  77. package/dist/starter_template.d.ts +0 -64
  78. package/dist/starter_template.js +0 -753
  79. package/dist/starter_template.test.d.ts +0 -1
  80. package/dist/starter_template.test.js +0 -115
  81. package/dist/version.d.ts +0 -1
  82. package/dist/version.js +0 -6
  83. package/dist/xlsx.d.ts +0 -2
  84. package/dist/xlsx.js +0 -489
  85. package/dist/xlsx.test.d.ts +0 -1
  86. package/dist/xlsx.test.js +0 -131
  87. /package/dist/{client.js → src/client.js} +0 -0
@@ -1,166 +0,0 @@
1
- /**
2
- * Stateless dispatcher: takes a parsed RPC envelope from the wrapper page
3
- * and routes to the matching LoticsClient method.
4
- *
5
- * Errors are thrown — the HTTP server caller serializes them to a 500 with
6
- * { message }. Same shape as the production iframe-host's error path.
7
- */
8
- const SUPPORTED_OPS = new Set([
9
- "query",
10
- "field_options",
11
- "workflow",
12
- "members",
13
- "context",
14
- "binding",
15
- "upload_url",
16
- "upload_complete",
17
- "comments.list",
18
- "comments.create",
19
- "comments.update",
20
- "comments.delete",
21
- "comments.counts",
22
- ]);
23
- export async function dispatchRpc(client, body, opts) {
24
- if (!body || typeof body.app_id !== "string" || typeof body.op !== "string") {
25
- throw new Error("RPC envelope must include app_id and op");
26
- }
27
- if (!SUPPORTED_OPS.has(body.op)) {
28
- throw new Error(`Unknown RPC op: ${body.op}`);
29
- }
30
- switch (body.op) {
31
- case "context": {
32
- // In production the iframe host supplies the context; here we resolve the
33
- // effective viewer from the CLI key (the view-as target when the client
34
- // carries `x-view-as-member-id`, else the key's owner), so `useViewer`
35
- // and `is_current_member` agree in the dev loop. The installation's
36
- // stored config is fetched live for the same parity: `useConfig()` must
37
- // render the same values in the dev loop as in production, including
38
- // edits made in the product mid-session (fresh on every app load, no
39
- // dev-server restart).
40
- const [who, app] = await Promise.all([client.whoami(), client.getApp(body.app_id)]);
41
- return {
42
- member_id: who.member_id,
43
- comments_enabled: opts?.commentsEnabled ?? false,
44
- config: app.config ?? {},
45
- };
46
- }
47
- case "query": {
48
- const p = body.payload;
49
- if (!p || typeof p.alias !== "string") {
50
- throw new Error("query payload must include `alias`");
51
- }
52
- return client.appQuery(body.app_id, {
53
- alias: p.alias,
54
- params: p.params,
55
- limit: p.limit,
56
- offset: p.offset,
57
- sort: p.sort,
58
- filter: p.filter,
59
- count: p.count,
60
- });
61
- }
62
- case "field_options": {
63
- const p = body.payload;
64
- if (!p || typeof p.alias !== "string") {
65
- throw new Error("field_options payload must include `alias`");
66
- }
67
- return client.appFieldOptions(body.app_id, p.alias);
68
- }
69
- case "workflow": {
70
- const p = body.payload;
71
- if (!p || typeof p.alias !== "string") {
72
- throw new Error("workflow payload must include `alias`");
73
- }
74
- return client.appWorkflow(body.app_id, p.alias, p.inputs);
75
- }
76
- case "members": {
77
- const p = body.payload;
78
- return client.appMembers(body.app_id, p?.group);
79
- }
80
- case "binding":
81
- return client.appBinding(body.app_id);
82
- case "upload_url": {
83
- const p = body.payload;
84
- if (!p ||
85
- typeof p.filename !== "string" ||
86
- typeof p.mime_type !== "string" ||
87
- typeof p.file_size !== "number") {
88
- throw new Error("upload_url payload must include filename, mime_type, file_size");
89
- }
90
- return client.appRequestFileUpload(body.app_id, {
91
- filename: p.filename,
92
- mime_type: p.mime_type,
93
- file_size: p.file_size,
94
- });
95
- }
96
- case "upload_complete": {
97
- const p = body.payload;
98
- if (!p ||
99
- typeof p.file_id !== "string" ||
100
- typeof p.file_storage_key !== "string" ||
101
- typeof p.filename !== "string") {
102
- throw new Error("upload_complete payload must include file_id, file_storage_key, filename");
103
- }
104
- return client.appCompleteFileUpload(body.app_id, {
105
- file_id: p.file_id,
106
- file_storage_key: p.file_storage_key,
107
- filename: p.filename,
108
- });
109
- }
110
- // Comment ops mirror the production iframe-host's `handleCommentRpc` return
111
- // shapes so the SDK hooks behave identically in dev. App authority + the
112
- // `comments` capability + tenant floor are enforced server-side.
113
- case "comments.list": {
114
- const p = body.payload;
115
- if (!p || typeof p.record_id !== "string") {
116
- throw new Error("comments.list payload must include record_id");
117
- }
118
- const comments = await client.appGetRecordComments(body.app_id, p.record_id);
119
- return { comments };
120
- }
121
- case "comments.create": {
122
- const p = body.payload;
123
- if (!p || typeof p.record_id !== "string" || typeof p.content !== "string") {
124
- throw new Error("comments.create payload must include record_id and content");
125
- }
126
- const created = await client.appCreateRecordComment(body.app_id, p.record_id, {
127
- content: p.content,
128
- file_ids: p.file_ids,
129
- });
130
- return { comments: created ? [created] : [] };
131
- }
132
- case "comments.update": {
133
- const p = body.payload;
134
- if (!p ||
135
- typeof p.record_id !== "string" ||
136
- typeof p.comment_id !== "string" ||
137
- typeof p.content !== "string") {
138
- throw new Error("comments.update payload must include record_id, comment_id, content");
139
- }
140
- const comment = await client.appUpdateRecordComment(body.app_id, p.record_id, p.comment_id, {
141
- content: p.content,
142
- files: p.files,
143
- });
144
- return { comment };
145
- }
146
- case "comments.delete": {
147
- const p = body.payload;
148
- if (!p || typeof p.record_id !== "string" || typeof p.comment_id !== "string") {
149
- throw new Error("comments.delete payload must include record_id and comment_id");
150
- }
151
- await client.appDeleteRecordComment(body.app_id, p.record_id, p.comment_id);
152
- return undefined;
153
- }
154
- case "comments.counts": {
155
- const p = body.payload;
156
- if (!p || typeof p.table_id !== "string") {
157
- throw new Error("comments.counts payload must include table_id");
158
- }
159
- return client.appGetTableCommentCounts(body.app_id, p.table_id);
160
- }
161
- default: {
162
- // Unreachable — SUPPORTED_OPS gates above.
163
- throw new Error(`Unhandled RPC op: ${body.op}`);
164
- }
165
- }
166
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,107 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { dispatchRpc } from "./rpc_handler.js";
3
- import { serializeRpcResult } from "./server.js";
4
- function mockClient(over) {
5
- return over;
6
- }
7
- const WHOAMI = {
8
- member_id: "mbr_viewer",
9
- email: "v@test.local",
10
- name: "Viewer",
11
- organization_id: "org_1",
12
- organization_name: "Org",
13
- };
14
- describe("dispatchRpc — context op", () => {
15
- it("resolves the viewer from whoami and the installation's stored config from the live app row", async () => {
16
- const client = mockClient({
17
- whoami: async () => WHOAMI,
18
- getApp: async () => ({
19
- id: "app_x",
20
- name: "App X",
21
- workspace_id: "wsp_1",
22
- current_version_id: null,
23
- config: { page_size: 50 },
24
- }),
25
- });
26
- const result = await dispatchRpc(client, { app_id: "app_x", op: "context", payload: {} }, { commentsEnabled: true });
27
- expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: true, config: { page_size: 50 } });
28
- });
29
- it("defaults comments_enabled to false and config to empty for a bespoke app", async () => {
30
- const client = mockClient({
31
- whoami: async () => WHOAMI,
32
- getApp: async () => ({
33
- id: "app_x",
34
- name: "App X",
35
- workspace_id: "wsp_1",
36
- current_version_id: null,
37
- config: null,
38
- }),
39
- });
40
- const result = await dispatchRpc(client, { app_id: "app_x", op: "context", payload: {} });
41
- expect(result).toEqual({ member_id: "mbr_viewer", comments_enabled: false, config: {} });
42
- });
43
- it("rejects an op that isn't supported", async () => {
44
- const client = mockClient({});
45
- await expect(dispatchRpc(client, { app_id: "app_x", op: "bogus", payload: {} })).rejects.toThrow(/Unknown RPC op/);
46
- });
47
- });
48
- describe("dispatchRpc — field_options op", () => {
49
- it("forwards the alias to appFieldOptions and returns its { fields } shape", async () => {
50
- const fields = {
51
- status: {
52
- label: "Status",
53
- options: [{ key: "opt_open", label: "Open", color: "blue" }],
54
- },
55
- };
56
- const client = mockClient({ appFieldOptions: async () => ({ fields }) });
57
- const result = await dispatchRpc(client, {
58
- app_id: "app_x",
59
- op: "field_options",
60
- payload: { alias: "records" },
61
- });
62
- expect(result).toEqual({ fields });
63
- });
64
- it("requires an alias in the payload", async () => {
65
- const client = mockClient({ appFieldOptions: async () => ({ fields: {} }) });
66
- await expect(dispatchRpc(client, { app_id: "app_x", op: "field_options", payload: {} })).rejects.toThrow(/alias/);
67
- });
68
- });
69
- describe("dispatchRpc — comment ops", () => {
70
- it("comments.list forwards to appGetRecordComments, wrapped as { comments }", async () => {
71
- const comments = [{ id: "cmt_1", content: "hi" }];
72
- const client = mockClient({ appGetRecordComments: async () => comments });
73
- const result = await dispatchRpc(client, {
74
- app_id: "app_x",
75
- op: "comments.list",
76
- payload: { record_id: "rec_1" },
77
- });
78
- expect(result).toEqual({ comments });
79
- });
80
- it("comments.counts forwards to appGetTableCommentCounts (counts, no content)", async () => {
81
- const counts = { rec_1: 2, rec_2: 1 };
82
- const client = mockClient({ appGetTableCommentCounts: async () => counts });
83
- const result = await dispatchRpc(client, {
84
- app_id: "app_x",
85
- op: "comments.counts",
86
- payload: { table_id: "tbl_1" },
87
- });
88
- expect(result).toEqual(counts);
89
- });
90
- it("requires the right payload per comment op", async () => {
91
- const client = mockClient({
92
- appGetRecordComments: async () => [],
93
- appGetTableCommentCounts: async () => ({}),
94
- });
95
- await expect(dispatchRpc(client, { app_id: "app_x", op: "comments.list", payload: {} })).rejects.toThrow(/record_id/);
96
- await expect(dispatchRpc(client, { app_id: "app_x", op: "comments.counts", payload: {} })).rejects.toThrow(/table_id/);
97
- });
98
- });
99
- describe("serializeRpcResult — every RPC response body is valid JSON", () => {
100
- it("serializes a void result (comments.delete) as null, never an empty body", () => {
101
- expect(serializeRpcResult(undefined)).toBe("null");
102
- expect(JSON.parse(serializeRpcResult(undefined))).toBeNull();
103
- });
104
- it("passes object results through", () => {
105
- expect(JSON.parse(serializeRpcResult({ comments: [] }))).toEqual({ comments: [] });
106
- });
107
- });
@@ -1,75 +0,0 @@
1
- /**
2
- * `lotics app dev` orchestrator.
3
- *
4
- * Two child processes managed in one Node lifecycle:
5
- * 1. Vite dev server (npx vite --port <vite-port>) — child_process.spawn,
6
- * stdio inherited so Vite's own logging surfaces to the developer.
7
- * 2. node:http server on <port> serving:
8
- * GET / → wrapper HTML (cached: no)
9
- * POST /_rpc → JSON in, dispatched via rpc_handler, JSON out
10
- * POST /_agent_run → SSE, piped from the run
11
- * PUT /_upload/:id → file bytes in (relayed to storage)
12
- * GET /_file/:token → file bytes out (relayed from storage)
13
- * * → 404
14
- *
15
- * SIGINT (Ctrl-C) → kill Vite child, close HTTP server, exit 0.
16
- *
17
- * Why the byte relays exist: dev runs against the PROD bucket (there is no dev
18
- * bucket), whose CORS allowlist holds the real app origins (`https://*.lotics.app`),
19
- * not `http://localhost:<port>`. So a browser transfer straight to/from the presigned
20
- * URL is blocked before it leaves the page — no upload could complete, and no preview
21
- * engine (PDF/Word/Excel all FETCH the bytes) could read a file. Relaying through THIS
22
- * server fixes both: Node has no same-origin policy, and the one cross-origin hop that
23
- * remains (the app iframe reading from us) is OUR response to allow.
24
- *
25
- * Neither relay ever takes a destination from the client: the page sends a `file_id`
26
- * or an opaque token, and the server transfers only to/from a URL IT minted or observed
27
- * for that id. No client-controlled target ⇒ no SSRF surface, nothing to allowlist.
28
- *
29
- * Production is untouched — it transfers direct-to-storage, keeping every byte off the
30
- * API server.
31
- */
32
- import { type ChildProcess } from "node:child_process";
33
- import { LoticsClient } from "../client.js";
34
- export interface DevServerArgs {
35
- projectDir: string;
36
- app_id: string;
37
- app_name: string;
38
- workspace_id: string;
39
- /** API URL the CLI is configured against — shown in the header banner. */
40
- api_url: string;
41
- /** Preferred wrapper port. If taken, auto-picks a free one. */
42
- port?: number;
43
- /** Preferred Vite port. If taken, auto-picks a free one. */
44
- vitePort?: number;
45
- client: LoticsClient;
46
- /** From `package.json#lotics.capabilities.comments` — fed to the `context` op
47
- * so `useComments` reflects the manifest in the dev loop. */
48
- commentsEnabled?: boolean;
49
- }
50
- export interface DevServerHandle {
51
- port: number;
52
- vitePort: number;
53
- /** Resolves when both Vite + HTTP server are listening. */
54
- ready: Promise<void>;
55
- /** Stops Vite + HTTP server. Safe to call multiple times. */
56
- stop: () => Promise<void>;
57
- }
58
- /**
59
- * Serialize an RPC result for the HTTP response body. Ops with no return
60
- * value (e.g. comments.delete) resolve `undefined` — in production they
61
- * travel over postMessage where undefined structured-clones fine, but
62
- * `JSON.stringify(undefined)` is not a string, so the body would be EMPTY
63
- * and the wrapper's `JSON.parse` throws "Unexpected end of JSON input".
64
- * Serialize those as `null`.
65
- */
66
- export declare function serializeRpcResult(result: unknown): string;
67
- export declare function startDevServer(args: DevServerArgs): Promise<DevServerHandle>;
68
- /** Used by the CLI command for cross-platform `open <url>`. Best-effort —
69
- * if the platform opener isn't installed (common in WSL, headless CI, some
70
- * Linux minimal images), we log a note and keep the dev server running so
71
- * the developer can copy/paste the URL manually. */
72
- export declare function openBrowser(url: string): void;
73
- declare global {
74
- type _UnusedChildProcess = ChildProcess;
75
- }