@zaflun/lumio-cli 0.3.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 +613 -41
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -295,7 +295,7 @@ function registerInit(program2) {
|
|
|
295
295
|
type: "list",
|
|
296
296
|
name: "category",
|
|
297
297
|
message: "Category:",
|
|
298
|
-
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme"]
|
|
298
|
+
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme", "automation_node"]
|
|
299
299
|
}
|
|
300
300
|
]);
|
|
301
301
|
const answers = { ...baseAnswers, targets: [], server: false };
|
|
@@ -311,6 +311,39 @@ function registerInit(program2) {
|
|
|
311
311
|
answers.platforms = platforms;
|
|
312
312
|
answers.targets = [];
|
|
313
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;
|
|
314
347
|
} else {
|
|
315
348
|
const targetAndServer = await inquirer.prompt([
|
|
316
349
|
{
|
|
@@ -325,6 +358,66 @@ function registerInit(program2) {
|
|
|
325
358
|
answers.targets = targetAndServer.targets;
|
|
326
359
|
answers.server = targetAndServer.server;
|
|
327
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
|
+
}
|
|
328
421
|
const createBody = {
|
|
329
422
|
name: answers.name,
|
|
330
423
|
description: answers.description,
|
|
@@ -351,6 +444,23 @@ function registerInit(program2) {
|
|
|
351
444
|
visibility: "private",
|
|
352
445
|
pricing: { type: "free" }
|
|
353
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
|
+
}
|
|
354
464
|
if (answers.category === "bot_module") {
|
|
355
465
|
config.platforms = answers.platforms;
|
|
356
466
|
config.triggers = {
|
|
@@ -361,29 +471,88 @@ function registerInit(program2) {
|
|
|
361
471
|
timers: []
|
|
362
472
|
};
|
|
363
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
|
+
}
|
|
364
503
|
fs2.writeFileSync(
|
|
365
504
|
path2.join(projectDir, "lumio.config.json"),
|
|
366
505
|
JSON.stringify(config, null, 2)
|
|
367
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/";
|
|
368
511
|
const pkg = {
|
|
369
512
|
name: answers.name.toLowerCase().replace(/\s+/g, "-"),
|
|
370
513
|
version: "0.1.0",
|
|
371
514
|
private: true,
|
|
372
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
|
+
},
|
|
373
523
|
dependencies: {
|
|
374
524
|
"@zaflun/lumio-sdk": "latest",
|
|
375
|
-
"react": "^19.
|
|
376
|
-
"react-dom": "^19.1.0"
|
|
525
|
+
...needsReact ? { "react": "^19.2.5" } : {}
|
|
377
526
|
},
|
|
378
527
|
devDependencies: {
|
|
379
|
-
"typescript": "^
|
|
380
|
-
"@types/react": "^19.1.0"
|
|
528
|
+
"typescript": "^6.0.3",
|
|
529
|
+
...needsReact ? { "@types/react": "^19.1.0" } : {},
|
|
530
|
+
...useTailwind ? { "tailwindcss": "^4.3.0" } : {}
|
|
381
531
|
}
|
|
382
532
|
};
|
|
383
533
|
fs2.writeFileSync(
|
|
384
534
|
path2.join(projectDir, "package.json"),
|
|
385
535
|
JSON.stringify(pkg, null, 2)
|
|
386
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);
|
|
387
556
|
const changelog = `# Changelog
|
|
388
557
|
|
|
389
558
|
## [0.1.0] - ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
@@ -393,15 +562,83 @@ function registerInit(program2) {
|
|
|
393
562
|
`;
|
|
394
563
|
fs2.writeFileSync(path2.join(projectDir, "CHANGELOG.md"), changelog);
|
|
395
564
|
fs2.mkdirSync(path2.join(projectDir, "src"), { recursive: true });
|
|
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
|
+
}
|
|
396
576
|
if (answers.category === "bot_module") {
|
|
397
577
|
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
398
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';
|
|
399
579
|
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), serverFunctions);
|
|
400
580
|
const schema = 'import { defineSchema } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({});\n';
|
|
401
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);
|
|
402
623
|
} else {
|
|
403
624
|
for (const target of answers.targets) {
|
|
404
|
-
|
|
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";
|
|
405
642
|
|
|
406
643
|
function App() {
|
|
407
644
|
return (
|
|
@@ -443,7 +680,7 @@ var ConfigError = class extends Error {
|
|
|
443
680
|
function parseConfigFile(jsonString) {
|
|
444
681
|
const raw = JSON.parse(jsonString);
|
|
445
682
|
return {
|
|
446
|
-
extensionId: raw.extensionId ?? "",
|
|
683
|
+
extensionId: raw.extensionId ?? raw.extension_id ?? "",
|
|
447
684
|
name: raw.name ?? "",
|
|
448
685
|
description: raw.description,
|
|
449
686
|
category: raw.category ?? "widget",
|
|
@@ -458,8 +695,18 @@ function parseConfigFile(jsonString) {
|
|
|
458
695
|
platforms: raw.platforms,
|
|
459
696
|
triggers: raw.triggers,
|
|
460
697
|
fonts: raw.fonts,
|
|
698
|
+
sounds: raw.sounds,
|
|
461
699
|
visibility: raw.visibility ?? "private",
|
|
462
|
-
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
|
|
463
710
|
};
|
|
464
711
|
}
|
|
465
712
|
function validateConfig(config) {
|
|
@@ -473,6 +720,27 @@ function validateConfig(config) {
|
|
|
473
720
|
if (!config.platforms || config.platforms.length === 0) {
|
|
474
721
|
throw new ConfigError("At least one platform is required for bot_module (twitch, youtube, kick, trovo, discord)");
|
|
475
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
|
+
}
|
|
476
744
|
} else if (!config.targets || config.targets.length === 0) {
|
|
477
745
|
throw new ConfigError("At least one target is required (editor, layer, or interactive)");
|
|
478
746
|
}
|
|
@@ -553,11 +821,120 @@ async function runBotModuleDevServer(config) {
|
|
|
553
821
|
});
|
|
554
822
|
}
|
|
555
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
|
+
|
|
556
933
|
// src/commands/dev.ts
|
|
557
934
|
function registerDev(program2) {
|
|
558
|
-
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").action(async (options) => {
|
|
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) => {
|
|
559
936
|
const configPath = path3.resolve("lumio.config.json");
|
|
560
|
-
if (options.botModule || fs3.existsSync(configPath)) {
|
|
937
|
+
if (options.botModule || options.automationNode || fs3.existsSync(configPath)) {
|
|
561
938
|
const configExists = fs3.existsSync(configPath);
|
|
562
939
|
if (configExists) {
|
|
563
940
|
const config = parseConfigFile(fs3.readFileSync(configPath, "utf-8"));
|
|
@@ -565,7 +942,11 @@ function registerDev(program2) {
|
|
|
565
942
|
await runBotModuleDevServer(config);
|
|
566
943
|
return;
|
|
567
944
|
}
|
|
568
|
-
|
|
945
|
+
if (options.automationNode || config.category === "automation_node") {
|
|
946
|
+
await runAutomationNodeDevServer(config);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
} else if (options.botModule || options.automationNode) {
|
|
569
950
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
570
951
|
process.exit(1);
|
|
571
952
|
}
|
|
@@ -581,6 +962,9 @@ function registerDev(program2) {
|
|
|
581
962
|
// src/commands/build.ts
|
|
582
963
|
import * as fs4 from "fs";
|
|
583
964
|
import * as path4 from "path";
|
|
965
|
+
import { execFile } from "child_process";
|
|
966
|
+
import { promisify } from "util";
|
|
967
|
+
var execFileAsync = promisify(execFile);
|
|
584
968
|
function registerBuild(program2) {
|
|
585
969
|
program2.command("build").description("Bundle extension as standalone HTML + source archive").action(async () => {
|
|
586
970
|
const configPath = path4.resolve("lumio.config.json");
|
|
@@ -602,6 +986,34 @@ function registerBuild(program2) {
|
|
|
602
986
|
if (config.category === "bot_module" && config.targets.length === 0) {
|
|
603
987
|
console.log(" Bot module \u2014 no UI targets to bundle.");
|
|
604
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
|
+
}
|
|
605
1017
|
for (const target of config.targets) {
|
|
606
1018
|
const entryPoint = path4.resolve("src", `${target}.tsx`);
|
|
607
1019
|
if (!fs4.existsSync(entryPoint)) {
|
|
@@ -612,11 +1024,39 @@ function registerBuild(program2) {
|
|
|
612
1024
|
entryPoints: [entryPoint],
|
|
613
1025
|
bundle: true,
|
|
614
1026
|
minify: true,
|
|
615
|
-
format: "
|
|
616
|
-
|
|
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}`,
|
|
617
1034
|
jsx: "automatic",
|
|
618
1035
|
target: "es2022",
|
|
619
|
-
|
|
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"],
|
|
620
1060
|
define: {
|
|
621
1061
|
"process.env.NODE_ENV": '"production"'
|
|
622
1062
|
}
|
|
@@ -628,15 +1068,19 @@ function registerBuild(program2) {
|
|
|
628
1068
|
const output = fs4.createWriteStream(sourceArchivePath);
|
|
629
1069
|
const archive = archiver("tar", { gzip: true });
|
|
630
1070
|
archive.pipe(output);
|
|
631
|
-
archive.
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
});
|
|
640
1084
|
await archive.finalize();
|
|
641
1085
|
console.log(" Archive: dist/source.tar.gz");
|
|
642
1086
|
console.log("Build complete.");
|
|
@@ -683,12 +1127,59 @@ function getVersionEntry(changelog, version) {
|
|
|
683
1127
|
}
|
|
684
1128
|
|
|
685
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;
|
|
686
1177
|
function registerDeploy(program2) {
|
|
687
|
-
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) => {
|
|
688
1179
|
const store = new CredentialStore();
|
|
689
|
-
const token = store.getToken();
|
|
1180
|
+
const token = options.token ?? store.getToken();
|
|
690
1181
|
if (!token) {
|
|
691
|
-
console.error("Not logged in. Run `lumio login`
|
|
1182
|
+
console.error("Not logged in. Run `lumio login` or provide --token.");
|
|
692
1183
|
process.exit(1);
|
|
693
1184
|
}
|
|
694
1185
|
const configPath = path5.resolve("lumio.config.json");
|
|
@@ -699,6 +1190,34 @@ function registerDeploy(program2) {
|
|
|
699
1190
|
const configJson = fs5.readFileSync(configPath, "utf-8");
|
|
700
1191
|
const config = parseConfigFile(configJson);
|
|
701
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
|
+
}
|
|
702
1221
|
const version = options.version ?? config.version;
|
|
703
1222
|
let releaseNotes = options.description ?? "";
|
|
704
1223
|
if (!releaseNotes) {
|
|
@@ -708,44 +1227,97 @@ function registerDeploy(program2) {
|
|
|
708
1227
|
const entry = getVersionEntry(changelog, version);
|
|
709
1228
|
if (entry) {
|
|
710
1229
|
releaseNotes = entry.content;
|
|
711
|
-
} else if (!LumioApiClient.isCI()) {
|
|
1230
|
+
} else if (!LumioApiClient.isCI() && !options.system) {
|
|
712
1231
|
console.error(`No changelog entry found for version ${version}.`);
|
|
713
1232
|
console.error("Add an entry to CHANGELOG.md or use -d to provide release notes.");
|
|
714
1233
|
process.exit(1);
|
|
715
1234
|
}
|
|
716
1235
|
}
|
|
717
1236
|
}
|
|
718
|
-
const distDir = path5.resolve("dist");
|
|
719
1237
|
if (!fs5.existsSync(distDir)) {
|
|
720
1238
|
console.error("No dist/ directory found. Run `lumio build` first.");
|
|
721
1239
|
process.exit(1);
|
|
722
1240
|
}
|
|
723
1241
|
console.log(`Deploying ${config.name} v${version}...`);
|
|
724
|
-
const
|
|
725
|
-
const
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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);
|
|
730
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);
|
|
1264
|
+
}
|
|
1265
|
+
const formData = new FormData();
|
|
731
1266
|
const manifest = JSON.parse(configJson);
|
|
732
1267
|
manifest.version = version;
|
|
733
1268
|
formData.append("version", version);
|
|
734
1269
|
formData.append("manifest", JSON.stringify(manifest));
|
|
735
1270
|
formData.append("release_notes", releaseNotes);
|
|
736
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...");
|
|
737
1301
|
try {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
);
|
|
742
|
-
console.log("Upload successful. Status: draft");
|
|
743
|
-
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`.");
|
|
744
1306
|
if (LumioApiClient.isCI()) {
|
|
745
1307
|
process.exit(0);
|
|
746
1308
|
}
|
|
747
1309
|
} catch (err) {
|
|
748
|
-
|
|
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
|
+
}
|
|
749
1321
|
process.exit(1);
|
|
750
1322
|
}
|
|
751
1323
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaflun/lumio-cli",
|
|
3
|
-
"version": "0.
|
|
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.3.
|
|
25
|
+
"@zaflun/lumio-extension-types": "0.3.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/archiver": "^6.0.3",
|