@lotics/cli 0.88.0 → 0.89.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 +162 -100
  3. package/dist/{client.d.ts → src/client.d.ts} +9 -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 +0,0 @@
1
- export {};
@@ -1,115 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { buildStarterTemplate, STARTER_FALLBACK_SDK_VERSION, STARTER_FALLBACK_UI_VERSION, STARTER_REACT_NATIVE_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 RN deps with __DEV__ + .web.js for the dev server", () => {
12
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
13
- // The optimizer runs a SEPARATE esbuild pass that ignores `resolve.extensions`
14
- // and the top-level `define`, so `__DEV__` + `.web.js` must be repeated here —
15
- // else a pre-bundled RN dep (e.g. react-native-svg, which @lotics/ui charts
16
- // use) resolves to its native build and `__DEV__` is undefined.
17
- expect(config).toMatch(/optimizeDeps:\s*\{/);
18
- expect(config).toContain('__DEV__: "false"');
19
- expect(config).toContain('".web.js"');
20
- });
21
- test("vite.config.ts force-prebundles react-native-web (else a DatePicker/Picker blanks the dev iframe)", () => {
22
- // RN-Web does `import normalizeColor from "@react-native/normalize-colors"`
23
- // (nested CJS). Un-prebundled, the dev server serves it as ESM with no
24
- // default → "does not provide an export named 'default'" + blank iframe the
25
- // moment a colour-touching component mounts. Prebundling interops it.
26
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
27
- expect(config).toMatch(/include:\s*\[[^\]]*"react-native-web"/);
28
- });
29
- test("vite.config.ts defines the RN __DEV__ global (else the bundle throws at load → blank iframe)", () => {
30
- // `__DEV__` is a Metro-injected global that RN-ecosystem deps reference at
31
- // module-eval time (e.g. @react-native-picker's UnimplementedView, pulled in
32
- // via @lotics/ui's DatePicker). Vite doesn't define it; undefined ⇒ a runtime
33
- // ReferenceError that no typecheck/lint/build catches. Pin it so it can't
34
- // silently regress and re-blank every app that imports such a dep.
35
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
36
- // Top-level define (covers the prod build + most dev modules)...
37
- expect(config).toMatch(/define:\s*\{\s*__DEV__:\s*"false"/);
38
- // ...and the dep-optimizer's separate esbuild pass (covers pre-bundled
39
- // deps under `lotics app dev`).
40
- expect(config).toMatch(/esbuildOptions:\s*\{\s*define:\s*\{\s*__DEV__:\s*"false"/);
41
- });
42
- test("vite.config.ts enables the vitest web optimizer with .web.js resolution (else tests rendering a Picker/DatePicker fail on Flow)", () => {
43
- // Vitest pre-bundles deps with its OWN esbuild optimizer, which is OFF by
44
- // default and ignores the top-level `optimizeDeps.resolveExtensions`. Without
45
- // it enabled + given `.web.js` first, it grabs react-native's native `.js` /
46
- // src Flow files (`import typeof`) that esbuild can't parse — so any test that
47
- // renders a tree pulling a Picker/DatePicker dies with "Unexpected token 'typeof'".
48
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
49
- expect(config).toMatch(/test:\s*\{[\s\S]*environment:\s*"jsdom"/);
50
- expect(config).toMatch(/optimizer:\s*\{\s*web:\s*\{\s*enabled:\s*true/);
51
- // The web optimizer carries the same `.web.js`-first resolution as the build.
52
- expect(config).toMatch(/web:\s*\{[\s\S]*resolveExtensions:[\s\S]*"\.web\.js"/);
53
- });
54
- test("vite.config.ts maps the RN `global` to globalThis (else rn-web Animated throws when a spring is torn down)", () => {
55
- // react-native-web's Animated reads `global.cancelAnimationFrame` (free var)
56
- // when a spring animation is interrupted — e.g. toggling @lotics/ui's Switch.
57
- // Undefined in a web bundle ⇒ "global is not defined". The rollup build hits
58
- // it; the dev esbuild path masks it, so it only surfaces in the deployed app
59
- // and no typecheck/lint/build catches it. Pin both define sites.
60
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
61
- expect(config).toMatch(/define:\s*\{\s*__DEV__:\s*"false",\s*global:\s*"globalThis"/);
62
- expect(config).toMatch(/esbuildOptions:\s*\{\s*define:\s*\{\s*__DEV__:\s*"false",\s*global:\s*"globalThis"/);
63
- });
64
- test("package.json pins react-native to the monorepo version (so apps typecheck @lotics/ui against the same RN types)", () => {
65
- const pkg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "package.json"));
66
- expect(pkg.dependencies["react-native"]).toBe(STARTER_REACT_NATIVE_VERSION);
67
- });
68
- test("package.json uses caller-supplied versions for @lotics/ui and @lotics/app-sdk", () => {
69
- const pkg = JSON.parse(fileNamed(buildStarterTemplate({ ...baseArgs, ui_version: "^9.9.9", sdk_version: "^2.2.2" }), "package.json"));
70
- expect(pkg.dependencies["@lotics/ui"]).toBe("^9.9.9");
71
- expect(pkg.dependencies["@lotics/app-sdk"]).toBe("^2.2.2");
72
- });
73
- test("package.json falls back to the static pins when versions are omitted", () => {
74
- // Used when `lotics app create` cannot reach the npm registry to resolve
75
- // `latest`. Keeps `app create` working offline at the cost of a stale pin.
76
- const pkg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "package.json"));
77
- expect(pkg.dependencies["@lotics/ui"]).toBe(`^${STARTER_FALLBACK_UI_VERSION}`);
78
- expect(pkg.dependencies["@lotics/app-sdk"]).toBe(`^${STARTER_FALLBACK_SDK_VERSION}`);
79
- });
80
- test("package.json includes react-router-dom for the in-app router example", () => {
81
- const pkg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "package.json"));
82
- expect(pkg.dependencies["react-router-dom"]).toBeDefined();
83
- });
84
- test("App.tsx routes via the SDK's AppRouter (real addressable URLs, both modes)", () => {
85
- // The scaffold demonstrates the recommended shape: the app uses react-router
86
- // through `AppRouter`, which makes screens addressable URLs in both modes.
87
- // Guards against the wiring being dropped back to a single screen.
88
- const app = fileNamed(buildStarterTemplate(baseArgs), "src/App.tsx");
89
- expect(app).toContain('from "react-router-dom"');
90
- expect(app).toContain('from "@lotics/app-sdk/router"');
91
- expect(app).toContain("<AppRouter routes={routes}");
92
- });
93
- test("vite.config.ts pins react-router-dom into the vitest web optimizer (else the routed App test splits the react instance)", () => {
94
- // react-router-dom ships compiled dist JS; un-pinned, its hooks load a second
95
- // react instance under vitest and the scaffold's App.test fails with "Invalid
96
- // hook call" the moment it renders RouterProvider.
97
- const config = fileNamed(buildStarterTemplate(baseArgs), "vite.config.ts");
98
- expect(config).toMatch(/include:\s*\[[\s\S]*"react-router-dom"/);
99
- });
100
- test("main tsconfig excludes BOTH the workflow bodies and their per-alias globals (GAP-59 fix)", () => {
101
- // npm run typecheck must skip src/workflows (DOM-lib skew) AND .lotics/workflows
102
- // (the per-alias ambient `trigger` declarations collide across aliases and
103
- // report false errors). Bodies are checked by `lotics app workflow check`.
104
- const cfg = JSON.parse(fileNamed(buildStarterTemplate(baseArgs), "tsconfig.json"));
105
- expect(cfg.exclude).toContain("src/workflows");
106
- expect(cfg.exclude).toContain(".lotics/workflows");
107
- });
108
- test("no longer ships the all-in-one tsconfig.workflows.json (it collided per-alias globals)", () => {
109
- // The dedicated config compiled every alias's globals into ONE program, so the
110
- // ambient `trigger` declarations collided. `lotics app workflow check` replaces
111
- // it with one isolated program per alias.
112
- const files = buildStarterTemplate(baseArgs);
113
- expect(files.find((f) => f.path === "tsconfig.workflows.json")).toBeUndefined();
114
- });
115
- });
package/dist/version.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const VERSION: string;
package/dist/version.js DELETED
@@ -1,6 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
- import path from "node:path";
4
- const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
5
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
6
- export const VERSION = pkg.version;
package/dist/xlsx.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare function printXlsxHelp(): void;
2
- export declare function runXlsxCommand(subcommand: string | undefined, toolArgs: string | undefined, restArgs: string[]): Promise<void>;
package/dist/xlsx.js DELETED
@@ -1,489 +0,0 @@
1
- import fs from "node:fs";
2
- import { fail, writeFileAtomic } from "./file_command_io.js";
3
- import { parseExcelBuffer } from "@lotics/xlsx/excel_parser";
4
- import { unzipXlsx } from "@lotics/xlsx/ooxml_zip";
5
- import { exportWorkbook } from "@lotics/xlsx/xlsx_writer";
6
- import { loadWorkbookFromSnapshot, WorkbookModel, SheetModel, StyleRegistry } from "@lotics/xlsx/workbook_model";
7
- import { FormulaEngine } from "@lotics/xlsx/formula_engine";
8
- import { refToRowCol, rowColToRef } from "@lotics/xlsx/excel_utils";
9
- import { setCellValueCommand, clearRangeCommand, mergeCellsCommand, unmergeCellsCommand, addSheetCommand, deleteSheetCommand, renameSheetCommand, insertRowsCommand, deleteRowsCommand, insertColsCommand, deleteColsCommand, setRangeStyleCommand, } from "@lotics/xlsx/spreadsheet_commands";
10
- function loadFile(filePath) {
11
- if (!fs.existsSync(filePath))
12
- fail(`File not found: ${filePath}`);
13
- const buffer = fs.readFileSync(filePath);
14
- const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
15
- const originalZip = unzipXlsx(arrayBuffer);
16
- const parsed = parseExcelBuffer(arrayBuffer);
17
- const workbook = loadWorkbookFromSnapshot(parsed);
18
- return { workbook, originalZip };
19
- }
20
- function exportAndWrite(loaded, filePath) {
21
- const bytes = exportWorkbook(loaded.workbook, loaded.originalZip);
22
- writeFileAtomic(filePath, bytes);
23
- }
24
- function resolveSheetIndex(workbook, sheetName) {
25
- const index = workbook.sheets.findIndex((s) => s.name === sheetName);
26
- if (index < 0) {
27
- const names = workbook.sheets.map((s) => `"${s.name}"`).join(", ");
28
- fail(`Sheet not found: "${sheetName}". Available: ${names}`);
29
- }
30
- return index;
31
- }
32
- function parseRange(range) {
33
- const [start, end] = range.includes(":") ? range.split(":") : [range, range];
34
- const s = refToRowCol(start.toUpperCase());
35
- const e = refToRowCol(end.toUpperCase());
36
- if (!s || !e)
37
- fail(`Invalid range: ${range}`);
38
- return {
39
- startRow: Math.min(s.row, e.row),
40
- startCol: Math.min(s.col, e.col),
41
- endRow: Math.max(s.row, e.row),
42
- endCol: Math.max(s.col, e.col),
43
- };
44
- }
45
- function parseInt10(s, name) {
46
- if (s === undefined)
47
- fail(`Missing ${name}`);
48
- const n = parseInt(s, 10);
49
- if (!Number.isFinite(n))
50
- fail(`Invalid ${name}: ${s}`);
51
- return n;
52
- }
53
- function lazyEngine(workbook) {
54
- let engine;
55
- return {
56
- get() {
57
- if (!engine) {
58
- engine = new FormulaEngine(workbook);
59
- engine.buildGraph();
60
- }
61
- return engine;
62
- },
63
- };
64
- }
65
- // =============================================================================
66
- // JSON shape: ParsedSpreadsheet (read) and SimpleWorkbook (write)
67
- // =============================================================================
68
- function readToJson(filePath) {
69
- const buffer = fs.readFileSync(filePath);
70
- const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
71
- const parsed = parseExcelBuffer(arrayBuffer);
72
- return {
73
- activeSheetIndex: parsed.activeSheetIndex,
74
- sheets: parsed.sheets.map((s) => ({
75
- name: s.name,
76
- totalRowCount: s.totalRowCount,
77
- truncated: s.truncated,
78
- cells: cellsFromParsedRows(s.rows),
79
- merges: s.mergedCells.map((m) => `${rowColToRef(m.startRow, m.startCol)}:${rowColToRef(m.endRow, m.endCol)}`),
80
- freeze: s.freezePane ? { row: s.freezePane.frozenRows, col: s.freezePane.frozenCols } : undefined,
81
- })),
82
- };
83
- }
84
- function cellsFromParsedRows(rows) {
85
- const out = {};
86
- for (const row of rows) {
87
- for (const cell of row.cells) {
88
- const ref = rowColToRef(row.index, cell.column);
89
- const value = cell.typedValue ?? cell.value;
90
- const entry = { value };
91
- if (cell.formula)
92
- entry.formula = cell.formula;
93
- out[ref] = entry;
94
- }
95
- }
96
- return out;
97
- }
98
- function buildWorkbookFromJson(input) {
99
- if (!input || !Array.isArray(input.sheets) || input.sheets.length === 0) {
100
- fail("Workbook JSON must include a non-empty `sheets` array");
101
- }
102
- for (let i = 0; i < input.sheets.length; i++)
103
- validateSimpleSheet(input.sheets[i], i);
104
- const styles = new StyleRegistry();
105
- const sheets = input.sheets.map((s) => new SheetModel(s.name, styles));
106
- const workbook = new WorkbookModel({ sheets, activeSheetIndex: 0, styles });
107
- for (let i = 0; i < input.sheets.length; i++) {
108
- applySheetCells(workbook, i, input.sheets[i]);
109
- }
110
- return workbook;
111
- }
112
- function validateSimpleSheet(s, index) {
113
- if (!s || typeof s !== "object")
114
- fail(`sheets[${index}] must be an object`);
115
- if (typeof s.name !== "string" || s.name === "")
116
- fail(`sheets[${index}].name must be a non-empty string`);
117
- if (s.cells !== undefined && (typeof s.cells !== "object" || s.cells === null || Array.isArray(s.cells))) {
118
- fail(`sheets[${index}].cells must be an object keyed by cell ref (e.g. {"A1": "value"})`);
119
- }
120
- if (s.merges !== undefined && !Array.isArray(s.merges)) {
121
- fail(`sheets[${index}].merges must be an array of ranges (e.g. ["A1:B2"])`);
122
- }
123
- if (s.freeze !== undefined) {
124
- if (typeof s.freeze !== "object" || s.freeze === null)
125
- fail(`sheets[${index}].freeze must be {row, col}`);
126
- if (typeof s.freeze.row !== "number" || typeof s.freeze.col !== "number") {
127
- fail(`sheets[${index}].freeze.row and .col must be numbers`);
128
- }
129
- }
130
- }
131
- function applySheetCells(workbook, sheetIndex, def) {
132
- const sheet = workbook.sheets[sheetIndex];
133
- if (def.cells) {
134
- for (const [ref, raw] of Object.entries(def.cells)) {
135
- const cell = normalizeSimpleCell(raw);
136
- const coord = refToRowCol(ref.toUpperCase());
137
- if (!coord)
138
- fail(`Invalid cell ref in sheet "${def.name}": ${ref}`);
139
- const stringValue = cell.formula ? `=${cell.formula}` : valueToInput(cell.value);
140
- const cmd = setCellValueCommand(workbook, undefined, sheetIndex, coord.row, coord.col, stringValue);
141
- cmd.execute();
142
- }
143
- }
144
- if (def.merges) {
145
- for (const range of def.merges) {
146
- const r = parseRange(range);
147
- const cmd = mergeCellsCommand(workbook, sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol);
148
- cmd.execute();
149
- }
150
- }
151
- if (def.freeze) {
152
- sheet.freeze = { row: def.freeze.row, col: def.freeze.col };
153
- }
154
- }
155
- function normalizeSimpleCell(raw) {
156
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
157
- return raw;
158
- }
159
- return { value: raw };
160
- }
161
- function valueToInput(value) {
162
- if (value === null || value === undefined)
163
- return "";
164
- return String(value);
165
- }
166
- // =============================================================================
167
- // Subcommand handlers
168
- // =============================================================================
169
- function xlsxRead(filePath) {
170
- if (!filePath)
171
- fail("Usage: lotics xlsx read <file>");
172
- const data = readToJson(filePath);
173
- console.log(JSON.stringify(data, null, 2));
174
- }
175
- function xlsxWrite(filePath, json) {
176
- if (!filePath || !json)
177
- fail("Usage: lotics xlsx write <file> '<json>'");
178
- let parsed;
179
- try {
180
- parsed = JSON.parse(json);
181
- }
182
- catch (e) {
183
- fail(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
184
- }
185
- const workbook = buildWorkbookFromJson(parsed);
186
- const bytes = exportWorkbook(workbook);
187
- writeFileAtomic(filePath, bytes);
188
- }
189
- function parseSheetRef(spec) {
190
- const idx = spec.indexOf("!");
191
- if (idx < 0)
192
- fail(`Expected <sheet>!<ref>, got: ${spec}`);
193
- return { sheet: spec.slice(0, idx), ref: spec.slice(idx + 1) };
194
- }
195
- function xlsxSetCell(filePath, rest) {
196
- const [sheetRef, value] = rest;
197
- if (!filePath || !sheetRef || value === undefined) {
198
- fail("Usage: lotics xlsx set-cell <file> <sheet>!<ref> '<value>'");
199
- }
200
- const { sheet, ref } = parseSheetRef(sheetRef);
201
- const coord = refToRowCol(ref.toUpperCase());
202
- if (!coord)
203
- fail(`Invalid cell ref: ${ref}`);
204
- const loaded = loadFile(filePath);
205
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheet);
206
- const engine = lazyEngine(loaded.workbook);
207
- const cmd = setCellValueCommand(loaded.workbook, engine.get(), sheetIndex, coord.row, coord.col, value);
208
- cmd.execute();
209
- exportAndWrite(loaded, filePath);
210
- }
211
- function xlsxClearRange(filePath, rest) {
212
- const [sheetRef] = rest;
213
- if (!filePath || !sheetRef)
214
- fail("Usage: lotics xlsx clear-range <file> <sheet>!<range>");
215
- const { sheet, ref } = parseSheetRef(sheetRef);
216
- const r = parseRange(ref);
217
- const loaded = loadFile(filePath);
218
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheet);
219
- const engine = lazyEngine(loaded.workbook);
220
- const cmd = clearRangeCommand(loaded.workbook, engine.get(), sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol);
221
- cmd.execute();
222
- exportAndWrite(loaded, filePath);
223
- }
224
- function xlsxMerge(filePath, rest) {
225
- const [sheetRef] = rest;
226
- if (!filePath || !sheetRef)
227
- fail("Usage: lotics xlsx merge <file> <sheet>!<range>");
228
- const { sheet, ref } = parseSheetRef(sheetRef);
229
- const r = parseRange(ref);
230
- const loaded = loadFile(filePath);
231
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheet);
232
- const cmd = mergeCellsCommand(loaded.workbook, sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol);
233
- cmd.execute();
234
- exportAndWrite(loaded, filePath);
235
- }
236
- function xlsxUnmerge(filePath, rest) {
237
- const [sheetRef] = rest;
238
- if (!filePath || !sheetRef)
239
- fail("Usage: lotics xlsx unmerge <file> <sheet>!<range>");
240
- const { sheet, ref } = parseSheetRef(sheetRef);
241
- const r = parseRange(ref);
242
- const mergeRef = `${rowColToRef(r.startRow, r.startCol)}:${rowColToRef(r.endRow, r.endCol)}`;
243
- const loaded = loadFile(filePath);
244
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheet);
245
- const cmd = unmergeCellsCommand(loaded.workbook, sheetIndex, mergeRef);
246
- cmd.execute();
247
- exportAndWrite(loaded, filePath);
248
- }
249
- function xlsxAddSheet(filePath, rest) {
250
- const [name] = rest;
251
- if (!filePath || !name)
252
- fail("Usage: lotics xlsx add-sheet <file> <name>");
253
- const loaded = loadFile(filePath);
254
- const cmd = addSheetCommand(loaded.workbook, name);
255
- cmd.execute();
256
- exportAndWrite(loaded, filePath);
257
- }
258
- function xlsxDeleteSheet(filePath, rest) {
259
- const [name] = rest;
260
- if (!filePath || !name)
261
- fail("Usage: lotics xlsx delete-sheet <file> <name>");
262
- const loaded = loadFile(filePath);
263
- const sheetIndex = resolveSheetIndex(loaded.workbook, name);
264
- const cmd = deleteSheetCommand(loaded.workbook, sheetIndex);
265
- cmd.execute();
266
- exportAndWrite(loaded, filePath);
267
- }
268
- function xlsxRenameSheet(filePath, rest) {
269
- const [oldName, newName] = rest;
270
- if (!filePath || !oldName || !newName)
271
- fail("Usage: lotics xlsx rename-sheet <file> <old> <new>");
272
- const loaded = loadFile(filePath);
273
- const sheetIndex = resolveSheetIndex(loaded.workbook, oldName);
274
- const cmd = renameSheetCommand(loaded.workbook, sheetIndex, newName);
275
- cmd.execute();
276
- exportAndWrite(loaded, filePath);
277
- }
278
- function xlsxInsertRows(filePath, rest) {
279
- const [sheetName, atStr, countStr] = rest;
280
- if (!filePath || !sheetName)
281
- fail("Usage: lotics xlsx insert-rows <file> <sheet> <at> <count>");
282
- const at = parseInt10(atStr, "at");
283
- const count = parseInt10(countStr, "count");
284
- const loaded = loadFile(filePath);
285
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheetName);
286
- const cmd = insertRowsCommand(loaded.workbook, sheetIndex, at, count);
287
- cmd.execute();
288
- exportAndWrite(loaded, filePath);
289
- }
290
- function xlsxDeleteRows(filePath, rest) {
291
- const [sheetName, atStr, countStr] = rest;
292
- if (!filePath || !sheetName)
293
- fail("Usage: lotics xlsx delete-rows <file> <sheet> <at> <count>");
294
- const at = parseInt10(atStr, "at");
295
- const count = parseInt10(countStr, "count");
296
- const loaded = loadFile(filePath);
297
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheetName);
298
- const cmd = deleteRowsCommand(loaded.workbook, sheetIndex, at, count);
299
- cmd.execute();
300
- exportAndWrite(loaded, filePath);
301
- }
302
- function xlsxInsertCols(filePath, rest) {
303
- const [sheetName, atStr, countStr] = rest;
304
- if (!filePath || !sheetName)
305
- fail("Usage: lotics xlsx insert-cols <file> <sheet> <at> <count>");
306
- const at = parseInt10(atStr, "at");
307
- const count = parseInt10(countStr, "count");
308
- const loaded = loadFile(filePath);
309
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheetName);
310
- const cmd = insertColsCommand(loaded.workbook, sheetIndex, at, count);
311
- cmd.execute();
312
- exportAndWrite(loaded, filePath);
313
- }
314
- function xlsxDeleteCols(filePath, rest) {
315
- const [sheetName, atStr, countStr] = rest;
316
- if (!filePath || !sheetName)
317
- fail("Usage: lotics xlsx delete-cols <file> <sheet> <at> <count>");
318
- const at = parseInt10(atStr, "at");
319
- const count = parseInt10(countStr, "count");
320
- const loaded = loadFile(filePath);
321
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheetName);
322
- const cmd = deleteColsCommand(loaded.workbook, sheetIndex, at, count);
323
- cmd.execute();
324
- exportAndWrite(loaded, filePath);
325
- }
326
- function xlsxSetStyle(filePath, rest) {
327
- const [sheetRef, styleJson] = rest;
328
- if (!filePath || !sheetRef || !styleJson) {
329
- fail("Usage: lotics xlsx set-style <file> <sheet>!<range> '<style-json>'");
330
- }
331
- const { sheet, ref } = parseSheetRef(sheetRef);
332
- const r = parseRange(ref);
333
- let style;
334
- try {
335
- style = JSON.parse(styleJson);
336
- }
337
- catch (e) {
338
- fail(`Invalid style JSON: ${e instanceof Error ? e.message : String(e)}`);
339
- }
340
- const loaded = loadFile(filePath);
341
- const sheetIndex = resolveSheetIndex(loaded.workbook, sheet);
342
- const cmd = setRangeStyleCommand(loaded.workbook, sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol, style);
343
- cmd.execute();
344
- exportAndWrite(loaded, filePath);
345
- }
346
- function xlsxBatch(filePath, json) {
347
- if (!filePath || !json)
348
- fail("Usage: lotics xlsx batch <file> '<json-array-of-ops>'");
349
- let ops;
350
- try {
351
- ops = JSON.parse(json);
352
- }
353
- catch (e) {
354
- fail(`Invalid batch JSON: ${e instanceof Error ? e.message : String(e)}`);
355
- }
356
- if (!Array.isArray(ops))
357
- fail("Batch must be a JSON array of ops");
358
- for (let i = 0; i < ops.length; i++) {
359
- const op = ops[i];
360
- if (!op || typeof op !== "object" || typeof op.op !== "string") {
361
- fail(`[op ${i}] each op must be an object with an "op" string field`);
362
- }
363
- }
364
- const loaded = loadFile(filePath);
365
- const engine = lazyEngine(loaded.workbook);
366
- for (let i = 0; i < ops.length; i++) {
367
- const op = ops[i];
368
- const cmd = buildBatchCommand(loaded.workbook, engine, op, i);
369
- cmd.execute();
370
- }
371
- exportAndWrite(loaded, filePath);
372
- }
373
- function buildBatchCommand(workbook, engine, op, index) {
374
- switch (op.op) {
375
- case "set-cell": {
376
- const sheetIndex = resolveSheetIndex(workbook, op.sheet);
377
- const coord = refToRowCol(op.ref.toUpperCase());
378
- if (!coord)
379
- fail(`[op ${index}] Invalid cell ref: ${op.ref}`);
380
- return setCellValueCommand(workbook, engine.get(), sheetIndex, coord.row, coord.col, op.value);
381
- }
382
- case "clear-range": {
383
- const sheetIndex = resolveSheetIndex(workbook, op.sheet);
384
- const r = parseRange(op.range);
385
- return clearRangeCommand(workbook, engine.get(), sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol);
386
- }
387
- case "merge": {
388
- const sheetIndex = resolveSheetIndex(workbook, op.sheet);
389
- const r = parseRange(op.range);
390
- return mergeCellsCommand(workbook, sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol);
391
- }
392
- case "unmerge": {
393
- const sheetIndex = resolveSheetIndex(workbook, op.sheet);
394
- const r = parseRange(op.range);
395
- const mergeRef = `${rowColToRef(r.startRow, r.startCol)}:${rowColToRef(r.endRow, r.endCol)}`;
396
- return unmergeCellsCommand(workbook, sheetIndex, mergeRef);
397
- }
398
- case "add-sheet":
399
- return addSheetCommand(workbook, op.name);
400
- case "delete-sheet":
401
- return deleteSheetCommand(workbook, resolveSheetIndex(workbook, op.name));
402
- case "rename-sheet":
403
- return renameSheetCommand(workbook, resolveSheetIndex(workbook, op.old), op.new);
404
- case "insert-rows":
405
- return insertRowsCommand(workbook, resolveSheetIndex(workbook, op.sheet), op.at, op.count);
406
- case "delete-rows":
407
- return deleteRowsCommand(workbook, resolveSheetIndex(workbook, op.sheet), op.at, op.count);
408
- case "insert-cols":
409
- return insertColsCommand(workbook, resolveSheetIndex(workbook, op.sheet), op.at, op.count);
410
- case "delete-cols":
411
- return deleteColsCommand(workbook, resolveSheetIndex(workbook, op.sheet), op.at, op.count);
412
- case "set-style": {
413
- const sheetIndex = resolveSheetIndex(workbook, op.sheet);
414
- const r = parseRange(op.range);
415
- return setRangeStyleCommand(workbook, sheetIndex, r.startRow, r.startCol, r.endRow, r.endCol, op.style);
416
- }
417
- default: {
418
- const exhaustive = op;
419
- void exhaustive;
420
- fail(`[op ${index}] Unknown op: ${JSON.stringify(op)}`);
421
- }
422
- }
423
- }
424
- // =============================================================================
425
- // Dispatch + help
426
- // =============================================================================
427
- export function printXlsxHelp() {
428
- console.error(`Lotics xlsx commands — manipulate .xlsx files in place.
429
- Uses Lotics' own xlsx engine; round-trips faithfully with the Lotics editor and templates.
430
-
431
- lotics xlsx read <file> Dump file as JSON (sheets, cells, merges, freeze)
432
- lotics xlsx write <file> '<json>' Create .xlsx from {"sheets":[{"name","cells":{"A1":...}}]}
433
- lotics xlsx set-cell <file> <sheet>!<ref> '<v>' Set cell value (prefix '=' for formula)
434
- lotics xlsx clear-range <file> <sheet>!<range> Clear all cells in a range
435
- lotics xlsx merge <file> <sheet>!<range> Merge a range into one cell
436
- lotics xlsx unmerge <file> <sheet>!<range> Unmerge a previously merged range
437
- lotics xlsx add-sheet <file> <name> Append a new sheet
438
- lotics xlsx delete-sheet <file> <name> Remove a sheet
439
- lotics xlsx rename-sheet <file> <old> <new> Rename a sheet
440
- lotics xlsx insert-rows <file> <sheet> <at> <n> Insert <n> rows at 1-based position
441
- lotics xlsx delete-rows <file> <sheet> <at> <n> Delete <n> rows starting at <at>
442
- lotics xlsx insert-cols <file> <sheet> <at> <n> Insert <n> columns at 1-based position
443
- lotics xlsx delete-cols <file> <sheet> <at> <n> Delete <n> columns starting at <at>
444
- lotics xlsx set-style <file> <sheet>!<range> '<style-json>'
445
- Apply partial CellStyle to range (fontBold, backgroundColor, ...)
446
- lotics xlsx batch <file> '<json-array-of-ops>' Apply many ops in one parse/export cycle
447
-
448
- Edit ops mutate the file atomically (temp file + rename).`);
449
- }
450
- export async function runXlsxCommand(subcommand, toolArgs, restArgs) {
451
- switch (subcommand) {
452
- case "read":
453
- return xlsxRead(toolArgs);
454
- case "write":
455
- return xlsxWrite(toolArgs, restArgs[0]);
456
- case "set-cell":
457
- return xlsxSetCell(toolArgs, restArgs);
458
- case "clear-range":
459
- return xlsxClearRange(toolArgs, restArgs);
460
- case "merge":
461
- return xlsxMerge(toolArgs, restArgs);
462
- case "unmerge":
463
- return xlsxUnmerge(toolArgs, restArgs);
464
- case "add-sheet":
465
- return xlsxAddSheet(toolArgs, restArgs);
466
- case "delete-sheet":
467
- return xlsxDeleteSheet(toolArgs, restArgs);
468
- case "rename-sheet":
469
- return xlsxRenameSheet(toolArgs, restArgs);
470
- case "insert-rows":
471
- return xlsxInsertRows(toolArgs, restArgs);
472
- case "delete-rows":
473
- return xlsxDeleteRows(toolArgs, restArgs);
474
- case "insert-cols":
475
- return xlsxInsertCols(toolArgs, restArgs);
476
- case "delete-cols":
477
- return xlsxDeleteCols(toolArgs, restArgs);
478
- case "set-style":
479
- return xlsxSetStyle(toolArgs, restArgs);
480
- case "batch":
481
- return xlsxBatch(toolArgs, restArgs[0]);
482
- default: {
483
- if (subcommand)
484
- console.error(`Unknown xlsx subcommand: ${subcommand}\n`);
485
- printXlsxHelp();
486
- process.exit(subcommand ? 1 : 0);
487
- }
488
- }
489
- }
@@ -1 +0,0 @@
1
- export {};