@lumerahq/cli 0.28.0 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NIVQLH6V.js → chunk-QPVRRJFJ.js} +20 -8
- package/dist/{functions-JUOVWQQH.js → functions-KP2NO4RC.js} +1 -1
- package/dist/index.js +9 -9
- package/dist/{project-2PYA24JV.js → project-OPA5UUMQ.js} +14 -4
- package/dist/{resources-R53XKJL2.js → resources-P4VA2JPE.js} +140 -12
- package/package.json +1 -1
- package/templates/default/pyproject.toml +1 -1
|
@@ -4,7 +4,7 @@ import { existsSync } from "fs";
|
|
|
4
4
|
import { join, resolve } from "path";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
var SUPPORTED_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "inspect", "invoke", "test"]);
|
|
7
|
-
var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.
|
|
7
|
+
var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.32.0,<0.33.0";
|
|
8
8
|
var FUNCTIONS_RUNNER_PROTOCOL_VERSION = 1;
|
|
9
9
|
function errorMessage(error, fallback) {
|
|
10
10
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
@@ -50,14 +50,18 @@ ${pc.dim("Usage:")}
|
|
|
50
50
|
lumera functions list
|
|
51
51
|
lumera functions inspect <function-id>
|
|
52
52
|
lumera functions invoke <function-id> --input <path|-> --local
|
|
53
|
+
lumera functions invoke <function-id> --input <path|-> --remote --project <project>
|
|
53
54
|
lumera functions test [-- <pytest-options>]
|
|
54
55
|
|
|
55
56
|
${pc.dim("Description:")}
|
|
56
|
-
Discover, inspect, invoke, and test configured Functions
|
|
57
|
-
|
|
57
|
+
Discover, inspect, invoke, and test configured Functions. Invocation can run
|
|
58
|
+
in a fresh local process or create a durable remote execution.
|
|
58
59
|
|
|
59
60
|
${pc.dim("Options:")}
|
|
60
|
-
--local
|
|
61
|
+
--local Run from the local project source
|
|
62
|
+
--remote Create a durable execution from the active release
|
|
63
|
+
--project <project> Project ID or external ID for remote invocation
|
|
64
|
+
--idempotency-key <k> Deduplicate matching remote invocation requests
|
|
61
65
|
--input <path|-> Read a JSON object from a project file or stdin (-)
|
|
62
66
|
--help, -h Show this help
|
|
63
67
|
|
|
@@ -65,6 +69,7 @@ ${pc.dim("Examples:")}
|
|
|
65
69
|
lumera functions list
|
|
66
70
|
lumera functions inspect invoices:approve
|
|
67
71
|
lumera functions invoke invoices:approve --input input.json --local
|
|
72
|
+
lumera functions invoke invoices:approve --input input.json --remote --project finance
|
|
68
73
|
cat input.json | lumera functions invoke invoices:approve --input - --local
|
|
69
74
|
lumera functions test
|
|
70
75
|
lumera functions test -- -k approve
|
|
@@ -74,10 +79,17 @@ function buildFunctionRunnerArgs(subcommand, args, projectRoot) {
|
|
|
74
79
|
if (!SUPPORTED_SUBCOMMANDS.has(subcommand)) {
|
|
75
80
|
throw new Error(`Unknown functions subcommand: ${subcommand}`);
|
|
76
81
|
}
|
|
77
|
-
if (subcommand === "invoke"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
82
|
+
if (subcommand === "invoke") {
|
|
83
|
+
const local = args.includes("--local");
|
|
84
|
+
const remote = args.includes("--remote");
|
|
85
|
+
if (local === remote) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Pass exactly one of --local or --remote for Function invocation."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (remote && !args.includes("--project")) {
|
|
91
|
+
throw new Error("--project is required for remote Function invocation.");
|
|
92
|
+
}
|
|
81
93
|
}
|
|
82
94
|
const runnerPrefix = ["run"];
|
|
83
95
|
if (existsSync(join(projectRoot, "uv.lock"))) {
|
package/dist/index.js
CHANGED
|
@@ -240,25 +240,25 @@ async function main() {
|
|
|
240
240
|
switch (command) {
|
|
241
241
|
// Resource commands
|
|
242
242
|
case "plan":
|
|
243
|
-
await import("./resources-
|
|
243
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.plan(args.slice(1)));
|
|
244
244
|
break;
|
|
245
245
|
case "apply":
|
|
246
|
-
await import("./resources-
|
|
246
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.apply(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "pull":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.pull(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "destroy":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.destroy(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "list":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.list(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "show":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.show(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "diff":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-P4VA2JPE.js").then((m) => m.diff(args.slice(1)));
|
|
262
262
|
break;
|
|
263
263
|
// Development
|
|
264
264
|
case "dev":
|
|
@@ -268,12 +268,12 @@ async function main() {
|
|
|
268
268
|
await import("./run-WHVUVIYB.js").then((m) => m.run(args.slice(1)));
|
|
269
269
|
break;
|
|
270
270
|
case "functions":
|
|
271
|
-
await import("./functions-
|
|
271
|
+
await import("./functions-KP2NO4RC.js").then(
|
|
272
272
|
(m) => m.functions(subcommand, args.slice(2))
|
|
273
273
|
);
|
|
274
274
|
break;
|
|
275
275
|
case "project":
|
|
276
|
-
await import("./project-
|
|
276
|
+
await import("./project-OPA5UUMQ.js").then(
|
|
277
277
|
(m) => m.project(subcommand, args.slice(2))
|
|
278
278
|
);
|
|
279
279
|
break;
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findFunctionsProjectRoot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QPVRRJFJ.js";
|
|
4
4
|
import "./chunk-PNKVD2UK.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/project.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
8
|
import pc from "picocolors";
|
|
9
|
-
var RELEASE_SDK_REQUIREMENT = "lumera[release]>=0.
|
|
9
|
+
var RELEASE_SDK_REQUIREMENT = "lumera[release]>=0.32.0,<0.33.0";
|
|
10
10
|
var RELEASE_PROTOCOL_VERSION = 1;
|
|
11
|
-
var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
|
|
11
|
+
var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
|
|
12
|
+
"build",
|
|
13
|
+
"inspect",
|
|
14
|
+
"contract-test",
|
|
15
|
+
"deploy",
|
|
16
|
+
"stage",
|
|
17
|
+
"activate"
|
|
18
|
+
]);
|
|
12
19
|
function showHelp() {
|
|
13
20
|
console.log(`
|
|
14
21
|
${pc.dim("Usage:")}
|
|
@@ -16,10 +23,13 @@ ${pc.dim("Usage:")}
|
|
|
16
23
|
lumera project release inspect <archive> [--expected-digest <sha256>]
|
|
17
24
|
lumera project release contract-test <archive> [--mode api|worker|all]
|
|
18
25
|
lumera project release deploy <archive> --project <project-id>
|
|
26
|
+
lumera project release stage <release-id> --project <project-id> --expected-generation <n>
|
|
27
|
+
lumera project release activate <deployment-id> --project <project-id> --expected-generation <n>
|
|
19
28
|
|
|
20
29
|
${pc.dim("Description:")}
|
|
21
30
|
Build, verify, and contract-test immutable Python 3.13/Linux/ARM64 project
|
|
22
|
-
releases,
|
|
31
|
+
releases, upload an inactive resource, stage a healthy runtime, and activate
|
|
32
|
+
it atomically with an expected generation.
|
|
23
33
|
`);
|
|
24
34
|
}
|
|
25
35
|
function buildReleaseRunnerArgs(command, args, projectRoot) {
|
|
@@ -93,10 +93,132 @@ var automationConfigRule = {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
// src/lib/lint/rules/
|
|
96
|
+
// src/lib/lint/rules/automation-main-guard.ts
|
|
97
|
+
import { spawnSync } from "child_process";
|
|
98
|
+
var PYTHON_AST_SCRIPT = String.raw`
|
|
99
|
+
import ast
|
|
100
|
+
import json
|
|
101
|
+
import sys
|
|
102
|
+
|
|
103
|
+
source = sys.stdin.read()
|
|
104
|
+
try:
|
|
105
|
+
module = ast.parse(source)
|
|
106
|
+
except SyntaxError:
|
|
107
|
+
print("null")
|
|
108
|
+
raise SystemExit(0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_name(node):
|
|
112
|
+
return isinstance(node, ast.Name) and node.id == "__name__"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_main_string(node):
|
|
116
|
+
if isinstance(node, ast.Str):
|
|
117
|
+
return node.s == "__main__"
|
|
118
|
+
return isinstance(node, ast.Constant) and node.value == "__main__"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_main_guard(node):
|
|
122
|
+
test = node.test
|
|
123
|
+
if not isinstance(test, ast.Compare):
|
|
124
|
+
return False
|
|
125
|
+
if len(test.ops) != 1 or not isinstance(test.ops[0], ast.Eq):
|
|
126
|
+
return False
|
|
127
|
+
if len(test.comparators) != 1:
|
|
128
|
+
return False
|
|
129
|
+
right = test.comparators[0]
|
|
130
|
+
return (is_name(test.left) and is_main_string(right)) or (
|
|
131
|
+
is_main_string(test.left) and is_name(right)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
for statement in module.body:
|
|
136
|
+
if not isinstance(statement, ast.If) or not is_main_guard(statement):
|
|
137
|
+
continue
|
|
138
|
+
get_segment = getattr(ast, "get_source_segment", None)
|
|
139
|
+
condition = get_segment(source, statement.test) if get_segment else None
|
|
140
|
+
if not condition:
|
|
141
|
+
condition = '__name__ == "__main__"'
|
|
142
|
+
snippet = "if " + " ".join(condition.split()) + ":"
|
|
143
|
+
print(json.dumps({"line": statement.lineno, "snippet": snippet}))
|
|
144
|
+
break
|
|
145
|
+
else:
|
|
146
|
+
print("null")
|
|
147
|
+
`;
|
|
148
|
+
var cachedPython3;
|
|
149
|
+
function findPython3() {
|
|
150
|
+
if (cachedPython3 !== void 0) return cachedPython3;
|
|
151
|
+
for (const executable of ["python3", "python"]) {
|
|
152
|
+
const probe = spawnSync(executable, ["-I", "-S", "--version"], {
|
|
153
|
+
encoding: "utf8",
|
|
154
|
+
timeout: 2e3,
|
|
155
|
+
windowsHide: true
|
|
156
|
+
});
|
|
157
|
+
const version = `${probe.stdout ?? ""}${probe.stderr ?? ""}`.trim();
|
|
158
|
+
if (probe.status === 0 && /^Python 3(?:\.|\s|$)/.test(version)) {
|
|
159
|
+
cachedPython3 = executable;
|
|
160
|
+
return executable;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
cachedPython3 = null;
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
97
166
|
function isRecord2(value) {
|
|
98
167
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
99
168
|
}
|
|
169
|
+
function functionEntrypoint(config) {
|
|
170
|
+
if (!isRecord2(config) || !isRecord2(config.function)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const name = config.function.name;
|
|
174
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
175
|
+
}
|
|
176
|
+
function findTopLevelMainGuard(source) {
|
|
177
|
+
const python = findPython3();
|
|
178
|
+
if (python === null) return null;
|
|
179
|
+
const parsed = spawnSync(python, ["-I", "-S", "-c", PYTHON_AST_SCRIPT], {
|
|
180
|
+
input: source,
|
|
181
|
+
encoding: "utf8",
|
|
182
|
+
maxBuffer: 1024 * 1024,
|
|
183
|
+
timeout: 5e3,
|
|
184
|
+
windowsHide: true
|
|
185
|
+
});
|
|
186
|
+
if (parsed.status !== 0) return null;
|
|
187
|
+
try {
|
|
188
|
+
const value = JSON.parse(parsed.stdout.trim());
|
|
189
|
+
if (!isRecord2(value) || typeof value.line !== "number" || typeof value.snippet !== "string") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return { line: value.line, snippet: value.snippet };
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var automationMainGuardRule = {
|
|
198
|
+
id: "automation-main-guard",
|
|
199
|
+
description: "Warns when a function automation contains a top-level __main__ guard.",
|
|
200
|
+
appliesTo: ["automation"],
|
|
201
|
+
check(target) {
|
|
202
|
+
const entrypoint = functionEntrypoint(target.config);
|
|
203
|
+
if (entrypoint === null) return [];
|
|
204
|
+
const guard = findTopLevelMainGuard(target.source);
|
|
205
|
+
if (guard === null) return [];
|
|
206
|
+
const warning2 = {
|
|
207
|
+
ruleId: "automation-main-guard",
|
|
208
|
+
target,
|
|
209
|
+
severity: "warning",
|
|
210
|
+
line: guard.line,
|
|
211
|
+
snippet: guard.snippet,
|
|
212
|
+
message: `Function automations must not contain a top-level __main__ guard. Lumera invokes "${entrypoint}" automatically; remove the guard.`
|
|
213
|
+
};
|
|
214
|
+
return [warning2];
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/lib/lint/rules/collection-schema.ts
|
|
219
|
+
function isRecord3(value) {
|
|
220
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
221
|
+
}
|
|
100
222
|
function error(target, message, snippet) {
|
|
101
223
|
return {
|
|
102
224
|
ruleId: "collection-schema",
|
|
@@ -118,7 +240,7 @@ var collectionSchemaRule = {
|
|
|
118
240
|
const message = err instanceof Error ? err.message : String(err);
|
|
119
241
|
return [error(target, `Invalid JSON: ${message}`)];
|
|
120
242
|
}
|
|
121
|
-
if (!
|
|
243
|
+
if (!isRecord3(parsed)) {
|
|
122
244
|
return [error(target, "Collection file must contain a JSON object.")];
|
|
123
245
|
}
|
|
124
246
|
const issues = [];
|
|
@@ -131,7 +253,7 @@ var collectionSchemaRule = {
|
|
|
131
253
|
const localFieldTypes = /* @__PURE__ */ new Map();
|
|
132
254
|
if (Array.isArray(fields)) {
|
|
133
255
|
for (const field of fields) {
|
|
134
|
-
if (
|
|
256
|
+
if (isRecord3(field) && typeof field.name === "string") {
|
|
135
257
|
localFieldTypes.set(field.name, field.type);
|
|
136
258
|
}
|
|
137
259
|
}
|
|
@@ -155,7 +277,7 @@ var collectionSchemaRule = {
|
|
|
155
277
|
} else {
|
|
156
278
|
for (let i = 0; i < fields.length; i++) {
|
|
157
279
|
const field = fields[i];
|
|
158
|
-
if (!
|
|
280
|
+
if (!isRecord3(field)) {
|
|
159
281
|
issues.push(error(target, `Field at index ${i} must be an object.`));
|
|
160
282
|
continue;
|
|
161
283
|
}
|
|
@@ -176,7 +298,7 @@ var collectionSchemaRule = {
|
|
|
176
298
|
} else {
|
|
177
299
|
for (let i = 0; i < indexes.length; i++) {
|
|
178
300
|
const index = indexes[i];
|
|
179
|
-
if (!
|
|
301
|
+
if (!isRecord3(index)) {
|
|
180
302
|
const hint = typeof index === "string" ? ' Indexes are objects like { "fields": ["title"], "unique": true }, not CREATE INDEX SQL strings.' : "";
|
|
181
303
|
issues.push(error(target, `Index at index ${i} must be an object.${hint}`));
|
|
182
304
|
continue;
|
|
@@ -193,7 +315,7 @@ var collectionSchemaRule = {
|
|
|
193
315
|
}
|
|
194
316
|
}
|
|
195
317
|
if (search !== void 0) {
|
|
196
|
-
if (!
|
|
318
|
+
if (!isRecord3(search)) {
|
|
197
319
|
issues.push(error(target, 'Optional field "search" must be an object when present.'));
|
|
198
320
|
} else {
|
|
199
321
|
const unknownSearchKeys = Object.keys(search).filter((key) => key !== "fulltext" && key !== "semantic");
|
|
@@ -205,14 +327,14 @@ var collectionSchemaRule = {
|
|
|
205
327
|
if (fulltext === void 0 && semantic === void 0) {
|
|
206
328
|
issues.push(error(target, '"search" must include "fulltext" or "semantic".'));
|
|
207
329
|
}
|
|
208
|
-
if (fulltext !== void 0 && !
|
|
330
|
+
if (fulltext !== void 0 && !isRecord3(fulltext)) {
|
|
209
331
|
issues.push(error(target, '"search.fulltext" must be an object when present.'));
|
|
210
|
-
} else if (
|
|
332
|
+
} else if (isRecord3(fulltext)) {
|
|
211
333
|
const allowed = /* @__PURE__ */ new Set(["fields", "language", "fuzzy_fields"]);
|
|
212
334
|
for (const key of Object.keys(fulltext).filter((key2) => !allowed.has(key2))) {
|
|
213
335
|
issues.push(error(target, `Unknown search.fulltext option "${key}".`));
|
|
214
336
|
}
|
|
215
|
-
if (!
|
|
337
|
+
if (!isRecord3(fulltext.fields) || Object.keys(fulltext.fields).length === 0) {
|
|
216
338
|
issues.push(error(target, '"search.fulltext.fields" must be a non-empty field-to-weight object.'));
|
|
217
339
|
} else {
|
|
218
340
|
if (Object.keys(fulltext.fields).length > 8) {
|
|
@@ -244,9 +366,9 @@ var collectionSchemaRule = {
|
|
|
244
366
|
}
|
|
245
367
|
}
|
|
246
368
|
}
|
|
247
|
-
if (semantic !== void 0 && !
|
|
369
|
+
if (semantic !== void 0 && !isRecord3(semantic)) {
|
|
248
370
|
issues.push(error(target, '"search.semantic" must be an object when present.'));
|
|
249
|
-
} else if (
|
|
371
|
+
} else if (isRecord3(semantic)) {
|
|
250
372
|
const allowed = /* @__PURE__ */ new Set(["fields", "model"]);
|
|
251
373
|
for (const key of Object.keys(semantic).filter((key2) => !allowed.has(key2))) {
|
|
252
374
|
issues.push(error(target, `Unknown search.semantic option "${key}".`));
|
|
@@ -653,7 +775,13 @@ var llmImportRule = {
|
|
|
653
775
|
};
|
|
654
776
|
|
|
655
777
|
// src/lib/lint/registry.ts
|
|
656
|
-
var ALL_RULES = [
|
|
778
|
+
var ALL_RULES = [
|
|
779
|
+
automationConfigRule,
|
|
780
|
+
automationMainGuardRule,
|
|
781
|
+
collectionSchemaRule,
|
|
782
|
+
hookVerifyRule,
|
|
783
|
+
llmImportRule
|
|
784
|
+
];
|
|
657
785
|
|
|
658
786
|
// src/lib/lint/format.ts
|
|
659
787
|
import { relative } from "path";
|
package/package.json
CHANGED