@lumerahq/cli 0.24.7 → 0.26.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/README.md +13 -1
- package/dist/functions-PVOVWMON.js +212 -0
- package/dist/index.js +20 -4
- package/dist/{init-B4DXTC7L.js → init-CUB3KH6M.js} +39 -30
- package/package.json +1 -1
- package/templates/default/README.md +10 -0
- package/templates/default/_gitignore +0 -1
- package/templates/default/platform/functions/__init__.py +1 -0
- package/templates/default/pyproject.toml +7 -2
- package/templates/default/template.json +1 -1
- package/templates/default/tests/functions/.gitkeep +1 -0
package/README.md
CHANGED
|
@@ -33,10 +33,22 @@ lumera destroy # Delete remote resources
|
|
|
33
33
|
|
|
34
34
|
lumera run <target> # Run script, automation, or invoke agent
|
|
35
35
|
|
|
36
|
+
lumera functions list # List locally configured Functions
|
|
37
|
+
lumera functions inspect invoices:approve
|
|
38
|
+
lumera functions invoke invoices:approve --input input.json --local
|
|
39
|
+
lumera functions test # Validate contracts and run Function tests
|
|
40
|
+
|
|
36
41
|
lumera flags list # List this sandbox's feature flags
|
|
37
42
|
lumera flags get <key> # Print one flag's value (--default <v> if unset)
|
|
38
43
|
```
|
|
39
44
|
|
|
45
|
+
Function commands are local-only in this release. Each command starts a fresh
|
|
46
|
+
SDK runner through `uv` and supplies the compatible Functions SDK as an
|
|
47
|
+
isolated overlay, so an older project lock cannot select a pre-Functions SDK.
|
|
48
|
+
`invoke` requires `--local` so it cannot be mistaken for a deployed invocation.
|
|
49
|
+
Function modules and test locations are configured under
|
|
50
|
+
`[tool.lumera.functions]` in `pyproject.toml`.
|
|
51
|
+
|
|
40
52
|
## Scaffolding Projects
|
|
41
53
|
|
|
42
54
|
### Interactive Mode
|
|
@@ -54,7 +66,7 @@ For CI/CD or scripted environments, use `-y` (or `--yes`) flag:
|
|
|
54
66
|
lumera init my-app -y # Creates ./my-app
|
|
55
67
|
lumera init my-app -y --dir ./apps # Creates ./apps
|
|
56
68
|
lumera init my-app -y --force # Overwrites if directory exists
|
|
57
|
-
lumera init my-app -y --no-install #
|
|
69
|
+
lumera init my-app -y --no-install # Defer Python and pnpm dependencies
|
|
58
70
|
```
|
|
59
71
|
|
|
60
72
|
### Init Options
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import "./chunk-PNKVD2UK.js";
|
|
2
|
+
|
|
3
|
+
// src/commands/functions.ts
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { join, resolve } from "path";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
var SUPPORTED_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "inspect", "invoke", "test"]);
|
|
9
|
+
var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.30.0,<0.31.0";
|
|
10
|
+
var FUNCTIONS_RUNNER_PROTOCOL_VERSION = 1;
|
|
11
|
+
function errorMessage(error, fallback) {
|
|
12
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
13
|
+
}
|
|
14
|
+
function isFunctionProtocolEnvelope(output) {
|
|
15
|
+
try {
|
|
16
|
+
const payload = JSON.parse(output);
|
|
17
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload) && "protocol_version" in payload && payload.protocol_version === FUNCTIONS_RUNNER_PROTOCOL_VERSION && "ok" in payload && typeof payload.ok === "boolean";
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function emitFunctionCliFailure(code, message, write = (value) => {
|
|
23
|
+
process.stdout.write(value);
|
|
24
|
+
}) {
|
|
25
|
+
write(
|
|
26
|
+
`${JSON.stringify({
|
|
27
|
+
protocol_version: FUNCTIONS_RUNNER_PROTOCOL_VERSION,
|
|
28
|
+
ok: false,
|
|
29
|
+
error: { code, message, retryable: false }
|
|
30
|
+
})}
|
|
31
|
+
`
|
|
32
|
+
);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
}
|
|
35
|
+
function findFunctionsProjectRoot(startDir = process.cwd()) {
|
|
36
|
+
let directory = resolve(startDir);
|
|
37
|
+
while (true) {
|
|
38
|
+
if (existsSync(join(directory, "pyproject.toml"))) {
|
|
39
|
+
return directory;
|
|
40
|
+
}
|
|
41
|
+
const parent = resolve(directory, "..");
|
|
42
|
+
if (parent === directory) break;
|
|
43
|
+
directory = parent;
|
|
44
|
+
}
|
|
45
|
+
throw new Error(
|
|
46
|
+
"Could not find Functions project root (no pyproject.toml found)"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function showHelp() {
|
|
50
|
+
console.log(`
|
|
51
|
+
${pc.dim("Usage:")}
|
|
52
|
+
lumera functions list
|
|
53
|
+
lumera functions inspect <function-id>
|
|
54
|
+
lumera functions invoke <function-id> --input <path|-> --local
|
|
55
|
+
lumera functions test [-- <pytest-options>]
|
|
56
|
+
|
|
57
|
+
${pc.dim("Description:")}
|
|
58
|
+
Discover, inspect, invoke, and test configured Functions in a fresh local
|
|
59
|
+
Python process. Phase 1 does not deploy or invoke Functions remotely.
|
|
60
|
+
|
|
61
|
+
${pc.dim("Options:")}
|
|
62
|
+
--local Required for invoke; remote invocation is unavailable
|
|
63
|
+
--input <path|-> Read a JSON object from a project file or stdin (-)
|
|
64
|
+
--help, -h Show this help
|
|
65
|
+
|
|
66
|
+
${pc.dim("Examples:")}
|
|
67
|
+
lumera functions list
|
|
68
|
+
lumera functions inspect invoices:approve
|
|
69
|
+
lumera functions invoke invoices:approve --input input.json --local
|
|
70
|
+
cat input.json | lumera functions invoke invoices:approve --input - --local
|
|
71
|
+
lumera functions test
|
|
72
|
+
lumera functions test -- -k approve
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
function buildFunctionRunnerArgs(subcommand, args, projectRoot) {
|
|
76
|
+
if (!SUPPORTED_SUBCOMMANDS.has(subcommand)) {
|
|
77
|
+
throw new Error(`Unknown functions subcommand: ${subcommand}`);
|
|
78
|
+
}
|
|
79
|
+
if (subcommand === "invoke" && !args.includes("--local")) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Function invocation is local-only in Phase 1; pass --local explicitly."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const runnerPrefix = ["run"];
|
|
85
|
+
if (existsSync(join(projectRoot, "uv.lock"))) {
|
|
86
|
+
runnerPrefix.push("--locked");
|
|
87
|
+
}
|
|
88
|
+
runnerPrefix.push("--with", FUNCTIONS_SDK_REQUIREMENT);
|
|
89
|
+
if (subcommand === "test") {
|
|
90
|
+
runnerPrefix.push("--with", "pytest>=8.0,<10.0");
|
|
91
|
+
}
|
|
92
|
+
return [
|
|
93
|
+
...runnerPrefix,
|
|
94
|
+
"python",
|
|
95
|
+
"-m",
|
|
96
|
+
"lumera.functions",
|
|
97
|
+
"--project-root",
|
|
98
|
+
projectRoot,
|
|
99
|
+
subcommand,
|
|
100
|
+
...args
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
async function runFunctionRunner(runnerArgs, projectRoot, spawnRunner = (command, args, options) => spawn(command, args, options), writeOutput = (value) => {
|
|
104
|
+
process.stdout.write(value);
|
|
105
|
+
}, writeDiagnostic = (value) => {
|
|
106
|
+
process.stderr.write(value);
|
|
107
|
+
}) {
|
|
108
|
+
return await new Promise((resolve2, reject) => {
|
|
109
|
+
const child = spawnRunner("uv", runnerArgs, {
|
|
110
|
+
cwd: projectRoot,
|
|
111
|
+
env: process.env,
|
|
112
|
+
shell: false,
|
|
113
|
+
// Buffer the single protocol value so a uv failure before Python starts
|
|
114
|
+
// cannot leave machine callers with stderr and no JSON response.
|
|
115
|
+
stdio: ["inherit", "pipe", "inherit"]
|
|
116
|
+
});
|
|
117
|
+
let protocolOutput = "";
|
|
118
|
+
child.stdout?.setEncoding("utf8");
|
|
119
|
+
child.stdout?.on("data", (chunk) => {
|
|
120
|
+
protocolOutput += chunk;
|
|
121
|
+
});
|
|
122
|
+
let settled = false;
|
|
123
|
+
const forwardSigint = () => child.kill("SIGINT");
|
|
124
|
+
const forwardSigterm = () => child.kill("SIGTERM");
|
|
125
|
+
const cleanup = () => {
|
|
126
|
+
process.off("SIGINT", forwardSigint);
|
|
127
|
+
process.off("SIGTERM", forwardSigterm);
|
|
128
|
+
};
|
|
129
|
+
process.once("SIGINT", forwardSigint);
|
|
130
|
+
process.once("SIGTERM", forwardSigterm);
|
|
131
|
+
child.once("error", (error) => {
|
|
132
|
+
if (settled) return;
|
|
133
|
+
settled = true;
|
|
134
|
+
cleanup();
|
|
135
|
+
if (error.code === "ENOENT") {
|
|
136
|
+
reject(
|
|
137
|
+
new Error(
|
|
138
|
+
"uv is not installed. Install it from https://docs.astral.sh/uv/"
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
reject(new Error(`Failed to start the Function runner: ${error.message}`));
|
|
144
|
+
});
|
|
145
|
+
child.once("close", (code) => {
|
|
146
|
+
if (settled) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
cleanup();
|
|
149
|
+
if (!isFunctionProtocolEnvelope(protocolOutput)) {
|
|
150
|
+
if (protocolOutput) writeDiagnostic(protocolOutput);
|
|
151
|
+
reject(
|
|
152
|
+
new Error(
|
|
153
|
+
`Function runner exited with status ${code ?? 1} before emitting a valid protocol envelope`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
writeOutput(protocolOutput);
|
|
159
|
+
resolve2(code ?? 1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function functions(subcommand, args, dependencies = {}) {
|
|
164
|
+
if (subcommand === void 0 || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || args.includes("--help") || args.includes("-h")) {
|
|
165
|
+
showHelp();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const findProjectRoot = dependencies.findProjectRoot ?? (() => findFunctionsProjectRoot());
|
|
169
|
+
const buildRunnerArgs = dependencies.buildRunnerArgs ?? buildFunctionRunnerArgs;
|
|
170
|
+
const runRunner = dependencies.runRunner ?? runFunctionRunner;
|
|
171
|
+
const emitFailure = dependencies.emitFailure ?? emitFunctionCliFailure;
|
|
172
|
+
let projectRoot;
|
|
173
|
+
try {
|
|
174
|
+
projectRoot = findProjectRoot();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
emitFailure(
|
|
177
|
+
"function_discovery_error",
|
|
178
|
+
errorMessage(error, "Could not locate the Functions project")
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let runnerArgs;
|
|
183
|
+
try {
|
|
184
|
+
runnerArgs = buildRunnerArgs(subcommand, args, projectRoot);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
emitFailure(
|
|
187
|
+
"invalid_arguments",
|
|
188
|
+
errorMessage(error, "Invalid Functions command arguments")
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let exitCode;
|
|
193
|
+
try {
|
|
194
|
+
exitCode = await runRunner(runnerArgs, projectRoot);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
emitFailure(
|
|
197
|
+
"function_runner_unavailable",
|
|
198
|
+
errorMessage(error, "The local Function runner is unavailable")
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (exitCode !== 0) {
|
|
203
|
+
process.exitCode = exitCode;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
buildFunctionRunnerArgs,
|
|
208
|
+
emitFunctionCliFailure,
|
|
209
|
+
findFunctionsProjectRoot,
|
|
210
|
+
functions,
|
|
211
|
+
runFunctionRunner
|
|
212
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,11 @@ import { dirname as dirname2, join as join2 } from "path";
|
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import pc from "picocolors";
|
|
12
12
|
|
|
13
|
+
// src/lib/command-output.ts
|
|
14
|
+
function commandOwnsStdout(command2) {
|
|
15
|
+
return command2 === "functions";
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
// src/lib/update-check.ts
|
|
14
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
20
|
import { homedir } from "os";
|
|
@@ -79,6 +84,7 @@ if (jsonMode) process.env.LUMERA_JSON = "1";
|
|
|
79
84
|
var args = rawArgs.filter((a) => a !== "--json");
|
|
80
85
|
var command = args[0];
|
|
81
86
|
var subcommand = args[1];
|
|
87
|
+
var ownsStdout = commandOwnsStdout(command);
|
|
82
88
|
var COMMANDS = [
|
|
83
89
|
"plan",
|
|
84
90
|
"apply",
|
|
@@ -89,6 +95,7 @@ var COMMANDS = [
|
|
|
89
95
|
"diff",
|
|
90
96
|
"dev",
|
|
91
97
|
"run",
|
|
98
|
+
"functions",
|
|
92
99
|
"init",
|
|
93
100
|
"register",
|
|
94
101
|
"templates",
|
|
@@ -150,6 +157,7 @@ ${pc.dim("Resource Commands:")}
|
|
|
150
157
|
${pc.dim("Development:")}
|
|
151
158
|
${pc.cyan("dev")} Start dev server
|
|
152
159
|
${pc.cyan("run")} <target> Run script, trigger automation, or invoke agent
|
|
160
|
+
${pc.cyan("functions")} <command> Develop and test Functions locally
|
|
153
161
|
|
|
154
162
|
${pc.dim("Project:")}
|
|
155
163
|
${pc.cyan("init")} [name] Scaffold a new project
|
|
@@ -194,6 +202,9 @@ ${pc.dim("Examples:")}
|
|
|
194
202
|
lumera run scripts/seed.py # Run a script
|
|
195
203
|
lumera run automations/sync # Trigger automation
|
|
196
204
|
lumera run agents/support "Hello" # Invoke an agent
|
|
205
|
+
lumera functions list # List locally configured Functions
|
|
206
|
+
lumera functions invoke invoices:approve --input input.json --local
|
|
207
|
+
lumera functions test # Validate Function contracts and tests
|
|
197
208
|
lumera dev # Start dev server
|
|
198
209
|
lumera flags list # List this sandbox's feature flags
|
|
199
210
|
lumera flags get studio_browser --default false # one flag (fallback if unset)
|
|
@@ -222,7 +233,7 @@ async function main() {
|
|
|
222
233
|
}
|
|
223
234
|
}
|
|
224
235
|
const startTime = performance.now();
|
|
225
|
-
const updateCheck = checkForUpdate(VERSION);
|
|
236
|
+
const updateCheck = ownsStdout ? Promise.resolve(null) : checkForUpdate(VERSION);
|
|
226
237
|
try {
|
|
227
238
|
switch (command) {
|
|
228
239
|
// Resource commands
|
|
@@ -254,9 +265,14 @@ async function main() {
|
|
|
254
265
|
case "run":
|
|
255
266
|
await import("./run-WHVUVIYB.js").then((m) => m.run(args.slice(1)));
|
|
256
267
|
break;
|
|
268
|
+
case "functions":
|
|
269
|
+
await import("./functions-PVOVWMON.js").then(
|
|
270
|
+
(m) => m.functions(subcommand, args.slice(2))
|
|
271
|
+
);
|
|
272
|
+
break;
|
|
257
273
|
// Project
|
|
258
274
|
case "init":
|
|
259
|
-
await import("./init-
|
|
275
|
+
await import("./init-CUB3KH6M.js").then((m) => m.init(args.slice(1)));
|
|
260
276
|
break;
|
|
261
277
|
case "register":
|
|
262
278
|
await import("./register-HRLBT4FI.js").then((m) => m.register(args.slice(1)));
|
|
@@ -309,14 +325,14 @@ async function main() {
|
|
|
309
325
|
process.exit(1);
|
|
310
326
|
}
|
|
311
327
|
}
|
|
312
|
-
if (!jsonMode) {
|
|
328
|
+
if (!jsonMode && !ownsStdout) {
|
|
313
329
|
const elapsed = performance.now() - startTime;
|
|
314
330
|
if (elapsed >= 500) {
|
|
315
331
|
console.log(pc.dim(`
|
|
316
332
|
Done in ${formatElapsed(elapsed)}`));
|
|
317
333
|
}
|
|
318
334
|
}
|
|
319
|
-
if (!jsonMode) {
|
|
335
|
+
if (!jsonMode && !ownsStdout) {
|
|
320
336
|
try {
|
|
321
337
|
const update = await Promise.race([
|
|
322
338
|
updateCheck,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listAllTemplates,
|
|
3
|
+
resolveTemplate
|
|
4
|
+
} from "./chunk-H357NP7T.js";
|
|
1
5
|
import {
|
|
2
6
|
installAllSkills,
|
|
3
7
|
syncClaudeMd
|
|
@@ -15,10 +19,6 @@ import {
|
|
|
15
19
|
setProjectId
|
|
16
20
|
} from "./chunk-JLVVHTBY.js";
|
|
17
21
|
import "./chunk-FJFIWC7G.js";
|
|
18
|
-
import {
|
|
19
|
-
listAllTemplates,
|
|
20
|
-
resolveTemplate
|
|
21
|
-
} from "./chunk-H357NP7T.js";
|
|
22
22
|
import "./chunk-PNKVD2UK.js";
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
@@ -130,14 +130,11 @@ function installUv() {
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
function createPythonVenv(targetDir) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} catch {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
133
|
+
function createPythonVenv(targetDir, runCommand = execSync) {
|
|
134
|
+
runCommand("uv sync --extra dev --no-install-project", {
|
|
135
|
+
cwd: targetDir,
|
|
136
|
+
stdio: "ignore"
|
|
137
|
+
});
|
|
141
138
|
}
|
|
142
139
|
function detectEditor() {
|
|
143
140
|
const envEditor = process.env.VISUAL || process.env.EDITOR;
|
|
@@ -377,6 +374,7 @@ async function init(args) {
|
|
|
377
374
|
copyDir(templateDir, targetDir, replacements);
|
|
378
375
|
ensureClaudeInstructionsLink(targetDir);
|
|
379
376
|
const installCommand = existsSync(join(targetDir, "pnpm-lock.yaml")) ? "pnpm install --frozen-lockfile" : "pnpm install";
|
|
377
|
+
const hasPythonProject = existsSync(join(targetDir, "pyproject.toml"));
|
|
380
378
|
function listFiles(dir, prefix = "") {
|
|
381
379
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
382
380
|
const relativePath = prefix + entry.name;
|
|
@@ -388,6 +386,31 @@ async function init(args) {
|
|
|
388
386
|
}
|
|
389
387
|
}
|
|
390
388
|
listFiles(targetDir);
|
|
389
|
+
if (opts.install && hasPythonProject) {
|
|
390
|
+
if (!isUvInstalled()) {
|
|
391
|
+
const stopUv = spinner("Installing uv (Python package manager)...");
|
|
392
|
+
if (installUv()) {
|
|
393
|
+
stopUv(pc.green("\u2713") + pc.dim(" uv installed successfully"));
|
|
394
|
+
} else {
|
|
395
|
+
stopUv(pc.red("\u2717") + pc.dim(" Failed to install uv"));
|
|
396
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
397
|
+
throw new Error(
|
|
398
|
+
"uv is required to resolve this Python project, so the incomplete generated directory was removed. Install uv from https://docs.astral.sh/uv/ and retry initialization, or pass --no-install to defer Python and JavaScript dependencies."
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const stopVenv = spinner("Creating Python venv with Lumera SDK...");
|
|
403
|
+
try {
|
|
404
|
+
createPythonVenv(targetDir);
|
|
405
|
+
stopVenv(pc.green("\u2713") + pc.dim(" Python venv created (.venv/) with lumera SDK"));
|
|
406
|
+
} catch {
|
|
407
|
+
stopVenv(pc.red("\u2717") + pc.dim(" Failed to resolve the Python environment"));
|
|
408
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
409
|
+
throw new Error(
|
|
410
|
+
"Python dependency resolution failed, so the incomplete generated directory was removed. Verify the declared Lumera SDK is available and retry initialization, or pass --no-install to defer Python and JavaScript dependencies."
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
391
414
|
if (isGitInstalled()) {
|
|
392
415
|
const stopGit = spinner("Initializing git repository...");
|
|
393
416
|
if (initGitRepo(targetDir, finalProjectName)) {
|
|
@@ -398,24 +421,6 @@ async function init(args) {
|
|
|
398
421
|
} else {
|
|
399
422
|
console.log(pc.yellow(" \u26A0"), pc.dim("Git not found \u2014 skipping repository initialization"));
|
|
400
423
|
}
|
|
401
|
-
let uvAvailable = isUvInstalled();
|
|
402
|
-
if (!uvAvailable) {
|
|
403
|
-
const stopUv = spinner("Installing uv (Python package manager)...");
|
|
404
|
-
if (installUv()) {
|
|
405
|
-
stopUv(pc.green("\u2713") + pc.dim(" uv installed successfully"));
|
|
406
|
-
uvAvailable = true;
|
|
407
|
-
} else {
|
|
408
|
-
stopUv(pc.yellow("\u26A0") + pc.dim(" Failed to install uv \u2014 install manually: https://docs.astral.sh/uv/"));
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (uvAvailable) {
|
|
412
|
-
const stopVenv = spinner("Creating Python venv with Lumera SDK...");
|
|
413
|
-
if (createPythonVenv(targetDir)) {
|
|
414
|
-
stopVenv(pc.green("\u2713") + pc.dim(" Python venv created (.venv/) with lumera SDK"));
|
|
415
|
-
} else {
|
|
416
|
-
stopVenv(pc.yellow("\u26A0") + pc.dim(" Failed to create Python venv"));
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
424
|
if (opts.install) {
|
|
420
425
|
const stopInstall = spinner("Installing dependencies...");
|
|
421
426
|
try {
|
|
@@ -467,6 +472,9 @@ async function init(args) {
|
|
|
467
472
|
console.log();
|
|
468
473
|
console.log(pc.cyan(` cd ${finalDirectory}`));
|
|
469
474
|
if (!opts.install) {
|
|
475
|
+
if (hasPythonProject) {
|
|
476
|
+
console.log(pc.cyan(" uv sync --extra dev --no-install-project"));
|
|
477
|
+
}
|
|
470
478
|
console.log(pc.cyan(` ${installCommand}`));
|
|
471
479
|
}
|
|
472
480
|
if (!registered) {
|
|
@@ -481,5 +489,6 @@ async function init(args) {
|
|
|
481
489
|
}
|
|
482
490
|
}
|
|
483
491
|
export {
|
|
492
|
+
createPythonVenv,
|
|
484
493
|
init
|
|
485
494
|
};
|
package/package.json
CHANGED
|
@@ -13,6 +13,16 @@ lumera apply # Deploy resources
|
|
|
13
13
|
|
|
14
14
|
- `platform/collections/` — Collection schemas (JSON)
|
|
15
15
|
- `platform/automations/` — Python automations
|
|
16
|
+
- `platform/functions/` — Typed Python Functions configured in `pyproject.toml`
|
|
16
17
|
- `platform/hooks/` — JavaScript hooks
|
|
17
18
|
- `src/` — React frontend (TanStack Router + Query)
|
|
18
19
|
- `scripts/` — Utility scripts
|
|
20
|
+
|
|
21
|
+
Add each Function module to `[tool.lumera.functions].modules`, then use
|
|
22
|
+
`lumera functions list`, `lumera functions invoke <id> --local`, and
|
|
23
|
+
`lumera functions test` for the local development loop. With dependency
|
|
24
|
+
installation enabled, scaffolding runs `uv sync` and includes `uv.lock` in the
|
|
25
|
+
initial commit. `--no-install` (used by Studio bootstrap) defers this until the
|
|
26
|
+
first Functions command or an explicit `uv sync`. Refresh and commit the lock
|
|
27
|
+
after changing Python dependencies so every developer and later release build
|
|
28
|
+
resolves the same SDK and dependency graph.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Project Function modules."""
|
|
@@ -4,11 +4,16 @@ version = "0.1.0"
|
|
|
4
4
|
description = "{{projectTitle}} - Lumera custom app"
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"lumera",
|
|
7
|
+
"lumera[functions]>=0.30.0,<0.31.0",
|
|
8
8
|
]
|
|
9
9
|
|
|
10
10
|
[project.optional-dependencies]
|
|
11
11
|
dev = [
|
|
12
12
|
"ruff",
|
|
13
|
-
"pytest",
|
|
13
|
+
"pytest>=8.0,<10.0",
|
|
14
14
|
]
|
|
15
|
+
|
|
16
|
+
[tool.lumera.functions]
|
|
17
|
+
source-root = "platform"
|
|
18
|
+
modules = []
|
|
19
|
+
test-paths = ["tests/functions"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|