@lotics/cli 0.63.0 → 0.65.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/app_commands.d.ts +17 -10
- package/dist/app_commands.js +72 -38
- package/dist/app_commands.test.js +32 -9
- package/dist/cli.js +11 -3
- package/dist/generate_app_workflows_dts.js +1 -1
- package/dist/generate_app_workflows_dts.test.js +10 -0
- package/dist/src/cli.js +457 -23
- package/package.json +1 -1
package/dist/app_commands.d.ts
CHANGED
|
@@ -87,16 +87,23 @@ export declare function writeWorkflowFile(projectDir: string, alias: string, sou
|
|
|
87
87
|
*/
|
|
88
88
|
export declare function stripWorkflowHeader(content: string): string;
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
90
|
+
* Heal a pre-existing app's `tsconfig.json` so its generated types load and its
|
|
91
|
+
* `npm run typecheck` stays honest. Idempotent, run on every pull / codegen /
|
|
92
|
+
* deploy. Two things the current starter ships but an app SCAFFOLDED before those
|
|
93
|
+
* releases (or with a hand-written tsconfig) can lack:
|
|
94
|
+
*
|
|
95
|
+
* 1. `include`: a bare `.lotics` is rewritten to the recursive `LOTICS_INCLUDE_GLOB`.
|
|
96
|
+
* A bare dot-dir is skipped by TypeScript's include-glob walk, so the codegen'd
|
|
97
|
+
* `.d.ts` never enter the program — every `useQuery`/`useWorkflow`/`useAgentRun`
|
|
98
|
+
* param and result silently falls back to `unknown`.
|
|
99
|
+
* 2. `exclude`: the workflow-body globs — the bodies use the app's DOM lib (the
|
|
100
|
+
* server doesn't) and the per-alias ambient globals collide on `trigger`,
|
|
101
|
+
* poisoning typecheck. Checked separately by `lotics app workflow check`.
|
|
102
|
+
*
|
|
103
|
+
* A missing / unparseable tsconfig is a non-fatal warn (the caller still succeeds);
|
|
104
|
+
* the author fixes the config.
|
|
98
105
|
*/
|
|
99
|
-
export declare function
|
|
106
|
+
export declare function ensureAppTsconfig(projectDir: string): void;
|
|
100
107
|
/**
|
|
101
108
|
* `lotics app codegen [path]` — regenerate every `.lotics/` artifact from the
|
|
102
109
|
* manifest + workspace schema, WITHOUT a deploy. The `.d.ts` companions are
|
|
@@ -235,7 +242,7 @@ export declare function undeclaredCapabilities(sourceText: string, declared: Rec
|
|
|
235
242
|
*/
|
|
236
243
|
export declare function appDeploy(client: LoticsClient, args: {
|
|
237
244
|
projectDir?: string;
|
|
238
|
-
message
|
|
245
|
+
message: string;
|
|
239
246
|
}): Promise<void>;
|
|
240
247
|
/**
|
|
241
248
|
* `lotics app dev [path] [--port=5174] [--vite-port=5173]`
|
package/dist/app_commands.js
CHANGED
|
@@ -64,6 +64,17 @@ const WORKFLOW_GLOBALS_DIR = path.join(".lotics", "workflows");
|
|
|
64
64
|
* with `/` separators — tsconfig globs are POSIX even on Windows.
|
|
65
65
|
*/
|
|
66
66
|
const WORKFLOW_TSCONFIG_EXCLUDES = ["src/workflows", ".lotics/workflows"];
|
|
67
|
+
/**
|
|
68
|
+
* The include glob that actually loads the generated `.lotics/*.d.ts` companions.
|
|
69
|
+
* A BARE `.lotics` entry loads NONE of them: TypeScript's include-glob walk skips
|
|
70
|
+
* dot-directories, so `useQuery` / `useWorkflow` / `useAgentRun` fall back to their
|
|
71
|
+
* untyped string overloads and every param / input / result silently becomes
|
|
72
|
+
* `unknown`. The current starter emits the glob (`starter_template.ts`); apps
|
|
73
|
+
* scaffolded before that fix shipped the bare form and need healing.
|
|
74
|
+
*/
|
|
75
|
+
const LOTICS_INCLUDE_GLOB = ".lotics/**/*";
|
|
76
|
+
/** The bare `.lotics` forms an older starter emitted — all skipped by the glob walk. */
|
|
77
|
+
const STALE_LOTICS_INCLUDES = new Set([".lotics", "./.lotics", ".lotics/"]);
|
|
67
78
|
/**
|
|
68
79
|
* The `async function __workflow(...)` wrapper a workflow body sits inside —
|
|
69
80
|
* the SAME envelope the server compiles the body within at `set_app_workflow`
|
|
@@ -304,20 +315,27 @@ function writeAppMeta(projectDir, meta) {
|
|
|
304
315
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
305
316
|
}
|
|
306
317
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
318
|
+
* Heal a pre-existing app's `tsconfig.json` so its generated types load and its
|
|
319
|
+
* `npm run typecheck` stays honest. Idempotent, run on every pull / codegen /
|
|
320
|
+
* deploy. Two things the current starter ships but an app SCAFFOLDED before those
|
|
321
|
+
* releases (or with a hand-written tsconfig) can lack:
|
|
322
|
+
*
|
|
323
|
+
* 1. `include`: a bare `.lotics` is rewritten to the recursive `LOTICS_INCLUDE_GLOB`.
|
|
324
|
+
* A bare dot-dir is skipped by TypeScript's include-glob walk, so the codegen'd
|
|
325
|
+
* `.d.ts` never enter the program — every `useQuery`/`useWorkflow`/`useAgentRun`
|
|
326
|
+
* param and result silently falls back to `unknown`.
|
|
327
|
+
* 2. `exclude`: the workflow-body globs — the bodies use the app's DOM lib (the
|
|
328
|
+
* server doesn't) and the per-alias ambient globals collide on `trigger`,
|
|
329
|
+
* poisoning typecheck. Checked separately by `lotics app workflow check`.
|
|
330
|
+
*
|
|
331
|
+
* A missing / unparseable tsconfig is a non-fatal warn (the caller still succeeds);
|
|
332
|
+
* the author fixes the config.
|
|
315
333
|
*/
|
|
316
|
-
export function
|
|
334
|
+
export function ensureAppTsconfig(projectDir) {
|
|
317
335
|
const tsconfigPath = path.join(projectDir, "tsconfig.json");
|
|
318
336
|
if (!fs.existsSync(tsconfigPath)) {
|
|
319
|
-
console.error(`⚠ No tsconfig.json at ${projectDir} — could not ensure
|
|
320
|
-
`
|
|
337
|
+
console.error(`⚠ No tsconfig.json at ${projectDir} — could not ensure "include" has "${LOTICS_INCLUDE_GLOB}" ` +
|
|
338
|
+
`(a bare .lotics loads none of the generated types) or "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")}.`);
|
|
321
339
|
return;
|
|
322
340
|
}
|
|
323
341
|
let parsed;
|
|
@@ -326,30 +344,47 @@ export function ensureWorkflowTsconfigExcludes(projectDir) {
|
|
|
326
344
|
}
|
|
327
345
|
catch (err) {
|
|
328
346
|
console.error(`⚠ Could not parse tsconfig.json (${err instanceof Error ? err.message : String(err)}) — ` +
|
|
329
|
-
`
|
|
347
|
+
`ensure "include" has "${LOTICS_INCLUDE_GLOB}" and "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")} manually.`);
|
|
330
348
|
return;
|
|
331
349
|
}
|
|
332
350
|
if (!parsed || typeof parsed !== "object")
|
|
333
351
|
return;
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
352
|
+
const changes = [];
|
|
353
|
+
// 1. Heal a stale bare `.lotics` include so the generated `.d.ts` actually load
|
|
354
|
+
// (a bare dot-dir is invisible to TypeScript's include-glob walk).
|
|
355
|
+
if (Array.isArray(parsed.include)) {
|
|
356
|
+
const inc = parsed.include.filter((e) => typeof e === "string");
|
|
357
|
+
if (inc.some((e) => STALE_LOTICS_INCLUDES.has(e))) {
|
|
358
|
+
const healed = inc.map((e) => (STALE_LOTICS_INCLUDES.has(e) ? LOTICS_INCLUDE_GLOB : e));
|
|
359
|
+
parsed.include = healed.filter((e, i) => healed.indexOf(e) === i); // dedupe if the glob was already present
|
|
360
|
+
changes.push(`rewrote a bare ".lotics" to "${LOTICS_INCLUDE_GLOB}" in "include" (a dot-dir loads zero generated types)`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// 2. Ensure the workflow-body excludes. `exclude` is a TOP-LEVEL tsconfig field —
|
|
364
|
+
// tsc ignores `compilerOptions.exclude`, so they must land at the top level.
|
|
365
|
+
const currentEx = Array.isArray(parsed.exclude)
|
|
366
|
+
? parsed.exclude.filter((e) => typeof e === "string")
|
|
367
|
+
: [];
|
|
368
|
+
const toAdd = WORKFLOW_TSCONFIG_EXCLUDES.filter((g) => !currentEx.includes(g));
|
|
369
|
+
if (toAdd.length > 0) {
|
|
370
|
+
parsed.exclude = [...currentEx, ...toAdd];
|
|
371
|
+
changes.push(`added ${toAdd.join(", ")} to "exclude"`);
|
|
372
|
+
}
|
|
373
|
+
if (changes.length === 0)
|
|
341
374
|
return;
|
|
342
|
-
parsed.exclude = [...currentStrings, ...toAdd];
|
|
343
375
|
fs.writeFileSync(tsconfigPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
344
|
-
console.error(`Patched tsconfig.json:
|
|
345
|
-
`(check them with: lotics app workflow check).`);
|
|
376
|
+
console.error(`Patched tsconfig.json: ${changes.join("; ")}.`);
|
|
346
377
|
}
|
|
347
378
|
/**
|
|
348
|
-
* Write
|
|
349
|
-
* manifest's
|
|
350
|
-
* dev / deploy
|
|
351
|
-
*
|
|
352
|
-
*
|
|
379
|
+
* Write the three `.lotics/app_{workflows,queries,agents}.d.ts` companions from
|
|
380
|
+
* the manifest's maps, then heal the app's tsconfig so they actually load. Called
|
|
381
|
+
* from `app create / pull / dev / deploy / codegen`, so the augmented `AppWorkflows`
|
|
382
|
+
* / `AppQueries` / `AppAgents` types stay in sync with the manifest.
|
|
383
|
+
*
|
|
384
|
+
* The heal is at the write boundary on purpose: a `.d.ts` written but not loaded is
|
|
385
|
+
* useless (a bare `.lotics` include is skipped by TypeScript's include-glob walk and
|
|
386
|
+
* loads zero of them), so `ensureAppTsconfig` couples "wrote the types" with "the
|
|
387
|
+
* program can see them" — no caller can do one without the other.
|
|
353
388
|
*/
|
|
354
389
|
function writeAppDts(projectDir, manifest) {
|
|
355
390
|
const dotLotics = path.join(projectDir, ".lotics");
|
|
@@ -361,6 +396,7 @@ function writeAppDts(projectDir, manifest) {
|
|
|
361
396
|
];
|
|
362
397
|
for (const [file, content] of written)
|
|
363
398
|
fs.writeFileSync(file, content);
|
|
399
|
+
ensureAppTsconfig(projectDir);
|
|
364
400
|
return written.map(([file]) => file);
|
|
365
401
|
}
|
|
366
402
|
/**
|
|
@@ -450,10 +486,6 @@ export async function appCodegen(args) {
|
|
|
450
486
|
// it, so the local typecheck tracks the current workspace schema. Aliases
|
|
451
487
|
// never pulled (no body file yet) are skipped — codegen isn't a pull.
|
|
452
488
|
await refreshWorkflowGlobals(args.client, projectDir, meta.app_id, Object.keys(meta.workflows ?? {}));
|
|
453
|
-
// Keep the main tsconfig excluding the workflow-body globs (idempotent) so
|
|
454
|
-
// npm run typecheck never loads the bodies or their colliding per-alias globals.
|
|
455
|
-
if (Object.keys(meta.workflows ?? {}).length > 0)
|
|
456
|
-
ensureWorkflowTsconfigExcludes(projectDir);
|
|
457
489
|
}
|
|
458
490
|
/**
|
|
459
491
|
* For each bound alias that already has a local body file, fetch its current
|
|
@@ -745,9 +777,6 @@ export async function appPull(client, args) {
|
|
|
745
777
|
if (written.length > 0) {
|
|
746
778
|
console.error(`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`);
|
|
747
779
|
}
|
|
748
|
-
// A pre-existing app's tsconfig may predate the workflow-body excludes; pulling
|
|
749
|
-
// bodies into it would break its npm run typecheck. Patch it idempotently.
|
|
750
|
-
ensureWorkflowTsconfigExcludes(targetPath);
|
|
751
780
|
}
|
|
752
781
|
console.error(`Installing npm dependencies...`);
|
|
753
782
|
await runNpm(["install"], targetPath);
|
|
@@ -806,7 +835,11 @@ function readAppSourceText(projectDir) {
|
|
|
806
835
|
* both archives in R2, creates an app_versions row, and atomically advances
|
|
807
836
|
* the app's current_version pointer.
|
|
808
837
|
*/
|
|
809
|
-
export async function appDeploy(client,
|
|
838
|
+
export async function appDeploy(client,
|
|
839
|
+
// `message` is required — each deploy is a version row read back by
|
|
840
|
+
// `lotics app versions`; a blank message loses the audit trail. The CLI
|
|
841
|
+
// enforces non-empty at the dispatch; the type enforces it for every caller.
|
|
842
|
+
args) {
|
|
810
843
|
const projectDir = path.resolve(args.projectDir ?? process.cwd());
|
|
811
844
|
const meta = readAppMeta(projectDir);
|
|
812
845
|
// Pre-flight (GAP-29): a capability the code calls but the manifest doesn't
|
|
@@ -1183,9 +1216,10 @@ export async function appWorkflowPull(client) {
|
|
|
1183
1216
|
const written = await writeWorkflowFiles(client, projectDir, meta.app_id, aliases);
|
|
1184
1217
|
console.error(`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` +
|
|
1185
1218
|
(written.length > 0 ? ` (${written.join(", ")})` : ""));
|
|
1186
|
-
//
|
|
1187
|
-
//
|
|
1188
|
-
|
|
1219
|
+
// This command writes workflow BODIES, not the `.d.ts` — so it doesn't reach
|
|
1220
|
+
// `writeAppDts`'s heal. Heal here so the bodies land excluded and a stale
|
|
1221
|
+
// `.lotics` include doesn't leave the app's other generated types dead.
|
|
1222
|
+
ensureAppTsconfig(projectDir);
|
|
1189
1223
|
}
|
|
1190
1224
|
/**
|
|
1191
1225
|
* `lotics app workflow check [alias]` — local TypeScript type check of the
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { stampPulledManifest, undeclaredCapabilities, appDirName, defaultPullTarget,
|
|
5
|
+
import { stampPulledManifest, undeclaredCapabilities, appDirName, defaultPullTarget, ensureAppTsconfig, appCodegen, appUiLink, appVersions, appWorkflowSet, appWorkflowPull, appExecuteWorkflow, writeWorkflowFile, writeWorkflowGlobals, stripWorkflowHeader, FALLBACK_ENVELOPE_PREFIX, FALLBACK_ENVELOPE_SUFFIX, } from "./app_commands.js";
|
|
6
6
|
/**
|
|
7
7
|
* `appPull` reads workflows from the live App row (server response), NOT from
|
|
8
8
|
* the manifest embedded in the extracted source archive. The frozen archive
|
|
@@ -680,7 +680,7 @@ describe("defaultPullTarget", () => {
|
|
|
680
680
|
expect(defaultPullTarget("app_X", "Sales Tracker")).toBe("Sales Tracker");
|
|
681
681
|
});
|
|
682
682
|
});
|
|
683
|
-
describe("
|
|
683
|
+
describe("ensureAppTsconfig", () => {
|
|
684
684
|
let dir;
|
|
685
685
|
beforeEach(() => {
|
|
686
686
|
dir = fs.mkdtempSync(path.join(tmpdir(), "lotics-tsconfig-"));
|
|
@@ -692,37 +692,60 @@ describe("ensureWorkflowTsconfigExcludes", () => {
|
|
|
692
692
|
const read = () => JSON.parse(fs.readFileSync(tsconfigPath(), "utf-8"));
|
|
693
693
|
it("adds both workflow globs to the TOP-LEVEL exclude", () => {
|
|
694
694
|
fs.writeFileSync(tsconfigPath(), JSON.stringify({ compilerOptions: {}, exclude: ["node_modules"] }));
|
|
695
|
-
|
|
695
|
+
ensureAppTsconfig(dir);
|
|
696
696
|
expect(read().exclude).toEqual(["node_modules", "src/workflows", ".lotics/workflows"]);
|
|
697
697
|
});
|
|
698
698
|
it("writes TOP-LEVEL even when a compilerOptions.exclude exists (tsc ignores the nested key)", () => {
|
|
699
699
|
fs.writeFileSync(tsconfigPath(), JSON.stringify({ compilerOptions: { exclude: ["x"] } }));
|
|
700
|
-
|
|
700
|
+
ensureAppTsconfig(dir);
|
|
701
701
|
const cfg = read();
|
|
702
702
|
expect(cfg.exclude).toEqual(["src/workflows", ".lotics/workflows"]);
|
|
703
703
|
expect(cfg.compilerOptions.exclude).toEqual(["x"]); // left untouched (and tsc-ignored)
|
|
704
704
|
});
|
|
705
705
|
it("preserves pre-existing top-level excludes", () => {
|
|
706
706
|
fs.writeFileSync(tsconfigPath(), JSON.stringify({ exclude: ["node_modules", "dist"] }));
|
|
707
|
-
|
|
707
|
+
ensureAppTsconfig(dir);
|
|
708
708
|
expect(read().exclude).toEqual(["node_modules", "dist", "src/workflows", ".lotics/workflows"]);
|
|
709
709
|
});
|
|
710
710
|
it("is idempotent — a second call writes nothing new", () => {
|
|
711
711
|
fs.writeFileSync(tsconfigPath(), JSON.stringify({ exclude: ["node_modules"] }));
|
|
712
|
-
|
|
712
|
+
ensureAppTsconfig(dir);
|
|
713
713
|
const afterFirst = fs.readFileSync(tsconfigPath(), "utf-8");
|
|
714
|
-
|
|
714
|
+
ensureAppTsconfig(dir);
|
|
715
715
|
expect(fs.readFileSync(tsconfigPath(), "utf-8")).toBe(afterFirst);
|
|
716
716
|
});
|
|
717
717
|
it("warns + does not throw or create a file when there is no tsconfig", () => {
|
|
718
|
-
expect(() =>
|
|
718
|
+
expect(() => ensureAppTsconfig(dir)).not.toThrow();
|
|
719
719
|
expect(fs.existsSync(tsconfigPath())).toBe(false);
|
|
720
720
|
});
|
|
721
721
|
it("warns + does not throw on an unparseable tsconfig (left as-is)", () => {
|
|
722
722
|
fs.writeFileSync(tsconfigPath(), "{ not json,, }");
|
|
723
|
-
expect(() =>
|
|
723
|
+
expect(() => ensureAppTsconfig(dir)).not.toThrow();
|
|
724
724
|
expect(fs.readFileSync(tsconfigPath(), "utf-8")).toBe("{ not json,, }");
|
|
725
725
|
});
|
|
726
|
+
it("rewrites a bare .lotics include to the glob (a dot-dir loads zero generated types)", () => {
|
|
727
|
+
fs.writeFileSync(tsconfigPath(), JSON.stringify({ include: ["src", ".lotics"], exclude: ["node_modules"] }));
|
|
728
|
+
ensureAppTsconfig(dir);
|
|
729
|
+
expect(read().include).toEqual(["src", ".lotics/**/*"]);
|
|
730
|
+
});
|
|
731
|
+
it("leaves an already-correct .lotics/**/* include untouched (idempotent)", () => {
|
|
732
|
+
fs.writeFileSync(tsconfigPath(), JSON.stringify({ include: ["src", ".lotics/**/*"], exclude: ["node_modules", "src/workflows", ".lotics/workflows"] }));
|
|
733
|
+
const before = fs.readFileSync(tsconfigPath(), "utf-8");
|
|
734
|
+
ensureAppTsconfig(dir);
|
|
735
|
+
expect(fs.readFileSync(tsconfigPath(), "utf-8")).toBe(before);
|
|
736
|
+
});
|
|
737
|
+
it("dedupes when both a bare .lotics and the glob are present", () => {
|
|
738
|
+
fs.writeFileSync(tsconfigPath(), JSON.stringify({ include: [".lotics", ".lotics/**/*", "src"] }));
|
|
739
|
+
ensureAppTsconfig(dir);
|
|
740
|
+
expect(read().include).toEqual([".lotics/**/*", "src"]);
|
|
741
|
+
});
|
|
742
|
+
it("heals the include AND adds the excludes in a single write", () => {
|
|
743
|
+
fs.writeFileSync(tsconfigPath(), JSON.stringify({ include: ["src", ".lotics"], exclude: ["node_modules"] }));
|
|
744
|
+
ensureAppTsconfig(dir);
|
|
745
|
+
const cfg = read();
|
|
746
|
+
expect(cfg.include).toEqual(["src", ".lotics/**/*"]);
|
|
747
|
+
expect(cfg.exclude).toEqual(["node_modules", "src/workflows", ".lotics/workflows"]);
|
|
748
|
+
});
|
|
726
749
|
});
|
|
727
750
|
describe("appVersions", () => {
|
|
728
751
|
let logLines;
|
package/dist/cli.js
CHANGED
|
@@ -71,7 +71,8 @@ COMMANDS
|
|
|
71
71
|
Download all files on a record file field
|
|
72
72
|
lotics app create <name> [path] Create a new custom-code app + scaffold locally
|
|
73
73
|
lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
|
|
74
|
-
lotics app deploy
|
|
74
|
+
lotics app deploy -m <message> Build + upload current dir as a new version
|
|
75
|
+
(-m is required — it's the version's audit trail)
|
|
75
76
|
(code + queries only — workflow bindings are
|
|
76
77
|
managed by set_app_workflow / remove_app_workflow)
|
|
77
78
|
lotics app versions [app_id] Show deploy history newest-first (version,
|
|
@@ -598,7 +599,7 @@ async function main() {
|
|
|
598
599
|
console.error("Usage:");
|
|
599
600
|
console.error(" lotics app create <name> [path] Scaffold a new app locally");
|
|
600
601
|
console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
|
|
601
|
-
console.error(" lotics app deploy
|
|
602
|
+
console.error(" lotics app deploy -m <message> Build + upload the current directory (-m required)");
|
|
602
603
|
console.error(" lotics app versions [app_id] Show deploy history (version, when, who, message)");
|
|
603
604
|
console.error(" lotics app codegen [path] Regenerate .lotics/* (types + field ids) — no deploy");
|
|
604
605
|
console.error(" lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end");
|
|
@@ -729,7 +730,14 @@ async function main() {
|
|
|
729
730
|
}
|
|
730
731
|
if (subcommand === "deploy") {
|
|
731
732
|
// The message is either `-m <message>` or a bare positional arg after `deploy`.
|
|
732
|
-
|
|
733
|
+
// Required: every deploy is a version row, and `lotics app versions` reads
|
|
734
|
+
// the message back — a blank one makes the deploy history useless.
|
|
735
|
+
const message = (flags.message ?? toolArgs)?.trim();
|
|
736
|
+
if (!message) {
|
|
737
|
+
console.error('Usage: lotics app deploy -m "<what changed + why>"');
|
|
738
|
+
console.error("A deploy message is required — it's the version's audit trail (see `lotics app versions`).");
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
733
741
|
await appDeploy(client, { message });
|
|
734
742
|
return;
|
|
735
743
|
}
|
|
@@ -122,7 +122,7 @@ function inputDeclToTsType(decl) {
|
|
|
122
122
|
case "date_range":
|
|
123
123
|
return "{ start: string; end: string }";
|
|
124
124
|
case "file":
|
|
125
|
-
return "string";
|
|
125
|
+
return decl.multi === true ? "ReadonlyArray<string>" : "string";
|
|
126
126
|
case "object": {
|
|
127
127
|
const fields = decl.fields !== null && typeof decl.fields === "object"
|
|
128
128
|
? decl.fields
|
|
@@ -70,4 +70,14 @@ describe("generateAppWorkflowsDts", () => {
|
|
|
70
70
|
});
|
|
71
71
|
expect(dts).toContain('owner: string; tags: ReadonlyArray<"a" | "b">');
|
|
72
72
|
});
|
|
73
|
+
it("types a multi file input as ReadonlyArray<string>, single file as string", () => {
|
|
74
|
+
const dts = generateAppWorkflowsDts({
|
|
75
|
+
attach: {
|
|
76
|
+
workflow_id: "wfl_f",
|
|
77
|
+
inputs: { one: { type: "file" }, many: { type: "file", multi: true } },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(dts).toContain("one: string");
|
|
81
|
+
expect(dts).toContain("many: ReadonlyArray<string>");
|
|
82
|
+
});
|
|
73
83
|
});
|
package/dist/src/cli.js
CHANGED
|
@@ -3387,7 +3387,7 @@ var require_jstat = __commonJS({
|
|
|
3387
3387
|
}
|
|
3388
3388
|
})(exports2, function() {
|
|
3389
3389
|
var jStat = (function(Math2, undefined2) {
|
|
3390
|
-
var
|
|
3390
|
+
var concat2 = Array.prototype.concat;
|
|
3391
3391
|
var slice = Array.prototype.slice;
|
|
3392
3392
|
var toString = Object.prototype.toString;
|
|
3393
3393
|
function calcRdx(n, m) {
|
|
@@ -3407,7 +3407,7 @@ var require_jstat = __commonJS({
|
|
|
3407
3407
|
return typeof num === "number" ? num - num === 0 : false;
|
|
3408
3408
|
}
|
|
3409
3409
|
function toVector(arr) {
|
|
3410
|
-
return
|
|
3410
|
+
return concat2.apply([], arr);
|
|
3411
3411
|
}
|
|
3412
3412
|
function jStat2() {
|
|
3413
3413
|
return new jStat2._init(arguments);
|
|
@@ -20136,7 +20136,7 @@ var require_BufferList = __commonJS({
|
|
|
20136
20136
|
}
|
|
20137
20137
|
return ret;
|
|
20138
20138
|
};
|
|
20139
|
-
BufferList.prototype.concat = function
|
|
20139
|
+
BufferList.prototype.concat = function concat2(n) {
|
|
20140
20140
|
if (this.length === 0) return Buffer2.alloc(0);
|
|
20141
20141
|
var ret = Buffer2.allocUnsafe(n >>> 0);
|
|
20142
20142
|
var p = this.head;
|
|
@@ -23322,7 +23322,7 @@ var require_StreamHelper = __commonJS({
|
|
|
23322
23322
|
return utils.transformTo(type, content);
|
|
23323
23323
|
}
|
|
23324
23324
|
}
|
|
23325
|
-
function
|
|
23325
|
+
function concat2(type, dataArray) {
|
|
23326
23326
|
var i2, index = 0, res = null, totalLength = 0;
|
|
23327
23327
|
for (i2 = 0; i2 < dataArray.length; i2++) {
|
|
23328
23328
|
totalLength += dataArray[i2].length;
|
|
@@ -23359,7 +23359,7 @@ var require_StreamHelper = __commonJS({
|
|
|
23359
23359
|
reject2(err2);
|
|
23360
23360
|
}).on("end", function() {
|
|
23361
23361
|
try {
|
|
23362
|
-
var result = transformZipOutput(resultType,
|
|
23362
|
+
var result = transformZipOutput(resultType, concat2(chunkType, dataArray), mimeType);
|
|
23363
23363
|
resolve(result);
|
|
23364
23364
|
} catch (e) {
|
|
23365
23365
|
reject2(e);
|
|
@@ -31803,7 +31803,7 @@ function inputDeclToTsType(decl) {
|
|
|
31803
31803
|
case "date_range":
|
|
31804
31804
|
return "{ start: string; end: string }";
|
|
31805
31805
|
case "file":
|
|
31806
|
-
return "string";
|
|
31806
|
+
return decl.multi === true ? "ReadonlyArray<string>" : "string";
|
|
31807
31807
|
case "object": {
|
|
31808
31808
|
const fields = decl.fields !== null && typeof decl.fields === "object" ? decl.fields : {};
|
|
31809
31809
|
return inputsToType(fields);
|
|
@@ -32219,6 +32219,8 @@ async function fetchLatestNpmVersion(packageName) {
|
|
|
32219
32219
|
var WORKFLOWS_DIR = path5.join("src", "workflows");
|
|
32220
32220
|
var WORKFLOW_GLOBALS_DIR = path5.join(".lotics", "workflows");
|
|
32221
32221
|
var WORKFLOW_TSCONFIG_EXCLUDES = ["src/workflows", ".lotics/workflows"];
|
|
32222
|
+
var LOTICS_INCLUDE_GLOB = ".lotics/**/*";
|
|
32223
|
+
var STALE_LOTICS_INCLUDES = /* @__PURE__ */ new Set([".lotics", "./.lotics", ".lotics/"]);
|
|
32222
32224
|
var FALLBACK_ENVELOPE_PREFIX = "async function __workflow(): Promise<__WorkflowReturn | void> {\n";
|
|
32223
32225
|
var FALLBACK_ENVELOPE_SUFFIX = "\n}";
|
|
32224
32226
|
function workflowFileHeader(alias) {
|
|
@@ -32362,11 +32364,11 @@ function writeAppMeta(projectDir, meta) {
|
|
|
32362
32364
|
pkg2.lotics = meta;
|
|
32363
32365
|
fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
32364
32366
|
}
|
|
32365
|
-
function
|
|
32367
|
+
function ensureAppTsconfig(projectDir) {
|
|
32366
32368
|
const tsconfigPath = path5.join(projectDir, "tsconfig.json");
|
|
32367
32369
|
if (!fs4.existsSync(tsconfigPath)) {
|
|
32368
32370
|
console.error(
|
|
32369
|
-
`\u26A0 No tsconfig.json at ${projectDir} \u2014 could not ensure
|
|
32371
|
+
`\u26A0 No tsconfig.json at ${projectDir} \u2014 could not ensure "include" has "${LOTICS_INCLUDE_GLOB}" (a bare .lotics loads none of the generated types) or "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")}.`
|
|
32370
32372
|
);
|
|
32371
32373
|
return;
|
|
32372
32374
|
}
|
|
@@ -32375,20 +32377,29 @@ function ensureWorkflowTsconfigExcludes(projectDir) {
|
|
|
32375
32377
|
parsed = JSON.parse(fs4.readFileSync(tsconfigPath, "utf-8"));
|
|
32376
32378
|
} catch (err2) {
|
|
32377
32379
|
console.error(
|
|
32378
|
-
`\u26A0 Could not parse tsconfig.json (${err2 instanceof Error ? err2.message : String(err2)}) \u2014
|
|
32380
|
+
`\u26A0 Could not parse tsconfig.json (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 ensure "include" has "${LOTICS_INCLUDE_GLOB}" and "exclude" has ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")} manually.`
|
|
32379
32381
|
);
|
|
32380
32382
|
return;
|
|
32381
32383
|
}
|
|
32382
32384
|
if (!parsed || typeof parsed !== "object") return;
|
|
32383
|
-
const
|
|
32384
|
-
|
|
32385
|
-
|
|
32386
|
-
|
|
32387
|
-
|
|
32385
|
+
const changes = [];
|
|
32386
|
+
if (Array.isArray(parsed.include)) {
|
|
32387
|
+
const inc = parsed.include.filter((e) => typeof e === "string");
|
|
32388
|
+
if (inc.some((e) => STALE_LOTICS_INCLUDES.has(e))) {
|
|
32389
|
+
const healed = inc.map((e) => STALE_LOTICS_INCLUDES.has(e) ? LOTICS_INCLUDE_GLOB : e);
|
|
32390
|
+
parsed.include = healed.filter((e, i2) => healed.indexOf(e) === i2);
|
|
32391
|
+
changes.push(`rewrote a bare ".lotics" to "${LOTICS_INCLUDE_GLOB}" in "include" (a dot-dir loads zero generated types)`);
|
|
32392
|
+
}
|
|
32393
|
+
}
|
|
32394
|
+
const currentEx = Array.isArray(parsed.exclude) ? parsed.exclude.filter((e) => typeof e === "string") : [];
|
|
32395
|
+
const toAdd = WORKFLOW_TSCONFIG_EXCLUDES.filter((g) => !currentEx.includes(g));
|
|
32396
|
+
if (toAdd.length > 0) {
|
|
32397
|
+
parsed.exclude = [...currentEx, ...toAdd];
|
|
32398
|
+
changes.push(`added ${toAdd.join(", ")} to "exclude"`);
|
|
32399
|
+
}
|
|
32400
|
+
if (changes.length === 0) return;
|
|
32388
32401
|
fs4.writeFileSync(tsconfigPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
32389
|
-
console.error(
|
|
32390
|
-
`Patched tsconfig.json: added ${toAdd.join(", ")} to "exclude" so npm run typecheck skips the workflow bodies (check them with: lotics app workflow check).`
|
|
32391
|
-
);
|
|
32402
|
+
console.error(`Patched tsconfig.json: ${changes.join("; ")}.`);
|
|
32392
32403
|
}
|
|
32393
32404
|
function writeAppDts(projectDir, manifest) {
|
|
32394
32405
|
const dotLotics = path5.join(projectDir, ".lotics");
|
|
@@ -32399,6 +32410,7 @@ function writeAppDts(projectDir, manifest) {
|
|
|
32399
32410
|
[path5.join(dotLotics, "app_agents.d.ts"), generateAppAgentsDts(manifest.agents)]
|
|
32400
32411
|
];
|
|
32401
32412
|
for (const [file, content] of written) fs4.writeFileSync(file, content);
|
|
32413
|
+
ensureAppTsconfig(projectDir);
|
|
32402
32414
|
return written.map(([file]) => file);
|
|
32403
32415
|
}
|
|
32404
32416
|
function readCodegenTablesAllowlist(projectDir) {
|
|
@@ -32447,7 +32459,6 @@ async function appCodegen(args) {
|
|
|
32447
32459
|
);
|
|
32448
32460
|
}
|
|
32449
32461
|
await refreshWorkflowGlobals(args.client, projectDir, meta.app_id, Object.keys(meta.workflows ?? {}));
|
|
32450
|
-
if (Object.keys(meta.workflows ?? {}).length > 0) ensureWorkflowTsconfigExcludes(projectDir);
|
|
32451
32462
|
}
|
|
32452
32463
|
async function refreshWorkflowGlobals(client, projectDir, app_id, aliases) {
|
|
32453
32464
|
for (const alias of aliases) {
|
|
@@ -32630,7 +32641,6 @@ async function appPull(client, args) {
|
|
|
32630
32641
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
|
|
32631
32642
|
);
|
|
32632
32643
|
}
|
|
32633
|
-
ensureWorkflowTsconfigExcludes(targetPath);
|
|
32634
32644
|
}
|
|
32635
32645
|
console.error(`Installing npm dependencies...`);
|
|
32636
32646
|
await runNpm(["install"], targetPath);
|
|
@@ -32945,7 +32955,7 @@ async function appWorkflowPull(client) {
|
|
|
32945
32955
|
console.error(
|
|
32946
32956
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
|
|
32947
32957
|
);
|
|
32948
|
-
|
|
32958
|
+
ensureAppTsconfig(projectDir);
|
|
32949
32959
|
}
|
|
32950
32960
|
async function appWorkflowCheck(args) {
|
|
32951
32961
|
const projectDir = process.cwd();
|
|
@@ -42644,8 +42654,426 @@ function ensureVmlArray(val) {
|
|
|
42644
42654
|
return Array.isArray(val) ? val : [val];
|
|
42645
42655
|
}
|
|
42646
42656
|
|
|
42657
|
+
// ../xlsx/src/biff8_reader.ts
|
|
42658
|
+
var OLE2_MAGIC2 = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
42659
|
+
var ENDOFCHAIN = 4294967294;
|
|
42660
|
+
var FREESECT = 4294967295;
|
|
42661
|
+
var DEFAULT_MAX_ROWS = 1e4;
|
|
42662
|
+
function isOle2(bytes) {
|
|
42663
|
+
return bytes.length >= 8 && OLE2_MAGIC2.every((b, i2) => bytes[i2] === b);
|
|
42664
|
+
}
|
|
42665
|
+
function readCfbStreams(bytes) {
|
|
42666
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
42667
|
+
const u162 = (o) => dv.getUint16(o, true);
|
|
42668
|
+
const u32 = (o) => dv.getUint32(o, true);
|
|
42669
|
+
const sectorSize = 1 << u162(30);
|
|
42670
|
+
const miniSectorSize = 1 << u162(32);
|
|
42671
|
+
const firstDirSector = u32(48);
|
|
42672
|
+
const miniCutoff = u32(56);
|
|
42673
|
+
const firstMiniFatSector = u32(60);
|
|
42674
|
+
const firstDifatSector = u32(68);
|
|
42675
|
+
if (sectorSize < 512 || sectorSize > 1 << 20) {
|
|
42676
|
+
throw new Error("Unsupported .xls: invalid OLE2 sector size");
|
|
42677
|
+
}
|
|
42678
|
+
const sectorOffset = (n) => sectorSize * (n + 1);
|
|
42679
|
+
const difat = [];
|
|
42680
|
+
for (let i2 = 0; i2 < 109; i2++) {
|
|
42681
|
+
const v = u32(76 + i2 * 4);
|
|
42682
|
+
if (v !== FREESECT) difat.push(v);
|
|
42683
|
+
}
|
|
42684
|
+
let ds = firstDifatSector;
|
|
42685
|
+
const entriesPerSector = sectorSize / 4;
|
|
42686
|
+
for (let guard = 0; ds !== ENDOFCHAIN && ds !== FREESECT && guard < 1 << 20; guard++) {
|
|
42687
|
+
const base = sectorOffset(ds);
|
|
42688
|
+
for (let i2 = 0; i2 < entriesPerSector - 1; i2++) {
|
|
42689
|
+
const v = u32(base + i2 * 4);
|
|
42690
|
+
if (v !== FREESECT) difat.push(v);
|
|
42691
|
+
}
|
|
42692
|
+
ds = u32(base + (entriesPerSector - 1) * 4);
|
|
42693
|
+
}
|
|
42694
|
+
const fat = [];
|
|
42695
|
+
for (const fatSector of difat) {
|
|
42696
|
+
const base = sectorOffset(fatSector);
|
|
42697
|
+
for (let i2 = 0; i2 < entriesPerSector; i2++) fat.push(u32(base + i2 * 4));
|
|
42698
|
+
}
|
|
42699
|
+
const readChain = (src, start, size, ss, offOf, chain) => {
|
|
42700
|
+
const maxSectors = Math.floor(src.length / ss) + 2;
|
|
42701
|
+
const parts = [];
|
|
42702
|
+
let s = start;
|
|
42703
|
+
for (let guard = 0; s !== ENDOFCHAIN && s !== FREESECT && s < chain.length && guard < maxSectors; guard++) {
|
|
42704
|
+
const o = offOf(s);
|
|
42705
|
+
parts.push(src.subarray(o, o + ss));
|
|
42706
|
+
s = chain[s];
|
|
42707
|
+
}
|
|
42708
|
+
const merged = concat(parts);
|
|
42709
|
+
return size != null ? merged.subarray(0, size) : merged;
|
|
42710
|
+
};
|
|
42711
|
+
const readBig = (start, size) => readChain(bytes, start, size, sectorSize, sectorOffset, fat);
|
|
42712
|
+
const dirBytes = readBig(firstDirSector, null);
|
|
42713
|
+
const ddv = new DataView(dirBytes.buffer, dirBytes.byteOffset, dirBytes.byteLength);
|
|
42714
|
+
const dir = [];
|
|
42715
|
+
for (let off = 0; off + 128 <= dirBytes.length; off += 128) {
|
|
42716
|
+
const nameLen = ddv.getUint16(off + 64, true);
|
|
42717
|
+
const type = ddv.getUint8(off + 66);
|
|
42718
|
+
if (nameLen < 2 || type === 0) continue;
|
|
42719
|
+
const name = utf16le(dirBytes.subarray(off, off + nameLen - 2));
|
|
42720
|
+
dir.push({ name, type, start: ddv.getUint32(off + 116, true), size: Number(ddv.getBigUint64(off + 120, true)) });
|
|
42721
|
+
}
|
|
42722
|
+
const root = dir.find((e) => e.type === 5);
|
|
42723
|
+
const miniStream = root ? readBig(root.start, root.size) : new Uint8Array(0);
|
|
42724
|
+
const miniFatBytes = readBig(firstMiniFatSector, null);
|
|
42725
|
+
const miniFat = [];
|
|
42726
|
+
for (let i2 = 0; i2 + 4 <= miniFatBytes.length; i2 += 4) miniFat.push(miniFatBytes[i2] | miniFatBytes[i2 + 1] << 8 | miniFatBytes[i2 + 2] << 16 | miniFatBytes[i2 + 3] << 24);
|
|
42727
|
+
const readMini = (start, size) => readChain(miniStream, start, size, miniSectorSize, (n) => n * miniSectorSize, miniFat);
|
|
42728
|
+
const readStream = (e) => e.size >= miniCutoff ? readBig(e.start, e.size) : readMini(e.start, e.size);
|
|
42729
|
+
return dir.filter((e) => e.type === 2).map((e) => ({ name: e.name, bytes: readStream(e) }));
|
|
42730
|
+
}
|
|
42731
|
+
var REC = {
|
|
42732
|
+
FORMULA: 6,
|
|
42733
|
+
EOF: 10,
|
|
42734
|
+
CONTINUE: 60,
|
|
42735
|
+
DATEMODE: 34,
|
|
42736
|
+
FILEPASS: 47,
|
|
42737
|
+
BLANK: 513,
|
|
42738
|
+
NUMBER: 515,
|
|
42739
|
+
LABEL: 516,
|
|
42740
|
+
STRING: 519,
|
|
42741
|
+
BOOLERR: 517,
|
|
42742
|
+
BOUNDSHEET: 133,
|
|
42743
|
+
FORMAT: 1054,
|
|
42744
|
+
XF: 224,
|
|
42745
|
+
RK: 638,
|
|
42746
|
+
MULRK: 189,
|
|
42747
|
+
MULBLANK: 190,
|
|
42748
|
+
LABELSST: 253,
|
|
42749
|
+
SST: 252,
|
|
42750
|
+
BOF: 2057
|
|
42751
|
+
};
|
|
42752
|
+
function* iterRecords(stream, from, to) {
|
|
42753
|
+
const dv = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
|
|
42754
|
+
let p = from;
|
|
42755
|
+
while (p + 4 <= to) {
|
|
42756
|
+
const type = dv.getUint16(p, true);
|
|
42757
|
+
const len = dv.getUint16(p + 2, true);
|
|
42758
|
+
const bodyStart = p + 4;
|
|
42759
|
+
if (bodyStart + len > stream.length) break;
|
|
42760
|
+
yield { type, body: stream.subarray(bodyStart, bodyStart + len), offset: p };
|
|
42761
|
+
p = bodyStart + len;
|
|
42762
|
+
}
|
|
42763
|
+
}
|
|
42764
|
+
function parseGlobals(stream) {
|
|
42765
|
+
const sstChunks = [];
|
|
42766
|
+
const formats = /* @__PURE__ */ new Map();
|
|
42767
|
+
const xfFormatIds = [];
|
|
42768
|
+
const boundsheets = [];
|
|
42769
|
+
let date1904 = false;
|
|
42770
|
+
let sawSst = false;
|
|
42771
|
+
let sstUnique = 0;
|
|
42772
|
+
let end = stream.length;
|
|
42773
|
+
let started = false;
|
|
42774
|
+
const dv = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
|
|
42775
|
+
for (const r of iterRecords(stream, 0, stream.length)) {
|
|
42776
|
+
if (r.type === REC.BOF) {
|
|
42777
|
+
if (started) {
|
|
42778
|
+
end = r.offset;
|
|
42779
|
+
break;
|
|
42780
|
+
}
|
|
42781
|
+
started = true;
|
|
42782
|
+
continue;
|
|
42783
|
+
}
|
|
42784
|
+
if (r.type === REC.EOF) {
|
|
42785
|
+
end = r.offset;
|
|
42786
|
+
break;
|
|
42787
|
+
}
|
|
42788
|
+
}
|
|
42789
|
+
for (const r of iterRecords(stream, 0, end)) {
|
|
42790
|
+
switch (r.type) {
|
|
42791
|
+
case REC.FILEPASS:
|
|
42792
|
+
throw new PasswordProtectedError();
|
|
42793
|
+
case REC.DATEMODE:
|
|
42794
|
+
date1904 = readU16(r.body, 0) === 1;
|
|
42795
|
+
break;
|
|
42796
|
+
case REC.SST:
|
|
42797
|
+
sawSst = true;
|
|
42798
|
+
sstUnique = readU32(r.body, 4);
|
|
42799
|
+
sstChunks.push(r.body.subarray(8));
|
|
42800
|
+
break;
|
|
42801
|
+
case REC.CONTINUE:
|
|
42802
|
+
if (sawSst) sstChunks.push(r.body);
|
|
42803
|
+
break;
|
|
42804
|
+
case REC.FORMAT: {
|
|
42805
|
+
const id = readU16(r.body, 0);
|
|
42806
|
+
formats.set(id, readUnicodeShort(r.body, 2, 2));
|
|
42807
|
+
break;
|
|
42808
|
+
}
|
|
42809
|
+
case REC.XF: {
|
|
42810
|
+
xfFormatIds.push(readU16(r.body, 2));
|
|
42811
|
+
break;
|
|
42812
|
+
}
|
|
42813
|
+
case REC.BOUNDSHEET: {
|
|
42814
|
+
const pos = dv.getUint32(r.offset + 4, true);
|
|
42815
|
+
boundsheets.push({ name: readUnicodeShort(r.body, 6, 1), pos });
|
|
42816
|
+
break;
|
|
42817
|
+
}
|
|
42818
|
+
default:
|
|
42819
|
+
break;
|
|
42820
|
+
}
|
|
42821
|
+
}
|
|
42822
|
+
const sst = parseSst(sstChunks, sstUnique);
|
|
42823
|
+
const xfFormats = xfFormatIds.map((id) => formats.get(id) ?? builtinFormat(id));
|
|
42824
|
+
return { sst, date1904, xfFormats, boundsheets };
|
|
42825
|
+
}
|
|
42826
|
+
function parseSst(chunks, count) {
|
|
42827
|
+
const out = [];
|
|
42828
|
+
if (chunks.length === 0) return out;
|
|
42829
|
+
let ci = 0;
|
|
42830
|
+
let off = 0;
|
|
42831
|
+
const atEnd = () => ci >= chunks.length;
|
|
42832
|
+
const cur = () => chunks[ci];
|
|
42833
|
+
const advanceChunk = () => {
|
|
42834
|
+
ci++;
|
|
42835
|
+
off = 0;
|
|
42836
|
+
};
|
|
42837
|
+
const rawU8 = () => {
|
|
42838
|
+
while (!atEnd() && off >= cur().length) advanceChunk();
|
|
42839
|
+
if (atEnd()) return 0;
|
|
42840
|
+
return cur()[off++];
|
|
42841
|
+
};
|
|
42842
|
+
const rawU16 = () => rawU8() | rawU8() << 8;
|
|
42843
|
+
const rawU32 = () => rawU8() | rawU8() << 8 | rawU8() << 16 | rawU8() << 24;
|
|
42844
|
+
const skip = (n) => {
|
|
42845
|
+
for (let i2 = 0; i2 < n; i2++) rawU8();
|
|
42846
|
+
};
|
|
42847
|
+
for (let s = 0; s < count && !atEnd(); s++) {
|
|
42848
|
+
const cch = rawU16();
|
|
42849
|
+
const grbit = rawU8();
|
|
42850
|
+
let high = (grbit & 1) !== 0;
|
|
42851
|
+
const rich = (grbit & 8) !== 0 ? rawU16() : 0;
|
|
42852
|
+
const ext = (grbit & 4) !== 0 ? rawU32() : 0;
|
|
42853
|
+
let str = "";
|
|
42854
|
+
let read = 0;
|
|
42855
|
+
while (read < cch) {
|
|
42856
|
+
if (off >= cur().length) {
|
|
42857
|
+
advanceChunk();
|
|
42858
|
+
if (atEnd()) break;
|
|
42859
|
+
high = (cur()[off++] & 1) !== 0;
|
|
42860
|
+
}
|
|
42861
|
+
if (high) {
|
|
42862
|
+
const code = cur()[off] | cur()[off + 1] << 8;
|
|
42863
|
+
off += 2;
|
|
42864
|
+
str += String.fromCharCode(code);
|
|
42865
|
+
} else {
|
|
42866
|
+
str += String.fromCharCode(cur()[off++]);
|
|
42867
|
+
}
|
|
42868
|
+
read++;
|
|
42869
|
+
}
|
|
42870
|
+
skip(rich * 4 + ext);
|
|
42871
|
+
out.push(str);
|
|
42872
|
+
}
|
|
42873
|
+
return out;
|
|
42874
|
+
}
|
|
42875
|
+
function parseSheet2(stream, start, name, g, maxRows) {
|
|
42876
|
+
const sheet = emptySheet(name);
|
|
42877
|
+
const grid = /* @__PURE__ */ new Map();
|
|
42878
|
+
let maxRow = -1;
|
|
42879
|
+
let truncated = false;
|
|
42880
|
+
const put = (row, col, cell) => {
|
|
42881
|
+
if (row >= maxRows) {
|
|
42882
|
+
truncated = true;
|
|
42883
|
+
return;
|
|
42884
|
+
}
|
|
42885
|
+
let r = grid.get(row);
|
|
42886
|
+
if (!r) {
|
|
42887
|
+
r = /* @__PURE__ */ new Map();
|
|
42888
|
+
grid.set(row, r);
|
|
42889
|
+
}
|
|
42890
|
+
r.set(col, cell);
|
|
42891
|
+
if (row > maxRow) maxRow = row;
|
|
42892
|
+
};
|
|
42893
|
+
let end = stream.length;
|
|
42894
|
+
let seenBof = false;
|
|
42895
|
+
for (const r of iterRecords(stream, start, stream.length)) {
|
|
42896
|
+
if (r.type === REC.BOF) {
|
|
42897
|
+
if (seenBof) {
|
|
42898
|
+
end = r.offset;
|
|
42899
|
+
break;
|
|
42900
|
+
}
|
|
42901
|
+
seenBof = true;
|
|
42902
|
+
continue;
|
|
42903
|
+
}
|
|
42904
|
+
if (r.type === REC.EOF) {
|
|
42905
|
+
end = r.offset;
|
|
42906
|
+
break;
|
|
42907
|
+
}
|
|
42908
|
+
}
|
|
42909
|
+
for (const r of iterRecords(stream, start, end)) {
|
|
42910
|
+
switch (r.type) {
|
|
42911
|
+
case REC.LABELSST: {
|
|
42912
|
+
const row = readU16(r.body, 0), col = readU16(r.body, 2), isst = readU32(r.body, 6);
|
|
42913
|
+
put(row, col, textCell(col, g.sst[isst] ?? ""));
|
|
42914
|
+
break;
|
|
42915
|
+
}
|
|
42916
|
+
case REC.LABEL: {
|
|
42917
|
+
const row = readU16(r.body, 0), col = readU16(r.body, 2);
|
|
42918
|
+
put(row, col, textCell(col, readUnicodeShort(r.body, 6, 2)));
|
|
42919
|
+
break;
|
|
42920
|
+
}
|
|
42921
|
+
case REC.NUMBER: {
|
|
42922
|
+
const row = readU16(r.body, 0), col = readU16(r.body, 2), xf = readU16(r.body, 4);
|
|
42923
|
+
put(row, col, numberCell(col, readF64(r.body, 6), g.xfFormats[xf]));
|
|
42924
|
+
break;
|
|
42925
|
+
}
|
|
42926
|
+
case REC.RK: {
|
|
42927
|
+
const row = readU16(r.body, 0), col = readU16(r.body, 2), xf = readU16(r.body, 4);
|
|
42928
|
+
put(row, col, numberCell(col, decodeRk(readU32(r.body, 6)), g.xfFormats[xf]));
|
|
42929
|
+
break;
|
|
42930
|
+
}
|
|
42931
|
+
case REC.MULRK: {
|
|
42932
|
+
const row = readU16(r.body, 0), first2 = readU16(r.body, 2);
|
|
42933
|
+
const n = (r.body.length - 6) / 6;
|
|
42934
|
+
for (let i2 = 0; i2 < n; i2++) {
|
|
42935
|
+
const xf = readU16(r.body, 4 + i2 * 6);
|
|
42936
|
+
const rk = readU32(r.body, 6 + i2 * 6);
|
|
42937
|
+
put(row, first2 + i2, numberCell(first2 + i2, decodeRk(rk), g.xfFormats[xf]));
|
|
42938
|
+
}
|
|
42939
|
+
break;
|
|
42940
|
+
}
|
|
42941
|
+
case REC.FORMULA: {
|
|
42942
|
+
const row = readU16(r.body, 0), col = readU16(r.body, 2), xf = readU16(r.body, 4);
|
|
42943
|
+
const cell = formulaCell(col, r.body, g.xfFormats[xf]);
|
|
42944
|
+
if (cell) put(row, col, cell);
|
|
42945
|
+
break;
|
|
42946
|
+
}
|
|
42947
|
+
// BLANK / MULBLANK / BOOLERR carry no data value we surface — skipped.
|
|
42948
|
+
default:
|
|
42949
|
+
break;
|
|
42950
|
+
}
|
|
42951
|
+
}
|
|
42952
|
+
const rows = [];
|
|
42953
|
+
for (let ri = 0; ri <= maxRow; ri++) {
|
|
42954
|
+
const r = grid.get(ri);
|
|
42955
|
+
if (!r) continue;
|
|
42956
|
+
const cells = [...r.values()].sort((a, b) => a.column - b.column);
|
|
42957
|
+
rows.push({ index: ri, height: sheet.defaultRowHeight, cells, hidden: false });
|
|
42958
|
+
}
|
|
42959
|
+
sheet.rows = rows;
|
|
42960
|
+
sheet.totalRowCount = maxRow + 1;
|
|
42961
|
+
sheet.truncated = truncated;
|
|
42962
|
+
return sheet;
|
|
42963
|
+
}
|
|
42964
|
+
function textCell(column, text) {
|
|
42965
|
+
return { column, value: text, typedValue: text, content: { type: "plain", text }, style: {} };
|
|
42966
|
+
}
|
|
42967
|
+
function numberCell(column, num, numFmt) {
|
|
42968
|
+
const value = Number.isFinite(num) ? String(num) : "";
|
|
42969
|
+
return {
|
|
42970
|
+
column,
|
|
42971
|
+
value,
|
|
42972
|
+
rawValue: num,
|
|
42973
|
+
typedValue: num,
|
|
42974
|
+
numFmtCode: numFmt && numFmt !== "General" ? numFmt : void 0,
|
|
42975
|
+
content: { type: "plain", text: value },
|
|
42976
|
+
style: {}
|
|
42977
|
+
};
|
|
42978
|
+
}
|
|
42979
|
+
function formulaCell(column, body, numFmt) {
|
|
42980
|
+
if (readU16(body, 12) === 65535) {
|
|
42981
|
+
const kind = body[6];
|
|
42982
|
+
if (kind === 1) {
|
|
42983
|
+
const b = body[8] !== 0;
|
|
42984
|
+
return { column, value: b ? "TRUE" : "FALSE", typedValue: b, content: { type: "plain", text: b ? "TRUE" : "FALSE" }, style: {} };
|
|
42985
|
+
}
|
|
42986
|
+
return null;
|
|
42987
|
+
}
|
|
42988
|
+
return numberCell(column, readF64(body, 6), numFmt);
|
|
42989
|
+
}
|
|
42990
|
+
function decodeRk(rk) {
|
|
42991
|
+
const div100 = (rk & 1) !== 0;
|
|
42992
|
+
const isInt = (rk & 2) !== 0;
|
|
42993
|
+
let n;
|
|
42994
|
+
if (isInt) {
|
|
42995
|
+
n = rk >> 2;
|
|
42996
|
+
} else {
|
|
42997
|
+
const buf = new ArrayBuffer(8);
|
|
42998
|
+
new DataView(buf).setUint32(4, rk & 4294967292, true);
|
|
42999
|
+
n = new DataView(buf).getFloat64(0, true);
|
|
43000
|
+
}
|
|
43001
|
+
return div100 ? n / 100 : n;
|
|
43002
|
+
}
|
|
43003
|
+
function readU16(b, o) {
|
|
43004
|
+
return b[o] | b[o + 1] << 8;
|
|
43005
|
+
}
|
|
43006
|
+
function readU32(b, o) {
|
|
43007
|
+
return (b[o] | b[o + 1] << 8 | b[o + 2] << 16 | b[o + 3] << 24) >>> 0;
|
|
43008
|
+
}
|
|
43009
|
+
function readF64(b, o) {
|
|
43010
|
+
return new DataView(b.buffer, b.byteOffset + o, 8).getFloat64(0, true);
|
|
43011
|
+
}
|
|
43012
|
+
function readUnicodeShort(b, offset, lenBytes) {
|
|
43013
|
+
const cch = lenBytes === 1 ? b[offset] : readU16(b, offset);
|
|
43014
|
+
const grbit = b[offset + lenBytes];
|
|
43015
|
+
const high = (grbit & 1) !== 0;
|
|
43016
|
+
let p = offset + lenBytes + 1;
|
|
43017
|
+
let s = "";
|
|
43018
|
+
for (let i2 = 0; i2 < cch; i2++) {
|
|
43019
|
+
if (high) {
|
|
43020
|
+
s += String.fromCharCode(b[p] | b[p + 1] << 8);
|
|
43021
|
+
p += 2;
|
|
43022
|
+
} else {
|
|
43023
|
+
s += String.fromCharCode(b[p]);
|
|
43024
|
+
p += 1;
|
|
43025
|
+
}
|
|
43026
|
+
}
|
|
43027
|
+
return s;
|
|
43028
|
+
}
|
|
43029
|
+
function utf16le(b) {
|
|
43030
|
+
let s = "";
|
|
43031
|
+
for (let i2 = 0; i2 + 1 < b.length; i2 += 2) s += String.fromCharCode(b[i2] | b[i2 + 1] << 8);
|
|
43032
|
+
return s;
|
|
43033
|
+
}
|
|
43034
|
+
function concat(parts) {
|
|
43035
|
+
let total = 0;
|
|
43036
|
+
for (const p of parts) total += p.length;
|
|
43037
|
+
const out = new Uint8Array(total);
|
|
43038
|
+
let o = 0;
|
|
43039
|
+
for (const p of parts) {
|
|
43040
|
+
out.set(p, o);
|
|
43041
|
+
o += p.length;
|
|
43042
|
+
}
|
|
43043
|
+
return out;
|
|
43044
|
+
}
|
|
43045
|
+
function builtinFormat(id) {
|
|
43046
|
+
const DATES = {
|
|
43047
|
+
14: "m/d/yyyy",
|
|
43048
|
+
15: "d-mmm-yy",
|
|
43049
|
+
16: "d-mmm",
|
|
43050
|
+
17: "mmm-yy",
|
|
43051
|
+
18: "h:mm AM/PM",
|
|
43052
|
+
19: "h:mm:ss AM/PM",
|
|
43053
|
+
20: "h:mm",
|
|
43054
|
+
21: "h:mm:ss",
|
|
43055
|
+
22: "m/d/yyyy h:mm",
|
|
43056
|
+
45: "mm:ss",
|
|
43057
|
+
46: "[h]:mm:ss",
|
|
43058
|
+
47: "mm:ss.0"
|
|
43059
|
+
};
|
|
43060
|
+
return DATES[id] ?? "General";
|
|
43061
|
+
}
|
|
43062
|
+
function parseBiff8(bytes, options) {
|
|
43063
|
+
const streams = readCfbStreams(bytes);
|
|
43064
|
+
if (streams.some((s) => /^EncryptedPackage$/i.test(s.name))) throw new PasswordProtectedError();
|
|
43065
|
+
const wb = streams.find((s) => /^workbook$/i.test(s.name)) ?? streams.find((s) => /^book$/i.test(s.name));
|
|
43066
|
+
if (!wb) throw new Error("Not a valid .xls: no Workbook stream in the OLE2 container");
|
|
43067
|
+
const g = parseGlobals(wb.bytes);
|
|
43068
|
+
const maxRows = options?.maxRowsPerSheet ?? DEFAULT_MAX_ROWS;
|
|
43069
|
+
const sheets = g.boundsheets.length > 0 ? g.boundsheets.map((bs) => parseSheet2(wb.bytes, bs.pos, bs.name, g, maxRows)) : [emptySheet("Sheet1")];
|
|
43070
|
+
return { sheets, activeSheetIndex: 0 };
|
|
43071
|
+
}
|
|
43072
|
+
|
|
42647
43073
|
// ../xlsx/src/excel_parser.ts
|
|
42648
43074
|
function parseExcelBuffer(arrayBuffer, options) {
|
|
43075
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
43076
|
+
if (isOle2(bytes)) return parseBiff8(bytes, options);
|
|
42649
43077
|
const zip = unzipXlsx(arrayBuffer);
|
|
42650
43078
|
return parseExcelFromZip(zip, options);
|
|
42651
43079
|
}
|
|
@@ -49203,7 +49631,8 @@ COMMANDS
|
|
|
49203
49631
|
Download all files on a record file field
|
|
49204
49632
|
lotics app create <name> [path] Create a new custom-code app + scaffold locally
|
|
49205
49633
|
lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
|
|
49206
|
-
lotics app deploy
|
|
49634
|
+
lotics app deploy -m <message> Build + upload current dir as a new version
|
|
49635
|
+
(-m is required \u2014 it's the version's audit trail)
|
|
49207
49636
|
(code + queries only \u2014 workflow bindings are
|
|
49208
49637
|
managed by set_app_workflow / remove_app_workflow)
|
|
49209
49638
|
lotics app versions [app_id] Show deploy history newest-first (version,
|
|
@@ -49683,7 +50112,7 @@ async function main() {
|
|
|
49683
50112
|
console.error("Usage:");
|
|
49684
50113
|
console.error(" lotics app create <name> [path] Scaffold a new app locally");
|
|
49685
50114
|
console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
|
|
49686
|
-
console.error(" lotics app deploy
|
|
50115
|
+
console.error(" lotics app deploy -m <message> Build + upload the current directory (-m required)");
|
|
49687
50116
|
console.error(" lotics app versions [app_id] Show deploy history (version, when, who, message)");
|
|
49688
50117
|
console.error(" lotics app codegen [path] Regenerate .lotics/* (types + field ids) \u2014 no deploy");
|
|
49689
50118
|
console.error(" lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end");
|
|
@@ -49807,7 +50236,12 @@ Available workspaces:`);
|
|
|
49807
50236
|
return;
|
|
49808
50237
|
}
|
|
49809
50238
|
if (subcommand === "deploy") {
|
|
49810
|
-
const message = flags.message ?? toolArgs;
|
|
50239
|
+
const message = (flags.message ?? toolArgs)?.trim();
|
|
50240
|
+
if (!message) {
|
|
50241
|
+
console.error('Usage: lotics app deploy -m "<what changed + why>"');
|
|
50242
|
+
console.error("A deploy message is required \u2014 it's the version's audit trail (see `lotics app versions`).");
|
|
50243
|
+
process.exit(1);
|
|
50244
|
+
}
|
|
49811
50245
|
await appDeploy(client, { message });
|
|
49812
50246
|
return;
|
|
49813
50247
|
}
|