@zaflun/lumio-cli 0.2.0 → 0.5.2
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/index.js +864 -121
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ function registerLogin(program2) {
|
|
|
75
75
|
const state = crypto.randomBytes(16).toString("hex");
|
|
76
76
|
const port = 9876;
|
|
77
77
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
78
|
-
await new Promise((
|
|
78
|
+
await new Promise((resolve10, reject) => {
|
|
79
79
|
const server = http.createServer(async (req, res) => {
|
|
80
80
|
if (!req.url?.startsWith("/callback")) {
|
|
81
81
|
res.writeHead(404);
|
|
@@ -118,7 +118,7 @@ function registerLogin(program2) {
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
server.close();
|
|
121
|
-
|
|
121
|
+
resolve10();
|
|
122
122
|
});
|
|
123
123
|
server.listen(port, async () => {
|
|
124
124
|
const pkceUrl = `${options.apiUrl}/v1/auth/cli/authorize?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
|
|
@@ -288,24 +288,136 @@ function registerInit(program2) {
|
|
|
288
288
|
}
|
|
289
289
|
} catch {
|
|
290
290
|
}
|
|
291
|
-
const
|
|
291
|
+
const baseAnswers = await inquirer.prompt([
|
|
292
292
|
{ type: "input", name: "name", message: "Extension name:", default: name },
|
|
293
293
|
{ type: "input", name: "description", message: "Description:" },
|
|
294
294
|
{
|
|
295
295
|
type: "list",
|
|
296
296
|
name: "category",
|
|
297
297
|
message: "Category:",
|
|
298
|
-
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme"]
|
|
299
|
-
}
|
|
300
|
-
{
|
|
301
|
-
type: "checkbox",
|
|
302
|
-
name: "targets",
|
|
303
|
-
message: "Targets:",
|
|
304
|
-
choices: ["editor", "layer", "interactive"],
|
|
305
|
-
default: ["editor", "layer"]
|
|
306
|
-
},
|
|
307
|
-
{ type: "confirm", name: "server", message: "Server functions?", default: false }
|
|
298
|
+
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme", "automation_node"]
|
|
299
|
+
}
|
|
308
300
|
]);
|
|
301
|
+
const answers = { ...baseAnswers, targets: [], server: false };
|
|
302
|
+
if (answers.category === "bot_module") {
|
|
303
|
+
const { platforms } = await inquirer.prompt([{
|
|
304
|
+
type: "checkbox",
|
|
305
|
+
name: "platforms",
|
|
306
|
+
message: "Which platforms should this bot module support?",
|
|
307
|
+
choices: ["twitch", "youtube", "kick", "trovo", "discord"],
|
|
308
|
+
default: ["twitch"],
|
|
309
|
+
validate: (input) => input.length > 0 || "Select at least one platform"
|
|
310
|
+
}]);
|
|
311
|
+
answers.platforms = platforms;
|
|
312
|
+
answers.targets = [];
|
|
313
|
+
answers.server = true;
|
|
314
|
+
} else if (answers.category === "automation_node") {
|
|
315
|
+
const nodeAnswers = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: "list",
|
|
318
|
+
name: "nodeType",
|
|
319
|
+
message: "Node type:",
|
|
320
|
+
choices: [
|
|
321
|
+
{ name: "Action \u2014 perform work when the automation runs", value: "action" },
|
|
322
|
+
{ name: "Trigger \u2014 start an automation from an external event", value: "trigger" },
|
|
323
|
+
{ name: "Logic \u2014 conditional branching", value: "logic" }
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
]);
|
|
327
|
+
let triggerMode;
|
|
328
|
+
if (nodeAnswers.nodeType === "trigger") {
|
|
329
|
+
const triggerAnswers = await inquirer.prompt([
|
|
330
|
+
{
|
|
331
|
+
type: "list",
|
|
332
|
+
name: "triggerMode",
|
|
333
|
+
message: "How does this trigger receive events?",
|
|
334
|
+
choices: [
|
|
335
|
+
{ name: "Webhook \u2014 receive HTTP POST from an external service", value: "webhook" },
|
|
336
|
+
{ name: "Polling \u2014 periodically check an external service", value: "polling" },
|
|
337
|
+
{ name: "Both \u2014 support webhook and polling", value: "both" }
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
]);
|
|
341
|
+
triggerMode = triggerAnswers.triggerMode;
|
|
342
|
+
}
|
|
343
|
+
answers.targets = ["editor"];
|
|
344
|
+
answers.server = true;
|
|
345
|
+
answers.nodeType = nodeAnswers.nodeType;
|
|
346
|
+
answers.triggerMode = triggerMode;
|
|
347
|
+
} else {
|
|
348
|
+
const targetAndServer = await inquirer.prompt([
|
|
349
|
+
{
|
|
350
|
+
type: "checkbox",
|
|
351
|
+
name: "targets",
|
|
352
|
+
message: "Targets:",
|
|
353
|
+
choices: ["editor", "layer", "interactive"],
|
|
354
|
+
default: ["editor", "layer"]
|
|
355
|
+
},
|
|
356
|
+
{ type: "confirm", name: "server", message: "Server functions?", default: false }
|
|
357
|
+
]);
|
|
358
|
+
answers.targets = targetAndServer.targets;
|
|
359
|
+
answers.server = targetAndServer.server;
|
|
360
|
+
}
|
|
361
|
+
let editorApproach = "schema";
|
|
362
|
+
if (answers.category !== "bot_module" && answers.category !== "automation_node") {
|
|
363
|
+
const editorChoice = await inquirer.prompt([{
|
|
364
|
+
type: "list",
|
|
365
|
+
name: "approach",
|
|
366
|
+
message: "How should the editor UI be built?",
|
|
367
|
+
choices: [
|
|
368
|
+
{ name: "config_schema only \u2014 auto-generated form from schema fields (recommended)", value: "schema" },
|
|
369
|
+
{ name: "editor.tsx only \u2014 fully custom React UI in sandbox", value: "custom" },
|
|
370
|
+
{ name: "Both \u2014 schema fields + custom editor.tsx for advanced controls", value: "both" }
|
|
371
|
+
],
|
|
372
|
+
default: "schema"
|
|
373
|
+
}]);
|
|
374
|
+
editorApproach = editorChoice.approach;
|
|
375
|
+
if (editorApproach === "schema") {
|
|
376
|
+
answers.targets = answers.targets.filter((t) => t !== "editor");
|
|
377
|
+
} else if (!answers.targets.includes("editor")) {
|
|
378
|
+
answers.targets.push("editor");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
answers.editorApproach = editorApproach;
|
|
382
|
+
let includeDesigner = false;
|
|
383
|
+
if (answers.category === "widget" || answers.category === "overlay") {
|
|
384
|
+
const designerAnswer = await inquirer.prompt([{
|
|
385
|
+
type: "confirm",
|
|
386
|
+
name: "includeDesigner",
|
|
387
|
+
message: "Include a visual designer? (fullpage design editor with live preview)",
|
|
388
|
+
default: false
|
|
389
|
+
}]);
|
|
390
|
+
includeDesigner = designerAnswer.includeDesigner;
|
|
391
|
+
}
|
|
392
|
+
let useTailwind = false;
|
|
393
|
+
if (answers.category !== "bot_module") {
|
|
394
|
+
const styleAnswer = await inquirer.prompt([
|
|
395
|
+
{
|
|
396
|
+
type: "list",
|
|
397
|
+
name: "styling",
|
|
398
|
+
message: "Styling approach:",
|
|
399
|
+
choices: [
|
|
400
|
+
{ name: "Tailwind CSS v4 (recommended)", value: "tailwind" },
|
|
401
|
+
{ name: "Plain CSS", value: "css" },
|
|
402
|
+
{ name: "Inline styles only", value: "inline" }
|
|
403
|
+
],
|
|
404
|
+
default: "tailwind"
|
|
405
|
+
}
|
|
406
|
+
]);
|
|
407
|
+
useTailwind = styleAnswer.styling === "tailwind";
|
|
408
|
+
answers.styling = styleAnswer.styling;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
await client.get("/v1/developer/profile");
|
|
412
|
+
} catch (err) {
|
|
413
|
+
if (err.status === 404) {
|
|
414
|
+
console.error(
|
|
415
|
+
"\n You need to apply as a developer first.\n Visit https://lumio.vision/account/developer to submit your application.\n"
|
|
416
|
+
);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
309
421
|
const createBody = {
|
|
310
422
|
name: answers.name,
|
|
311
423
|
description: answers.description,
|
|
@@ -327,34 +439,120 @@ function registerInit(program2) {
|
|
|
327
439
|
version: "0.1.0",
|
|
328
440
|
targets: answers.targets,
|
|
329
441
|
server: answers.server,
|
|
330
|
-
permissions: [],
|
|
442
|
+
permissions: answers.category === "bot_module" ? ["chat:read", "chat:send"] : [],
|
|
331
443
|
egress: { allowHosts: [], allowPorts: [443] },
|
|
332
444
|
visibility: "private",
|
|
333
445
|
pricing: { type: "free" }
|
|
334
446
|
};
|
|
447
|
+
if (editorApproach === "schema" || editorApproach === "both") {
|
|
448
|
+
config.config_schema = [
|
|
449
|
+
{ key: "title", type: "string", label: "Title", default_value: answers.name, required: false, placeholder: "Enter a title..." },
|
|
450
|
+
{ key: "fontSize", type: "number", label: "Font Size", default_value: 16, required: false, min: 8, max: 200, step: 1, unit: "px" },
|
|
451
|
+
{ key: "color", type: "color", label: "Color", default_value: "#ffffff", required: false }
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
if (includeDesigner) {
|
|
455
|
+
config.designer_schema = [
|
|
456
|
+
{ key: "primaryColor", type: "color", label: "Primary Color", default_value: "#43F7CD", required: false },
|
|
457
|
+
{ key: "backgroundColor", type: "color", label: "Background Color", default_value: "#1a1a2e", required: false },
|
|
458
|
+
{ key: "fontFamily", type: "font", label: "Font Family", default_value: "Inter", required: false }
|
|
459
|
+
];
|
|
460
|
+
if (config.config_schema && Array.isArray(config.config_schema)) {
|
|
461
|
+
config.config_schema.push({ key: "design", type: "designer", label: "Widget Designer", required: false, default_value: null });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (answers.category === "bot_module") {
|
|
465
|
+
config.platforms = answers.platforms;
|
|
466
|
+
config.triggers = {
|
|
467
|
+
commands: [{ name: "hello", description: "Say hello", cooldownGlobal: 5, cooldownUser: 10, minRole: "everyone" }],
|
|
468
|
+
keywords: [],
|
|
469
|
+
patterns: [],
|
|
470
|
+
events: [],
|
|
471
|
+
timers: []
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (answers.category === "automation_node") {
|
|
475
|
+
const nodeType = answers.nodeType;
|
|
476
|
+
const triggerMode = answers.triggerMode;
|
|
477
|
+
config.node_type = nodeType;
|
|
478
|
+
const nodeConfig = {
|
|
479
|
+
input_schema: {
|
|
480
|
+
type: "object",
|
|
481
|
+
properties: {
|
|
482
|
+
value: { type: "string", title: "Input Value" }
|
|
483
|
+
},
|
|
484
|
+
required: ["value"]
|
|
485
|
+
},
|
|
486
|
+
output_schema: {
|
|
487
|
+
type: "object",
|
|
488
|
+
properties: {
|
|
489
|
+
result: { type: "string" }
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
if (nodeType === "trigger") {
|
|
494
|
+
nodeConfig.trigger_mode = triggerMode;
|
|
495
|
+
if (triggerMode === "polling" || triggerMode === "both") {
|
|
496
|
+
nodeConfig.poll_interval_seconds = 30;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
nodeConfig.icon = "puzzle";
|
|
500
|
+
nodeConfig.color = "#a855f7";
|
|
501
|
+
config.node = nodeConfig;
|
|
502
|
+
}
|
|
335
503
|
fs2.writeFileSync(
|
|
336
504
|
path2.join(projectDir, "lumio.config.json"),
|
|
337
505
|
JSON.stringify(config, null, 2)
|
|
338
506
|
);
|
|
507
|
+
const isBotModule = answers.category === "bot_module";
|
|
508
|
+
const isAutomationNode = answers.category === "automation_node";
|
|
509
|
+
const needsReact = !isBotModule || isAutomationNode;
|
|
510
|
+
const lintDir = isBotModule ? "server/" : "src/";
|
|
339
511
|
const pkg = {
|
|
340
512
|
name: answers.name.toLowerCase().replace(/\s+/g, "-"),
|
|
341
513
|
version: "0.1.0",
|
|
342
514
|
private: true,
|
|
343
515
|
type: "module",
|
|
516
|
+
scripts: {
|
|
517
|
+
"dev": isBotModule ? "lumio dev --bot-module" : isAutomationNode ? "lumio dev --automation-node" : "lumio dev",
|
|
518
|
+
"build": "lumio build",
|
|
519
|
+
"deploy": "lumio deploy",
|
|
520
|
+
"typecheck": "tsc --noEmit",
|
|
521
|
+
"lint": `eslint ${lintDir}`
|
|
522
|
+
},
|
|
344
523
|
dependencies: {
|
|
345
524
|
"@zaflun/lumio-sdk": "latest",
|
|
346
|
-
"react": "^19.
|
|
347
|
-
"react-dom": "^19.1.0"
|
|
525
|
+
...needsReact ? { "react": "^19.2.5" } : {}
|
|
348
526
|
},
|
|
349
527
|
devDependencies: {
|
|
350
|
-
"typescript": "^
|
|
351
|
-
"@types/react": "^19.1.0"
|
|
528
|
+
"typescript": "^6.0.3",
|
|
529
|
+
...needsReact ? { "@types/react": "^19.1.0" } : {},
|
|
530
|
+
...useTailwind ? { "tailwindcss": "^4.3.0" } : {}
|
|
352
531
|
}
|
|
353
532
|
};
|
|
354
533
|
fs2.writeFileSync(
|
|
355
534
|
path2.join(projectDir, "package.json"),
|
|
356
535
|
JSON.stringify(pkg, null, 2)
|
|
357
536
|
);
|
|
537
|
+
const tsconfig = {
|
|
538
|
+
compilerOptions: {
|
|
539
|
+
target: "ES2022",
|
|
540
|
+
module: "ESNext",
|
|
541
|
+
moduleResolution: "bundler",
|
|
542
|
+
jsx: "react-jsx",
|
|
543
|
+
strict: true,
|
|
544
|
+
esModuleInterop: true,
|
|
545
|
+
skipLibCheck: true,
|
|
546
|
+
outDir: "dist"
|
|
547
|
+
},
|
|
548
|
+
include: answers.category === "bot_module" ? ["server/**/*"] : ["src/**/*", "server/**/*"]
|
|
549
|
+
};
|
|
550
|
+
fs2.writeFileSync(
|
|
551
|
+
path2.join(projectDir, "tsconfig.json"),
|
|
552
|
+
JSON.stringify(tsconfig, null, 2)
|
|
553
|
+
);
|
|
554
|
+
const eslintConfig = 'import eslintJs from "@eslint/js";\nimport tseslint from "typescript-eslint";\n\nexport default tseslint.config(\n eslintJs.configs.recommended,\n ...tseslint.configs.recommended,\n {\n rules: {\n "quotes": ["error", "double", { avoidEscape: true }],\n },\n },\n);\n';
|
|
555
|
+
fs2.writeFileSync(path2.join(projectDir, "eslint.config.mjs"), eslintConfig);
|
|
358
556
|
const changelog = `# Changelog
|
|
359
557
|
|
|
360
558
|
## [0.1.0] - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
@@ -364,8 +562,83 @@ function registerInit(program2) {
|
|
|
364
562
|
`;
|
|
365
563
|
fs2.writeFileSync(path2.join(projectDir, "CHANGELOG.md"), changelog);
|
|
366
564
|
fs2.mkdirSync(path2.join(projectDir, "src"), { recursive: true });
|
|
367
|
-
|
|
368
|
-
|
|
565
|
+
if (useTailwind) {
|
|
566
|
+
fs2.writeFileSync(
|
|
567
|
+
path2.join(projectDir, "src", "globals.css"),
|
|
568
|
+
'@import "tailwindcss";\n'
|
|
569
|
+
);
|
|
570
|
+
} else if (needsReact && answers.styling === "css") {
|
|
571
|
+
fs2.writeFileSync(
|
|
572
|
+
path2.join(projectDir, "src", "globals.css"),
|
|
573
|
+
"/* Add your custom styles here */\n"
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
if (answers.category === "bot_module") {
|
|
577
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
578
|
+
const serverFunctions = 'import { command } from "@zaflun/lumio-sdk/server";\n\nexport const hello = command("hello", async (ctx, args) => {\n return { reply: `Hello ${ctx.user?.name ?? "stranger"}!` };\n});\n';
|
|
579
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), serverFunctions);
|
|
580
|
+
const schema = 'import { defineSchema } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({});\n';
|
|
581
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
|
|
582
|
+
} else if (answers.category === "automation_node") {
|
|
583
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
584
|
+
const nodeType = answers.nodeType;
|
|
585
|
+
const triggerMode = answers.triggerMode;
|
|
586
|
+
let serverHandler;
|
|
587
|
+
if (nodeType === "action") {
|
|
588
|
+
serverHandler = 'import { defineAutomationAction } from "@zaflun/lumio-sdk/server";\n\ndefineAutomationAction(async (ctx) => {\n const value = ctx.inputData.value;\n\n // TODO: Implement your action logic\n // Use ctx.fetch() for external API calls\n // Use ctx.cache.get/set for temporary storage\n // Use ctx.installConfig for extension-level settings\n // Use ctx.nodeConfig for per-node-instance settings\n\n return {\n output: { result: `Processed: ${value}` },\n status: "success",\n };\n});\n';
|
|
589
|
+
} else if (nodeType === "trigger") {
|
|
590
|
+
const parts = [];
|
|
591
|
+
if (triggerMode === "webhook" || triggerMode === "both") {
|
|
592
|
+
parts.push(" onWebhook: async (ctx) => {\n const payload = ctx.webhookBody;\n\n // TODO: Validate the incoming webhook payload\n // Return { fired: false } to ignore the event\n // Return { fired: true, output: { ... } } to trigger the automation\n\n return {\n fired: true,\n output: {\n result: JSON.stringify(payload),\n },\n };\n }");
|
|
593
|
+
}
|
|
594
|
+
if (triggerMode === "polling" || triggerMode === "both") {
|
|
595
|
+
parts.push(' onPoll: async (ctx) => {\n const lastCheck = await ctx.cache.get("last_check_ts");\n\n // TODO: Check your external service for new events\n // const res = await ctx.fetch("https://api.example.com/events?since=" + lastCheck);\n // const events = await res.json();\n\n await ctx.cache.set("last_check_ts", Date.now().toString());\n\n // Return null or { fired: false } if no new events\n // Return { fired: true, output: { ... } } to trigger the automation\n return null;\n }');
|
|
596
|
+
}
|
|
597
|
+
serverHandler = `import { defineAutomationTrigger } from "@zaflun/lumio-sdk/server";
|
|
598
|
+
|
|
599
|
+
defineAutomationTrigger({
|
|
600
|
+
${parts.join(",\n")}
|
|
601
|
+
});
|
|
602
|
+
`;
|
|
603
|
+
} else {
|
|
604
|
+
serverHandler = 'import { defineAutomationLogic } from "@zaflun/lumio-sdk/server";\n\ndefineAutomationLogic(async (ctx) => {\n const value = ctx.inputData.value;\n const threshold = ctx.nodeConfig.threshold ?? 50;\n\n // TODO: Implement your branching logic\n // Return the branch key that matches an output handle\n\n if (Number(value) > Number(threshold)) {\n return "above";\n }\n return "below";\n});\n';
|
|
605
|
+
}
|
|
606
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "handler.ts"), serverHandler);
|
|
607
|
+
const editorComponent = `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
|
|
608
|
+
import { useLumioConfig } from "@zaflun/lumio-sdk/hooks";
|
|
609
|
+
|
|
610
|
+
function ConfigPanel() {
|
|
611
|
+
const [config, setConfig] = useLumioConfig();
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<CompactView title="${answers.name} \u2014 Settings">
|
|
615
|
+
<Text content="Configure your automation node here." />
|
|
616
|
+
</CompactView>
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
Lumio.render(<ConfigPanel />, { target: "editor" });
|
|
621
|
+
`;
|
|
622
|
+
fs2.writeFileSync(path2.join(projectDir, "src", "editor.tsx"), editorComponent);
|
|
623
|
+
} else {
|
|
624
|
+
for (const target of answers.targets) {
|
|
625
|
+
if (target === "editor" && editorApproach === "schema") continue;
|
|
626
|
+
const isEditorTarget = target === "editor";
|
|
627
|
+
const content = isEditorTarget ? `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
|
|
628
|
+
import { useLumioConfig } from "@zaflun/lumio-sdk";
|
|
629
|
+
|
|
630
|
+
function Editor() {
|
|
631
|
+
const [config, setConfig] = useLumioConfig();
|
|
632
|
+
|
|
633
|
+
return (
|
|
634
|
+
<CompactView title="${answers.name} \u2014 Advanced">
|
|
635
|
+
<Text content="Custom editor controls go here." />
|
|
636
|
+
</CompactView>
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export default Editor;
|
|
641
|
+
` : `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
|
|
369
642
|
|
|
370
643
|
function App() {
|
|
371
644
|
return (
|
|
@@ -377,14 +650,15 @@ function App() {
|
|
|
377
650
|
|
|
378
651
|
Lumio.render(<App />, { target: "${target}" });
|
|
379
652
|
`;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
653
|
+
fs2.writeFileSync(path2.join(projectDir, "src", `${target}.tsx`), content);
|
|
654
|
+
}
|
|
655
|
+
if (answers.server) {
|
|
656
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
657
|
+
const schema = 'import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({\n items: defineTable({\n text: v.string(),\n completed: v.boolean(),\n }).storage("instance"),\n});\n';
|
|
658
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
|
|
659
|
+
const functions = 'import { queryRows, insertRow, patchRow, deleteRow, arg } from "@zaflun/lumio-sdk/server";\n\nexport const getItems = queryRows("items");\nexport const addItem = insertRow("items", { text: arg("text"), completed: false }, { editorOnly: true });\nexport const toggleItem = patchRow("items", arg("id"), { completed: arg("completed") }, { editorOnly: true });\nexport const removeItem = deleteRow("items", arg("id"), { editorOnly: true });\n';
|
|
660
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), functions);
|
|
661
|
+
}
|
|
388
662
|
}
|
|
389
663
|
console.log(`
|
|
390
664
|
Done! Created ${projectDir}/`);
|
|
@@ -393,17 +667,6 @@ Done! Created ${projectDir}/`);
|
|
|
393
667
|
}
|
|
394
668
|
|
|
395
669
|
// src/commands/dev.ts
|
|
396
|
-
function registerDev(program2) {
|
|
397
|
-
program2.command("dev").description("Start local dev server with hot-reload and mock events").option("-p, --port <port>", "Dev server port", "3100").action(async (options) => {
|
|
398
|
-
console.log(`Starting Lumio dev server on port ${options.port}...`);
|
|
399
|
-
console.log("Mock events enabled. Hot-reload active.");
|
|
400
|
-
console.log("Open http://localhost:" + options.port + " in your browser.");
|
|
401
|
-
console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
|
|
402
|
-
console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// src/commands/build.ts
|
|
407
670
|
import * as fs3 from "fs";
|
|
408
671
|
import * as path3 from "path";
|
|
409
672
|
|
|
@@ -417,7 +680,7 @@ var ConfigError = class extends Error {
|
|
|
417
680
|
function parseConfigFile(jsonString) {
|
|
418
681
|
const raw = JSON.parse(jsonString);
|
|
419
682
|
return {
|
|
420
|
-
extensionId: raw.extensionId ?? "",
|
|
683
|
+
extensionId: raw.extensionId ?? raw.extension_id ?? "",
|
|
421
684
|
name: raw.name ?? "",
|
|
422
685
|
description: raw.description,
|
|
423
686
|
category: raw.category ?? "widget",
|
|
@@ -429,9 +692,21 @@ function parseConfigFile(jsonString) {
|
|
|
429
692
|
allowHosts: raw.egress?.allowHosts ?? [],
|
|
430
693
|
allowPorts: raw.egress?.allowPorts ?? []
|
|
431
694
|
},
|
|
695
|
+
platforms: raw.platforms,
|
|
696
|
+
triggers: raw.triggers,
|
|
432
697
|
fonts: raw.fonts,
|
|
698
|
+
sounds: raw.sounds,
|
|
433
699
|
visibility: raw.visibility ?? "private",
|
|
434
|
-
pricing: raw.pricing ?? { type: "free" }
|
|
700
|
+
pricing: raw.pricing ?? { type: "free" },
|
|
701
|
+
nodeType: raw.node_type ?? raw.nodeType,
|
|
702
|
+
node: raw.node ? {
|
|
703
|
+
inputSchema: raw.node.input_schema ?? raw.node.inputSchema ?? {},
|
|
704
|
+
outputSchema: raw.node.output_schema ?? raw.node.outputSchema ?? {},
|
|
705
|
+
triggerMode: raw.node.trigger_mode ?? raw.node.triggerMode,
|
|
706
|
+
pollIntervalSeconds: raw.node.poll_interval_seconds ?? raw.node.pollIntervalSeconds,
|
|
707
|
+
icon: raw.node.icon,
|
|
708
|
+
color: raw.node.color
|
|
709
|
+
} : void 0
|
|
435
710
|
};
|
|
436
711
|
}
|
|
437
712
|
function validateConfig(config) {
|
|
@@ -441,7 +716,32 @@ function validateConfig(config) {
|
|
|
441
716
|
if (!config.name) {
|
|
442
717
|
throw new ConfigError("name is required in lumio.config.json");
|
|
443
718
|
}
|
|
444
|
-
if (
|
|
719
|
+
if (config.category === "bot_module") {
|
|
720
|
+
if (!config.platforms || config.platforms.length === 0) {
|
|
721
|
+
throw new ConfigError("At least one platform is required for bot_module (twitch, youtube, kick, trovo, discord)");
|
|
722
|
+
}
|
|
723
|
+
} else if (config.category === "automation_node") {
|
|
724
|
+
if (!config.nodeType) {
|
|
725
|
+
throw new ConfigError("nodeType is required for automation_node extensions (trigger, action, or logic)");
|
|
726
|
+
}
|
|
727
|
+
if (!["trigger", "action", "logic"].includes(config.nodeType)) {
|
|
728
|
+
throw new ConfigError("nodeType must be one of: trigger, action, logic");
|
|
729
|
+
}
|
|
730
|
+
if (!config.node) {
|
|
731
|
+
throw new ConfigError("node configuration is required for automation_node extensions");
|
|
732
|
+
}
|
|
733
|
+
if (config.nodeType === "trigger" && !config.node.triggerMode) {
|
|
734
|
+
throw new ConfigError("node.triggerMode is required for trigger nodes (webhook, polling, or both)");
|
|
735
|
+
}
|
|
736
|
+
if (config.nodeType !== "trigger" && config.node.triggerMode) {
|
|
737
|
+
throw new ConfigError("node.triggerMode is only valid for trigger nodes");
|
|
738
|
+
}
|
|
739
|
+
if (config.node.pollIntervalSeconds !== void 0) {
|
|
740
|
+
if (config.node.pollIntervalSeconds < 10 || config.node.pollIntervalSeconds > 300) {
|
|
741
|
+
throw new ConfigError("node.pollIntervalSeconds must be between 10 and 300");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
} else if (!config.targets || config.targets.length === 0) {
|
|
445
745
|
throw new ConfigError("At least one target is required (editor, layer, or interactive)");
|
|
446
746
|
}
|
|
447
747
|
if (!config.version) {
|
|
@@ -449,28 +749,274 @@ function validateConfig(config) {
|
|
|
449
749
|
}
|
|
450
750
|
}
|
|
451
751
|
|
|
752
|
+
// src/commands/dev-bot-module.ts
|
|
753
|
+
import * as readline from "readline";
|
|
754
|
+
import chalk from "chalk";
|
|
755
|
+
function parseUserPrefix(input) {
|
|
756
|
+
const defaultUser = {
|
|
757
|
+
id: "user-001",
|
|
758
|
+
name: "dev_user",
|
|
759
|
+
displayName: "Dev User",
|
|
760
|
+
platform: "twitch",
|
|
761
|
+
role: "everyone",
|
|
762
|
+
isMod: false,
|
|
763
|
+
isVip: false,
|
|
764
|
+
isSub: false
|
|
765
|
+
};
|
|
766
|
+
if (!input.startsWith("@")) {
|
|
767
|
+
return { user: defaultUser, message: input };
|
|
768
|
+
}
|
|
769
|
+
const spaceIdx = input.indexOf(" ");
|
|
770
|
+
if (spaceIdx === -1) return { user: defaultUser, message: input };
|
|
771
|
+
const prefix = input.substring(1, spaceIdx);
|
|
772
|
+
const message = input.substring(spaceIdx + 1);
|
|
773
|
+
const parts = prefix.split(":");
|
|
774
|
+
const name = parts[parts.length - 1];
|
|
775
|
+
const rolePrefix = parts.length > 1 ? parts[0] : "everyone";
|
|
776
|
+
return {
|
|
777
|
+
user: {
|
|
778
|
+
id: `user-${name}`,
|
|
779
|
+
name,
|
|
780
|
+
displayName: name,
|
|
781
|
+
platform: "twitch",
|
|
782
|
+
role: rolePrefix,
|
|
783
|
+
isMod: rolePrefix === "mod",
|
|
784
|
+
isVip: rolePrefix === "vip",
|
|
785
|
+
isSub: ["sub", "vip", "mod", "broadcaster"].includes(rolePrefix)
|
|
786
|
+
},
|
|
787
|
+
message
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
async function runBotModuleDevServer(config) {
|
|
791
|
+
console.log(chalk.bold("\n\u{1F916} Bot Module Dev Server"));
|
|
792
|
+
console.log(`Extension: ${config.name}`);
|
|
793
|
+
console.log(`Platforms: ${config.platforms?.join(", ") ?? "none"}`);
|
|
794
|
+
console.log(chalk.dim("\nType chat messages. Prefix with @username or @role:username."));
|
|
795
|
+
console.log(chalk.dim("Examples: !hello | @sub:viewer !points 100 | @mod:admin gg"));
|
|
796
|
+
console.log(chalk.dim("Type !quit to exit.\n"));
|
|
797
|
+
const rl = readline.createInterface({
|
|
798
|
+
input: process.stdin,
|
|
799
|
+
output: process.stdout,
|
|
800
|
+
prompt: chalk.cyan("> ")
|
|
801
|
+
});
|
|
802
|
+
rl.prompt();
|
|
803
|
+
rl.on("line", async (line) => {
|
|
804
|
+
const trimmed = line.trim();
|
|
805
|
+
if (!trimmed) {
|
|
806
|
+
rl.prompt();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (trimmed === "!quit") {
|
|
810
|
+
rl.close();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const { user, message } = parseUserPrefix(trimmed);
|
|
814
|
+
console.log(chalk.dim(` [${user.role}:${user.name}] ${message}`));
|
|
815
|
+
console.log(chalk.yellow(" \u2192 Handler execution not yet wired (requires esbuild + V8)"));
|
|
816
|
+
rl.prompt();
|
|
817
|
+
});
|
|
818
|
+
rl.on("close", () => {
|
|
819
|
+
console.log(chalk.dim("\nBot Module Dev Server stopped."));
|
|
820
|
+
process.exit(0);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/commands/dev-automation-node.ts
|
|
825
|
+
import * as readline2 from "readline";
|
|
826
|
+
import chalk2 from "chalk";
|
|
827
|
+
async function runAutomationNodeDevServer(config) {
|
|
828
|
+
const nodeType = config.nodeType ?? "action";
|
|
829
|
+
console.log(chalk2.bold("\n\u2699\uFE0F Automation Node Dev Server"));
|
|
830
|
+
console.log(`Extension: ${config.name}`);
|
|
831
|
+
console.log(`Node type: ${nodeType}`);
|
|
832
|
+
if (nodeType === "trigger" && config.node?.triggerMode) {
|
|
833
|
+
console.log(`Trigger mode: ${config.node.triggerMode}`);
|
|
834
|
+
}
|
|
835
|
+
console.log(chalk2.dim("\nCommands:"));
|
|
836
|
+
console.log(chalk2.dim(" /execute [json] \u2014 Execute the node with input data"));
|
|
837
|
+
if (nodeType === "trigger") {
|
|
838
|
+
console.log(chalk2.dim(" /webhook [json] \u2014 Simulate a webhook POST payload"));
|
|
839
|
+
console.log(chalk2.dim(" /poll \u2014 Trigger a poll cycle"));
|
|
840
|
+
}
|
|
841
|
+
console.log(chalk2.dim(" /config [json] \u2014 Set node instance config"));
|
|
842
|
+
console.log(chalk2.dim(" /install-config [j] \u2014 Set extension install config"));
|
|
843
|
+
console.log(chalk2.dim(" /quit \u2014 Exit\n"));
|
|
844
|
+
let nodeConfig = {};
|
|
845
|
+
let installConfig = {};
|
|
846
|
+
const rl = readline2.createInterface({
|
|
847
|
+
input: process.stdin,
|
|
848
|
+
output: process.stdout,
|
|
849
|
+
prompt: chalk2.cyan("> ")
|
|
850
|
+
});
|
|
851
|
+
rl.prompt();
|
|
852
|
+
rl.on("line", async (line) => {
|
|
853
|
+
const trimmed = line.trim();
|
|
854
|
+
if (!trimmed) {
|
|
855
|
+
rl.prompt();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (trimmed === "/quit") {
|
|
859
|
+
rl.close();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (trimmed.startsWith("/config ")) {
|
|
863
|
+
try {
|
|
864
|
+
nodeConfig = JSON.parse(trimmed.slice("/config ".length));
|
|
865
|
+
console.log(chalk2.green(" Node config updated."));
|
|
866
|
+
} catch {
|
|
867
|
+
console.log(chalk2.red(" Invalid JSON."));
|
|
868
|
+
}
|
|
869
|
+
rl.prompt();
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (trimmed.startsWith("/install-config ")) {
|
|
873
|
+
try {
|
|
874
|
+
installConfig = JSON.parse(trimmed.slice("/install-config ".length));
|
|
875
|
+
console.log(chalk2.green(" Install config updated."));
|
|
876
|
+
} catch {
|
|
877
|
+
console.log(chalk2.red(" Invalid JSON."));
|
|
878
|
+
}
|
|
879
|
+
rl.prompt();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (trimmed.startsWith("/execute")) {
|
|
883
|
+
const jsonStr = trimmed.slice("/execute".length).trim();
|
|
884
|
+
let inputData = {};
|
|
885
|
+
if (jsonStr) {
|
|
886
|
+
try {
|
|
887
|
+
inputData = JSON.parse(jsonStr);
|
|
888
|
+
} catch {
|
|
889
|
+
console.log(chalk2.red(" Invalid JSON."));
|
|
890
|
+
rl.prompt();
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
console.log(chalk2.dim(` [${nodeType}] Executing with input: ${JSON.stringify(inputData)}`));
|
|
895
|
+
console.log(chalk2.dim(` [${nodeType}] Node config: ${JSON.stringify(nodeConfig)}`));
|
|
896
|
+
console.log(chalk2.dim(` [${nodeType}] Install config: ${JSON.stringify(installConfig)}`));
|
|
897
|
+
console.log(chalk2.yellow(" \u2192 Handler execution not yet wired (requires esbuild + V8)"));
|
|
898
|
+
rl.prompt();
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (nodeType === "trigger" && trimmed.startsWith("/webhook")) {
|
|
902
|
+
const jsonStr = trimmed.slice("/webhook".length).trim();
|
|
903
|
+
let body = {};
|
|
904
|
+
if (jsonStr) {
|
|
905
|
+
try {
|
|
906
|
+
body = JSON.parse(jsonStr);
|
|
907
|
+
} catch {
|
|
908
|
+
console.log(chalk2.red(" Invalid JSON."));
|
|
909
|
+
rl.prompt();
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
console.log(chalk2.dim(` [trigger:webhook] Received payload: ${JSON.stringify(body)}`));
|
|
914
|
+
console.log(chalk2.yellow(" \u2192 Handler execution not yet wired (requires esbuild + V8)"));
|
|
915
|
+
rl.prompt();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (nodeType === "trigger" && trimmed === "/poll") {
|
|
919
|
+
console.log(chalk2.dim(" [trigger:poll] Polling..."));
|
|
920
|
+
console.log(chalk2.yellow(" \u2192 Handler execution not yet wired (requires esbuild + V8)"));
|
|
921
|
+
rl.prompt();
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
console.log(chalk2.dim(" Unknown command. Type /execute, /webhook, /poll, /config, or /quit."));
|
|
925
|
+
rl.prompt();
|
|
926
|
+
});
|
|
927
|
+
rl.on("close", () => {
|
|
928
|
+
console.log(chalk2.dim("\nAutomation Node Dev Server stopped."));
|
|
929
|
+
process.exit(0);
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/commands/dev.ts
|
|
934
|
+
function registerDev(program2) {
|
|
935
|
+
program2.command("dev").description("Start local dev server with hot-reload and mock events").option("-p, --port <port>", "Dev server port", "3100").option("--bot-module", "Start interactive bot module chat simulator").option("--automation-node", "Start automation node dev simulator").action(async (options) => {
|
|
936
|
+
const configPath = path3.resolve("lumio.config.json");
|
|
937
|
+
if (options.botModule || options.automationNode || fs3.existsSync(configPath)) {
|
|
938
|
+
const configExists = fs3.existsSync(configPath);
|
|
939
|
+
if (configExists) {
|
|
940
|
+
const config = parseConfigFile(fs3.readFileSync(configPath, "utf-8"));
|
|
941
|
+
if (options.botModule || config.category === "bot_module") {
|
|
942
|
+
await runBotModuleDevServer(config);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (options.automationNode || config.category === "automation_node") {
|
|
946
|
+
await runAutomationNodeDevServer(config);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
} else if (options.botModule || options.automationNode) {
|
|
950
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
console.log(`Starting Lumio dev server on port ${options.port}...`);
|
|
955
|
+
console.log("Mock events enabled. Hot-reload active.");
|
|
956
|
+
console.log("Open http://localhost:" + options.port + " in your browser.");
|
|
957
|
+
console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
|
|
958
|
+
console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
452
962
|
// src/commands/build.ts
|
|
963
|
+
import * as fs4 from "fs";
|
|
964
|
+
import * as path4 from "path";
|
|
965
|
+
import { execFile } from "child_process";
|
|
966
|
+
import { promisify } from "util";
|
|
967
|
+
var execFileAsync = promisify(execFile);
|
|
453
968
|
function registerBuild(program2) {
|
|
454
969
|
program2.command("build").description("Bundle extension as standalone HTML + source archive").action(async () => {
|
|
455
|
-
const configPath =
|
|
456
|
-
if (!
|
|
970
|
+
const configPath = path4.resolve("lumio.config.json");
|
|
971
|
+
if (!fs4.existsSync(configPath)) {
|
|
457
972
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
458
973
|
process.exit(1);
|
|
459
974
|
}
|
|
460
|
-
const configJson =
|
|
975
|
+
const configJson = fs4.readFileSync(configPath, "utf-8");
|
|
461
976
|
const config = parseConfigFile(configJson);
|
|
462
977
|
validateConfig(config);
|
|
463
|
-
const changelogPath =
|
|
464
|
-
if (!
|
|
978
|
+
const changelogPath = path4.resolve("CHANGELOG.md");
|
|
979
|
+
if (!fs4.existsSync(changelogPath)) {
|
|
465
980
|
console.warn("Warning: No CHANGELOG.md found. It is recommended to maintain a changelog.");
|
|
466
981
|
}
|
|
467
982
|
console.log(`Building ${config.name} v${config.version}...`);
|
|
468
|
-
const distDir =
|
|
469
|
-
|
|
983
|
+
const distDir = path4.resolve("dist");
|
|
984
|
+
fs4.mkdirSync(distDir, { recursive: true });
|
|
470
985
|
const { build } = await import("esbuild");
|
|
986
|
+
if (config.category === "bot_module" && config.targets.length === 0) {
|
|
987
|
+
console.log(" Bot module \u2014 no UI targets to bundle.");
|
|
988
|
+
}
|
|
989
|
+
const cssEntryFile = ["src/globals.css", "src/style.css", "src/index.css"].find((f) => fs4.existsSync(path4.resolve(f)));
|
|
990
|
+
let compiledCssPath = null;
|
|
991
|
+
if (cssEntryFile) {
|
|
992
|
+
const cssContent = fs4.readFileSync(path4.resolve(cssEntryFile), "utf-8");
|
|
993
|
+
const usesTailwind = cssContent.includes("tailwindcss") || cssContent.includes("@tailwind");
|
|
994
|
+
if (usesTailwind) {
|
|
995
|
+
console.log(" Tailwind CSS v4 detected \u2014 compiling...");
|
|
996
|
+
compiledCssPath = path4.join(distDir, "styles.css");
|
|
997
|
+
try {
|
|
998
|
+
await execFileAsync("npx", [
|
|
999
|
+
"@tailwindcss/cli",
|
|
1000
|
+
"-i",
|
|
1001
|
+
path4.resolve(cssEntryFile),
|
|
1002
|
+
"-o",
|
|
1003
|
+
compiledCssPath,
|
|
1004
|
+
"--minify"
|
|
1005
|
+
], { cwd: path4.resolve(".") });
|
|
1006
|
+
console.log(" Compiled: dist/styles.css");
|
|
1007
|
+
} catch {
|
|
1008
|
+
console.warn(" Warning: Tailwind CSS compilation failed. Install tailwindcss v4: npm add -D tailwindcss@^4.3");
|
|
1009
|
+
compiledCssPath = null;
|
|
1010
|
+
}
|
|
1011
|
+
} else {
|
|
1012
|
+
compiledCssPath = path4.join(distDir, "styles.css");
|
|
1013
|
+
fs4.copyFileSync(path4.resolve(cssEntryFile), compiledCssPath);
|
|
1014
|
+
console.log(` Copied: ${cssEntryFile} \u2192 dist/styles.css`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
471
1017
|
for (const target of config.targets) {
|
|
472
|
-
const entryPoint =
|
|
473
|
-
if (!
|
|
1018
|
+
const entryPoint = path4.resolve("src", `${target}.tsx`);
|
|
1019
|
+
if (!fs4.existsSync(entryPoint)) {
|
|
474
1020
|
console.warn(`Warning: Entry point ${entryPoint} not found, skipping target "${target}".`);
|
|
475
1021
|
continue;
|
|
476
1022
|
}
|
|
@@ -478,11 +1024,39 @@ function registerBuild(program2) {
|
|
|
478
1024
|
entryPoints: [entryPoint],
|
|
479
1025
|
bundle: true,
|
|
480
1026
|
minify: true,
|
|
481
|
-
format: "
|
|
482
|
-
|
|
1027
|
+
format: "iife",
|
|
1028
|
+
globalName: "LumioExtension",
|
|
1029
|
+
footer: {
|
|
1030
|
+
js: "if(typeof LumioExtension!=='undefined'&&typeof LumioExtension.default==='function'){var _L=(typeof require!=='undefined'?require('@zaflun/lumio-sdk'):{}).Lumio;if(_L)_L.render(LumioExtension.default);}"
|
|
1031
|
+
},
|
|
1032
|
+
outdir: distDir,
|
|
1033
|
+
entryNames: `${target}`,
|
|
483
1034
|
jsx: "automatic",
|
|
484
1035
|
target: "es2022",
|
|
485
|
-
|
|
1036
|
+
loader: {
|
|
1037
|
+
".css": "css",
|
|
1038
|
+
// Asset file loaders — copies to dist/assets/ with content hash
|
|
1039
|
+
".png": "file",
|
|
1040
|
+
".jpg": "file",
|
|
1041
|
+
".jpeg": "file",
|
|
1042
|
+
".gif": "file",
|
|
1043
|
+
".svg": "file",
|
|
1044
|
+
".webp": "file",
|
|
1045
|
+
".avif": "file",
|
|
1046
|
+
".ico": "file",
|
|
1047
|
+
".mp4": "file",
|
|
1048
|
+
".webm": "file",
|
|
1049
|
+
".ogg": "file",
|
|
1050
|
+
".mp3": "file",
|
|
1051
|
+
".wav": "file",
|
|
1052
|
+
".woff": "file",
|
|
1053
|
+
".woff2": "file",
|
|
1054
|
+
".ttf": "file",
|
|
1055
|
+
".otf": "file",
|
|
1056
|
+
".lottie": "file"
|
|
1057
|
+
},
|
|
1058
|
+
assetNames: "assets/[name]-[hash]",
|
|
1059
|
+
external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "@zaflun/lumio-sdk", "@zaflun/lumio-protocol"],
|
|
486
1060
|
define: {
|
|
487
1061
|
"process.env.NODE_ENV": '"production"'
|
|
488
1062
|
}
|
|
@@ -490,19 +1064,23 @@ function registerBuild(program2) {
|
|
|
490
1064
|
console.log(` Built: dist/${target}.js`);
|
|
491
1065
|
}
|
|
492
1066
|
const { default: archiver } = await import("archiver");
|
|
493
|
-
const sourceArchivePath =
|
|
494
|
-
const output =
|
|
1067
|
+
const sourceArchivePath = path4.join(distDir, "source.tar.gz");
|
|
1068
|
+
const output = fs4.createWriteStream(sourceArchivePath);
|
|
495
1069
|
const archive = archiver("tar", { gzip: true });
|
|
496
1070
|
archive.pipe(output);
|
|
497
|
-
archive.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
1071
|
+
archive.glob("**/*", {
|
|
1072
|
+
cwd: path4.resolve("."),
|
|
1073
|
+
ignore: [
|
|
1074
|
+
"node_modules/**",
|
|
1075
|
+
"dist/**",
|
|
1076
|
+
".git/**",
|
|
1077
|
+
".next/**",
|
|
1078
|
+
".lumio-dist/**",
|
|
1079
|
+
"*.tgz",
|
|
1080
|
+
"*.tar.gz"
|
|
1081
|
+
],
|
|
1082
|
+
dot: true
|
|
1083
|
+
});
|
|
506
1084
|
await archive.finalize();
|
|
507
1085
|
console.log(" Archive: dist/source.tar.gz");
|
|
508
1086
|
console.log("Build complete.");
|
|
@@ -510,8 +1088,8 @@ function registerBuild(program2) {
|
|
|
510
1088
|
}
|
|
511
1089
|
|
|
512
1090
|
// src/commands/deploy.ts
|
|
513
|
-
import * as
|
|
514
|
-
import * as
|
|
1091
|
+
import * as fs5 from "fs";
|
|
1092
|
+
import * as path5 from "path";
|
|
515
1093
|
|
|
516
1094
|
// src/changelog/parser.ts
|
|
517
1095
|
var VERSION_HEADER_REGEX = /^## \[([^\]]+)\](?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/;
|
|
@@ -549,77 +1127,205 @@ function getVersionEntry(changelog, version) {
|
|
|
549
1127
|
}
|
|
550
1128
|
|
|
551
1129
|
// src/commands/deploy.ts
|
|
1130
|
+
function collectFiles(dir, base = dir) {
|
|
1131
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1132
|
+
const files = [];
|
|
1133
|
+
for (const entry of entries) {
|
|
1134
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1135
|
+
if (entry.isDirectory()) {
|
|
1136
|
+
files.push(...collectFiles(fullPath, base));
|
|
1137
|
+
} else if (entry.isFile()) {
|
|
1138
|
+
files.push(path5.relative(base, fullPath));
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return files;
|
|
1142
|
+
}
|
|
1143
|
+
var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1144
|
+
"js",
|
|
1145
|
+
"css",
|
|
1146
|
+
"html",
|
|
1147
|
+
"map",
|
|
1148
|
+
"png",
|
|
1149
|
+
"jpg",
|
|
1150
|
+
"jpeg",
|
|
1151
|
+
"gif",
|
|
1152
|
+
"svg",
|
|
1153
|
+
"webp",
|
|
1154
|
+
"avif",
|
|
1155
|
+
"ico",
|
|
1156
|
+
"mp4",
|
|
1157
|
+
"webm",
|
|
1158
|
+
"ogg",
|
|
1159
|
+
"mp3",
|
|
1160
|
+
"wav",
|
|
1161
|
+
"m4a",
|
|
1162
|
+
"aac",
|
|
1163
|
+
"flac",
|
|
1164
|
+
"woff",
|
|
1165
|
+
"woff2",
|
|
1166
|
+
"ttf",
|
|
1167
|
+
"otf",
|
|
1168
|
+
"eot",
|
|
1169
|
+
"json",
|
|
1170
|
+
"lottie"
|
|
1171
|
+
]);
|
|
1172
|
+
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "webm", "m4a", "aac", "flac"]);
|
|
1173
|
+
var LARGE_SOUND_THRESHOLD = 5 * 1024 * 1024;
|
|
1174
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1175
|
+
var MAX_TOTAL_SIZE = 50 * 1024 * 1024;
|
|
1176
|
+
var MAX_FILES = 200;
|
|
552
1177
|
function registerDeploy(program2) {
|
|
553
|
-
program2.command("deploy").description("Upload extension to Lumio (status: draft)").option("-v, --version <version>", "Version override").option("-d, --description <text>", "Release notes override").option("--breaking", "Mark as breaking change").option("--draft", "Explicitly mark as draft").action(async (options) => {
|
|
1178
|
+
program2.command("deploy").description("Upload extension to Lumio (status: draft)").option("-v, --version <version>", "Version override").option("-d, --description <text>", "Release notes override").option("--breaking", "Mark as breaking change").option("--draft", "Explicitly mark as draft").option("--token <token>", "API token (for CI, skips login)").option("--system", "Deploy as system extension (no developer profile required)").action(async (options) => {
|
|
554
1179
|
const store = new CredentialStore();
|
|
555
|
-
const token = store.getToken();
|
|
1180
|
+
const token = options.token ?? store.getToken();
|
|
556
1181
|
if (!token) {
|
|
557
|
-
console.error("Not logged in. Run `lumio login`
|
|
1182
|
+
console.error("Not logged in. Run `lumio login` or provide --token.");
|
|
558
1183
|
process.exit(1);
|
|
559
1184
|
}
|
|
560
|
-
const configPath =
|
|
561
|
-
if (!
|
|
1185
|
+
const configPath = path5.resolve("lumio.config.json");
|
|
1186
|
+
if (!fs5.existsSync(configPath)) {
|
|
562
1187
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
563
1188
|
process.exit(1);
|
|
564
1189
|
}
|
|
565
|
-
const configJson =
|
|
1190
|
+
const configJson = fs5.readFileSync(configPath, "utf-8");
|
|
566
1191
|
const config = parseConfigFile(configJson);
|
|
567
1192
|
validateConfig(config);
|
|
1193
|
+
const distDir = path5.resolve("dist");
|
|
1194
|
+
if (config.sounds && config.sounds.length > 0) {
|
|
1195
|
+
for (const sound of config.sounds) {
|
|
1196
|
+
const soundFile = sound.file;
|
|
1197
|
+
const ext = soundFile.split(".").pop()?.toLowerCase() ?? "";
|
|
1198
|
+
if (!AUDIO_EXTENSIONS.has(ext)) {
|
|
1199
|
+
console.error(
|
|
1200
|
+
`Error: Sound file "${soundFile}" has unsupported extension ".${ext}". Allowed audio formats: ${[...AUDIO_EXTENSIONS].join(", ")}.`
|
|
1201
|
+
);
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
1204
|
+
const soundAbsPath = path5.join(distDir, "sounds", soundFile);
|
|
1205
|
+
if (fs5.existsSync(distDir) && !fs5.existsSync(soundAbsPath)) {
|
|
1206
|
+
console.error(
|
|
1207
|
+
`Error: Sound file "${soundFile}" declared in lumio.config.json was not found at dist/sounds/${soundFile}. Run \`lumio build\` first.`
|
|
1208
|
+
);
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
if (fs5.existsSync(soundAbsPath)) {
|
|
1212
|
+
const stat = fs5.statSync(soundAbsPath);
|
|
1213
|
+
if (stat.size > LARGE_SOUND_THRESHOLD) {
|
|
1214
|
+
console.warn(
|
|
1215
|
+
` Warning: Sound file "${soundFile}" is ${(stat.size / 1024 / 1024).toFixed(1)} MB. Consider compressing large audio files to reduce load times.`
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
568
1221
|
const version = options.version ?? config.version;
|
|
569
1222
|
let releaseNotes = options.description ?? "";
|
|
570
1223
|
if (!releaseNotes) {
|
|
571
|
-
const changelogPath =
|
|
572
|
-
if (
|
|
573
|
-
const changelog =
|
|
1224
|
+
const changelogPath = path5.resolve("CHANGELOG.md");
|
|
1225
|
+
if (fs5.existsSync(changelogPath)) {
|
|
1226
|
+
const changelog = fs5.readFileSync(changelogPath, "utf-8");
|
|
574
1227
|
const entry = getVersionEntry(changelog, version);
|
|
575
1228
|
if (entry) {
|
|
576
1229
|
releaseNotes = entry.content;
|
|
577
|
-
} else if (!LumioApiClient.isCI()) {
|
|
1230
|
+
} else if (!LumioApiClient.isCI() && !options.system) {
|
|
578
1231
|
console.error(`No changelog entry found for version ${version}.`);
|
|
579
1232
|
console.error("Add an entry to CHANGELOG.md or use -d to provide release notes.");
|
|
580
1233
|
process.exit(1);
|
|
581
1234
|
}
|
|
582
1235
|
}
|
|
583
1236
|
}
|
|
584
|
-
|
|
585
|
-
if (!fs4.existsSync(distDir)) {
|
|
1237
|
+
if (!fs5.existsSync(distDir)) {
|
|
586
1238
|
console.error("No dist/ directory found. Run `lumio build` first.");
|
|
587
1239
|
process.exit(1);
|
|
588
1240
|
}
|
|
589
1241
|
console.log(`Deploying ${config.name} v${version}...`);
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1242
|
+
const apiUrl = process.env.LUMIO_API_URL ?? store.getApiUrl();
|
|
1243
|
+
const client = new LumioApiClient(apiUrl, token);
|
|
1244
|
+
const allFiles = collectFiles(distDir);
|
|
1245
|
+
const sourceFile = "source.tar.gz";
|
|
1246
|
+
const bundleFiles = allFiles.filter((f) => {
|
|
1247
|
+
if (f === sourceFile) return false;
|
|
1248
|
+
if (f === "bundle.zip") return false;
|
|
1249
|
+
const ext = f.split(".").pop()?.toLowerCase() ?? "";
|
|
1250
|
+
return ALLOWED_EXTENSIONS.has(ext);
|
|
1251
|
+
});
|
|
1252
|
+
if (bundleFiles.length === 0) {
|
|
1253
|
+
console.error("No uploadable files found in dist/. Run `lumio build` first.");
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
if (bundleFiles.length > MAX_FILES) {
|
|
1257
|
+
console.error(`Too many files (${bundleFiles.length}). Maximum is ${MAX_FILES}.`);
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
const sourceArchive = path5.join(distDir, sourceFile);
|
|
1261
|
+
if (!fs5.existsSync(sourceArchive)) {
|
|
1262
|
+
console.error("Error: source.tar.gz not found in dist/. Run `lumio build` first.");
|
|
1263
|
+
process.exit(1);
|
|
596
1264
|
}
|
|
1265
|
+
const formData = new FormData();
|
|
597
1266
|
const manifest = JSON.parse(configJson);
|
|
598
1267
|
manifest.version = version;
|
|
599
1268
|
formData.append("version", version);
|
|
600
1269
|
formData.append("manifest", JSON.stringify(manifest));
|
|
601
1270
|
formData.append("release_notes", releaseNotes);
|
|
602
1271
|
formData.append("is_breaking", String(!!options.breaking));
|
|
1272
|
+
const sourceBuffer = fs5.readFileSync(sourceArchive);
|
|
1273
|
+
if (sourceBuffer.length === 0) {
|
|
1274
|
+
console.error("Error: source.tar.gz is empty. Run `lumio build` to regenerate.");
|
|
1275
|
+
process.exit(1);
|
|
1276
|
+
}
|
|
1277
|
+
formData.append("source", new Blob([sourceBuffer]), "source.tar.gz");
|
|
1278
|
+
console.log(` Source: source.tar.gz (${(sourceBuffer.length / 1024).toFixed(1)} KB)`);
|
|
1279
|
+
let totalSize = 0;
|
|
1280
|
+
for (const relPath of bundleFiles) {
|
|
1281
|
+
const absPath = path5.join(distDir, relPath);
|
|
1282
|
+
const buffer = fs5.readFileSync(absPath);
|
|
1283
|
+
if (buffer.length === 0) {
|
|
1284
|
+
console.warn(` Warning: Skipping empty file: ${relPath}`);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
1288
|
+
console.error(`Error: File '${relPath}' exceeds ${MAX_FILE_SIZE / 1024 / 1024} MB limit.`);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
totalSize += buffer.length;
|
|
1292
|
+
if (totalSize > MAX_TOTAL_SIZE) {
|
|
1293
|
+
console.error(`Error: Total bundle size exceeds ${MAX_TOTAL_SIZE / 1024 / 1024} MB limit.`);
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
formData.append("files", new Blob([buffer]), relPath);
|
|
1297
|
+
console.log(` File: ${relPath} (${(buffer.length / 1024).toFixed(1)} KB)`);
|
|
1298
|
+
}
|
|
1299
|
+
console.log(` Total: ${bundleFiles.length} files (${(totalSize / 1024).toFixed(1)} KB)`);
|
|
1300
|
+
console.log(" Uploading...");
|
|
603
1301
|
try {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
);
|
|
608
|
-
console.log("Upload successful. Status: draft");
|
|
609
|
-
console.log("Submit for review via the dashboard or `lumio status`.");
|
|
1302
|
+
const uploadPath = options.system ? `/v1/system/extensions/${config.extensionId}/versions/upload` : `/v1/developer/extensions/${config.extensionId}/versions/upload`;
|
|
1303
|
+
await client.uploadFile(uploadPath, formData);
|
|
1304
|
+
console.log("\n Upload successful. Status: draft");
|
|
1305
|
+
console.log(" Submit for review via the dashboard or `lumio status`.");
|
|
610
1306
|
if (LumioApiClient.isCI()) {
|
|
611
1307
|
process.exit(0);
|
|
612
1308
|
}
|
|
613
1309
|
} catch (err) {
|
|
614
|
-
|
|
1310
|
+
const status = err.status;
|
|
1311
|
+
const message = err.message ?? String(err);
|
|
1312
|
+
console.error(`
|
|
1313
|
+
Deploy failed${status ? ` (HTTP ${status})` : ""}: ${message}`);
|
|
1314
|
+
if (status === 409) {
|
|
1315
|
+
console.error(" This version already exists. Bump the version in lumio.config.json.");
|
|
1316
|
+
} else if (status === 413) {
|
|
1317
|
+
console.error(" Bundle or source archive exceeds the maximum upload size.");
|
|
1318
|
+
} else if (status === 401 || status === 403) {
|
|
1319
|
+
console.error(" Authentication failed. Run `lumio login` to refresh your token.");
|
|
1320
|
+
}
|
|
615
1321
|
process.exit(1);
|
|
616
1322
|
}
|
|
617
1323
|
});
|
|
618
1324
|
}
|
|
619
1325
|
|
|
620
1326
|
// src/commands/status.ts
|
|
621
|
-
import * as
|
|
622
|
-
import * as
|
|
1327
|
+
import * as fs6 from "fs";
|
|
1328
|
+
import * as path6 from "path";
|
|
623
1329
|
function registerStatus(program2) {
|
|
624
1330
|
program2.command("status").description("Show review status of the latest version").action(async () => {
|
|
625
1331
|
const store = new CredentialStore();
|
|
@@ -628,12 +1334,12 @@ function registerStatus(program2) {
|
|
|
628
1334
|
console.error("Not logged in. Run `lumio login` first.");
|
|
629
1335
|
process.exit(1);
|
|
630
1336
|
}
|
|
631
|
-
const configPath =
|
|
632
|
-
if (!
|
|
1337
|
+
const configPath = path6.resolve("lumio.config.json");
|
|
1338
|
+
if (!fs6.existsSync(configPath)) {
|
|
633
1339
|
console.error("lumio.config.json not found.");
|
|
634
1340
|
process.exit(1);
|
|
635
1341
|
}
|
|
636
|
-
const config = parseConfigFile(
|
|
1342
|
+
const config = parseConfigFile(fs6.readFileSync(configPath, "utf-8"));
|
|
637
1343
|
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
638
1344
|
try {
|
|
639
1345
|
const versions = await client.get(
|
|
@@ -662,8 +1368,8 @@ function registerStatus(program2) {
|
|
|
662
1368
|
}
|
|
663
1369
|
|
|
664
1370
|
// src/commands/logs.ts
|
|
665
|
-
import * as
|
|
666
|
-
import * as
|
|
1371
|
+
import * as fs7 from "fs";
|
|
1372
|
+
import * as path7 from "path";
|
|
667
1373
|
function registerLogs(program2) {
|
|
668
1374
|
program2.command("logs").description("Stream server function logs via SSE").option("-f, --follow", "Follow log output", true).option("-n, --lines <count>", "Number of recent lines", "50").action(async (options) => {
|
|
669
1375
|
const store = new CredentialStore();
|
|
@@ -672,12 +1378,12 @@ function registerLogs(program2) {
|
|
|
672
1378
|
console.error("Not logged in. Run `lumio login` first.");
|
|
673
1379
|
process.exit(1);
|
|
674
1380
|
}
|
|
675
|
-
const configPath =
|
|
676
|
-
if (!
|
|
1381
|
+
const configPath = path7.resolve("lumio.config.json");
|
|
1382
|
+
if (!fs7.existsSync(configPath)) {
|
|
677
1383
|
console.error("lumio.config.json not found.");
|
|
678
1384
|
process.exit(1);
|
|
679
1385
|
}
|
|
680
|
-
const config = parseConfigFile(
|
|
1386
|
+
const config = parseConfigFile(fs7.readFileSync(configPath, "utf-8"));
|
|
681
1387
|
const apiUrl = store.getApiUrl();
|
|
682
1388
|
const logsUrl = `${apiUrl}/v1/developer/extensions/${config.extensionId}/logs?lines=${options.lines}`;
|
|
683
1389
|
console.log(`Streaming logs for ${config.name}...`);
|
|
@@ -714,8 +1420,8 @@ function registerLogs(program2) {
|
|
|
714
1420
|
}
|
|
715
1421
|
|
|
716
1422
|
// src/commands/env.ts
|
|
717
|
-
import * as
|
|
718
|
-
import * as
|
|
1423
|
+
import * as fs8 from "fs";
|
|
1424
|
+
import * as path8 from "path";
|
|
719
1425
|
function validateSecretKey(key) {
|
|
720
1426
|
return key.length > 0 && /^[A-Z0-9_]+$/.test(key);
|
|
721
1427
|
}
|
|
@@ -724,12 +1430,12 @@ function formatSecretList(secrets) {
|
|
|
724
1430
|
return secrets.map((s) => ` ${s.key} (created ${s.created_at})`).join("\n");
|
|
725
1431
|
}
|
|
726
1432
|
function loadConfig() {
|
|
727
|
-
const configPath =
|
|
728
|
-
if (!
|
|
1433
|
+
const configPath = path8.resolve("lumio.config.json");
|
|
1434
|
+
if (!fs8.existsSync(configPath)) {
|
|
729
1435
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
730
1436
|
process.exit(1);
|
|
731
1437
|
}
|
|
732
|
-
return parseConfigFile(
|
|
1438
|
+
return parseConfigFile(fs8.readFileSync(configPath, "utf-8"));
|
|
733
1439
|
}
|
|
734
1440
|
function requireClient() {
|
|
735
1441
|
const store = new CredentialStore();
|
|
@@ -771,8 +1477,27 @@ function registerEnv(program2) {
|
|
|
771
1477
|
}
|
|
772
1478
|
|
|
773
1479
|
// src/commands/run.ts
|
|
774
|
-
import * as
|
|
775
|
-
import * as
|
|
1480
|
+
import * as fs9 from "fs";
|
|
1481
|
+
import * as path9 from "path";
|
|
1482
|
+
var VALID_FUNCTION_TYPES = [
|
|
1483
|
+
"query",
|
|
1484
|
+
"mutation",
|
|
1485
|
+
"action",
|
|
1486
|
+
"command",
|
|
1487
|
+
"keyword",
|
|
1488
|
+
"pattern",
|
|
1489
|
+
"event",
|
|
1490
|
+
"timer",
|
|
1491
|
+
"moderate"
|
|
1492
|
+
];
|
|
1493
|
+
var BOT_HANDLER_TYPES = [
|
|
1494
|
+
"command",
|
|
1495
|
+
"keyword",
|
|
1496
|
+
"pattern",
|
|
1497
|
+
"event",
|
|
1498
|
+
"timer",
|
|
1499
|
+
"moderate"
|
|
1500
|
+
];
|
|
776
1501
|
function parseFunctionRef(ref) {
|
|
777
1502
|
const colonIndex = ref.indexOf(":");
|
|
778
1503
|
if (colonIndex === -1) {
|
|
@@ -780,20 +1505,22 @@ function parseFunctionRef(ref) {
|
|
|
780
1505
|
}
|
|
781
1506
|
const type = ref.slice(0, colonIndex);
|
|
782
1507
|
const name = ref.slice(colonIndex + 1);
|
|
783
|
-
if (!
|
|
784
|
-
throw new Error(
|
|
1508
|
+
if (!VALID_FUNCTION_TYPES.includes(type)) {
|
|
1509
|
+
throw new Error(
|
|
1510
|
+
`Function type must be one of: ${VALID_FUNCTION_TYPES.join(", ")}. Got: "${type}"`
|
|
1511
|
+
);
|
|
785
1512
|
}
|
|
786
1513
|
return { type, name };
|
|
787
1514
|
}
|
|
788
1515
|
function registerRun(program2) {
|
|
789
|
-
program2.command("run <type:name> [args]").description("Execute a server function locally (e.g. lumio run query:getPlayers)").action(async (typeAndName, argsJson) => {
|
|
1516
|
+
program2.command("run <type:name> [args]").description("Execute a server function locally (e.g. lumio run query:getPlayers)").option("--user <name>", "Mock user name for bot handlers", "testuser").option("--role <role>", "Mock user role for bot handlers", "everyone").option("--message <text>", "Mock chat message for bot handlers").option("--platform <platform>", "Mock platform for bot handlers", "twitch").action(async (typeAndName, argsJson, options) => {
|
|
790
1517
|
const fnRef = parseFunctionRef(typeAndName);
|
|
791
|
-
const configPath =
|
|
792
|
-
if (!
|
|
1518
|
+
const configPath = path9.resolve("lumio.config.json");
|
|
1519
|
+
if (!fs9.existsSync(configPath)) {
|
|
793
1520
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
794
1521
|
process.exit(1);
|
|
795
1522
|
}
|
|
796
|
-
const config = parseConfigFile(
|
|
1523
|
+
const config = parseConfigFile(fs9.readFileSync(configPath, "utf-8"));
|
|
797
1524
|
const store = new CredentialStore();
|
|
798
1525
|
const token = store.getToken();
|
|
799
1526
|
if (!token) {
|
|
@@ -802,10 +1529,26 @@ function registerRun(program2) {
|
|
|
802
1529
|
}
|
|
803
1530
|
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
804
1531
|
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
1532
|
+
const body = { type: fnRef.type, args };
|
|
1533
|
+
if (BOT_HANDLER_TYPES.includes(fnRef.type)) {
|
|
1534
|
+
body.user = {
|
|
1535
|
+
id: "test-user-001",
|
|
1536
|
+
name: options.user,
|
|
1537
|
+
displayName: options.user,
|
|
1538
|
+
platform: options.platform,
|
|
1539
|
+
role: options.role,
|
|
1540
|
+
isMod: options.role === "moderator",
|
|
1541
|
+
isVip: options.role === "vip",
|
|
1542
|
+
isSub: ["subscriber", "vip", "moderator", "broadcaster"].includes(options.role)
|
|
1543
|
+
};
|
|
1544
|
+
if (options.message) {
|
|
1545
|
+
body.message = { text: options.message, platformMessageId: "test-msg-001" };
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
805
1548
|
try {
|
|
806
1549
|
const result = await client.post(
|
|
807
1550
|
`/v1/extension-installs/${config.extensionId}/execute/${fnRef.name}`,
|
|
808
|
-
|
|
1551
|
+
body
|
|
809
1552
|
);
|
|
810
1553
|
console.log(JSON.stringify(result, null, 2));
|
|
811
1554
|
} catch (err) {
|
|
@@ -816,16 +1559,16 @@ function registerRun(program2) {
|
|
|
816
1559
|
}
|
|
817
1560
|
|
|
818
1561
|
// src/commands/dashboard.ts
|
|
819
|
-
import * as
|
|
820
|
-
import * as
|
|
1562
|
+
import * as fs10 from "fs";
|
|
1563
|
+
import * as path10 from "path";
|
|
821
1564
|
function registerDashboard(program2) {
|
|
822
1565
|
program2.command("dashboard").description("Open the extension dashboard in browser").action(async () => {
|
|
823
|
-
const configPath =
|
|
824
|
-
if (!
|
|
1566
|
+
const configPath = path10.resolve("lumio.config.json");
|
|
1567
|
+
if (!fs10.existsSync(configPath)) {
|
|
825
1568
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
826
1569
|
process.exit(1);
|
|
827
1570
|
}
|
|
828
|
-
const config = parseConfigFile(
|
|
1571
|
+
const config = parseConfigFile(fs10.readFileSync(configPath, "utf-8"));
|
|
829
1572
|
const baseUrl = process.env.LUMIO_WEB_URL ?? "https://lumio.vision";
|
|
830
1573
|
const url = `${baseUrl}/dashboard/extensions/${config.extensionId}`;
|
|
831
1574
|
console.log(`Opening ${url}...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaflun/lumio-cli",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Lumio Extension CLI — Build, test, and deploy Lumio extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"inquirer": "^12.6.0",
|
|
23
23
|
"open": "^10.1.2",
|
|
24
24
|
"ora": "^8.2.0",
|
|
25
|
-
"@zaflun/lumio-extension-types": "0.
|
|
25
|
+
"@zaflun/lumio-extension-types": "0.3.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/archiver": "^6.0.3",
|