@lotics/cli 0.162.0 → 0.163.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 +566 -145
- 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
|
|
@@ -45964,6 +45966,7 @@ var COMMANDS = [
|
|
|
45964
45966
|
},
|
|
45965
45967
|
{
|
|
45966
45968
|
verbs: ["xlsx"],
|
|
45969
|
+
ownsTail: true,
|
|
45967
45970
|
help: [
|
|
45968
45971
|
" lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem",
|
|
45969
45972
|
" (uses the bundled Lotics xlsx engine; prefer over",
|
|
@@ -45972,6 +45975,7 @@ var COMMANDS = [
|
|
|
45972
45975
|
},
|
|
45973
45976
|
{
|
|
45974
45977
|
verbs: ["docx"],
|
|
45978
|
+
ownsTail: true,
|
|
45975
45979
|
help: [
|
|
45976
45980
|
" lotics docx <subcommand> ... Read/write/edit .docx files on your local filesystem",
|
|
45977
45981
|
" (uses the bundled Lotics OOXML engine; preserves",
|
|
@@ -46003,6 +46007,32 @@ function renderCommandsHelp() {
|
|
|
46003
46007
|
function knownVerbs() {
|
|
46004
46008
|
return new Set(COMMANDS.flatMap((group) => [...group.verbs, ...group.aliases ?? []]));
|
|
46005
46009
|
}
|
|
46010
|
+
var SUBCOMMAND_ARGS = /* @__PURE__ */ new Map([
|
|
46011
|
+
["xlsx read", { valueFlags: ["--sheet", "--range"], flags: ["--with-format"] }],
|
|
46012
|
+
["xlsx set-cell", { content: true }],
|
|
46013
|
+
["docx read", { flags: ["--include-opaque"] }],
|
|
46014
|
+
["docx append-paragraph", { flags: ["--style="], content: true }],
|
|
46015
|
+
["docx insert-paragraph", { flags: ["--style=", "--at="], content: true }],
|
|
46016
|
+
["docx delete-block", { flags: ["--at="] }],
|
|
46017
|
+
["docx replace-text", { content: true }]
|
|
46018
|
+
]);
|
|
46019
|
+
function commandOwnsTail(command) {
|
|
46020
|
+
if (command === void 0) return false;
|
|
46021
|
+
return COMMANDS.some(
|
|
46022
|
+
(group) => group.ownsTail === true && [...group.verbs, ...group.aliases ?? []].includes(command)
|
|
46023
|
+
);
|
|
46024
|
+
}
|
|
46025
|
+
function subcommandArgs(command, subcommand) {
|
|
46026
|
+
if (command === void 0 || subcommand === void 0) return void 0;
|
|
46027
|
+
return SUBCOMMAND_ARGS.get(`${command} ${subcommand}`);
|
|
46028
|
+
}
|
|
46029
|
+
function knownSubcommandFlags(command, subcommand) {
|
|
46030
|
+
const spec = subcommandArgs(command, subcommand);
|
|
46031
|
+
return [...spec?.valueFlags ?? [], ...spec?.flags ?? []];
|
|
46032
|
+
}
|
|
46033
|
+
function matchesKnownFlag(known, arg) {
|
|
46034
|
+
return known.some((k) => k.endsWith("=") ? arg.startsWith(k) : arg === k);
|
|
46035
|
+
}
|
|
46006
46036
|
|
|
46007
46037
|
// src/report_command.ts
|
|
46008
46038
|
import fs6 from "node:fs";
|
|
@@ -46141,6 +46171,22 @@ function buildStarterTemplate(args) {
|
|
|
46141
46171
|
typecheck: "tsc --noEmit",
|
|
46142
46172
|
// Scope to src/ — oxlint's default scan walks node_modules too.
|
|
46143
46173
|
lint: "oxlint src",
|
|
46174
|
+
// No `--passWithNoTests`, deliberately. It would be justified by a
|
|
46175
|
+
// TEMPORARY condition ("the scaffold seeds nothing") and then ship
|
|
46176
|
+
// forever in every app's package.json and CI, where its only
|
|
46177
|
+
// remaining effect is to turn "vitest discovered no test files" into
|
|
46178
|
+
// success — a green suite over a renamed `src/`, a changed `include`
|
|
46179
|
+
// or a broken vitest config, indefinitely. A detector that cannot
|
|
46180
|
+
// fire when the thing is ABSENT rather than excessive is not one.
|
|
46181
|
+
//
|
|
46182
|
+
// What the scaffold seeds instead is `src/harness.test.ts`, which
|
|
46183
|
+
// asserts the HARNESS and nothing else. The file it replaces
|
|
46184
|
+
// (`src/App.test.tsx`) asserted the scaffold's placeholder copy
|
|
46185
|
+
// ("First item"), so it failed the moment the author did the very
|
|
46186
|
+
// thing the closing instructions tell them to do — edit
|
|
46187
|
+
// `src/App.tsx` — and a smoke test that renders `<App/>` would fail
|
|
46188
|
+
// the same way for any real app whose first screen reaches the host
|
|
46189
|
+
// bridge on mount. The harness test touches neither.
|
|
46144
46190
|
test: "vitest run"
|
|
46145
46191
|
},
|
|
46146
46192
|
dependencies: {
|
|
@@ -46716,15 +46762,33 @@ declare module "*.css";
|
|
|
46716
46762
|
`
|
|
46717
46763
|
},
|
|
46718
46764
|
{
|
|
46719
|
-
path: "src/
|
|
46765
|
+
path: "src/harness.test.ts",
|
|
46720
46766
|
content: `import { describe, test, expect } from "vitest";
|
|
46721
|
-
import {
|
|
46722
|
-
|
|
46767
|
+
import { getAppBinding } from "@lotics/app-sdk";
|
|
46768
|
+
|
|
46769
|
+
/**
|
|
46770
|
+
* The one test the scaffold seeds, and it is about the TEST SETUP rather than
|
|
46771
|
+
* about your app \u2014 so editing src/App.tsx can never break it, and \`npm test\`
|
|
46772
|
+
* never has to be green over zero tests.
|
|
46773
|
+
*
|
|
46774
|
+
* What it pins: tests run under jsdom, and vitest.setup.ts's echo stub answers
|
|
46775
|
+
* for \`getAppBinding\`. That second one is the whole reason the setup file
|
|
46776
|
+
* exists: a package-linked app's generated .lotics/app_fields.ts awaits it while
|
|
46777
|
+
* a test file is still COLLECTING, so without the stub every test fails before
|
|
46778
|
+
* any of them runs \u2014 including pure logic tests that never touch a field id.
|
|
46779
|
+
*
|
|
46780
|
+
* Delete it once you have tests of your own, or keep it; it costs nothing.
|
|
46781
|
+
*/
|
|
46782
|
+
describe("test harness", () => {
|
|
46783
|
+
test("runs in a DOM", () => {
|
|
46784
|
+
expect(typeof document).toBe("object");
|
|
46785
|
+
});
|
|
46723
46786
|
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
46787
|
+
test("resolves the app binding from the setup stub, so the app graph collects", async () => {
|
|
46788
|
+
const binding = await getAppBinding();
|
|
46789
|
+
// The echo stub answers any alias with a self-identifying ':test:' id \u2014 one
|
|
46790
|
+
// that can never be mistaken for a real fld_\u2026 from the workspace.
|
|
46791
|
+
expect(binding.fields["anything.at.all"]).toContain(":test:");
|
|
46728
46792
|
});
|
|
46729
46793
|
});
|
|
46730
46794
|
`
|
|
@@ -64810,7 +64874,14 @@ var formulaAggregateOperationSchema = external_exports.enum([
|
|
|
64810
64874
|
"median",
|
|
64811
64875
|
"min",
|
|
64812
64876
|
"max",
|
|
64813
|
-
"range"
|
|
64877
|
+
"range",
|
|
64878
|
+
"earliest",
|
|
64879
|
+
"latest",
|
|
64880
|
+
"date_range",
|
|
64881
|
+
"checked",
|
|
64882
|
+
"unchecked",
|
|
64883
|
+
"percent_checked",
|
|
64884
|
+
"percent_unchecked"
|
|
64814
64885
|
]);
|
|
64815
64886
|
var rollupAggregateOperationSchema = external_exports.enum([
|
|
64816
64887
|
"empty",
|
|
@@ -64910,25 +64981,25 @@ var aggregateOperationSchema = external_exports.enum([
|
|
|
64910
64981
|
"string_agg"
|
|
64911
64982
|
]);
|
|
64912
64983
|
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"],
|
|
64984
|
+
empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
64985
|
+
filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
64986
|
+
percent_empty: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
64987
|
+
percent_filled: ["text", "number", "date", "select", "select_member", "select_record_link", "files", "formula", "rollup", "autonumber"],
|
|
64988
|
+
unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup", "autonumber"],
|
|
64989
|
+
percent_unique: ["text", "number", "date", "select", "select_member", "files", "formula", "rollup", "autonumber"],
|
|
64919
64990
|
sum: ["number", "formula", "rollup"],
|
|
64920
64991
|
avg: ["number", "formula", "rollup"],
|
|
64921
64992
|
median: ["number", "formula", "rollup"],
|
|
64922
64993
|
min: ["number", "formula", "rollup"],
|
|
64923
64994
|
max: ["number", "formula", "rollup"],
|
|
64924
64995
|
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"]
|
|
64996
|
+
earliest: ["date", "formula", "rollup"],
|
|
64997
|
+
latest: ["date", "formula", "rollup"],
|
|
64998
|
+
date_range: ["date", "formula", "rollup"],
|
|
64999
|
+
checked: ["boolean", "formula"],
|
|
65000
|
+
unchecked: ["boolean", "formula"],
|
|
65001
|
+
percent_checked: ["boolean", "formula"],
|
|
65002
|
+
percent_unchecked: ["boolean", "formula"]
|
|
64932
65003
|
};
|
|
64933
65004
|
var FIELD_TYPE_OPERATIONS = /* @__PURE__ */ new Map();
|
|
64934
65005
|
for (const [operation, fieldTypes] of Object.entries(OPERATION_FIELD_TYPES)) {
|
|
@@ -65608,8 +65679,7 @@ var appWorkflowInputRecordLinkSchema = appWorkflowInputBaseSchema.extend({
|
|
|
65608
65679
|
table_id: zod_default.string().describe("Records must belong to this table. Validated at execute time + workflow save."),
|
|
65609
65680
|
multi: zod_default.boolean().optional().describe("When true, accept an array of record_ids. Defaults to single.")
|
|
65610
65681
|
});
|
|
65611
|
-
var
|
|
65612
|
-
type: zod_default.literal("select"),
|
|
65682
|
+
var selectOptionSourceShape = {
|
|
65613
65683
|
options: zod_default.array(
|
|
65614
65684
|
zod_default.object({
|
|
65615
65685
|
label: zod_default.string(),
|
|
@@ -65617,20 +65687,27 @@ var appWorkflowInputSelectSchema = appWorkflowInputBaseSchema.extend({
|
|
|
65617
65687
|
})
|
|
65618
65688
|
).min(1).optional().describe("Inline option set. Provide EITHER `options` OR `field`, never both."),
|
|
65619
65689
|
field: zod_default.string().optional().describe(
|
|
65620
|
-
"A select field's key (fld_\u2026) whose CURRENT options back this
|
|
65690
|
+
"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
65691
|
),
|
|
65622
65692
|
multi: zod_default.boolean().optional()
|
|
65623
|
-
}
|
|
65624
|
-
|
|
65625
|
-
|
|
65626
|
-
|
|
65627
|
-
ctx.
|
|
65628
|
-
|
|
65629
|
-
|
|
65630
|
-
|
|
65631
|
-
|
|
65632
|
-
|
|
65633
|
-
});
|
|
65693
|
+
};
|
|
65694
|
+
function checkExactlyOneOptionSource(kind) {
|
|
65695
|
+
return (ctx) => {
|
|
65696
|
+
const hasOptions = ctx.value.options !== void 0;
|
|
65697
|
+
const hasField = ctx.value.field !== void 0;
|
|
65698
|
+
if (hasOptions === hasField) {
|
|
65699
|
+
ctx.issues.push({
|
|
65700
|
+
code: "custom",
|
|
65701
|
+
input: ctx.value,
|
|
65702
|
+
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`
|
|
65703
|
+
});
|
|
65704
|
+
}
|
|
65705
|
+
};
|
|
65706
|
+
}
|
|
65707
|
+
var appWorkflowInputSelectSchema = appWorkflowInputBaseSchema.extend({
|
|
65708
|
+
type: zod_default.literal("select"),
|
|
65709
|
+
...selectOptionSourceShape
|
|
65710
|
+
}).check(checkExactlyOneOptionSource("input"));
|
|
65634
65711
|
var appWorkflowInputDateRangeSchema = appWorkflowInputBaseSchema.extend({
|
|
65635
65712
|
type: zod_default.literal("date_range"),
|
|
65636
65713
|
include_time: zod_default.boolean().optional().describe("When true, the range includes time components (datetime). Defaults to false.")
|
|
@@ -65701,11 +65778,14 @@ var appWorkflowOutputSchema = zod_default.lazy(
|
|
|
65701
65778
|
table_id: zod_default.string(),
|
|
65702
65779
|
multi: zod_default.boolean().optional()
|
|
65703
65780
|
}),
|
|
65781
|
+
// The SAME shape and the SAME check as the select INPUT, not a copy of
|
|
65782
|
+
// them: the two surfaces declare one vocabulary (GAP-294), so accepting
|
|
65783
|
+
// `field` on one and not the other — or validating it differently — just
|
|
65784
|
+
// moves the drift somewhere nobody looks.
|
|
65704
65785
|
appWorkflowOutputBaseSchema.extend({
|
|
65705
65786
|
type: zod_default.literal("select"),
|
|
65706
|
-
|
|
65707
|
-
|
|
65708
|
-
}),
|
|
65787
|
+
...selectOptionSourceShape
|
|
65788
|
+
}).check(checkExactlyOneOptionSource("output")),
|
|
65709
65789
|
appWorkflowOutputBaseSchema.extend({
|
|
65710
65790
|
type: zod_default.literal("object"),
|
|
65711
65791
|
fields: zod_default.record(zod_default.string(), appWorkflowOutputSchema)
|
|
@@ -66075,7 +66155,6 @@ __export(expression_array_exports, {
|
|
|
66075
66155
|
includes: () => includes,
|
|
66076
66156
|
intersection: () => intersection2,
|
|
66077
66157
|
intersectionBy: () => intersectionBy,
|
|
66078
|
-
isEmpty: () => isEmpty2,
|
|
66079
66158
|
last: () => last2,
|
|
66080
66159
|
list: () => list,
|
|
66081
66160
|
maxBy: () => maxBy,
|
|
@@ -66530,13 +66609,6 @@ function size(arr) {
|
|
|
66530
66609
|
}
|
|
66531
66610
|
return arr.length;
|
|
66532
66611
|
}
|
|
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
66612
|
function includes(collection, value2) {
|
|
66541
66613
|
if (collection == null) return false;
|
|
66542
66614
|
if (Array.isArray(collection)) return collection.includes(value2);
|
|
@@ -66807,6 +66879,7 @@ function capitalize(str) {
|
|
|
66807
66879
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
66808
66880
|
}
|
|
66809
66881
|
function replace(str, search, replacement) {
|
|
66882
|
+
if (str == null) return "";
|
|
66810
66883
|
if (typeof str !== "string") {
|
|
66811
66884
|
throw new Error("replace: expected string, got " + typeof str);
|
|
66812
66885
|
}
|
|
@@ -66819,6 +66892,7 @@ function replace(str, search, replacement) {
|
|
|
66819
66892
|
return str.replace(search, replacement);
|
|
66820
66893
|
}
|
|
66821
66894
|
function replaceAll(str, search, replacement) {
|
|
66895
|
+
if (str == null) return "";
|
|
66822
66896
|
if (typeof str !== "string") {
|
|
66823
66897
|
throw new Error("replaceAll: expected string, got " + typeof str);
|
|
66824
66898
|
}
|
|
@@ -66851,6 +66925,7 @@ function endsWith(str, suffix) {
|
|
|
66851
66925
|
return str.endsWith(suffix);
|
|
66852
66926
|
}
|
|
66853
66927
|
function substring(str, start, end) {
|
|
66928
|
+
if (str == null) return "";
|
|
66854
66929
|
if (typeof str !== "string") {
|
|
66855
66930
|
throw new Error("substring: expected string, got " + typeof str);
|
|
66856
66931
|
}
|
|
@@ -66863,6 +66938,7 @@ function substring(str, start, end) {
|
|
|
66863
66938
|
return str.substring(start, end);
|
|
66864
66939
|
}
|
|
66865
66940
|
function padStart(str, length2, char) {
|
|
66941
|
+
if (str == null) return "";
|
|
66866
66942
|
if (typeof str !== "string") {
|
|
66867
66943
|
throw new Error("padStart: expected string, got " + typeof str);
|
|
66868
66944
|
}
|
|
@@ -66875,6 +66951,7 @@ function padStart(str, length2, char) {
|
|
|
66875
66951
|
return str.padStart(length2, char);
|
|
66876
66952
|
}
|
|
66877
66953
|
function padEnd(str, length2, char) {
|
|
66954
|
+
if (str == null) return "";
|
|
66878
66955
|
if (typeof str !== "string") {
|
|
66879
66956
|
throw new Error("padEnd: expected string, got " + typeof str);
|
|
66880
66957
|
}
|
|
@@ -66980,6 +67057,7 @@ function numberToWords(value2) {
|
|
|
66980
67057
|
return negative ? "\xE2m " + result : result;
|
|
66981
67058
|
}
|
|
66982
67059
|
function formatNumber(value2, decimals) {
|
|
67060
|
+
if (value2 == null) return "";
|
|
66983
67061
|
if (typeof value2 !== "number" || Number.isNaN(value2)) {
|
|
66984
67062
|
throw new Error("formatNumber: expected number, got " + typeof value2);
|
|
66985
67063
|
}
|
|
@@ -67007,15 +67085,17 @@ __export(expression_math_exports, {
|
|
|
67007
67085
|
sum: () => sum
|
|
67008
67086
|
});
|
|
67009
67087
|
function mean(arr) {
|
|
67010
|
-
if (arr == null
|
|
67088
|
+
if (arr == null) return null;
|
|
67089
|
+
if (!Array.isArray(arr)) {
|
|
67011
67090
|
throw new Error("mean: expected array, got " + typeof arr);
|
|
67012
67091
|
}
|
|
67013
|
-
|
|
67014
|
-
|
|
67092
|
+
const present = arr.filter((item) => item != null);
|
|
67093
|
+
if (present.length === 0) {
|
|
67094
|
+
return null;
|
|
67015
67095
|
}
|
|
67016
|
-
const numArr =
|
|
67017
|
-
const
|
|
67018
|
-
const result =
|
|
67096
|
+
const numArr = present;
|
|
67097
|
+
const total = numArr.reduce((a, b) => a + b, 0);
|
|
67098
|
+
const result = total / numArr.length;
|
|
67019
67099
|
if (isNaN(result)) {
|
|
67020
67100
|
throw new Error("mean: array contains non-numeric values");
|
|
67021
67101
|
}
|
|
@@ -67028,7 +67108,7 @@ function min(...values3) {
|
|
|
67028
67108
|
if (values3.length === 1 && Array.isArray(values3[0])) {
|
|
67029
67109
|
const arr = values3[0];
|
|
67030
67110
|
if (arr.length === 0) {
|
|
67031
|
-
|
|
67111
|
+
return null;
|
|
67032
67112
|
}
|
|
67033
67113
|
return arr.reduce((minVal, item) => {
|
|
67034
67114
|
if (minVal == null) return item;
|
|
@@ -67037,9 +67117,9 @@ function min(...values3) {
|
|
|
67037
67117
|
return item < minVal ? item : minVal;
|
|
67038
67118
|
}
|
|
67039
67119
|
return String(item) < String(minVal) ? item : minVal;
|
|
67040
|
-
});
|
|
67120
|
+
}) ?? null;
|
|
67041
67121
|
}
|
|
67042
|
-
|
|
67122
|
+
const smallest = values3.reduce((minVal, item) => {
|
|
67043
67123
|
if (minVal == null) return item;
|
|
67044
67124
|
if (item == null) return minVal;
|
|
67045
67125
|
if (typeof minVal === "number" && typeof item === "number") {
|
|
@@ -67047,6 +67127,7 @@ function min(...values3) {
|
|
|
67047
67127
|
}
|
|
67048
67128
|
return String(item) < String(minVal) ? item : minVal;
|
|
67049
67129
|
});
|
|
67130
|
+
return smallest ?? null;
|
|
67050
67131
|
}
|
|
67051
67132
|
function max(...values3) {
|
|
67052
67133
|
if (values3.length === 0) {
|
|
@@ -67055,7 +67136,7 @@ function max(...values3) {
|
|
|
67055
67136
|
if (values3.length === 1 && Array.isArray(values3[0])) {
|
|
67056
67137
|
const arr = values3[0];
|
|
67057
67138
|
if (arr.length === 0) {
|
|
67058
|
-
|
|
67139
|
+
return null;
|
|
67059
67140
|
}
|
|
67060
67141
|
return arr.reduce((maxVal, item) => {
|
|
67061
67142
|
if (maxVal == null) return item;
|
|
@@ -67064,9 +67145,9 @@ function max(...values3) {
|
|
|
67064
67145
|
return item > maxVal ? item : maxVal;
|
|
67065
67146
|
}
|
|
67066
67147
|
return String(item) > String(maxVal) ? item : maxVal;
|
|
67067
|
-
});
|
|
67148
|
+
}) ?? null;
|
|
67068
67149
|
}
|
|
67069
|
-
|
|
67150
|
+
const largest = values3.reduce((maxVal, item) => {
|
|
67070
67151
|
if (maxVal == null) return item;
|
|
67071
67152
|
if (item == null) return maxVal;
|
|
67072
67153
|
if (typeof maxVal === "number" && typeof item === "number") {
|
|
@@ -67074,8 +67155,10 @@ function max(...values3) {
|
|
|
67074
67155
|
}
|
|
67075
67156
|
return String(item) > String(maxVal) ? item : maxVal;
|
|
67076
67157
|
});
|
|
67158
|
+
return largest ?? null;
|
|
67077
67159
|
}
|
|
67078
67160
|
function round(value2, precision) {
|
|
67161
|
+
if (value2 == null) return null;
|
|
67079
67162
|
if (typeof value2 !== "number") {
|
|
67080
67163
|
throw new Error("round: expected number, got " + typeof value2);
|
|
67081
67164
|
}
|
|
@@ -67094,24 +67177,28 @@ function sum(arr) {
|
|
|
67094
67177
|
return arr.reduce((a, b) => a + b, 0);
|
|
67095
67178
|
}
|
|
67096
67179
|
function abs(n) {
|
|
67180
|
+
if (n == null) return null;
|
|
67097
67181
|
if (typeof n !== "number") {
|
|
67098
67182
|
throw new Error("abs: expected number, got " + typeof n);
|
|
67099
67183
|
}
|
|
67100
67184
|
return Math.abs(n);
|
|
67101
67185
|
}
|
|
67102
67186
|
function ceil(n) {
|
|
67187
|
+
if (n == null) return null;
|
|
67103
67188
|
if (typeof n !== "number") {
|
|
67104
67189
|
throw new Error("ceil: expected number, got " + typeof n);
|
|
67105
67190
|
}
|
|
67106
67191
|
return Math.ceil(n);
|
|
67107
67192
|
}
|
|
67108
67193
|
function floor(n) {
|
|
67194
|
+
if (n == null) return null;
|
|
67109
67195
|
if (typeof n !== "number") {
|
|
67110
67196
|
throw new Error("floor: expected number, got " + typeof n);
|
|
67111
67197
|
}
|
|
67112
67198
|
return Math.floor(n);
|
|
67113
67199
|
}
|
|
67114
67200
|
function clamp(n, min2, max3) {
|
|
67201
|
+
if (n == null) return null;
|
|
67115
67202
|
if (typeof n !== "number") {
|
|
67116
67203
|
throw new Error("clamp: expected number, got " + typeof n);
|
|
67117
67204
|
}
|
|
@@ -67129,6 +67216,7 @@ function clamp(n, min2, max3) {
|
|
|
67129
67216
|
return n;
|
|
67130
67217
|
}
|
|
67131
67218
|
function percentage(part, total, decimals) {
|
|
67219
|
+
if (part == null || total == null) return null;
|
|
67132
67220
|
if (typeof part !== "number") {
|
|
67133
67221
|
throw new Error("percentage: part must be a number");
|
|
67134
67222
|
}
|
|
@@ -67149,6 +67237,7 @@ function percentage(part, total, decimals) {
|
|
|
67149
67237
|
return percent;
|
|
67150
67238
|
}
|
|
67151
67239
|
function pow(base, exponent) {
|
|
67240
|
+
if (base == null || exponent == null) return null;
|
|
67152
67241
|
if (typeof base !== "number") {
|
|
67153
67242
|
throw new Error("pow: base must be a number");
|
|
67154
67243
|
}
|
|
@@ -67158,6 +67247,7 @@ function pow(base, exponent) {
|
|
|
67158
67247
|
return Math.pow(base, exponent);
|
|
67159
67248
|
}
|
|
67160
67249
|
function sqrt(n) {
|
|
67250
|
+
if (n == null) return null;
|
|
67161
67251
|
if (typeof n !== "number") {
|
|
67162
67252
|
throw new Error("sqrt: expected number, got " + typeof n);
|
|
67163
67253
|
}
|
|
@@ -67167,6 +67257,7 @@ function sqrt(n) {
|
|
|
67167
67257
|
return Math.sqrt(n);
|
|
67168
67258
|
}
|
|
67169
67259
|
function mod(n, divisor) {
|
|
67260
|
+
if (n == null || divisor == null) return null;
|
|
67170
67261
|
if (typeof n !== "number") {
|
|
67171
67262
|
throw new Error("mod: expected number, got " + typeof n);
|
|
67172
67263
|
}
|
|
@@ -67339,7 +67430,7 @@ __export(expression_type_exports, {
|
|
|
67339
67430
|
coalesce: () => coalesce,
|
|
67340
67431
|
isArray: () => isArray2,
|
|
67341
67432
|
isBoolean: () => isBoolean,
|
|
67342
|
-
isEmpty: () =>
|
|
67433
|
+
isEmpty: () => isEmpty2,
|
|
67343
67434
|
isNotNull: () => isNotNull,
|
|
67344
67435
|
isNull: () => isNull,
|
|
67345
67436
|
isNumber: () => isNumber,
|
|
@@ -67351,6 +67442,22 @@ __export(expression_type_exports, {
|
|
|
67351
67442
|
toString: () => toString,
|
|
67352
67443
|
typeOf: () => typeOf
|
|
67353
67444
|
});
|
|
67445
|
+
|
|
67446
|
+
// ../shared/src/schemas/database_types.ts
|
|
67447
|
+
function isEmptyFieldValue(value2) {
|
|
67448
|
+
if (value2 === null || value2 === void 0) {
|
|
67449
|
+
return true;
|
|
67450
|
+
}
|
|
67451
|
+
if (typeof value2 === "string") {
|
|
67452
|
+
return value2 === "";
|
|
67453
|
+
}
|
|
67454
|
+
if (Array.isArray(value2)) {
|
|
67455
|
+
return value2.length === 0;
|
|
67456
|
+
}
|
|
67457
|
+
return false;
|
|
67458
|
+
}
|
|
67459
|
+
|
|
67460
|
+
// ../shared/src/expression_type.ts
|
|
67354
67461
|
function isArray2(val) {
|
|
67355
67462
|
return Array.isArray(val);
|
|
67356
67463
|
}
|
|
@@ -67373,6 +67480,7 @@ function coalesce(...values3) {
|
|
|
67373
67480
|
return void 0;
|
|
67374
67481
|
}
|
|
67375
67482
|
function toNumber(val) {
|
|
67483
|
+
if (val == null) return null;
|
|
67376
67484
|
if (typeof val === "number") return val;
|
|
67377
67485
|
if (typeof val === "string") {
|
|
67378
67486
|
const parsed = parseFloat(val);
|
|
@@ -67384,9 +67492,7 @@ function toNumber(val) {
|
|
|
67384
67492
|
throw new Error("toNumber: expected number or string, got " + typeof val);
|
|
67385
67493
|
}
|
|
67386
67494
|
function toString(val) {
|
|
67387
|
-
if (val == null)
|
|
67388
|
-
throw new Error("toString: cannot convert null or undefined to string");
|
|
67389
|
-
}
|
|
67495
|
+
if (val == null) return "";
|
|
67390
67496
|
return String(val);
|
|
67391
67497
|
}
|
|
67392
67498
|
function isNull(val) {
|
|
@@ -67395,13 +67501,11 @@ function isNull(val) {
|
|
|
67395
67501
|
function isNotNull(val) {
|
|
67396
67502
|
return !isNull(val);
|
|
67397
67503
|
}
|
|
67398
|
-
function
|
|
67399
|
-
|
|
67400
|
-
if (typeof val === "string") return val.length === 0;
|
|
67401
|
-
if (Array.isArray(val)) return val.length === 0;
|
|
67402
|
-
return false;
|
|
67504
|
+
function isEmpty2(val) {
|
|
67505
|
+
return isEmptyFieldValue(val);
|
|
67403
67506
|
}
|
|
67404
67507
|
function parseJson(value2) {
|
|
67508
|
+
if (value2 == null) return null;
|
|
67405
67509
|
if (typeof value2 !== "string") {
|
|
67406
67510
|
throw new Error("parseJson: expected a JSON string, got " + typeof value2);
|
|
67407
67511
|
}
|
|
@@ -71002,7 +71106,10 @@ ${args.instructions.replace(/\s+$/, "")}
|
|
|
71002
71106
|
`;
|
|
71003
71107
|
}
|
|
71004
71108
|
function stripAgentHeader(text) {
|
|
71005
|
-
return text.replace(/<!--\s*lotics:[\s\S]*?-->\n?/g, "")
|
|
71109
|
+
return normalizeAgentInstructions(text.replace(/<!--\s*lotics:[\s\S]*?-->\n?/g, ""));
|
|
71110
|
+
}
|
|
71111
|
+
function normalizeAgentInstructions(text) {
|
|
71112
|
+
return text.replace(/\n{3,}/g, "\n\n").trim();
|
|
71006
71113
|
}
|
|
71007
71114
|
|
|
71008
71115
|
// src/npm_registry.ts
|
|
@@ -71121,6 +71228,49 @@ function writeSynced(projectDir, kind, alias, entry) {
|
|
|
71121
71228
|
pkg2.lotics.synced[kind] = { ...pkg2.lotics.synced[kind], [alias]: entry };
|
|
71122
71229
|
fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
71123
71230
|
}
|
|
71231
|
+
function readManifestEntry(projectDir, kind, alias) {
|
|
71232
|
+
const pkg2 = readManifest(projectDir);
|
|
71233
|
+
if (!pkg2.lotics) return { found: false };
|
|
71234
|
+
const entry = { found: true };
|
|
71235
|
+
let found = false;
|
|
71236
|
+
const declarations = pkg2.lotics[kind];
|
|
71237
|
+
if (declarations && typeof declarations === "object" && !Array.isArray(declarations)) {
|
|
71238
|
+
const map3 = declarations;
|
|
71239
|
+
if (alias in map3) {
|
|
71240
|
+
entry.declaration = map3[alias];
|
|
71241
|
+
found = true;
|
|
71242
|
+
}
|
|
71243
|
+
}
|
|
71244
|
+
const baselines = pkg2.lotics.synced?.[kind];
|
|
71245
|
+
if (baselines && alias in baselines) {
|
|
71246
|
+
entry.synced = baselines[alias];
|
|
71247
|
+
found = true;
|
|
71248
|
+
}
|
|
71249
|
+
return found ? entry : { found: false };
|
|
71250
|
+
}
|
|
71251
|
+
function readManifest(projectDir) {
|
|
71252
|
+
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
71253
|
+
return JSON.parse(fs8.readFileSync(pkgPath2, "utf-8"));
|
|
71254
|
+
}
|
|
71255
|
+
function removeFromManifest(projectDir, kind, alias) {
|
|
71256
|
+
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
71257
|
+
const pkg2 = readManifest(projectDir);
|
|
71258
|
+
if (!pkg2.lotics) return;
|
|
71259
|
+
const declarations = pkg2.lotics[kind];
|
|
71260
|
+
if (declarations && typeof declarations === "object" && !Array.isArray(declarations)) {
|
|
71261
|
+
delete declarations[alias];
|
|
71262
|
+
}
|
|
71263
|
+
const baselines = pkg2.lotics.synced?.[kind];
|
|
71264
|
+
if (baselines) delete baselines[alias];
|
|
71265
|
+
fs8.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
71266
|
+
}
|
|
71267
|
+
function parkPrunedDeclaration(projectDir, kind, alias, removed) {
|
|
71268
|
+
const dir = path10.join(projectDir, ".lotics", "pruned", kind);
|
|
71269
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71270
|
+
const file2 = path10.join(dir, `${alias}.json`);
|
|
71271
|
+
fs8.writeFileSync(file2, JSON.stringify(removed, null, 2) + "\n");
|
|
71272
|
+
return path10.relative(projectDir, file2);
|
|
71273
|
+
}
|
|
71124
71274
|
var WORKFLOWS_DIR = path10.join("src", "workflows");
|
|
71125
71275
|
var WORKFLOW_GLOBALS_DIR = path10.join(".lotics", "workflows");
|
|
71126
71276
|
var AGENTS_DIR = path10.join("src", "agents");
|
|
@@ -71270,13 +71420,52 @@ function writeAgentFiles(projectDir, agents, opts) {
|
|
|
71270
71420
|
}
|
|
71271
71421
|
return written;
|
|
71272
71422
|
}
|
|
71423
|
+
function parkLiveAgentProse(projectDir, alias, instructions) {
|
|
71424
|
+
const dir = path10.join(projectDir, ".lotics", "agents");
|
|
71425
|
+
const file2 = path10.join(dir, `${alias}.live.md`);
|
|
71426
|
+
try {
|
|
71427
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71428
|
+
fs8.writeFileSync(file2, normalizeAgentInstructions(instructions) + "\n");
|
|
71429
|
+
return path10.relative(projectDir, file2);
|
|
71430
|
+
} catch (err2) {
|
|
71431
|
+
console.error(
|
|
71432
|
+
`\u26A0 Could not write the live prose for ${alias} to ${path10.relative(projectDir, file2)}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
71433
|
+
);
|
|
71434
|
+
return null;
|
|
71435
|
+
}
|
|
71436
|
+
}
|
|
71437
|
+
function reportUnseenLive(kind, unseen, appId) {
|
|
71438
|
+
if (unseen.length === 0) return;
|
|
71439
|
+
const plural = unseen.length === 1;
|
|
71440
|
+
console.error(
|
|
71441
|
+
`
|
|
71442
|
+
\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"}.
|
|
71443
|
+
Your next deploy will be REFUSED for ${plural ? "this alias" : "these aliases"}, which is the point: pushing would overwrite their edit.
|
|
71444
|
+
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.`
|
|
71445
|
+
);
|
|
71446
|
+
}
|
|
71447
|
+
function parkLiveWorkflowBody(projectDir, alias, source) {
|
|
71448
|
+
const dir = path10.join(projectDir, WORKFLOW_GLOBALS_DIR);
|
|
71449
|
+
const file2 = path10.join(dir, `${alias}.live.ts`);
|
|
71450
|
+
try {
|
|
71451
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
71452
|
+
fs8.writeFileSync(file2, normalizeWorkflowBody(source) + "\n");
|
|
71453
|
+
return path10.relative(projectDir, file2);
|
|
71454
|
+
} catch (err2) {
|
|
71455
|
+
console.error(
|
|
71456
|
+
`\u26A0 Could not write the live body for ${alias} to ${path10.relative(projectDir, file2)}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
71457
|
+
);
|
|
71458
|
+
return null;
|
|
71459
|
+
}
|
|
71460
|
+
}
|
|
71273
71461
|
function localPromptUnpushed(projectDir, alias, live) {
|
|
71274
71462
|
const file2 = agentFilePath2(projectDir, alias);
|
|
71275
71463
|
if (!fs8.existsSync(file2)) return false;
|
|
71276
|
-
const onDisk = fs8.readFileSync(file2, "utf-8");
|
|
71464
|
+
const onDisk = stripAgentHeader(fs8.readFileSync(file2, "utf-8"));
|
|
71465
|
+
if (onDisk === normalizeAgentInstructions(live)) return false;
|
|
71277
71466
|
const baseline = readSynced(projectDir).agents[alias]?.content;
|
|
71278
|
-
if (baseline === void 0) return
|
|
71279
|
-
return contentSha(
|
|
71467
|
+
if (baseline === void 0) return true;
|
|
71468
|
+
return contentSha(onDisk) !== baseline;
|
|
71280
71469
|
}
|
|
71281
71470
|
function generatedWorkflowDescription(appName, alias) {
|
|
71282
71471
|
return `Workflow invoked by app "${appName}" via useWorkflow("${alias}").`;
|
|
@@ -71285,14 +71474,15 @@ function localBodyUnpushed(projectDir, alias, live) {
|
|
|
71285
71474
|
const file2 = workflowFilePath(projectDir, alias);
|
|
71286
71475
|
if (!fs8.existsSync(file2)) return false;
|
|
71287
71476
|
const onDisk = fs8.readFileSync(file2, "utf-8");
|
|
71477
|
+
if (stripWorkflowHeader(onDisk).trim() === normalizeWorkflowBody(live).trim()) return false;
|
|
71288
71478
|
const baseline = readSynced(projectDir).workflows[alias]?.content;
|
|
71289
|
-
if (baseline === void 0)
|
|
71290
|
-
return stripWorkflowHeader(onDisk).trim() !== normalizeWorkflowBody(live).trim();
|
|
71291
|
-
}
|
|
71479
|
+
if (baseline === void 0) return true;
|
|
71292
71480
|
return contentSha(stripWorkflowHeader(onDisk)) !== baseline;
|
|
71293
71481
|
}
|
|
71294
71482
|
async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows, opts) {
|
|
71295
71483
|
const written = [];
|
|
71484
|
+
const kept = [];
|
|
71485
|
+
const unseenLive = [];
|
|
71296
71486
|
for (const [alias, declaration] of Object.entries(workflows)) {
|
|
71297
71487
|
const res = await client.getAppWorkflow(app_id, alias);
|
|
71298
71488
|
if (res.error && !isMissingWorkflowBodyError(res.error)) {
|
|
@@ -71317,7 +71507,16 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
|
|
|
71317
71507
|
toWorkflowDtsDeclaration(declaration)
|
|
71318
71508
|
);
|
|
71319
71509
|
if (!opts.overwrite && localBodyUnpushed(projectDir, alias, source)) {
|
|
71320
|
-
|
|
71510
|
+
kept.push(`src/workflows/${alias}.ts`);
|
|
71511
|
+
const previous = readSynced(projectDir).workflows[alias];
|
|
71512
|
+
if (body_sha && (previous?.live === void 0 || previous.live === body_sha)) {
|
|
71513
|
+
writeSynced(projectDir, "workflows", alias, { ...previous, live: body_sha });
|
|
71514
|
+
} else if (body_sha) {
|
|
71515
|
+
unseenLive.push({
|
|
71516
|
+
alias,
|
|
71517
|
+
parked: parkLiveWorkflowBody(projectDir, alias, source)
|
|
71518
|
+
});
|
|
71519
|
+
}
|
|
71321
71520
|
continue;
|
|
71322
71521
|
}
|
|
71323
71522
|
writeWorkflowFile(projectDir, alias, source, envelope2);
|
|
@@ -71341,7 +71540,7 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
|
|
|
71341
71540
|
}
|
|
71342
71541
|
written.push(alias);
|
|
71343
71542
|
}
|
|
71344
|
-
return written;
|
|
71543
|
+
return { written, kept, unseenLive };
|
|
71345
71544
|
}
|
|
71346
71545
|
function writeWorkflowDescription(projectDir, alias, description) {
|
|
71347
71546
|
const pkgPath2 = path10.join(projectDir, "package.json");
|
|
@@ -72041,15 +72240,21 @@ async function hydrateAppProject(client, targetPath, app, opts) {
|
|
|
72041
72240
|
);
|
|
72042
72241
|
const workflows = app.workflows ?? {};
|
|
72043
72242
|
if (Object.keys(workflows).length > 0) {
|
|
72044
|
-
const written = await writeWorkflowFiles(
|
|
72045
|
-
|
|
72046
|
-
|
|
72047
|
-
|
|
72243
|
+
const { written, kept, unseenLive } = await writeWorkflowFiles(
|
|
72244
|
+
client,
|
|
72245
|
+
targetPath,
|
|
72246
|
+
app.id,
|
|
72247
|
+
app.name,
|
|
72248
|
+
workflows,
|
|
72249
|
+
{ overwrite: opts.force === true }
|
|
72250
|
+
);
|
|
72251
|
+
opts.kept.push(...kept);
|
|
72048
72252
|
if (written.length > 0) {
|
|
72049
72253
|
console.error(
|
|
72050
72254
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
|
|
72051
72255
|
);
|
|
72052
72256
|
}
|
|
72257
|
+
reportUnseenLive("workflow body", unseenLive, app.id);
|
|
72053
72258
|
}
|
|
72054
72259
|
const agents = app.agents ?? {};
|
|
72055
72260
|
if (Object.keys(agents).length > 0) {
|
|
@@ -72057,17 +72262,31 @@ async function hydrateAppProject(client, targetPath, app, opts) {
|
|
|
72057
72262
|
overwrite: opts.force === true,
|
|
72058
72263
|
kept: opts.kept
|
|
72059
72264
|
});
|
|
72060
|
-
|
|
72061
|
-
|
|
72062
|
-
|
|
72063
|
-
|
|
72064
|
-
|
|
72265
|
+
const unseenLiveProse = [];
|
|
72266
|
+
for (const [alias, declaration] of Object.entries(agents)) {
|
|
72267
|
+
const instructions = declaration?.instructions;
|
|
72268
|
+
if (typeof instructions !== "string") continue;
|
|
72269
|
+
const live = contentSha(instructions);
|
|
72270
|
+
if (written.includes(alias)) {
|
|
72271
|
+
writeSynced(targetPath, "agents", alias, {
|
|
72272
|
+
content: contentSha(normalizeAgentInstructions(instructions)),
|
|
72273
|
+
live
|
|
72274
|
+
});
|
|
72275
|
+
continue;
|
|
72276
|
+
}
|
|
72277
|
+
const previous = readSynced(targetPath).agents[alias];
|
|
72278
|
+
if (previous?.live === void 0 || previous.live === live) {
|
|
72279
|
+
writeSynced(targetPath, "agents", alias, { ...previous, live });
|
|
72280
|
+
continue;
|
|
72281
|
+
}
|
|
72282
|
+
unseenLiveProse.push({ alias, parked: parkLiveAgentProse(targetPath, alias, instructions) });
|
|
72065
72283
|
}
|
|
72066
72284
|
if (written.length > 0) {
|
|
72067
72285
|
console.error(
|
|
72068
72286
|
`Wrote ${written.length} agent instruction ${written.length === 1 ? "file" : "files"} to ${AGENTS_DIR}/ (${written.join(", ")})`
|
|
72069
72287
|
);
|
|
72070
72288
|
}
|
|
72289
|
+
reportUnseenLive("prompt", unseenLiveProse, app.id);
|
|
72071
72290
|
}
|
|
72072
72291
|
await runNpmInstall(targetPath);
|
|
72073
72292
|
writeAppDtsKitLinks(targetPath);
|
|
@@ -72090,16 +72309,17 @@ async function appPull(client, args) {
|
|
|
72090
72309
|
console.error(`Downloading source archive...`);
|
|
72091
72310
|
await downloadToFile(sourceUrl, tmpFile);
|
|
72092
72311
|
const keptLocal = [];
|
|
72312
|
+
const restoredFromArchive = [];
|
|
72093
72313
|
const stagingDir = fs8.mkdtempSync(path10.join(tmpdir(), `lotics-pull-${app.id}-`));
|
|
72094
72314
|
try {
|
|
72095
72315
|
await runTar(["-xzf", tmpFile, "-C", stagingDir], stagingDir);
|
|
72096
72316
|
console.error(`Extracting to ${targetPath}...`);
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72100
|
-
|
|
72101
|
-
|
|
72102
|
-
);
|
|
72317
|
+
const copied = copyTree(stagingDir, targetPath, {
|
|
72318
|
+
overwrite: args.force === true,
|
|
72319
|
+
skip: [WORKFLOWS_DIR, AGENTS_DIR].map((d) => d.split(path10.sep).join("/"))
|
|
72320
|
+
});
|
|
72321
|
+
keptLocal.push(...copied.kept);
|
|
72322
|
+
restoredFromArchive.push(...copied.created);
|
|
72103
72323
|
} finally {
|
|
72104
72324
|
if (fs8.existsSync(tmpFile)) fs8.unlinkSync(tmpFile);
|
|
72105
72325
|
fs8.rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -72114,6 +72334,7 @@ async function appPull(client, args) {
|
|
|
72114
72334
|
...args.force !== void 0 ? { force: args.force } : {},
|
|
72115
72335
|
kept: keptLocal
|
|
72116
72336
|
});
|
|
72337
|
+
reportRestoredFiles(restoredFromArchive, prior !== null);
|
|
72117
72338
|
reportKeptLocalFiles(keptLocal, app.id, {
|
|
72118
72339
|
// `prior` is non-null whenever the stamp was held — `stampAfterPull` cannot
|
|
72119
72340
|
// hold against nothing — so the fallback only covers the un-held branch,
|
|
@@ -72275,7 +72496,8 @@ async function appDeploy(client, args) {
|
|
|
72275
72496
|
if (args.prune) {
|
|
72276
72497
|
await pruneOrphanedBindings(
|
|
72277
72498
|
client,
|
|
72278
|
-
|
|
72499
|
+
projectDir,
|
|
72500
|
+
{ app_id: meta3.app_id },
|
|
72279
72501
|
orphanedBindings(called, liveAfter),
|
|
72280
72502
|
called.dynamic
|
|
72281
72503
|
);
|
|
@@ -72443,7 +72665,7 @@ function driftedAgentAliases(projectDir, liveAgents) {
|
|
|
72443
72665
|
const file2 = agentFilePath2(projectDir, alias);
|
|
72444
72666
|
if (!fs8.existsSync(file2)) continue;
|
|
72445
72667
|
const local = stripAgentHeader(fs8.readFileSync(file2, "utf-8"));
|
|
72446
|
-
if (local === "" || local === (live.instructions ?? "")) continue;
|
|
72668
|
+
if (local === "" || local === normalizeAgentInstructions(live.instructions ?? "")) continue;
|
|
72447
72669
|
drifted.push(alias);
|
|
72448
72670
|
}
|
|
72449
72671
|
return drifted.sort();
|
|
@@ -72505,28 +72727,31 @@ function warnAboutOrphanedBindings(called, live) {
|
|
|
72505
72727
|
);
|
|
72506
72728
|
return lines.length;
|
|
72507
72729
|
}
|
|
72508
|
-
async function pruneOrphanedBindings(client,
|
|
72730
|
+
async function pruneOrphanedBindings(client, projectDir, app, orphans, dynamic) {
|
|
72509
72731
|
const targets = [
|
|
72510
72732
|
...orphans.queries.map((alias) => ({
|
|
72511
72733
|
tool: "remove_app_query",
|
|
72734
|
+
kind: "queries",
|
|
72512
72735
|
alias,
|
|
72513
72736
|
label: `query ${alias}`,
|
|
72514
|
-
|
|
72737
|
+
setCommand: `lotics app query set ${alias}`
|
|
72515
72738
|
})),
|
|
72516
72739
|
...orphans.workflows.map((alias) => ({
|
|
72517
72740
|
tool: "remove_app_workflow",
|
|
72741
|
+
kind: "workflows",
|
|
72518
72742
|
alias,
|
|
72519
72743
|
label: `workflow ${alias}`,
|
|
72520
|
-
|
|
72744
|
+
setCommand: `lotics app workflow set ${alias}`
|
|
72521
72745
|
})),
|
|
72522
72746
|
...orphans.agents.map((alias) => ({
|
|
72523
72747
|
tool: "remove_app_agent",
|
|
72748
|
+
kind: "agents",
|
|
72524
72749
|
alias,
|
|
72525
72750
|
label: `agent ${alias}`,
|
|
72526
|
-
|
|
72751
|
+
setCommand: `lotics app agent set ${alias}`
|
|
72527
72752
|
}))
|
|
72528
72753
|
];
|
|
72529
|
-
if (targets.length === 0) return
|
|
72754
|
+
if (targets.length === 0) return;
|
|
72530
72755
|
if (dynamic.length > 0) {
|
|
72531
72756
|
console.error(
|
|
72532
72757
|
`
|
|
@@ -72534,19 +72759,63 @@ async function pruneOrphanedBindings(client, appId, orphans, dynamic) {
|
|
|
72534
72759
|
` + targets.map((t) => ` \u2022 ${t.label}`).join("\n") + `
|
|
72535
72760
|
Make those call sites literal and the next deploy will clean them up, or remove the ones you know are dead by hand.`
|
|
72536
72761
|
);
|
|
72537
|
-
return
|
|
72762
|
+
return;
|
|
72538
72763
|
}
|
|
72539
|
-
|
|
72764
|
+
const tablesBefore = new Set(resolveCodegenTableIds(projectDir, readAppMeta(projectDir).queries ?? {}));
|
|
72765
|
+
let removedLocally = false;
|
|
72540
72766
|
for (const target of targets) {
|
|
72541
|
-
const res = await client.execute(target.tool, { app_id:
|
|
72767
|
+
const res = await client.execute(target.tool, { app_id: app.app_id, alias: target.alias });
|
|
72542
72768
|
if (res.error) {
|
|
72543
|
-
failed += 1;
|
|
72544
72769
|
console.error(` \u2717 could not unbind ${target.label} \u2014 ${res.error}`);
|
|
72545
72770
|
continue;
|
|
72546
72771
|
}
|
|
72547
|
-
|
|
72772
|
+
let line;
|
|
72773
|
+
try {
|
|
72774
|
+
const entry = readManifestEntry(projectDir, target.kind, target.alias);
|
|
72775
|
+
if (entry.found) {
|
|
72776
|
+
const parked = parkPrunedDeclaration(projectDir, target.kind, target.alias, {
|
|
72777
|
+
declaration: entry.declaration,
|
|
72778
|
+
synced: entry.synced
|
|
72779
|
+
});
|
|
72780
|
+
removeFromManifest(projectDir, target.kind, target.alias);
|
|
72781
|
+
removedLocally = true;
|
|
72782
|
+
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}`;
|
|
72783
|
+
} else {
|
|
72784
|
+
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>)`;
|
|
72785
|
+
}
|
|
72786
|
+
} catch (err2) {
|
|
72787
|
+
console.error(
|
|
72788
|
+
` \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.`
|
|
72789
|
+
);
|
|
72790
|
+
continue;
|
|
72791
|
+
}
|
|
72792
|
+
console.error(line);
|
|
72793
|
+
}
|
|
72794
|
+
if (removedLocally) {
|
|
72795
|
+
try {
|
|
72796
|
+
const meta3 = readAppMeta(projectDir);
|
|
72797
|
+
writeAppDts(projectDir, {
|
|
72798
|
+
workflows: meta3.workflows,
|
|
72799
|
+
queries: meta3.queries,
|
|
72800
|
+
agents: meta3.agents
|
|
72801
|
+
});
|
|
72802
|
+
if (orphans.queries.length > 0) {
|
|
72803
|
+
const dropped = [...tablesBefore].filter(
|
|
72804
|
+
(id) => !resolveCodegenTableIds(projectDir, meta3.queries ?? {}).includes(id)
|
|
72805
|
+
);
|
|
72806
|
+
if (dropped.length > 0) {
|
|
72807
|
+
console.error(
|
|
72808
|
+
` \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.`
|
|
72809
|
+
);
|
|
72810
|
+
}
|
|
72811
|
+
await writeGeneratedAppFields(client, projectDir, app, meta3.queries ?? {});
|
|
72812
|
+
}
|
|
72813
|
+
} catch (err2) {
|
|
72814
|
+
console.error(
|
|
72815
|
+
` \u2717 could not refresh the generated companions after pruning \u2014 ${err2 instanceof Error ? err2.message : String(err2)}. Run lotics app codegen.`
|
|
72816
|
+
);
|
|
72817
|
+
}
|
|
72548
72818
|
}
|
|
72549
|
-
return failed;
|
|
72550
72819
|
}
|
|
72551
72820
|
function derivedDeployMessage(pending) {
|
|
72552
72821
|
const parts = [
|
|
@@ -72558,6 +72827,7 @@ function derivedDeployMessage(pending) {
|
|
|
72558
72827
|
}
|
|
72559
72828
|
function copyTree(from, to, opts, relative = "") {
|
|
72560
72829
|
const kept = [];
|
|
72830
|
+
const created = [];
|
|
72561
72831
|
for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
|
|
72562
72832
|
const src = path10.join(from, entry.name);
|
|
72563
72833
|
const dest = path10.join(to, entry.name);
|
|
@@ -72565,17 +72835,32 @@ function copyTree(from, to, opts, relative = "") {
|
|
|
72565
72835
|
if (opts.skip?.includes(rel.split(path10.sep).join("/"))) continue;
|
|
72566
72836
|
if (entry.isDirectory()) {
|
|
72567
72837
|
fs8.mkdirSync(dest, { recursive: true });
|
|
72568
|
-
|
|
72838
|
+
const child = copyTree(src, dest, opts, rel);
|
|
72839
|
+
kept.push(...child.kept);
|
|
72840
|
+
created.push(...child.created);
|
|
72569
72841
|
continue;
|
|
72570
72842
|
}
|
|
72571
|
-
|
|
72843
|
+
const existed = fs8.existsSync(dest);
|
|
72844
|
+
if (!opts.overwrite && existed) {
|
|
72572
72845
|
if (fs8.readFileSync(src).equals(fs8.readFileSync(dest))) continue;
|
|
72573
72846
|
kept.push(rel);
|
|
72574
72847
|
continue;
|
|
72575
72848
|
}
|
|
72576
72849
|
fs8.copyFileSync(src, dest);
|
|
72850
|
+
if (!existed) created.push(rel);
|
|
72577
72851
|
}
|
|
72578
|
-
return kept;
|
|
72852
|
+
return { kept, created };
|
|
72853
|
+
}
|
|
72854
|
+
function reportRestoredFiles(restored, isRefresh) {
|
|
72855
|
+
if (!isRefresh || restored.length === 0) return;
|
|
72856
|
+
const shown = restored.slice(0, 10);
|
|
72857
|
+
console.error(
|
|
72858
|
+
`
|
|
72859
|
+
Restored ${restored.length} file${restored.length === 1 ? "" : "s"} the app's last deployed source carries and your tree did not:
|
|
72860
|
+
` + shown.map((f) => ` \u2022 ${f}`).join("\n") + (restored.length > shown.length ? `
|
|
72861
|
+
\u2022 \u2026and ${restored.length - shown.length} more` : "") + `
|
|
72862
|
+
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.`
|
|
72863
|
+
);
|
|
72579
72864
|
}
|
|
72580
72865
|
function reportKeptLocalFiles(kept, appId, motion) {
|
|
72581
72866
|
if (kept.length === 0) return;
|
|
@@ -73135,15 +73420,19 @@ async function appWorkflowPull(client, args = {}) {
|
|
|
73135
73420
|
console.error(`App ${meta3.app_id} has no bound workflows.`);
|
|
73136
73421
|
return;
|
|
73137
73422
|
}
|
|
73138
|
-
const kept =
|
|
73139
|
-
|
|
73140
|
-
|
|
73141
|
-
|
|
73142
|
-
|
|
73423
|
+
const { written, kept, unseenLive } = await writeWorkflowFiles(
|
|
73424
|
+
client,
|
|
73425
|
+
projectDir,
|
|
73426
|
+
meta3.app_id,
|
|
73427
|
+
app.name,
|
|
73428
|
+
workflows,
|
|
73429
|
+
{ overwrite: args.force === true }
|
|
73430
|
+
);
|
|
73143
73431
|
console.error(
|
|
73144
73432
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
|
|
73145
73433
|
);
|
|
73146
73434
|
reportKeptLocalFiles(kept, meta3.app_id, null);
|
|
73435
|
+
reportUnseenLive("workflow body", unseenLive, meta3.app_id);
|
|
73147
73436
|
ensureAppTsconfig(projectDir);
|
|
73148
73437
|
}
|
|
73149
73438
|
async function appWorkflowCheck(args) {
|
|
@@ -73291,6 +73580,23 @@ function parseArgs(argv) {
|
|
|
73291
73580
|
let i2 = 0;
|
|
73292
73581
|
while (i2 < argv.length) {
|
|
73293
73582
|
const arg = argv[i2];
|
|
73583
|
+
if (subcommand !== void 0 && commandOwnsTail(command) && arg !== "--help" && arg !== "-h") {
|
|
73584
|
+
const local = subcommandArgs(command, subcommand);
|
|
73585
|
+
const looksLikeFlag = arg.length > 1 && arg.startsWith("-");
|
|
73586
|
+
const declared = looksLikeFlag && local !== void 0 && matchesKnownFlag([...local.valueFlags ?? [], ...local.flags ?? []], arg);
|
|
73587
|
+
if (declared) {
|
|
73588
|
+
restArgs.push(arg);
|
|
73589
|
+
if ((local?.valueFlags ?? []).includes(arg) && i2 + 1 < argv.length) restArgs.push(argv[++i2]);
|
|
73590
|
+
} else if (looksLikeFlag && local?.content !== true) {
|
|
73591
|
+
unknownFlag = arg;
|
|
73592
|
+
} else if (!toolArgs) {
|
|
73593
|
+
toolArgs = arg;
|
|
73594
|
+
} else {
|
|
73595
|
+
restArgs.push(arg);
|
|
73596
|
+
}
|
|
73597
|
+
i2++;
|
|
73598
|
+
continue;
|
|
73599
|
+
}
|
|
73294
73600
|
switch (arg) {
|
|
73295
73601
|
case "--json":
|
|
73296
73602
|
flags.json = true;
|
|
@@ -73715,11 +74021,13 @@ var CliError = class extends Error {
|
|
|
73715
74021
|
function fail2(message2) {
|
|
73716
74022
|
throw new CliError(message2);
|
|
73717
74023
|
}
|
|
73718
|
-
function rejectUnknownFlags(rest2,
|
|
73719
|
-
const
|
|
73720
|
-
const unknown2 = rest2.filter((a) => a.startsWith("--") && !
|
|
74024
|
+
function rejectUnknownFlags(rest2, command, subcommand) {
|
|
74025
|
+
const known = knownSubcommandFlags(command, subcommand);
|
|
74026
|
+
const unknown2 = rest2.filter((a) => a.startsWith("--") && !matchesKnownFlag(known, a));
|
|
73721
74027
|
if (unknown2.length === 0) return;
|
|
73722
|
-
fail2(
|
|
74028
|
+
fail2(
|
|
74029
|
+
`Unknown ${unknown2.length > 1 ? "flags" : "flag"} for ${command} ${subcommand}: ${unknown2.join(", ")}. Supported: ${known.join(", ")}.`
|
|
74030
|
+
);
|
|
73723
74031
|
}
|
|
73724
74032
|
function rejectExtraArgs(rest2, arity, usage) {
|
|
73725
74033
|
if (rest2.length <= arity) return;
|
|
@@ -94808,6 +95116,37 @@ function collectRecomputeReport(workbook, unreadable) {
|
|
|
94808
95116
|
return { total, unevaluated };
|
|
94809
95117
|
}
|
|
94810
95118
|
|
|
95119
|
+
// ../xlsx/src/numfmt_type.ts
|
|
95120
|
+
function classifyNumFmt(code) {
|
|
95121
|
+
if (!code || code === "General") return "general";
|
|
95122
|
+
const first3 = code.split(";")[0] ?? code;
|
|
95123
|
+
const currency = hasCurrencyTokens(first3);
|
|
95124
|
+
const stripped = stripQuotedLiterals(first3);
|
|
95125
|
+
if (stripped.trim() === "@") return "text";
|
|
95126
|
+
if (hasDateTokens(stripped)) return "date";
|
|
95127
|
+
if (stripped.includes("%")) return "percent";
|
|
95128
|
+
if (currency) return "currency";
|
|
95129
|
+
if (/[#0?]/.test(stripped)) return "numeric";
|
|
95130
|
+
return "general";
|
|
95131
|
+
}
|
|
95132
|
+
function stripQuotedLiterals(code) {
|
|
95133
|
+
return code.replace(/"[^"]*"/g, "").replace(/\\./g, " ");
|
|
95134
|
+
}
|
|
95135
|
+
function hasDateTokens(code) {
|
|
95136
|
+
if (/\b(y{1,4}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/p)\b/i.test(code)) return true;
|
|
95137
|
+
if (/[ymdhs]\s*[-\/.:\s]\s*m/i.test(code)) return true;
|
|
95138
|
+
if (/m\s*[-\/.:\s]\s*[ymdhs]/i.test(code)) return true;
|
|
95139
|
+
return false;
|
|
95140
|
+
}
|
|
95141
|
+
function hasCurrencyTokens(code) {
|
|
95142
|
+
if (/[$¥€£₫¢]/.test(code)) return true;
|
|
95143
|
+
if (/\[\$/.test(code)) return true;
|
|
95144
|
+
return false;
|
|
95145
|
+
}
|
|
95146
|
+
function isPaddedIdentifier(input) {
|
|
95147
|
+
return /^[+-]?0\d/.test(input);
|
|
95148
|
+
}
|
|
95149
|
+
|
|
94811
95150
|
// ../xlsx/src/clipboard.ts
|
|
94812
95151
|
function adjustFormulaRefsForRowShift(formula, atRow, delta) {
|
|
94813
95152
|
return formula.replace(
|
|
@@ -94835,17 +95174,18 @@ function adjustFormulaRefsForColShift(formula, atCol, delta) {
|
|
|
94835
95174
|
}
|
|
94836
95175
|
|
|
94837
95176
|
// ../xlsx/src/spreadsheet_commands.ts
|
|
94838
|
-
function setCellValueCommand(workbook, engine, sheetIndex, row, col, newValue) {
|
|
95177
|
+
function setCellValueCommand(workbook, engine, sheetIndex, row, col, newValue, options) {
|
|
94839
95178
|
const sheet = workbook.sheets[sheetIndex];
|
|
94840
95179
|
const ref2 = rowColToRef(row, col);
|
|
94841
95180
|
const oldCell = sheet.getCell(ref2);
|
|
94842
95181
|
const oldValue = oldCell?.value ?? null;
|
|
94843
95182
|
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
95183
|
const styleIndex = oldCell?.styleIndex ?? 0;
|
|
94848
95184
|
const numFmtCode = oldCell?.numFmtCode ?? "General";
|
|
95185
|
+
const isText = typeof newValue === "string" && options?.typed !== true;
|
|
95186
|
+
const isFormula = isText && newValue.startsWith("=");
|
|
95187
|
+
const formula = isFormula ? newValue.slice(1) : void 0;
|
|
95188
|
+
const value2 = isFormula ? null : isText ? parseInputValue(newValue, numFmtCode) : newValue;
|
|
94849
95189
|
return {
|
|
94850
95190
|
description: `Set ${ref2} = ${newValue}`,
|
|
94851
95191
|
execute() {
|
|
@@ -95384,6 +95724,43 @@ function deleteColsCommand(workbook, sheetIndex, at2, count) {
|
|
|
95384
95724
|
}
|
|
95385
95725
|
};
|
|
95386
95726
|
}
|
|
95727
|
+
function setRangeNumFmtCommand(workbook, sheetIndex, startRow, startCol, endRow, endCol, numFmtCode) {
|
|
95728
|
+
const sheet = workbook.sheets[sheetIndex];
|
|
95729
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
95730
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
95731
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
95732
|
+
const ref2 = rowColToRef(r, c);
|
|
95733
|
+
const cell = sheet.getCell(ref2);
|
|
95734
|
+
snapshot.set(ref2, cell ? { value: cell.value, styleIndex: cell.styleIndex, numFmtCode: cell.numFmtCode, formula: cell.formula } : null);
|
|
95735
|
+
}
|
|
95736
|
+
}
|
|
95737
|
+
const rangeRef = `${rowColToRef(startRow, startCol)}:${rowColToRef(endRow, endCol)}`;
|
|
95738
|
+
return {
|
|
95739
|
+
description: `Format ${rangeRef} as ${numFmtCode}`,
|
|
95740
|
+
execute() {
|
|
95741
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
95742
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
95743
|
+
const ref2 = rowColToRef(r, c);
|
|
95744
|
+
const cell = sheet.getCell(ref2);
|
|
95745
|
+
if (cell) {
|
|
95746
|
+
sheet.set(ref2, cell.value, sheet.styles?.get(cell.styleIndex), numFmtCode, cell.formula);
|
|
95747
|
+
} else {
|
|
95748
|
+
sheet.set(ref2, null, void 0, numFmtCode);
|
|
95749
|
+
}
|
|
95750
|
+
}
|
|
95751
|
+
}
|
|
95752
|
+
},
|
|
95753
|
+
undo() {
|
|
95754
|
+
for (const [ref2, snap] of snapshot) {
|
|
95755
|
+
if (snap) {
|
|
95756
|
+
sheet.set(ref2, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
|
|
95757
|
+
} else {
|
|
95758
|
+
sheet.deleteCell(ref2);
|
|
95759
|
+
}
|
|
95760
|
+
}
|
|
95761
|
+
}
|
|
95762
|
+
};
|
|
95763
|
+
}
|
|
95387
95764
|
function addSheetCommand(workbook, name2) {
|
|
95388
95765
|
return {
|
|
95389
95766
|
description: `Add sheet ${name2}`,
|
|
@@ -95426,10 +95803,23 @@ function renameSheetCommand(workbook, index, newName) {
|
|
|
95426
95803
|
}
|
|
95427
95804
|
};
|
|
95428
95805
|
}
|
|
95429
|
-
function
|
|
95806
|
+
function batchCommand(description, commands) {
|
|
95807
|
+
return {
|
|
95808
|
+
description,
|
|
95809
|
+
execute() {
|
|
95810
|
+
for (const cmd of commands) cmd.execute();
|
|
95811
|
+
},
|
|
95812
|
+
undo() {
|
|
95813
|
+
for (let i2 = commands.length - 1; i2 >= 0; i2--) commands[i2].undo();
|
|
95814
|
+
}
|
|
95815
|
+
};
|
|
95816
|
+
}
|
|
95817
|
+
function parseInputValue(input, numFmtCode) {
|
|
95430
95818
|
if (input === "") return null;
|
|
95819
|
+
if (classifyNumFmt(numFmtCode) === "text") return input;
|
|
95431
95820
|
if (input.toUpperCase() === "TRUE") return true;
|
|
95432
95821
|
if (input.toUpperCase() === "FALSE") return false;
|
|
95822
|
+
if (isPaddedIdentifier(input.trim())) return input;
|
|
95433
95823
|
const num3 = Number(input);
|
|
95434
95824
|
if (!isNaN(num3) && input.trim() !== "") return num3;
|
|
95435
95825
|
return input;
|
|
@@ -95702,21 +96092,20 @@ function applySheetCells(workbook, sheetIndex, def) {
|
|
|
95702
96092
|
const sheet = workbook.sheets[sheetIndex];
|
|
95703
96093
|
if (def.cells) {
|
|
95704
96094
|
for (const [ref2, raw] of Object.entries(def.cells)) {
|
|
95705
|
-
const cell = normalizeSimpleCell(raw, def.name
|
|
95706
|
-
const
|
|
96095
|
+
const cell = normalizeSimpleCell(raw, `sheet "${def.name}" cell ${ref2}`);
|
|
96096
|
+
const upper2 = ref2.toUpperCase();
|
|
96097
|
+
const coord = refToRowCol(upper2);
|
|
95707
96098
|
if (!coord) fail2(`Invalid cell ref in sheet "${def.name}": ${ref2}`);
|
|
95708
|
-
|
|
95709
|
-
|
|
95710
|
-
|
|
96099
|
+
sheet.set(
|
|
96100
|
+
upper2,
|
|
96101
|
+
cell.formula ? null : cell.value ?? null,
|
|
96102
|
+
void 0,
|
|
96103
|
+
cell.numFmt,
|
|
96104
|
+
cell.formula ?? void 0
|
|
96105
|
+
);
|
|
95711
96106
|
if (cell.style) {
|
|
95712
96107
|
setRangeStyleCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.style).execute();
|
|
95713
96108
|
}
|
|
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
96109
|
}
|
|
95721
96110
|
}
|
|
95722
96111
|
if (def.merges) {
|
|
@@ -95744,22 +96133,33 @@ function applySheetCells(workbook, sheetIndex, def) {
|
|
|
95744
96133
|
}
|
|
95745
96134
|
}
|
|
95746
96135
|
}
|
|
95747
|
-
function normalizeSimpleCell(raw,
|
|
96136
|
+
function normalizeSimpleCell(raw, label) {
|
|
95748
96137
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
95749
96138
|
const unknown2 = Object.keys(raw).filter((k) => !SIMPLE_CELL_KEYS.has(k));
|
|
95750
96139
|
if (unknown2.length > 0) {
|
|
95751
96140
|
fail2(
|
|
95752
|
-
`Unknown propert${unknown2.length > 1 ? "ies" : "y"} on
|
|
96141
|
+
`Unknown propert${unknown2.length > 1 ? "ies" : "y"} on ${label}: ${unknown2.join(", ")}. Supported: ${[...SIMPLE_CELL_KEYS].join(", ")}.`
|
|
95753
96142
|
);
|
|
95754
96143
|
}
|
|
96144
|
+
if (raw.formula === void 0 || raw.formula === null) {
|
|
96145
|
+
if (typeof raw.value === "string" && raw.value.startsWith("=")) {
|
|
96146
|
+
console.error(
|
|
96147
|
+
`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.`
|
|
96148
|
+
);
|
|
96149
|
+
}
|
|
96150
|
+
}
|
|
95755
96151
|
return raw;
|
|
95756
96152
|
}
|
|
96153
|
+
if (raw !== null && !["string", "number", "boolean"].includes(typeof raw)) {
|
|
96154
|
+
fail2(
|
|
96155
|
+
`Value of ${label} must be a string, number, boolean, null, or a { ${[...SIMPLE_CELL_KEYS].join(", ")} } object, got ${JSON.stringify(raw) ?? typeof raw}.`
|
|
96156
|
+
);
|
|
96157
|
+
}
|
|
96158
|
+
if (typeof raw === "string" && raw.startsWith("=")) {
|
|
96159
|
+
return { formula: raw.slice(1) };
|
|
96160
|
+
}
|
|
95757
96161
|
return { value: raw };
|
|
95758
96162
|
}
|
|
95759
|
-
function valueToInput(value2) {
|
|
95760
|
-
if (value2 === null || value2 === void 0) return "";
|
|
95761
|
-
return String(value2);
|
|
95762
|
-
}
|
|
95763
96163
|
function readOptionFlag(rest2, name2) {
|
|
95764
96164
|
const i2 = rest2.indexOf(name2);
|
|
95765
96165
|
if (i2 < 0) return void 0;
|
|
@@ -95774,7 +96174,6 @@ function splitOptionalSheetRef(spec) {
|
|
|
95774
96174
|
}
|
|
95775
96175
|
function xlsxRead(filePath, rest2) {
|
|
95776
96176
|
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
96177
|
const rangeArg = readOptionFlag(rest2, "--range");
|
|
95779
96178
|
const sheetArg = readOptionFlag(rest2, "--sheet");
|
|
95780
96179
|
const withFormat = rest2.includes("--with-format");
|
|
@@ -95984,7 +96383,29 @@ function buildBatchCommand(workbook, engine, op, index) {
|
|
|
95984
96383
|
const sheetIndex = resolveSheetIndex(workbook, op.sheet);
|
|
95985
96384
|
const coord = refToRowCol(op.ref.toUpperCase());
|
|
95986
96385
|
if (!coord) fail2(`[op ${index}] Invalid cell ref: ${op.ref}`);
|
|
95987
|
-
|
|
96386
|
+
const cell = normalizeSimpleCell(op.value, `[op ${index}] set-cell ${op.sheet}!${op.ref}`);
|
|
96387
|
+
const commands = [
|
|
96388
|
+
cell.formula ? (
|
|
96389
|
+
// The entry path is the only one that writes a formula, and it takes
|
|
96390
|
+
// it spelled the way a person types it. Re-adding the `=` this
|
|
96391
|
+
// surface's reader just stripped is lossless and keeps the formula
|
|
96392
|
+
// write in one place.
|
|
96393
|
+
setCellValueCommand(workbook, engine.get(), sheetIndex, coord.row, coord.col, `=${cell.formula}`)
|
|
96394
|
+
) : setCellValueCommand(workbook, engine.get(), sheetIndex, coord.row, coord.col, cell.value ?? null, {
|
|
96395
|
+
typed: true
|
|
96396
|
+
})
|
|
96397
|
+
];
|
|
96398
|
+
if (cell.numFmt !== void 0) {
|
|
96399
|
+
commands.push(
|
|
96400
|
+
setRangeNumFmtCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.numFmt)
|
|
96401
|
+
);
|
|
96402
|
+
}
|
|
96403
|
+
if (cell.style) {
|
|
96404
|
+
commands.push(
|
|
96405
|
+
setRangeStyleCommand(workbook, sheetIndex, coord.row, coord.col, coord.row, coord.col, cell.style)
|
|
96406
|
+
);
|
|
96407
|
+
}
|
|
96408
|
+
return commands.length === 1 ? commands[0] : batchCommand(`Set ${op.sheet}!${op.ref}`, commands);
|
|
95988
96409
|
}
|
|
95989
96410
|
case "clear-range": {
|
|
95990
96411
|
const sheetIndex = resolveSheetIndex(workbook, op.sheet);
|
|
@@ -96041,6 +96462,8 @@ Uses Lotics' own xlsx engine; round-trips faithfully with the Lotics editor and
|
|
|
96041
96462
|
lotics xlsx write <file> '<json>' Create .xlsx from {"sheets":[{"name","cells":{"A1":...}}]}
|
|
96042
96463
|
A cell is a bare value, or {value|formula, numFmt, style}:
|
|
96043
96464
|
{"A1":{"value":1234,"numFmt":"#,##0 \\"\u20AB\\"","style":{"fontBold":true}}}
|
|
96465
|
+
A bare "=A1+B1" is a formula; an object's "value" is
|
|
96466
|
+
always a literal, so use {"formula":"A1+B1"} there.
|
|
96044
96467
|
A sheet also takes merges, freeze, colWidths ({"A":34}),
|
|
96045
96468
|
rowHeights ({"1":44}). Unknown properties are rejected.
|
|
96046
96469
|
lotics xlsx set-cell <file> <sheet>!<ref> '<v>' Set cell value (prefix '=' for formula)
|
|
@@ -101655,7 +102078,6 @@ function buildDoc(input) {
|
|
|
101655
102078
|
}
|
|
101656
102079
|
async function docxRead(filePath, rest2) {
|
|
101657
102080
|
if (!filePath) fail2("Usage: lotics docx read <file> [--include-opaque]");
|
|
101658
|
-
rejectUnknownFlags(rest2, ["--include-opaque"], "docx read");
|
|
101659
102081
|
const includeOpaque = rest2.includes("--include-opaque");
|
|
101660
102082
|
const doc = await loadFile2(filePath);
|
|
101661
102083
|
console.log(JSON.stringify(readToJson2(doc, includeOpaque), null, 2));
|
|
@@ -101690,7 +102112,7 @@ function firstPositional(rest2) {
|
|
|
101690
102112
|
return rest2.find((a) => !a.startsWith("--"));
|
|
101691
102113
|
}
|
|
101692
102114
|
async function docxAppendParagraph(filePath, rest2) {
|
|
101693
|
-
rejectUnknownFlags(rest2,
|
|
102115
|
+
rejectUnknownFlags(rest2, "docx", "append-paragraph");
|
|
101694
102116
|
const text = firstPositional(rest2);
|
|
101695
102117
|
if (!filePath || text === void 0) fail2("Usage: lotics docx append-paragraph <file> '<text>' [--style=NAME]");
|
|
101696
102118
|
const style = getStyleFlag(rest2);
|
|
@@ -101700,7 +102122,7 @@ async function docxAppendParagraph(filePath, rest2) {
|
|
|
101700
102122
|
await writeDoc(filePath, replaceChildren(doc, newChildren));
|
|
101701
102123
|
}
|
|
101702
102124
|
async function docxInsertParagraph(filePath, rest2) {
|
|
101703
|
-
rejectUnknownFlags(rest2,
|
|
102125
|
+
rejectUnknownFlags(rest2, "docx", "insert-paragraph");
|
|
101704
102126
|
const text = firstPositional(rest2);
|
|
101705
102127
|
const at2 = getAtFlag(rest2);
|
|
101706
102128
|
if (!filePath || text === void 0 || at2 === void 0) {
|
|
@@ -101717,7 +102139,6 @@ async function docxInsertParagraph(filePath, rest2) {
|
|
|
101717
102139
|
await writeDoc(filePath, replaceChildren(doc, newChildren));
|
|
101718
102140
|
}
|
|
101719
102141
|
async function docxDeleteBlock(filePath, rest2) {
|
|
101720
|
-
rejectUnknownFlags(rest2, ["--at="], "docx delete-block");
|
|
101721
102142
|
const at2 = getAtFlag(rest2);
|
|
101722
102143
|
if (!filePath || at2 === void 0) fail2("Usage: lotics docx delete-block <file> --at=<index>");
|
|
101723
102144
|
const doc = await loadFile2(filePath);
|