@lotics/cli 0.162.0 → 0.164.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.js +601 -150
- package/dist/src/client.d.ts +15 -0
- package/dist/src/client.js +10 -0
- package/docs/cli_reference.md +3 -3
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -44177,7 +44177,9 @@ function gatewayErrorMessage(status) {
|
|
|
44177
44177
|
}
|
|
44178
44178
|
function transportErrorMessage(status, parsed) {
|
|
44179
44179
|
const jsonMessage = parsed && typeof parsed.message === "string" ? parsed.message : null;
|
|
44180
|
-
|
|
44180
|
+
if (jsonMessage === null) return gatewayErrorMessage(status);
|
|
44181
|
+
const authored = typeof parsed.code === "string" && parsed.code.length > 0;
|
|
44182
|
+
return status < 500 || authored ? jsonMessage : gatewayErrorMessage(status);
|
|
44181
44183
|
}
|
|
44182
44184
|
|
|
44183
44185
|
// src/client.ts
|
|
@@ -44478,6 +44480,16 @@ var LoticsClient = class {
|
|
|
44478
44480
|
async retirePackage(package_id, body) {
|
|
44479
44481
|
return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/retire`, body);
|
|
44480
44482
|
}
|
|
44483
|
+
/**
|
|
44484
|
+
* Edit a starter's registry listing — the name and description a stranger
|
|
44485
|
+
* reads before copying, and what every copy's app row is created from.
|
|
44486
|
+
*
|
|
44487
|
+
* A version is an immutable snapshot; the listing is not. Omit a field to
|
|
44488
|
+
* leave it, pass `description: null` to clear it. Owner-org admin-only.
|
|
44489
|
+
*/
|
|
44490
|
+
async editPackageListing(package_id, body) {
|
|
44491
|
+
return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/listing`, body);
|
|
44492
|
+
}
|
|
44481
44493
|
// --- Packages (registry reads + installations) ---
|
|
44482
44494
|
// Authoring is server-side: apps via `POST /v1/apps/{id}/package-publish|release`,
|
|
44483
44495
|
// content packages via the `publish_content`/`release_content` tools. There is no
|
|
@@ -45964,6 +45976,7 @@ var COMMANDS = [
|
|
|
45964
45976
|
},
|
|
45965
45977
|
{
|
|
45966
45978
|
verbs: ["xlsx"],
|
|
45979
|
+
ownsTail: true,
|
|
45967
45980
|
help: [
|
|
45968
45981
|
" lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem",
|
|
45969
45982
|
" (uses the bundled Lotics xlsx engine; prefer over",
|
|
@@ -45972,6 +45985,7 @@ var COMMANDS = [
|
|
|
45972
45985
|
},
|
|
45973
45986
|
{
|
|
45974
45987
|
verbs: ["docx"],
|
|
45988
|
+
ownsTail: true,
|
|
45975
45989
|
help: [
|
|
45976
45990
|
" lotics docx <subcommand> ... Read/write/edit .docx files on your local filesystem",
|
|
45977
45991
|
" (uses the bundled Lotics OOXML engine; preserves",
|
|
@@ -46003,6 +46017,32 @@ function renderCommandsHelp() {
|
|
|
46003
46017
|
function knownVerbs() {
|
|
46004
46018
|
return new Set(COMMANDS.flatMap((group) => [...group.verbs, ...group.aliases ?? []]));
|
|
46005
46019
|
}
|
|
46020
|
+
var SUBCOMMAND_ARGS = /* @__PURE__ */ new Map([
|
|
46021
|
+
["xlsx read", { valueFlags: ["--sheet", "--range"], flags: ["--with-format"] }],
|
|
46022
|
+
["xlsx set-cell", { content: true }],
|
|
46023
|
+
["docx read", { flags: ["--include-opaque"] }],
|
|
46024
|
+
["docx append-paragraph", { flags: ["--style="], content: true }],
|
|
46025
|
+
["docx insert-paragraph", { flags: ["--style=", "--at="], content: true }],
|
|
46026
|
+
["docx delete-block", { flags: ["--at="] }],
|
|
46027
|
+
["docx replace-text", { content: true }]
|
|
46028
|
+
]);
|
|
46029
|
+
function commandOwnsTail(command) {
|
|
46030
|
+
if (command === void 0) return false;
|
|
46031
|
+
return COMMANDS.some(
|
|
46032
|
+
(group) => group.ownsTail === true && [...group.verbs, ...group.aliases ?? []].includes(command)
|
|
46033
|
+
);
|
|
46034
|
+
}
|
|
46035
|
+
function subcommandArgs(command, subcommand) {
|
|
46036
|
+
if (command === void 0 || subcommand === void 0) return void 0;
|
|
46037
|
+
return SUBCOMMAND_ARGS.get(`${command} ${subcommand}`);
|
|
46038
|
+
}
|
|
46039
|
+
function knownSubcommandFlags(command, subcommand) {
|
|
46040
|
+
const spec = subcommandArgs(command, subcommand);
|
|
46041
|
+
return [...spec?.valueFlags ?? [], ...spec?.flags ?? []];
|
|
46042
|
+
}
|
|
46043
|
+
function matchesKnownFlag(known, arg) {
|
|
46044
|
+
return known.some((k) => k.endsWith("=") ? arg.startsWith(k) : arg === k);
|
|
46045
|
+
}
|
|
46006
46046
|
|
|
46007
46047
|
// src/report_command.ts
|
|
46008
46048
|
import fs6 from "node:fs";
|
|
@@ -46141,6 +46181,22 @@ function buildStarterTemplate(args) {
|
|
|
46141
46181
|
typecheck: "tsc --noEmit",
|
|
46142
46182
|
// Scope to src/ — oxlint's default scan walks node_modules too.
|
|
46143
46183
|
lint: "oxlint src",
|
|
46184
|
+
// No `--passWithNoTests`, deliberately. It would be justified by a
|
|
46185
|
+
// TEMPORARY condition ("the scaffold seeds nothing") and then ship
|
|
46186
|
+
// forever in every app's package.json and CI, where its only
|
|
46187
|
+
// remaining effect is to turn "vitest discovered no test files" into
|
|
46188
|
+
// success — a green suite over a renamed `src/`, a changed `include`
|
|
46189
|
+
// or a broken vitest config, indefinitely. A detector that cannot
|
|
46190
|
+
// fire when the thing is ABSENT rather than excessive is not one.
|
|
46191
|
+
//
|
|
46192
|
+
// What the scaffold seeds instead is `src/harness.test.ts`, which
|
|
46193
|
+
// asserts the HARNESS and nothing else. The file it replaces
|
|
46194
|
+
// (`src/App.test.tsx`) asserted the scaffold's placeholder copy
|
|
46195
|
+
// ("First item"), so it failed the moment the author did the very
|
|
46196
|
+
// thing the closing instructions tell them to do — edit
|
|
46197
|
+
// `src/App.tsx` — and a smoke test that renders `<App/>` would fail
|
|
46198
|
+
// the same way for any real app whose first screen reaches the host
|
|
46199
|
+
// bridge on mount. The harness test touches neither.
|
|
46144
46200
|
test: "vitest run"
|
|
46145
46201
|
},
|
|
46146
46202
|
dependencies: {
|
|
@@ -46716,15 +46772,33 @@ declare module "*.css";
|
|
|
46716
46772
|
`
|
|
46717
46773
|
},
|
|
46718
46774
|
{
|
|
46719
|
-
path: "src/
|
|
46775
|
+
path: "src/harness.test.ts",
|
|
46720
46776
|
content: `import { describe, test, expect } from "vitest";
|
|
46721
|
-
import {
|
|
46722
|
-
|
|
46777
|
+
import { getAppBinding } from "@lotics/app-sdk";
|
|
46778
|
+
|
|
46779
|
+
/**
|
|
46780
|
+
* The one test the scaffold seeds, and it is about the TEST SETUP rather than
|
|
46781
|
+
* about your app \u2014 so editing src/App.tsx can never break it, and \`npm test\`
|
|
46782
|
+
* never has to be green over zero tests.
|
|
46783
|
+
*
|
|
46784
|
+
* What it pins: tests run under jsdom, and vitest.setup.ts's echo stub answers
|
|
46785
|
+
* for \`getAppBinding\`. That second one is the whole reason the setup file
|
|
46786
|
+
* exists: a package-linked app's generated .lotics/app_fields.ts awaits it while
|
|
46787
|
+
* a test file is still COLLECTING, so without the stub every test fails before
|
|
46788
|
+
* any of them runs \u2014 including pure logic tests that never touch a field id.
|
|
46789
|
+
*
|
|
46790
|
+
* Delete it once you have tests of your own, or keep it; it costs nothing.
|
|
46791
|
+
*/
|
|
46792
|
+
describe("test harness", () => {
|
|
46793
|
+
test("runs in a DOM", () => {
|
|
46794
|
+
expect(typeof document).toBe("object");
|
|
46795
|
+
});
|
|
46723
46796
|
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
46797
|
+
test("resolves the app binding from the setup stub, so the app graph collects", async () => {
|
|
46798
|
+
const binding = await getAppBinding();
|
|
46799
|
+
// The echo stub answers any alias with a self-identifying ':test:' id \u2014 one
|
|
46800
|
+
// that can never be mistaken for a real fld_\u2026 from the workspace.
|
|
46801
|
+
expect(binding.fields["anything.at.all"]).toContain(":test:");
|
|
46728
46802
|
});
|
|
46729
46803
|
});
|
|
46730
46804
|
`
|
|
@@ -62911,7 +62985,20 @@ var readSourceSchema = zod_default.discriminatedUnion("from", [
|
|
|
62911
62985
|
zod_default.object({ from: zod_default.literal("trigger_record") }),
|
|
62912
62986
|
zod_default.object({ from: zod_default.literal("trigger_record_prev") }),
|
|
62913
62987
|
zod_default.object({ from: zod_default.literal("trigger_changes") }),
|
|
62914
|
-
|
|
62988
|
+
// `name` is the loop's bind name, and it is OPTIONAL for one reason: every
|
|
62989
|
+
// body stored before names existed emits the bare form, and those resolve to
|
|
62990
|
+
// the innermost frame — today's behaviour, and correct for any loop that is
|
|
62991
|
+
// not nested. Without a name a read inside a NESTED loop cannot say which
|
|
62992
|
+
// loop it means, so both binds resolve to the inner item and a join between
|
|
62993
|
+
// two collections silently matches nothing. The parser emits the name now, so
|
|
62994
|
+
// a body gets correct nesting the first time it is saved through it.
|
|
62995
|
+
zod_default.object({ from: zod_default.literal("foreach_item"), name: zod_default.string().min(1).optional() }),
|
|
62996
|
+
// No `name` here, deliberately: the JS surface spells the index as one
|
|
62997
|
+
// reserved `index` keyword with no per-loop form, so the innermost loop is
|
|
62998
|
+
// the only thing a read can mean. A name would be representable, honoured at
|
|
62999
|
+
// run time, and silently rewritten to the innermost by the renderer — which
|
|
63000
|
+
// prints every index as `index` — reintroducing the corruption above for a
|
|
63001
|
+
// case nothing can author.
|
|
62915
63002
|
zod_default.object({ from: zod_default.literal("foreach_index") }),
|
|
62916
63003
|
zod_default.object({
|
|
62917
63004
|
from: zod_default.literal("step_output"),
|
|
@@ -64810,7 +64897,14 @@ var formulaAggregateOperationSchema = external_exports.enum([
|
|
|
64810
64897
|
"median",
|
|
64811
64898
|
"min",
|
|
64812
64899
|
"max",
|
|
64813
|
-
"range"
|
|
64900
|
+
"range",
|
|
64901
|
+
"earliest",
|
|
64902
|
+
"latest",
|
|
64903
|
+
"date_range",
|
|
64904
|
+
"checked",
|
|
64905
|
+
"unchecked",
|
|
64906
|
+
"percent_checked",
|
|
64907
|
+
"percent_unchecked"
|
|
64814
64908
|
]);
|
|
64815
64909
|
var rollupAggregateOperationSchema = external_exports.enum([
|
|
64816
64910
|
"empty",
|
|
@@ -64910,25 +65004,25 @@ var aggregateOperationSchema = external_exports.enum([
|
|
|
64910
65004
|
"string_agg"
|
|
64911
65005
|
]);
|
|
64912
65006
|
var OPERATION_FIELD_TYPES = {
|
|
64913
|
-
empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup"],
|
|
64914
|
-
filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup"],
|
|
64915
|
-
percent_empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup"],
|
|
64916
|
-
percent_filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup"],
|
|
64917
|
-
unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup"],
|
|
64918
|
-
percent_unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup"],
|
|
65007
|
+
empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
65008
|
+
filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
65009
|
+
percent_empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
65010
|
+
percent_filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
65011
|
+
unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup", "autonumber"],
|
|
65012
|
+
percent_unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup", "autonumber"],
|
|
64919
65013
|
sum: ["number", "formula", "rollup"],
|
|
64920
65014
|
avg: ["number", "formula", "rollup"],
|
|
64921
65015
|
median: ["number", "formula", "rollup"],
|
|
64922
65016
|
min: ["number", "formula", "rollup"],
|
|
64923
65017
|
max: ["number", "formula", "rollup"],
|
|
64924
65018
|
range: ["number", "formula", "rollup"],
|
|
64925
|
-
earliest: ["date", "rollup"],
|
|
64926
|
-
latest: ["date", "rollup"],
|
|
64927
|
-
date_range: ["date", "rollup"],
|
|
64928
|
-
checked: ["boolean"],
|
|
64929
|
-
unchecked: ["boolean"],
|
|
64930
|
-
percent_checked: ["boolean"],
|
|
64931
|
-
percent_unchecked: ["boolean"]
|
|
65019
|
+
earliest: ["date", "formula", "rollup"],
|
|
65020
|
+
latest: ["date", "formula", "rollup"],
|
|
65021
|
+
date_range: ["date", "formula", "rollup"],
|
|
65022
|
+
checked: ["boolean", "formula"],
|
|
65023
|
+
unchecked: ["boolean", "formula"],
|
|
65024
|
+
percent_checked: ["boolean", "formula"],
|
|
65025
|
+
percent_unchecked: ["boolean", "formula"]
|
|
64932
65026
|
};
|
|
64933
65027
|
var FIELD_TYPE_OPERATIONS = /* @__PURE__ */ new Map();
|
|
64934
65028
|
for (const [operation, fieldTypes] of Object.entries(OPERATION_FIELD_TYPES)) {
|
|
@@ -65608,8 +65702,7 @@ var appWorkflowInputRecordLinkSchema = appWorkflowInputBaseSchema.extend({
|
|
|
65608
65702
|
table_id: zod_default.string().describe("Records must belong to this table. Validated at execute time + workflow save."),
|
|
65609
65703
|
multi: zod_default.boolean().optional().describe("When true, accept an array of record_ids. Defaults to single.")
|
|
65610
65704
|
});
|
|
65611
|
-
var
|
|
65612
|
-
type: zod_default.literal("select"),
|
|
65705
|
+
var selectOptionSourceShape = {
|
|
65613
65706
|
options: zod_default.array(
|
|
65614
65707
|
zod_default.object({
|
|
65615
65708
|
label: zod_default.string(),
|
|
@@ -65617,20 +65710,27 @@ var appWorkflowInputSelectSchema = appWorkflowInputBaseSchema.extend({
|
|
|
65617
65710
|
})
|
|
65618
65711
|
).min(1).optional().describe("Inline option set. Provide EITHER `options` OR `field`, never both."),
|
|
65619
65712
|
field: zod_default.string().optional().describe(
|
|
65620
|
-
"A select field's key (fld_\u2026) whose CURRENT options back this
|
|
65713
|
+
"A select field's key (fld_\u2026) whose CURRENT options back this declaration \u2014 resolved at type-gen, validated at run time, drift-proof. Provide EITHER `field` OR `options`, never both."
|
|
65621
65714
|
),
|
|
65622
65715
|
multi: zod_default.boolean().optional()
|
|
65623
|
-
}
|
|
65624
|
-
|
|
65625
|
-
|
|
65626
|
-
|
|
65627
|
-
ctx.
|
|
65628
|
-
|
|
65629
|
-
|
|
65630
|
-
|
|
65631
|
-
|
|
65632
|
-
|
|
65633
|
-
});
|
|
65716
|
+
};
|
|
65717
|
+
function checkExactlyOneOptionSource(kind) {
|
|
65718
|
+
return (ctx) => {
|
|
65719
|
+
const hasOptions = ctx.value.options !== void 0;
|
|
65720
|
+
const hasField = ctx.value.field !== void 0;
|
|
65721
|
+
if (hasOptions === hasField) {
|
|
65722
|
+
ctx.issues.push({
|
|
65723
|
+
code: "custom",
|
|
65724
|
+
input: ctx.value,
|
|
65725
|
+
message: `select ${kind} must declare exactly one of \`options\` (inline) or \`field\` (a fld_\u2026 key resolved to the field's current options) \u2014 not both, not neither`
|
|
65726
|
+
});
|
|
65727
|
+
}
|
|
65728
|
+
};
|
|
65729
|
+
}
|
|
65730
|
+
var appWorkflowInputSelectSchema = appWorkflowInputBaseSchema.extend({
|
|
65731
|
+
type: zod_default.literal("select"),
|
|
65732
|
+
...selectOptionSourceShape
|
|
65733
|
+
}).check(checkExactlyOneOptionSource("input"));
|
|
65634
65734
|
var appWorkflowInputDateRangeSchema = appWorkflowInputBaseSchema.extend({
|
|
65635
65735
|
type: zod_default.literal("date_range"),
|
|
65636
65736
|
include_time: zod_default.boolean().optional().describe("When true, the range includes time components (datetime). Defaults to false.")
|
|
@@ -65701,11 +65801,14 @@ var appWorkflowOutputSchema = zod_default.lazy(
|
|
|
65701
65801
|
table_id: zod_default.string(),
|
|
65702
65802
|
multi: zod_default.boolean().optional()
|
|
65703
65803
|
}),
|
|
65804
|
+
// The SAME shape and the SAME check as the select INPUT, not a copy of
|
|
65805
|
+
// them: the two surfaces declare one vocabulary (GAP-294), so accepting
|
|
65806
|
+
// `field` on one and not the other — or validating it differently — just
|
|
65807
|
+
// moves the drift somewhere nobody looks.
|
|
65704
65808
|
appWorkflowOutputBaseSchema.extend({
|
|
65705
65809
|
type: zod_default.literal("select"),
|
|
65706
|
-
|
|
65707
|
-
|
|
65708
|
-
}),
|
|
65810
|
+
...selectOptionSourceShape
|
|
65811
|
+
}).check(checkExactlyOneOptionSource("output")),
|
|
65709
65812
|
appWorkflowOutputBaseSchema.extend({
|
|
65710
65813
|
type: zod_default.literal("object"),
|
|
65711
65814
|
fields: zod_default.record(zod_default.string(), appWorkflowOutputSchema)
|
|
@@ -66075,7 +66178,6 @@ __export(expression_array_exports, {
|
|
|
66075
66178
|
includes: () => includes,
|
|
66076
66179
|
intersection: () => intersection2,
|
|
66077
66180
|
intersectionBy: () => intersectionBy,
|
|
66078
|
-
isEmpty: () => isEmpty2,
|
|
66079
66181
|
last: () => last2,
|
|
66080
66182
|
list: () => list,
|
|
66081
66183
|
maxBy: () => maxBy,
|
|
@@ -66530,13 +66632,6 @@ function size(arr) {
|
|
|
66530
66632
|
}
|
|
66531
66633
|
return arr.length;
|
|
66532
66634
|
}
|
|
66533
|
-
function isEmpty2(arr) {
|
|
66534
|
-
if (arr == null) return true;
|
|
66535
|
-
if (!Array.isArray(arr)) {
|
|
66536
|
-
throw new Error("isEmpty: expected array, got " + typeof arr);
|
|
66537
|
-
}
|
|
66538
|
-
return arr.length === 0;
|
|
66539
|
-
}
|
|
66540
66635
|
function includes(collection, value2) {
|
|
66541
66636
|
if (collection == null) return false;
|
|
66542
66637
|
if (Array.isArray(collection)) return collection.includes(value2);
|
|
@@ -66807,6 +66902,7 @@ function capitalize(str) {
|
|
|
66807
66902
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
66808
66903
|
}
|
|
66809
66904
|
function replace(str, search, replacement) {
|
|
66905
|
+
if (str == null) return "";
|
|
66810
66906
|
if (typeof str !== "string") {
|
|
66811
66907
|
throw new Error("replace: expected string, got " + typeof str);
|
|
66812
66908
|
}
|
|
@@ -66819,6 +66915,7 @@ function replace(str, search, replacement) {
|
|
|
66819
66915
|
return str.replace(search, replacement);
|
|
66820
66916
|
}
|
|
66821
66917
|
function replaceAll(str, search, replacement) {
|
|
66918
|
+
if (str == null) return "";
|
|
66822
66919
|
if (typeof str !== "string") {
|
|
66823
66920
|
throw new Error("replaceAll: expected string, got " + typeof str);
|
|
66824
66921
|
}
|
|
@@ -66851,6 +66948,7 @@ function endsWith(str, suffix) {
|
|
|
66851
66948
|
return str.endsWith(suffix);
|
|
66852
66949
|
}
|
|
66853
66950
|
function substring(str, start, end) {
|
|
66951
|
+
if (str == null) return "";
|
|
66854
66952
|
if (typeof str !== "string") {
|
|
66855
66953
|
throw new Error("substring: expected string, got " + typeof str);
|
|
66856
66954
|
}
|
|
@@ -66863,6 +66961,7 @@ function substring(str, start, end) {
|
|
|
66863
66961
|
return str.substring(start, end);
|
|
66864
66962
|
}
|
|
66865
66963
|
function padStart(str, length2, char) {
|
|
66964
|
+
if (str == null) return "";
|
|
66866
66965
|
if (typeof str !== "string") {
|
|
66867
66966
|
throw new Error("padStart: expected string, got " + typeof str);
|
|
66868
66967
|
}
|
|
@@ -66875,6 +66974,7 @@ function padStart(str, length2, char) {
|
|
|
66875
66974
|
return str.padStart(length2, char);
|
|
66876
66975
|
}
|
|
66877
66976
|
function padEnd(str, length2, char) {
|
|
66977
|
+
if (str == null) return "";
|
|
66878
66978
|
if (typeof str !== "string") {
|
|
66879
66979
|
throw new Error("padEnd: expected string, got " + typeof str);
|
|
66880
66980
|
}
|
|
@@ -66980,6 +67080,7 @@ function numberToWords(value2) {
|
|
|
66980
67080
|
return negative ? "\xE2m " + result : result;
|
|
66981
67081
|
}
|
|
66982
67082
|
function formatNumber(value2, decimals) {
|
|
67083
|
+
if (value2 == null) return "";
|
|
66983
67084
|
if (typeof value2 !== "number" || Number.isNaN(value2)) {
|
|
66984
67085
|
throw new Error("formatNumber: expected number, got " + typeof value2);
|
|
66985
67086
|
}
|
|
@@ -67007,15 +67108,17 @@ __export(expression_math_exports, {
|
|
|
67007
67108
|
sum: () => sum
|
|
67008
67109
|
});
|
|
67009
67110
|
function mean(arr) {
|
|
67010
|
-
if (arr == null
|
|
67111
|
+
if (arr == null) return null;
|
|
67112
|
+
if (!Array.isArray(arr)) {
|
|
67011
67113
|
throw new Error("mean: expected array, got " + typeof arr);
|
|
67012
67114
|
}
|
|
67013
|
-
|
|
67014
|
-
|
|
67115
|
+
const present = arr.filter((item) => item != null);
|
|
67116
|
+
if (present.length === 0) {
|
|
67117
|
+
return null;
|
|
67015
67118
|
}
|
|
67016
|
-
const numArr =
|
|
67017
|
-
const
|
|
67018
|
-
const result =
|
|
67119
|
+
const numArr = present;
|
|
67120
|
+
const total = numArr.reduce((a, b) => a + b, 0);
|
|
67121
|
+
const result = total / numArr.length;
|
|
67019
67122
|
if (isNaN(result)) {
|
|
67020
67123
|
throw new Error("mean: array contains non-numeric values");
|
|
67021
67124
|
}
|
|
@@ -67028,7 +67131,7 @@ function min(...values3) {
|
|
|
67028
67131
|
if (values3.length === 1 && Array.isArray(values3[0])) {
|
|
67029
67132
|
const arr = values3[0];
|
|
67030
67133
|
if (arr.length === 0) {
|
|
67031
|
-
|
|
67134
|
+
return null;
|
|
67032
67135
|
}
|
|
67033
67136
|
return arr.reduce((minVal, item) => {
|
|
67034
67137
|
if (minVal == null) return item;
|
|
@@ -67037,9 +67140,9 @@ function min(...values3) {
|
|
|
67037
67140
|
return item < minVal ? item : minVal;
|
|
67038
67141
|
}
|
|
67039
67142
|
return String(item) < String(minVal) ? item : minVal;
|
|
67040
|
-
});
|
|
67143
|
+
}) ?? null;
|
|
67041
67144
|
}
|
|
67042
|
-
|
|
67145
|
+
const smallest = values3.reduce((minVal, item) => {
|
|
67043
67146
|
if (minVal == null) return item;
|
|
67044
67147
|
if (item == null) return minVal;
|
|
67045
67148
|
if (typeof minVal === "number" && typeof item === "number") {
|
|
@@ -67047,6 +67150,7 @@ function min(...values3) {
|
|
|
67047
67150
|
}
|
|
67048
67151
|
return String(item) < String(minVal) ? item : minVal;
|
|
67049
67152
|
});
|
|
67153
|
+
return smallest ?? null;
|
|
67050
67154
|
}
|
|
67051
67155
|
function max(...values3) {
|
|
67052
67156
|
if (values3.length === 0) {
|
|
@@ -67055,7 +67159,7 @@ function max(...values3) {
|
|
|
67055
67159
|
if (values3.length === 1 && Array.isArray(values3[0])) {
|
|
67056
67160
|
const arr = values3[0];
|
|
67057
67161
|
if (arr.length === 0) {
|
|
67058
|
-
|
|
67162
|
+
return null;
|
|
67059
67163
|
}
|
|
67060
67164
|
return arr.reduce((maxVal, item) => {
|
|
67061
67165
|
if (maxVal == null) return item;
|
|
@@ -67064,9 +67168,9 @@ function max(...values3) {
|
|
|
67064
67168
|
return item > maxVal ? item : maxVal;
|
|
67065
67169
|
}
|
|
67066
67170
|
return String(item) > String(maxVal) ? item : maxVal;
|
|
67067
|
-
});
|
|
67171
|
+
}) ?? null;
|
|
67068
67172
|
}
|
|
67069
|
-
|
|
67173
|
+
const largest = values3.reduce((maxVal, item) => {
|
|
67070
67174
|
if (maxVal == null) return item;
|
|
67071
67175
|
if (item == null) return maxVal;
|
|
67072
67176
|
if (typeof maxVal === "number" && typeof item === "number") {
|
|
@@ -67074,8 +67178,10 @@ function max(...values3) {
|
|
|
67074
67178
|
}
|
|
67075
67179
|
return String(item) > String(maxVal) ? item : maxVal;
|
|
67076
67180
|
});
|
|
67181
|
+
return largest ?? null;
|
|
67077
67182
|
}
|
|
67078
67183
|
function round(value2, precision) {
|
|
67184
|
+
if (value2 == null) return null;
|
|
67079
67185
|
if (typeof value2 !== "number") {
|
|
67080
67186
|
throw new Error("round: expected number, got " + typeof value2);
|
|
67081
67187
|
}
|
|
@@ -67094,24 +67200,28 @@ function sum(arr) {
|
|
|
67094
67200
|
return arr.reduce((a, b) => a + b, 0);
|
|
67095
67201
|
}
|
|
67096
67202
|
function abs(n) {
|
|
67203
|
+
if (n == null) return null;
|
|
67097
67204
|
if (typeof n !== "number") {
|
|
67098
67205
|
throw new Error("abs: expected number, got " + typeof n);
|
|
67099
67206
|
}
|
|
67100
67207
|
return Math.abs(n);
|
|
67101
67208
|
}
|
|
67102
67209
|
function ceil(n) {
|
|
67210
|
+
if (n == null) return null;
|
|
67103
67211
|
if (typeof n !== "number") {
|
|
67104
67212
|
throw new Error("ceil: expected number, got " + typeof n);
|
|
67105
67213
|
}
|
|
67106
67214
|
return Math.ceil(n);
|
|
67107
67215
|
}
|
|
67108
67216
|
function floor(n) {
|
|
67217
|
+
if (n == null) return null;
|
|
67109
67218
|
if (typeof n !== "number") {
|
|
67110
67219
|
throw new Error("floor: expected number, got " + typeof n);
|
|
67111
67220
|
}
|
|
67112
67221
|
return Math.floor(n);
|
|
67113
67222
|
}
|
|
67114
67223
|
function clamp(n, min2, max3) {
|
|
67224
|
+
if (n == null) return null;
|
|
67115
67225
|
if (typeof n !== "number") {
|
|
67116
67226
|
throw new Error("clamp: expected number, got " + typeof n);
|
|
67117
67227
|
}
|
|
@@ -67129,6 +67239,7 @@ function clamp(n, min2, max3) {
|
|
|
67129
67239
|
return n;
|
|
67130
67240
|
}
|
|
67131
67241
|
function percentage(part, total, decimals) {
|
|
67242
|
+
if (part == null || total == null) return null;
|
|
67132
67243
|
if (typeof part !== "number") {
|
|
67133
67244
|
throw new Error("percentage: part must be a number");
|
|
67134
67245
|
}
|
|
@@ -67149,6 +67260,7 @@ function percentage(part, total, decimals) {
|
|
|
67149
67260
|
return percent;
|
|
67150
67261
|
}
|
|
67151
67262
|
function pow(base, exponent) {
|
|
67263
|
+
if (base == null || exponent == null) return null;
|
|
67152
67264
|
if (typeof base !== "number") {
|
|
67153
67265
|
throw new Error("pow: base must be a number");
|
|
67154
67266
|
}
|
|
@@ -67158,6 +67270,7 @@ function pow(base, exponent) {
|
|
|
67158
67270
|
return Math.pow(base, exponent);
|
|
67159
67271
|
}
|
|
67160
67272
|
function sqrt(n) {
|
|
67273
|
+
if (n == null) return null;
|
|
67161
67274
|
if (typeof n !== "number") {
|
|
67162
67275
|
throw new Error("sqrt: expected number, got " + typeof n);
|
|
67163
67276
|
}
|
|
@@ -67167,6 +67280,7 @@ function sqrt(n) {
|
|
|
67167
67280
|
return Math.sqrt(n);
|
|
67168
67281
|
}
|
|
67169
67282
|
function mod(n, divisor) {
|
|
67283
|
+
if (n == null || divisor == null) return null;
|
|
67170
67284
|
if (typeof n !== "number") {
|
|
67171
67285
|
throw new Error("mod: expected number, got " + typeof n);
|
|
67172
67286
|
}
|
|
@@ -67339,7 +67453,7 @@ __export(expression_type_exports, {
|
|
|
67339
67453
|
coalesce: () => coalesce,
|
|
67340
67454
|
isArray: () => isArray2,
|
|
67341
67455
|
isBoolean: () => isBoolean,
|
|
67342
|
-
isEmpty: () =>
|
|
67456
|
+
isEmpty: () => isEmpty2,
|
|
67343
67457
|
isNotNull: () => isNotNull,
|
|
67344
67458
|
isNull: () => isNull,
|
|
67345
67459
|
isNumber: () => isNumber,
|
|
@@ -67351,6 +67465,22 @@ __export(expression_type_exports, {
|
|
|
67351
67465
|
toString: () => toString,
|
|
67352
67466
|
typeOf: () => typeOf
|
|
67353
67467
|
});
|
|
67468
|
+
|
|
67469
|
+
// ../shared/src/schemas/database_types.ts
|
|
67470
|
+
function isEmptyFieldValue(value2) {
|
|
67471
|
+
if (value2 === null || value2 === void 0) {
|
|
67472
|
+
return true;
|
|
67473
|
+
}
|
|
67474
|
+
if (typeof value2 === "string") {
|
|
67475
|
+
return value2 === "";
|
|
67476
|
+
}
|
|
67477
|
+
if (Array.isArray(value2)) {
|
|
67478
|
+
return value2.length === 0;
|
|
67479
|
+
}
|
|
67480
|
+
return false;
|
|
67481
|
+
}
|
|
67482
|
+
|
|
67483
|
+
// ../shared/src/expression_type.ts
|
|
67354
67484
|
function isArray2(val) {
|
|
67355
67485
|
return Array.isArray(val);
|
|
67356
67486
|
}
|
|
@@ -67373,6 +67503,7 @@ function coalesce(...values3) {
|
|
|
67373
67503
|
return void 0;
|
|
67374
67504
|
}
|
|
67375
67505
|
function toNumber(val) {
|
|
67506
|
+
if (val == null) return null;
|
|
67376
67507
|
if (typeof val === "number") return val;
|
|
67377
67508
|
if (typeof val === "string") {
|
|
67378
67509
|
const parsed = parseFloat(val);
|
|
@@ -67384,9 +67515,7 @@ function toNumber(val) {
|
|
|
67384
67515
|
throw new Error("toNumber: expected number or string, got " + typeof val);
|
|
67385
67516
|
}
|
|
67386
67517
|
function toString(val) {
|
|
67387
|
-
if (val == null)
|
|
67388
|
-
throw new Error("toString: cannot convert null or undefined to string");
|
|
67389
|
-
}
|
|
67518
|
+
if (val == null) return "";
|
|
67390
67519
|
return String(val);
|
|
67391
67520
|
}
|
|
67392
67521
|
function isNull(val) {
|
|
@@ -67395,13 +67524,11 @@ function isNull(val) {
|
|
|
67395
67524
|
function isNotNull(val) {
|
|
67396
67525
|
return !isNull(val);
|
|
67397
67526
|
}
|
|
67398
|
-
function
|
|
67399
|
-
|
|
67400
|
-
if (typeof val === "string") return val.length === 0;
|
|
67401
|
-
if (Array.isArray(val)) return val.length === 0;
|
|
67402
|
-
return false;
|
|
67527
|
+
function isEmpty2(val) {
|
|
67528
|
+
return isEmptyFieldValue(val);
|
|
67403
67529
|
}
|
|
67404
67530
|
function parseJson(value2) {
|
|
67531
|
+
if (value2 == null) return null;
|
|
67405
67532
|
if (typeof value2 !== "string") {
|
|
67406
67533
|
throw new Error("parseJson: expected a JSON string, got " + typeof value2);
|
|
67407
67534
|
}
|
|
@@ -69066,7 +69193,7 @@ function walkRead(node, scope) {
|
|
|
69066
69193
|
return readFromLambdaParam(root, chain2);
|
|
69067
69194
|
}
|
|
69068
69195
|
if (scope.foreachBinds.includes(root)) {
|
|
69069
|
-
return readFromForeachItem(chain2);
|
|
69196
|
+
return readFromForeachItem(root, chain2);
|
|
69070
69197
|
}
|
|
69071
69198
|
if (scope.stepIds.has(root)) {
|
|
69072
69199
|
return readFromStepOutput(root, chain2);
|
|
@@ -69192,10 +69319,13 @@ function readFromRuntime(chain2) {
|
|
|
69192
69319
|
return { kind: "read", source, path: path14 };
|
|
69193
69320
|
});
|
|
69194
69321
|
}
|
|
69195
|
-
function readFromForeachItem(chain2) {
|
|
69322
|
+
function readFromForeachItem(name2, chain2) {
|
|
69196
69323
|
return wrapOptionalGuards(chain2, (segments) => ({
|
|
69197
69324
|
kind: "read",
|
|
69198
|
-
|
|
69325
|
+
// NAMED, so a read inside a nested loop says which loop it means. Without
|
|
69326
|
+
// it both binds resolved to the innermost item, and a join between two
|
|
69327
|
+
// collections matched nothing while the workflow reported success.
|
|
69328
|
+
source: { from: "foreach_item", name: name2 },
|
|
69199
69329
|
path: classifyRecordPath(segments)
|
|
69200
69330
|
}));
|
|
69201
69331
|
}
|
|
@@ -70151,7 +70281,11 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
|
|
|
70151
70281
|
type: "bind",
|
|
70152
70282
|
expression: patternEntryExpression(entry, {
|
|
70153
70283
|
kind: "read",
|
|
70154
|
-
|
|
70284
|
+
// The loop's synthetic bind name, stated rather than left to the
|
|
70285
|
+
// innermost-frame fallback — these bind steps happen to run where
|
|
70286
|
+
// this loop IS innermost, and naming it keeps that true if the
|
|
70287
|
+
// lowering ever moves.
|
|
70288
|
+
source: { from: "foreach_item", name: bindName },
|
|
70155
70289
|
path: [entry.segment]
|
|
70156
70290
|
})
|
|
70157
70291
|
});
|
|
@@ -71002,7 +71136,10 @@ ${args.instructions.replace(/\s+$/, "")}
|
|
|
71002
71136
|
`;
|
|
71003
71137
|
}
|
|
71004
71138
|
function stripAgentHeader(text) {
|
|
71005
|
-
return text.replace(/<!--\s*lotics:[\s\S]*?-->\n?/g, "")
|
|
71139
|
+
return normalizeAgentInstructions(text.replace(/<!--\s*lotics:[\s\S]*?-->\n?/g, ""));
|
|
71140
|
+
}
|
|
71141
|
+
function normalizeAgentInstructions(text) {
|
|
71142
|
+
return text.replace(/\n{3,}/g, "\n\n").trim();
|
|
71006
71143
|
}
|
|
71007
71144
|
|
|
71008
71145
|
// src/npm_registry.ts
|
|
@@ -71121,6 +71258,49 @@ function writeSynced(projectDir, kind, alias, entry) {
|
|
|
71121
71258
|
pkg2.lotics.synced[kind] = { ...pkg2.lotics.synced[kind], [alias]: entry };
|
|
71122
71259
|
fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
71123
71260
|
}
|
|
71261
|
+
function readManifestEntry(projectDir, kind, alias) {
|
|
71262
|
+
const pkg2 = readManifest(projectDir);
|
|
71263
|
+
if (!pkg2.lotics) return { found: false };
|
|
71264
|
+
const entry = { found: true };
|
|
71265
|
+
let found = false;
|
|
71266
|
+
const declarations = pkg2.lotics[kind];
|
|
71267
|
+
if (declarations && typeof declarations === "object" && !Array.isArray(declarations)) {
|
|
71268
|
+
const map3 = declarations;
|
|
71269
|
+
if (alias in map3) {
|
|
71270
|
+
entry.declaration = map3[alias];
|
|
71271
|
+
found = true;
|
|
71272
|
+
}
|
|
71273
|
+
}
|
|
71274
|
+
const baselines = pkg2.lotics.synced?.[kind];
|
|
71275
|
+
if (baselines && alias in baselines) {
|
|
71276
|
+
entry.synced = baselines[alias];
|
|
71277
|
+
found = true;
|
|
71278
|
+
}
|
|
71279
|
+
return found ? entry : { found: false };
|
|
71280
|
+
}
|
|
71281
|
+
function readManifest(projectDir) {
|
|
71282
|
+
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
71283
|
+
return JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
|
|
71284
|
+
}
|
|
71285
|
+
function removeFromManifest(projectDir, kind, alias) {
|
|
71286
|
+
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
71287
|
+
const pkg2 = readManifest(projectDir);
|
|
71288
|
+
if (!pkg2.lotics) return;
|
|
71289
|
+
const declarations = pkg2.lotics[kind];
|
|
71290
|
+
if (declarations && typeof declarations === "object" && !Array.isArray(declarations)) {
|
|
71291
|
+
delete declarations[alias];
|
|
71292
|
+
}
|
|
71293
|
+
const baselines = pkg2.lotics.synced?.[kind];
|
|
71294
|
+
if (baselines) delete baselines[alias];
|
|
71295
|
+
fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
71296
|
+
}
|
|
71297
|
+
function parkPrunedDeclaration(projectDir, kind, alias, removed) {
|
|
71298
|
+
const dir = path10.join(projectDir, ".lotics", "pruned", kind);
|
|
71299
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71300
|
+
const file2 = path10.join(dir, `${alias}.json`);
|
|
71301
|
+
fs8.writeFileSync(file2, JSON.stringify(removed, null, 2) + "\n");
|
|
71302
|
+
return path10.relative(projectDir, file2);
|
|
71303
|
+
}
|
|
71124
71304
|
var WORKFLOWS_DIR = path10.join("src", "workflows");
|
|
71125
71305
|
var WORKFLOW_GLOBALS_DIR = path10.join(".lotics", "workflows");
|
|
71126
71306
|
var AGENTS_DIR = path10.join("src", "agents");
|
|
@@ -71270,13 +71450,52 @@ function writeAgentFiles(projectDir, agents, opts) {
|
|
|
71270
71450
|
}
|
|
71271
71451
|
return written;
|
|
71272
71452
|
}
|
|
71453
|
+
function parkLiveAgentProse(projectDir, alias, instructions) {
|
|
71454
|
+
const dir = path10.join(projectDir, ".lotics", "agents");
|
|
71455
|
+
const file2 = path10.join(dir, `${alias}.live.md`);
|
|
71456
|
+
try {
|
|
71457
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71458
|
+
fs8.writeFileSync(file2, normalizeAgentInstructions(instructions) + "\n");
|
|
71459
|
+
return path10.relative(projectDir, file2);
|
|
71460
|
+
} catch (err2) {
|
|
71461
|
+
console.error(
|
|
71462
|
+
`\u26A0 Could not write the live prose for ${alias} to ${path10.relative(projectDir, file2)}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
71463
|
+
);
|
|
71464
|
+
return null;
|
|
71465
|
+
}
|
|
71466
|
+
}
|
|
71467
|
+
function reportUnseenLive(kind, unseen, appId) {
|
|
71468
|
+
if (unseen.length === 0) return;
|
|
71469
|
+
const plural = unseen.length === 1;
|
|
71470
|
+
console.error(
|
|
71471
|
+
`
|
|
71472
|
+
\u26A0 Kept your ${plural ? kind : `${kind}s`} for ` + unseen.map((u) => u.parked ? `${u.alias} (live copy written to ${u.parked})` : u.alias).join(", ") + ` \u2014 and the live ${kind} has CHANGED since this checkout last saw it, so somebody else edited ${plural ? "it" : "them"}.
|
|
71473
|
+
Your next deploy will be REFUSED for ${plural ? "this alias" : "these aliases"}, which is the point: pushing would overwrite their edit.
|
|
71474
|
+
Merge what you want from the live copy into your file and deploy, or run 'lotics app pull ${appId} --force' to take the live version and discard yours.`
|
|
71475
|
+
);
|
|
71476
|
+
}
|
|
71477
|
+
function parkLiveWorkflowBody(projectDir, alias, source) {
|
|
71478
|
+
const dir = path10.join(projectDir, WORKFLOW_GLOBALS_DIR);
|
|
71479
|
+
const file2 = path10.join(dir, `${alias}.live.ts`);
|
|
71480
|
+
try {
|
|
71481
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71482
|
+
fs8.writeFileSync(file2, normalizeWorkflowBody(source) + "\n");
|
|
71483
|
+
return path10.relative(projectDir, file2);
|
|
71484
|
+
} catch (err2) {
|
|
71485
|
+
console.error(
|
|
71486
|
+
`\u26A0 Could not write the live body for ${alias} to ${path10.relative(projectDir, file2)}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
71487
|
+
);
|
|
71488
|
+
return null;
|
|
71489
|
+
}
|
|
71490
|
+
}
|
|
71273
71491
|
function localPromptUnpushed(projectDir, alias, live) {
|
|
71274
71492
|
const file2 = agentFilePath2(projectDir, alias);
|
|
71275
71493
|
if (!fs8.existsSync(file2)) return false;
|
|
71276
|
-
const onDisk = fs8.readFileSync(file2, "utf-8");
|
|
71494
|
+
const onDisk = stripAgentHeader(fs8.readFileSync(file2, "utf-8"));
|
|
71495
|
+
if (onDisk === normalizeAgentInstructions(live)) return false;
|
|
71277
71496
|
const baseline = readSynced(projectDir).agents[alias]?.content;
|
|
71278
|
-
if (baseline === void 0) return
|
|
71279
|
-
return contentSha(
|
|
71497
|
+
if (baseline === void 0) return true;
|
|
71498
|
+
return contentSha(onDisk) !== baseline;
|
|
71280
71499
|
}
|
|
71281
71500
|
function generatedWorkflowDescription(appName, alias) {
|
|
71282
71501
|
return `Workflow invoked by app "${appName}" via useWorkflow("${alias}").`;
|
|
@@ -71285,14 +71504,15 @@ function localBodyUnpushed(projectDir, alias, live) {
|
|
|
71285
71504
|
const file2 = workflowFilePath(projectDir, alias);
|
|
71286
71505
|
if (!fs8.existsSync(file2)) return false;
|
|
71287
71506
|
const onDisk = fs8.readFileSync(file2, "utf-8");
|
|
71507
|
+
if (stripWorkflowHeader(onDisk).trim() === normalizeWorkflowBody(live).trim()) return false;
|
|
71288
71508
|
const baseline = readSynced(projectDir).workflows[alias]?.content;
|
|
71289
|
-
if (baseline === void 0)
|
|
71290
|
-
return stripWorkflowHeader(onDisk).trim() !== normalizeWorkflowBody(live).trim();
|
|
71291
|
-
}
|
|
71509
|
+
if (baseline === void 0) return true;
|
|
71292
71510
|
return contentSha(stripWorkflowHeader(onDisk)) !== baseline;
|
|
71293
71511
|
}
|
|
71294
71512
|
async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows, opts) {
|
|
71295
71513
|
const written = [];
|
|
71514
|
+
const kept = [];
|
|
71515
|
+
const unseenLive = [];
|
|
71296
71516
|
for (const [alias, declaration] of Object.entries(workflows)) {
|
|
71297
71517
|
const res = await client.getAppWorkflow(app_id, alias);
|
|
71298
71518
|
if (res.error && !isMissingWorkflowBodyError(res.error)) {
|
|
@@ -71317,7 +71537,16 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
|
|
|
71317
71537
|
toWorkflowDtsDeclaration(declaration)
|
|
71318
71538
|
);
|
|
71319
71539
|
if (!opts.overwrite && localBodyUnpushed(projectDir, alias, source)) {
|
|
71320
|
-
|
|
71540
|
+
kept.push(`src/workflows/${alias}.ts`);
|
|
71541
|
+
const previous = readSynced(projectDir).workflows[alias];
|
|
71542
|
+
if (body_sha && (previous?.live === void 0 || previous.live === body_sha)) {
|
|
71543
|
+
writeSynced(projectDir, "workflows", alias, { ...previous, live: body_sha });
|
|
71544
|
+
} else if (body_sha) {
|
|
71545
|
+
unseenLive.push({
|
|
71546
|
+
alias,
|
|
71547
|
+
parked: parkLiveWorkflowBody(projectDir, alias, source)
|
|
71548
|
+
});
|
|
71549
|
+
}
|
|
71321
71550
|
continue;
|
|
71322
71551
|
}
|
|
71323
71552
|
writeWorkflowFile(projectDir, alias, source, envelope2);
|
|
@@ -71341,7 +71570,7 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
|
|
|
71341
71570
|
}
|
|
71342
71571
|
written.push(alias);
|
|
71343
71572
|
}
|
|
71344
|
-
return written;
|
|
71573
|
+
return { written, kept, unseenLive };
|
|
71345
71574
|
}
|
|
71346
71575
|
function writeWorkflowDescription(projectDir, alias, description) {
|
|
71347
71576
|
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
@@ -72041,15 +72270,21 @@ async function hydrateAppProject(client, targetPath, app, opts) {
|
|
|
72041
72270
|
);
|
|
72042
72271
|
const workflows = app.workflows ?? {};
|
|
72043
72272
|
if (Object.keys(workflows).length > 0) {
|
|
72044
|
-
const written = await writeWorkflowFiles(
|
|
72045
|
-
|
|
72046
|
-
|
|
72047
|
-
|
|
72273
|
+
const { written, kept, unseenLive } = await writeWorkflowFiles(
|
|
72274
|
+
client,
|
|
72275
|
+
targetPath,
|
|
72276
|
+
app.id,
|
|
72277
|
+
app.name,
|
|
72278
|
+
workflows,
|
|
72279
|
+
{ overwrite: opts.force === true }
|
|
72280
|
+
);
|
|
72281
|
+
opts.kept.push(...kept);
|
|
72048
72282
|
if (written.length > 0) {
|
|
72049
72283
|
console.error(
|
|
72050
72284
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
|
|
72051
72285
|
);
|
|
72052
72286
|
}
|
|
72287
|
+
reportUnseenLive("workflow body", unseenLive, app.id);
|
|
72053
72288
|
}
|
|
72054
72289
|
const agents = app.agents ?? {};
|
|
72055
72290
|
if (Object.keys(agents).length > 0) {
|
|
@@ -72057,17 +72292,31 @@ async function hydrateAppProject(client, targetPath, app, opts) {
|
|
|
72057
72292
|
overwrite: opts.force === true,
|
|
72058
72293
|
kept: opts.kept
|
|
72059
72294
|
});
|
|
72060
|
-
|
|
72061
|
-
|
|
72062
|
-
|
|
72063
|
-
|
|
72064
|
-
|
|
72295
|
+
const unseenLiveProse = [];
|
|
72296
|
+
for (const [alias, declaration] of Object.entries(agents)) {
|
|
72297
|
+
const instructions = declaration?.instructions;
|
|
72298
|
+
if (typeof instructions !== "string") continue;
|
|
72299
|
+
const live = contentSha(instructions);
|
|
72300
|
+
if (written.includes(alias)) {
|
|
72301
|
+
writeSynced(targetPath, "agents", alias, {
|
|
72302
|
+
content: contentSha(normalizeAgentInstructions(instructions)),
|
|
72303
|
+
live
|
|
72304
|
+
});
|
|
72305
|
+
continue;
|
|
72306
|
+
}
|
|
72307
|
+
const previous = readSynced(targetPath).agents[alias];
|
|
72308
|
+
if (previous?.live === void 0 || previous.live === live) {
|
|
72309
|
+
writeSynced(targetPath, "agents", alias, { ...previous, live });
|
|
72310
|
+
continue;
|
|
72311
|
+
}
|
|
72312
|
+
unseenLiveProse.push({ alias, parked: parkLiveAgentProse(targetPath, alias, instructions) });
|
|
72065
72313
|
}
|
|
72066
72314
|
if (written.length > 0) {
|
|
72067
72315
|
console.error(
|
|
72068
72316
|
`Wrote ${written.length} agent instruction ${written.length === 1 ? "file" : "files"} to ${AGENTS_DIR}/ (${written.join(", ")})`
|
|
72069
72317
|
);
|
|
72070
72318
|
}
|
|
72319
|
+
reportUnseenLive("prompt", unseenLiveProse, app.id);
|
|
72071
72320
|
}
|
|
72072
72321
|
await runNpmInstall(targetPath);
|
|
72073
72322
|
writeAppDtsKitLinks(targetPath);
|
|
@@ -72090,16 +72339,17 @@ async function appPull(client, args) {
|
|
|
72090
72339
|
console.error(`Downloading source archive...`);
|
|
72091
72340
|
await downloadToFile(sourceUrl, tmpFile);
|
|
72092
72341
|
const keptLocal = [];
|
|
72342
|
+
const restoredFromArchive = [];
|
|
72093
72343
|
const stagingDir = fs8.mkdtempSync(path10.join(tmpdir(), `lotics-pull-${app.id}-`));
|
|
72094
72344
|
try {
|
|
72095
72345
|
await runTar(["-xzf", tmpFile, "-C", stagingDir], stagingDir);
|
|
72096
72346
|
console.error(`Extracting to ${targetPath}...`);
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72100
|
-
|
|
72101
|
-
|
|
72102
|
-
);
|
|
72347
|
+
const copied = copyTree(stagingDir, targetPath, {
|
|
72348
|
+
overwrite: args.force === true,
|
|
72349
|
+
skip: [WORKFLOWS_DIR, AGENTS_DIR].map((d) => d.split(path10.sep).join("/"))
|
|
72350
|
+
});
|
|
72351
|
+
keptLocal.push(...copied.kept);
|
|
72352
|
+
restoredFromArchive.push(...copied.created);
|
|
72103
72353
|
} finally {
|
|
72104
72354
|
if (fs8.existsSync(tmpFile)) fs8.unlinkSync(tmpFile);
|
|
72105
72355
|
fs8.rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -72114,6 +72364,7 @@ async function appPull(client, args) {
|
|
|
72114
72364
|
...args.force !== void 0 ? { force: args.force } : {},
|
|
72115
72365
|
kept: keptLocal
|
|
72116
72366
|
});
|
|
72367
|
+
reportRestoredFiles(restoredFromArchive, prior !== null);
|
|
72117
72368
|
reportKeptLocalFiles(keptLocal, app.id, {
|
|
72118
72369
|
// `prior` is non-null whenever the stamp was held — `stampAfterPull` cannot
|
|
72119
72370
|
// hold against nothing — so the fallback only covers the un-held branch,
|
|
@@ -72275,7 +72526,8 @@ async function appDeploy(client, args) {
|
|
|
72275
72526
|
if (args.prune) {
|
|
72276
72527
|
await pruneOrphanedBindings(
|
|
72277
72528
|
client,
|
|
72278
|
-
|
|
72529
|
+
projectDir,
|
|
72530
|
+
{ app_id: meta3.app_id },
|
|
72279
72531
|
orphanedBindings(called, liveAfter),
|
|
72280
72532
|
called.dynamic
|
|
72281
72533
|
);
|
|
@@ -72443,7 +72695,7 @@ function driftedAgentAliases(projectDir, liveAgents) {
|
|
|
72443
72695
|
const file2 = agentFilePath2(projectDir, alias);
|
|
72444
72696
|
if (!fs8.existsSync(file2)) continue;
|
|
72445
72697
|
const local = stripAgentHeader(fs8.readFileSync(file2, "utf-8"));
|
|
72446
|
-
if (local === "" || local === (live.instructions ?? "")) continue;
|
|
72698
|
+
if (local === "" || local === normalizeAgentInstructions(live.instructions ?? "")) continue;
|
|
72447
72699
|
drifted.push(alias);
|
|
72448
72700
|
}
|
|
72449
72701
|
return drifted.sort();
|
|
@@ -72505,28 +72757,31 @@ function warnAboutOrphanedBindings(called, live) {
|
|
|
72505
72757
|
);
|
|
72506
72758
|
return lines.length;
|
|
72507
72759
|
}
|
|
72508
|
-
async function pruneOrphanedBindings(client,
|
|
72760
|
+
async function pruneOrphanedBindings(client, projectDir, app, orphans, dynamic) {
|
|
72509
72761
|
const targets = [
|
|
72510
72762
|
...orphans.queries.map((alias) => ({
|
|
72511
72763
|
tool: "remove_app_query",
|
|
72764
|
+
kind: "queries",
|
|
72512
72765
|
alias,
|
|
72513
72766
|
label: `query ${alias}`,
|
|
72514
|
-
|
|
72767
|
+
setCommand: `lotics app query set ${alias}`
|
|
72515
72768
|
})),
|
|
72516
72769
|
...orphans.workflows.map((alias) => ({
|
|
72517
72770
|
tool: "remove_app_workflow",
|
|
72771
|
+
kind: "workflows",
|
|
72518
72772
|
alias,
|
|
72519
72773
|
label: `workflow ${alias}`,
|
|
72520
|
-
|
|
72774
|
+
setCommand: `lotics app workflow set ${alias}`
|
|
72521
72775
|
})),
|
|
72522
72776
|
...orphans.agents.map((alias) => ({
|
|
72523
72777
|
tool: "remove_app_agent",
|
|
72778
|
+
kind: "agents",
|
|
72524
72779
|
alias,
|
|
72525
72780
|
label: `agent ${alias}`,
|
|
72526
|
-
|
|
72781
|
+
setCommand: `lotics app agent set ${alias}`
|
|
72527
72782
|
}))
|
|
72528
72783
|
];
|
|
72529
|
-
if (targets.length === 0) return
|
|
72784
|
+
if (targets.length === 0) return;
|
|
72530
72785
|
if (dynamic.length > 0) {
|
|
72531
72786
|
console.error(
|
|
72532
72787
|
`
|
|
@@ -72534,19 +72789,63 @@ async function pruneOrphanedBindings(client, appId, orphans, dynamic) {
|
|
|
72534
72789
|
` + targets.map((t) => ` \u2022 ${t.label}`).join("\n") + `
|
|
72535
72790
|
Make those call sites literal and the next deploy will clean them up, or remove the ones you know are dead by hand.`
|
|
72536
72791
|
);
|
|
72537
|
-
return
|
|
72792
|
+
return;
|
|
72538
72793
|
}
|
|
72539
|
-
|
|
72794
|
+
const tablesBefore = new Set(resolveCodegenTableIds(projectDir, readAppMeta(projectDir).queries ?? {}));
|
|
72795
|
+
let removedLocally = false;
|
|
72540
72796
|
for (const target of targets) {
|
|
72541
|
-
const res = await client.execute(target.tool, { app_id:
|
|
72797
|
+
const res = await client.execute(target.tool, { app_id: app.app_id, alias: target.alias });
|
|
72542
72798
|
if (res.error) {
|
|
72543
|
-
failed += 1;
|
|
72544
72799
|
console.error(` \u2717 could not unbind ${target.label} \u2014 ${res.error}`);
|
|
72545
72800
|
continue;
|
|
72546
72801
|
}
|
|
72547
|
-
|
|
72802
|
+
let line;
|
|
72803
|
+
try {
|
|
72804
|
+
const entry = readManifestEntry(projectDir, target.kind, target.alias);
|
|
72805
|
+
if (entry.found) {
|
|
72806
|
+
const parked = parkPrunedDeclaration(projectDir, target.kind, target.alias, {
|
|
72807
|
+
declaration: entry.declaration,
|
|
72808
|
+
synced: entry.synced
|
|
72809
|
+
});
|
|
72810
|
+
removeFromManifest(projectDir, target.kind, target.alias);
|
|
72811
|
+
removedLocally = true;
|
|
72812
|
+
line = ` \u2713 unbound ${target.label} + removed package.json#lotics.${target.kind}.${target.alias} \u2014 restore: put ${parked} back under lotics.${target.kind}.${target.alias}, then ${target.setCommand}`;
|
|
72813
|
+
} else {
|
|
72814
|
+
line = ` \u2713 unbound ${target.label} \u2014 nothing was declared for it in package.json, so there is nothing to restore from this project (recover it with lotics app pull --from-version <apv_\u2026>)`;
|
|
72815
|
+
}
|
|
72816
|
+
} catch (err2) {
|
|
72817
|
+
console.error(
|
|
72818
|
+
` \u2713 unbound ${target.label} server-side, but could not update the local project \u2014 ${err2 instanceof Error ? err2.message : String(err2)}. lotics.${target.kind}.${target.alias} is still declared: remove it by hand, or the next deploy re-creates the binding.`
|
|
72819
|
+
);
|
|
72820
|
+
continue;
|
|
72821
|
+
}
|
|
72822
|
+
console.error(line);
|
|
72823
|
+
}
|
|
72824
|
+
if (removedLocally) {
|
|
72825
|
+
try {
|
|
72826
|
+
const meta3 = readAppMeta(projectDir);
|
|
72827
|
+
writeAppDts(projectDir, {
|
|
72828
|
+
workflows: meta3.workflows,
|
|
72829
|
+
queries: meta3.queries,
|
|
72830
|
+
agents: meta3.agents
|
|
72831
|
+
});
|
|
72832
|
+
if (orphans.queries.length > 0) {
|
|
72833
|
+
const dropped = [...tablesBefore].filter(
|
|
72834
|
+
(id) => !resolveCodegenTableIds(projectDir, meta3.queries ?? {}).includes(id)
|
|
72835
|
+
);
|
|
72836
|
+
if (dropped.length > 0) {
|
|
72837
|
+
console.error(
|
|
72838
|
+
` \u26A0 .lotics/app_fields.ts no longer covers ${dropped.join(", ")} \u2014 ${dropped.length === 1 ? "that table was" : "those tables were"} named only by the pruned quer${orphans.queries.length === 1 ? "y" : "ies"}. If your source still uses F/OPT for ${dropped.length === 1 ? "it" : "them"}, add ${dropped.map((id) => `"${id}"`).join(", ")} to package.json#lotics.codegen.tables and re-run lotics app codegen.`
|
|
72839
|
+
);
|
|
72840
|
+
}
|
|
72841
|
+
await writeGeneratedAppFields(client, projectDir, app, meta3.queries ?? {});
|
|
72842
|
+
}
|
|
72843
|
+
} catch (err2) {
|
|
72844
|
+
console.error(
|
|
72845
|
+
` \u2717 could not refresh the generated companions after pruning \u2014 ${err2 instanceof Error ? err2.message : String(err2)}. Run lotics app codegen.`
|
|
72846
|
+
);
|
|
72847
|
+
}
|
|
72548
72848
|
}
|
|
72549
|
-
return failed;
|
|
72550
72849
|
}
|
|
72551
72850
|
function derivedDeployMessage(pending) {
|
|
72552
72851
|
const parts = [
|
|
@@ -72558,6 +72857,7 @@ function derivedDeployMessage(pending) {
|
|
|
72558
72857
|
}
|
|
72559
72858
|
function copyTree(from, to, opts, relative = "") {
|
|
72560
72859
|
const kept = [];
|
|
72860
|
+
const created = [];
|
|
72561
72861
|
for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
|
|
72562
72862
|
const src = path10.join(from, entry.name);
|
|
72563
72863
|
const dest = path10.join(to, entry.name);
|
|
@@ -72565,17 +72865,32 @@ function copyTree(from, to, opts, relative = "") {
|
|
|
72565
72865
|
if (opts.skip?.includes(rel.split(path10.sep).join("/"))) continue;
|
|
72566
72866
|
if (entry.isDirectory()) {
|
|
72567
72867
|
fs8.mkdirSync(dest, { recursive: true });
|
|
72568
|
-
|
|
72868
|
+
const child = copyTree(src, dest, opts, rel);
|
|
72869
|
+
kept.push(...child.kept);
|
|
72870
|
+
created.push(...child.created);
|
|
72569
72871
|
continue;
|
|
72570
72872
|
}
|
|
72571
|
-
|
|
72873
|
+
const existed = fs8.existsSync(dest);
|
|
72874
|
+
if (!opts.overwrite && existed) {
|
|
72572
72875
|
if (fs8.readFileSync(src).equals(fs8.readFileSync(dest))) continue;
|
|
72573
72876
|
kept.push(rel);
|
|
72574
72877
|
continue;
|
|
72575
72878
|
}
|
|
72576
72879
|
fs8.copyFileSync(src, dest);
|
|
72880
|
+
if (!existed) created.push(rel);
|
|
72577
72881
|
}
|
|
72578
|
-
return kept;
|
|
72882
|
+
return { kept, created };
|
|
72883
|
+
}
|
|
72884
|
+
function reportRestoredFiles(restored, isRefresh) {
|
|
72885
|
+
if (!isRefresh || restored.length === 0) return;
|
|
72886
|
+
const shown = restored.slice(0, 10);
|
|
72887
|
+
console.error(
|
|
72888
|
+
`
|
|
72889
|
+
Restored ${restored.length} file${restored.length === 1 ? "" : "s"} the app's last deployed source carries and your tree did not:
|
|
72890
|
+
` + shown.map((f) => ` \u2022 ${f}`).join("\n") + (restored.length > shown.length ? `
|
|
72891
|
+
\u2022 \u2026and ${restored.length - shown.length} more` : "") + `
|
|
72892
|
+
A pull mirrors the last DEPLOYED source, so a file you deleted here comes back until the deletion ships. Delete it again and run 'lotics app deploy' to make it stick.`
|
|
72893
|
+
);
|
|
72579
72894
|
}
|
|
72580
72895
|
function reportKeptLocalFiles(kept, appId, motion) {
|
|
72581
72896
|
if (kept.length === 0) return;
|
|
@@ -73135,15 +73450,19 @@ async function appWorkflowPull(client, args = {}) {
|
|
|
73135
73450
|
console.error(`App ${meta3.app_id} has no bound workflows.`);
|
|
73136
73451
|
return;
|
|
73137
73452
|
}
|
|
73138
|
-
const kept =
|
|
73139
|
-
|
|
73140
|
-
|
|
73141
|
-
|
|
73142
|
-
|
|
73453
|
+
const { written, kept, unseenLive } = await writeWorkflowFiles(
|
|
73454
|
+
client,
|
|
73455
|
+
projectDir,
|
|
73456
|
+
meta3.app_id,
|
|
73457
|
+
app.name,
|
|
73458
|
+
workflows,
|
|
73459
|
+
{ overwrite: args.force === true }
|
|
73460
|
+
);
|
|
73143
73461
|
console.error(
|
|
73144
73462
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
|
|
73145
73463
|
);
|
|
73146
73464
|
reportKeptLocalFiles(kept, meta3.app_id, null);
|
|
73465
|
+
reportUnseenLive("workflow body", unseenLive, meta3.app_id);
|
|
73147
73466
|
ensureAppTsconfig(projectDir);
|
|
73148
73467
|
}
|
|
73149
73468
|
async function appWorkflowCheck(args) {
|
|
@@ -73291,6 +73610,23 @@ function parseArgs(argv) {
|
|
|
73291
73610
|
let i2 = 0;
|
|
73292
73611
|
while (i2 < argv.length) {
|
|
73293
73612
|
const arg = argv[i2];
|
|
73613
|
+
if (subcommand !== void 0 && commandOwnsTail(command) && arg !== "--help" && arg !== "-h") {
|
|
73614
|
+
const local = subcommandArgs(command, subcommand);
|
|
73615
|
+
const looksLikeFlag = arg.length > 1 && arg.startsWith("-");
|
|
73616
|
+
const declared = looksLikeFlag && local !== void 0 && matchesKnownFlag([...local.valueFlags ?? [], ...local.flags ?? []], arg);
|
|
73617
|
+
if (declared) {
|
|
73618
|
+
restArgs.push(arg);
|
|
73619
|
+
if ((local?.valueFlags ?? []).includes(arg) && i2 + 1 < argv.length) restArgs.push(argv[++i2]);
|
|
73620
|
+
} else if (looksLikeFlag && local?.content !== true) {
|
|
73621
|
+
unknownFlag = arg;
|
|
73622
|
+
} else if (!toolArgs) {
|
|
73623
|
+
toolArgs = arg;
|
|
73624
|
+
} else {
|
|
73625
|
+
restArgs.push(arg);
|
|
73626
|
+
}
|
|
73627
|
+
i2++;
|
|
73628
|
+
continue;
|
|
73629
|
+
}
|
|
73294
73630
|
switch (arg) {
|
|
73295
73631
|
case "--json":
|
|
73296
73632
|
flags.json = true;
|
|
@@ -73715,11 +74051,13 @@ var CliError = class extends Error {
|
|
|
73715
74051
|
function fail2(message2) {
|
|
73716
74052
|
throw new CliError(message2);
|
|
73717
74053
|
}
|
|
73718
|
-
function rejectUnknownFlags(rest2,
|
|
73719
|
-
const
|
|
73720
|
-
const unknown2 = rest2.filter((a) => a.startsWith("--") && !
|
|
74054
|
+
function rejectUnknownFlags(rest2, command, subcommand) {
|
|
74055
|
+
const known = knownSubcommandFlags(command, subcommand);
|
|
74056
|
+
const unknown2 = rest2.filter((a) => a.startsWith("--") && !matchesKnownFlag(known, a));
|
|
73721
74057
|
if (unknown2.length === 0) return;
|
|
73722
|
-
fail2(
|
|
74058
|
+
fail2(
|
|
74059
|
+
`Unknown ${unknown2.length > 1 ? "flags" : "flag"} for ${command} ${subcommand}: ${unknown2.join(", ")}. Supported: ${known.join(", ")}.`
|
|
74060
|
+
);
|
|
73723
74061
|
}
|
|
73724
74062
|
function rejectExtraArgs(rest2, arity, usage) {
|
|
73725
74063
|
if (rest2.length <= arity) return;
|
|
@@ -94808,6 +95146,37 @@ function collectRecomputeReport(workbook, unreadable) {
|
|
|
94808
95146
|
return { total, unevaluated };
|
|
94809
95147
|
}
|
|
94810
95148
|
|
|
95149
|
+
// ../xlsx/src/numfmt_type.ts
|
|
95150
|
+
function classifyNumFmt(code) {
|
|
95151
|
+
if (!code || code === "General") return "general";
|
|
95152
|
+
const first3 = code.split(";")[0] ?? code;
|
|
95153
|
+
const currency = hasCurrencyTokens(first3);
|
|
95154
|
+
const stripped = stripQuotedLiterals(first3);
|
|
95155
|
+
if (stripped.trim() === "@") return "text";
|
|
95156
|
+
if (hasDateTokens(stripped)) return "date";
|
|
95157
|
+
if (stripped.includes("%")) return "percent";
|
|
95158
|
+
if (currency) return "currency";
|
|
95159
|
+
if (/[#0?]/.test(stripped)) return "numeric";
|
|
95160
|
+
return "general";
|
|
95161
|
+
}
|
|
95162
|
+
function stripQuotedLiterals(code) {
|
|
95163
|
+
return code.replace(/"[^"]*"/g, "").replace(/\\./g, " ");
|
|
95164
|
+
}
|
|
95165
|
+
function hasDateTokens(code) {
|
|
95166
|
+
if (/\b(y{1,4}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/p)\b/i.test(code)) return true;
|
|
95167
|
+
if (/[ymdhs]\s*[-\/.:\s]\s*m/i.test(code)) return true;
|
|
95168
|
+
if (/m\s*[-\/.:\s]\s*[ymdhs]/i.test(code)) return true;
|
|
95169
|
+
return false;
|
|
95170
|
+
}
|
|
95171
|
+
function hasCurrencyTokens(code) {
|
|
95172
|
+
if (/[$¥€£₫¢]/.test(code)) return true;
|
|
95173
|
+
if (/\[\$/.test(code)) return true;
|
|
95174
|
+
return false;
|
|
95175
|
+
}
|
|
95176
|
+
function isPaddedIdentifier(input) {
|
|
95177
|
+
return /^[+-]?0\d/.test(input);
|
|
95178
|
+
}
|
|
95179
|
+
|
|
94811
95180
|
// ../xlsx/src/clipboard.ts
|
|
94812
95181
|
function adjustFormulaRefsForRowShift(formula, atRow, delta) {
|
|
94813
95182
|
return formula.replace(
|
|
@@ -94835,17 +95204,18 @@ function adjustFormulaRefsForColShift(formula, atCol, delta) {
|
|
|
94835
95204
|
}
|
|
94836
95205
|
|
|
94837
95206
|
// ../xlsx/src/spreadsheet_commands.ts
|
|
94838
|
-
function setCellValueCommand(workbook, engine, sheetIndex, row, col, newValue) {
|
|
95207
|
+
function setCellValueCommand(workbook, engine, sheetIndex, row, col, newValue, options) {
|
|
94839
95208
|
const sheet = workbook.sheets[sheetIndex];
|
|
94840
95209
|
const ref2 = rowColToRef(row, col);
|
|
94841
95210
|
const oldCell = sheet.getCell(ref2);
|
|
94842
95211
|
const oldValue = oldCell?.value ?? null;
|
|
94843
95212
|
const oldFormula = oldCell?.formula;
|
|
94844
|
-
const isFormula = newValue.startsWith("=");
|
|
94845
|
-
const formula = isFormula ? newValue.slice(1) : void 0;
|
|
94846
|
-
const value2 = isFormula ? null : parseInputValue(newValue);
|
|
94847
95213
|
const styleIndex = oldCell?.styleIndex ?? 0;
|
|
94848
95214
|
const numFmtCode = oldCell?.numFmtCode ?? "General";
|
|
95215
|
+
const isText = typeof newValue === "string" && options?.typed !== true;
|
|
95216
|
+
const isFormula = isText && newValue.startsWith("=");
|
|
95217
|
+
const formula = isFormula ? newValue.slice(1) : void 0;
|
|
95218
|
+
const value2 = isFormula ? null : isText ? parseInputValue(newValue, numFmtCode) : newValue;
|
|
94849
95219
|
return {
|
|
94850
95220
|
description: `Set ${ref2} = ${newValue}`,
|
|
94851
95221
|
execute() {
|
|
@@ -95384,6 +95754,43 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
|
|
|
95384
95754
|
}
|
|
95385
95755
|
};
|
|
95386
95756
|
}
|
|
95757
|
+
function setRangeNumFmtCommand(workbook, sheetIndex, startRow, startCol, endRow, endCol, numFmtCode) {
|
|
95758
|
+
const sheet = workbook.sheets[sheetIndex];
|
|
95759
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
95760
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
95761
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
95762
|
+
const ref2 = rowColToRef(r, c);
|
|
95763
|
+
const cell = sheet.getCell(ref2);
|
|
95764
|
+
snapshot.set(ref2, cell ? { value: cell.value, styleIndex: cell.styleIndex, numFmtCode: cell.numFmtCode, formula: cell.formula } : null);
|
|
95765
|
+
}
|
|
95766
|
+
}
|
|
95767
|
+
const rangeRef = `${rowColToRef(startRow, startCol)}:${rowColToRef(endRow, endCol)}`;
|
|
95768
|
+
return {
|
|
95769
|
+
description: `Format ${rangeRef} as ${numFmtCode}`,
|
|
95770
|
+
execute() {
|
|
95771
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
95772
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
95773
|
+
const ref2 = rowColToRef(r, c);
|
|
95774
|
+
const cell = sheet.getCell(ref2);
|
|
95775
|
+
if (cell) {
|
|
95776
|
+
sheet.set(ref2, cell.value, sheet.styles?.get(cell.styleIndex), numFmtCode, cell.formula);
|
|
95777
|
+
} else {
|
|
95778
|
+
sheet.set(ref2, null, void 0, numFmtCode);
|
|
95779
|
+
}
|
|
95780
|
+
}
|
|
95781
|
+
}
|
|
95782
|
+
},
|
|
95783
|
+
undo() {
|
|
95784
|
+
for (const [ref2, snap] of snapshot) {
|
|
95785
|
+
if (snap) {
|
|
95786
|
+
sheet.set(ref2, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
|
|
95787
|
+
} else {
|
|
95788
|
+
sheet.deleteCell(ref2);
|
|
95789
|
+
}
|
|
95790
|
+
}
|
|
95791
|
+
}
|
|
95792
|
+
};
|
|
95793
|
+
}
|
|
95387
95794
|
function addSheetCommand(workbook, name2) {
|
|
95388
95795
|
return {
|
|
95389
95796
|
description: `Add sheet ${name2}`,
|
|
@@ -95426,10 +95833,23 @@ function renameSheetCommand(workbook, index, newName) {
|
|
|
95426
95833
|
}
|
|
95427
95834
|
};
|
|
95428
95835
|
}
|
|
95429
|
-
function
|
|
95836
|
+
function batchCommand(description, commands) {
|
|
95837
|
+
return {
|
|
95838
|
+
description,
|
|
95839
|
+
execute() {
|
|
95840
|
+
for (const cmd of commands) cmd.execute();
|
|
95841
|
+
},
|
|
95842
|
+
undo() {
|
|
95843
|
+
for (let i2 = commands.length - 1; i2 >= 0; i2--) commands[i2].undo();
|
|
95844
|
+
}
|
|
95845
|
+
};
|
|
95846
|
+
}
|
|
95847
|
+
function parseInputValue(input, numFmtCode) {
|
|
95430
95848
|
if (input === "") return null;
|
|
95849
|
+
if (classifyNumFmt(numFmtCode) === "text") return input;
|
|
95431
95850
|
if (input.toUpperCase() === "TRUE") return true;
|
|
95432
95851
|
if (input.toUpperCase() === "FALSE") return false;
|
|
95852
|
+
if (isPaddedIdentifier(input.trim())) return input;
|
|
95433
95853
|
const num3 = Number(input);
|
|
95434
95854
|
if (!isNaN(num3) && input.trim() !== "") return num3;
|
|
95435
95855
|
return input;
|
|
@@ -95702,21 +96122,20 @@ function applySheetCells(workbook, sheetIndex, def) {
|
|
|
95702
96122
|
const sheet = workbook.sheets[sheetIndex];
|
|
95703
96123
|
if (def.cells) {
|
|
95704
96124
|
for (const [ref2, raw] of Object.entries(def.cells)) {
|
|
95705
|
-
const cell = normalizeSimpleCell(raw, def.name
|
|
95706
|
-
const
|
|
96125
|
+
const cell = normalizeSimpleCell(raw, `sheet "${def.name}" cell ${ref2}`);
|
|
96126
|
+
const upper2 = ref2.toUpperCase();
|
|
96127
|
+
const coord = refToRowCol(upper2);
|
|
95707
96128
|
if (!coord) fail2(`Invalid cell ref in sheet "${def.name}": ${ref2}`);
|
|
95708
|
-
|
|
95709
|
-
|
|
95710
|
-
|
|
96129
|
+
sheet.set(
|
|
96130
|
+
upper2,
|
|
96131
|
+
cell.formula ? null : cell.value ?? null,
|
|
96132
|
+
void 0,
|
|
96133
|
+
cell.numFmt,
|
|
96134
|
+
cell.formula ?? void 0
|
|
96135
|
+
);
|
|
95711
96136
|
if (cell.style) {
|
|
95712
96137
|
setRangeStyleCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.style).execute();
|
|
95713
96138
|
}
|
|
95714
|
-
if (cell.numFmt !== void 0) {
|
|
95715
|
-
const upper2 = ref2.toUpperCase();
|
|
95716
|
-
const target = sheet.getCell(upper2);
|
|
95717
|
-
if (target) target.numFmtCode = cell.numFmt;
|
|
95718
|
-
else sheet.set(upper2, null, cell.style, cell.numFmt);
|
|
95719
|
-
}
|
|
95720
96139
|
}
|
|
95721
96140
|
}
|
|
95722
96141
|
if (def.merges) {
|
|
@@ -95744,22 +96163,33 @@ function applySheetCells(workbook, sheetIndex, def) {
|
|
|
95744
96163
|
}
|
|
95745
96164
|
}
|
|
95746
96165
|
}
|
|
95747
|
-
function normalizeSimpleCell(raw,
|
|
96166
|
+
function normalizeSimpleCell(raw, label) {
|
|
95748
96167
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
95749
96168
|
const unknown2 = Object.keys(raw).filter((k) => !SIMPLE_CELL_KEYS.has(k));
|
|
95750
96169
|
if (unknown2.length > 0) {
|
|
95751
96170
|
fail2(
|
|
95752
|
-
`Unknown propert${unknown2.length > 1 ? "ies" : "y"} on
|
|
96171
|
+
`Unknown propert${unknown2.length > 1 ? "ies" : "y"} on ${label}: ${unknown2.join(", ")}. Supported: ${[...SIMPLE_CELL_KEYS].join(", ")}.`
|
|
95753
96172
|
);
|
|
95754
96173
|
}
|
|
96174
|
+
if (raw.formula === void 0 || raw.formula === null) {
|
|
96175
|
+
if (typeof raw.value === "string" && raw.value.startsWith("=")) {
|
|
96176
|
+
console.error(
|
|
96177
|
+
`Warning: ${label} was written as the text ${JSON.stringify(raw.value)}. An object cell's "value" is a literal; write {"formula": ${JSON.stringify(raw.value.slice(1))}} or the bare string ${JSON.stringify(raw.value)} for a formula.`
|
|
96178
|
+
);
|
|
96179
|
+
}
|
|
96180
|
+
}
|
|
95755
96181
|
return raw;
|
|
95756
96182
|
}
|
|
96183
|
+
if (raw !== null && !["string", "number", "boolean"].includes(typeof raw)) {
|
|
96184
|
+
fail2(
|
|
96185
|
+
`Value of ${label} must be a string, number, boolean, null, or a { ${[...SIMPLE_CELL_KEYS].join(", ")} } object, got ${JSON.stringify(raw) ?? typeof raw}.`
|
|
96186
|
+
);
|
|
96187
|
+
}
|
|
96188
|
+
if (typeof raw === "string" && raw.startsWith("=")) {
|
|
96189
|
+
return { formula: raw.slice(1) };
|
|
96190
|
+
}
|
|
95757
96191
|
return { value: raw };
|
|
95758
96192
|
}
|
|
95759
|
-
function valueToInput(value2) {
|
|
95760
|
-
if (value2 === null || value2 === void 0) return "";
|
|
95761
|
-
return String(value2);
|
|
95762
|
-
}
|
|
95763
96193
|
function readOptionFlag(rest2, name2) {
|
|
95764
96194
|
const i2 = rest2.indexOf(name2);
|
|
95765
96195
|
if (i2 < 0) return void 0;
|
|
@@ -95774,7 +96204,6 @@ function splitOptionalSheetRef(spec) {
|
|
|
95774
96204
|
}
|
|
95775
96205
|
function xlsxRead(filePath, rest2) {
|
|
95776
96206
|
if (!filePath) fail2("Usage: lotics xlsx read <file> [--sheet <name>] [--range <sheet>!<A1:G60>] [--with-format]");
|
|
95777
|
-
rejectUnknownFlags(rest2, ["--sheet", "--range", "--with-format"], "xlsx read");
|
|
95778
96207
|
const rangeArg = readOptionFlag(rest2, "--range");
|
|
95779
96208
|
const sheetArg = readOptionFlag(rest2, "--sheet");
|
|
95780
96209
|
const withFormat = rest2.includes("--with-format");
|
|
@@ -95984,7 +96413,29 @@ function buildBatchCommand(workbook, engine, op, index) {
|
|
|
95984
96413
|
const sheetIndex = resolveSheetIndex(workbook, op.sheet);
|
|
95985
96414
|
const coord = refToRowCol(op.ref.toUpperCase());
|
|
95986
96415
|
if (!coord) fail2(`[op ${index}] Invalid cell ref: ${op.ref}`);
|
|
95987
|
-
|
|
96416
|
+
const cell = normalizeSimpleCell(op.value, `[op ${index}] set-cell ${op.sheet}!${op.ref}`);
|
|
96417
|
+
const commands = [
|
|
96418
|
+
cell.formula ? (
|
|
96419
|
+
// The entry path is the only one that writes a formula, and it takes
|
|
96420
|
+
// it spelled the way a person types it. Re-adding the `=` this
|
|
96421
|
+
// surface's reader just stripped is lossless and keeps the formula
|
|
96422
|
+
// write in one place.
|
|
96423
|
+
setCellValueCommand(workbook, engine.get(), sheetIndex, coord.row, coord.col, `=${cell.formula}`)
|
|
96424
|
+
) : setCellValueCommand(workbook, engine.get(), sheetIndex, coord.row, coord.col, cell.value ?? null, {
|
|
96425
|
+
typed: true
|
|
96426
|
+
})
|
|
96427
|
+
];
|
|
96428
|
+
if (cell.numFmt !== void 0) {
|
|
96429
|
+
commands.push(
|
|
96430
|
+
setRangeNumFmtCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.numFmt)
|
|
96431
|
+
);
|
|
96432
|
+
}
|
|
96433
|
+
if (cell.style) {
|
|
96434
|
+
commands.push(
|
|
96435
|
+
setRangeStyleCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.style)
|
|
96436
|
+
);
|
|
96437
|
+
}
|
|
96438
|
+
return commands.length === 1 ? commands[0] : batchCommand(`Set ${op.sheet}!${op.ref}`, commands);
|
|
95988
96439
|
}
|
|
95989
96440
|
case "clear-range": {
|
|
95990
96441
|
const sheetIndex = resolveSheetIndex(workbook, op.sheet);
|
|
@@ -96041,6 +96492,8 @@ Uses Lotics' own xlsx engine; round-trips faithfully with the Lotics editor and
|
|
|
96041
96492
|
lotics xlsx write <file> '<json>' Create .xlsx from {"sheets":[{"name","cells":{"A1":...}}]}
|
|
96042
96493
|
A cell is a bare value, or {value|formula, numFmt, style}:
|
|
96043
96494
|
{"A1":{"value":1234,"numFmt":"#,##0 \\"\u20AB\\"","style":{"fontBold":true}}}
|
|
96495
|
+
A bare "=A1+B1" is a formula; an object's "value" is
|
|
96496
|
+
always a literal, so use {"formula":"A1+B1"} there.
|
|
96044
96497
|
A sheet also takes merges, freeze, colWidths ({"A":34}),
|
|
96045
96498
|
rowHeights ({"1":44}). Unknown properties are rejected.
|
|
96046
96499
|
lotics xlsx set-cell <file> <sheet>!<ref> '<v>' Set cell value (prefix '=' for formula)
|
|
@@ -101655,7 +102108,6 @@ function buildDoc(input) {
|
|
|
101655
102108
|
}
|
|
101656
102109
|
async function docxRead(filePath, rest2) {
|
|
101657
102110
|
if (!filePath) fail2("Usage: lotics docx read <file> [--include-opaque]");
|
|
101658
|
-
rejectUnknownFlags(rest2, ["--include-opaque"], "docx read");
|
|
101659
102111
|
const includeOpaque = rest2.includes("--include-opaque");
|
|
101660
102112
|
const doc = await loadFile2(filePath);
|
|
101661
102113
|
console.log(JSON.stringify(readToJson2(doc, includeOpaque), null, 2));
|
|
@@ -101690,7 +102142,7 @@ function firstPositional(rest2) {
|
|
|
101690
102142
|
return rest2.find((a) => !a.startsWith("--"));
|
|
101691
102143
|
}
|
|
101692
102144
|
async function docxAppendParagraph(filePath, rest2) {
|
|
101693
|
-
rejectUnknownFlags(rest2,
|
|
102145
|
+
rejectUnknownFlags(rest2, "docx", "append-paragraph");
|
|
101694
102146
|
const text = firstPositional(rest2);
|
|
101695
102147
|
if (!filePath || text === void 0) fail2("Usage: lotics docx append-paragraph <file> '<text>' [--style=NAME]");
|
|
101696
102148
|
const style = getStyleFlag(rest2);
|
|
@@ -101700,7 +102152,7 @@ async function docxAppendParagraph(filePath, rest2) {
|
|
|
101700
102152
|
await writeDoc(filePath, replaceChildren(doc, newChildren));
|
|
101701
102153
|
}
|
|
101702
102154
|
async function docxInsertParagraph(filePath, rest2) {
|
|
101703
|
-
rejectUnknownFlags(rest2,
|
|
102155
|
+
rejectUnknownFlags(rest2, "docx", "insert-paragraph");
|
|
101704
102156
|
const text = firstPositional(rest2);
|
|
101705
102157
|
const at2 = getAtFlag(rest2);
|
|
101706
102158
|
if (!filePath || text === void 0 || at2 === void 0) {
|
|
@@ -101717,7 +102169,6 @@ async function docxInsertParagraph(filePath, rest2) {
|
|
|
101717
102169
|
await writeDoc(filePath, replaceChildren(doc, newChildren));
|
|
101718
102170
|
}
|
|
101719
102171
|
async function docxDeleteBlock(filePath, rest2) {
|
|
101720
|
-
rejectUnknownFlags(rest2, ["--at="], "docx delete-block");
|
|
101721
102172
|
const at2 = getAtFlag(rest2);
|
|
101722
102173
|
if (!filePath || at2 === void 0) fail2("Usage: lotics docx delete-block <file> --at=<index>");
|
|
101723
102174
|
const doc = await loadFile2(filePath);
|