lumia-plugin 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/examples/base-plugin/README.md +1 -1
- package/examples/base-plugin/main.js +139 -132
- package/examples/base-plugin/package.json +8 -8
- package/package.json +1 -1
- package/scripts/build-plugin.js +1 -1
- package/scripts/create-plugin.js +1 -1
- package/scripts/utils.js +100 -80
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Lumia Stream Plugin CLI
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The `lumia-plugin` package bundles the command-line tools for creating, building, and validating Lumia Stream plugins without pulling the full SDK into your runtime dependencies.
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
npx
|
|
9
|
-
npx
|
|
10
|
-
npx
|
|
8
|
+
npx lumia-plugin create <directory>
|
|
9
|
+
npx lumia-plugin build [--dir <path>] [--out <file>]
|
|
10
|
+
npx lumia-plugin validate <plugin-directory>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Run any command with `--help` to see detailed options.
|
|
@@ -11,7 +11,7 @@ This template demonstrates a handful of common Lumia Stream plugin capabilities:
|
|
|
11
11
|
Use the CLI to copy and customise the template:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
npx
|
|
14
|
+
npx lumia-plugin create my-plugin
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
After scaffolding you can tailor the manifest, code, and README to match your idea.
|
|
@@ -1,143 +1,150 @@
|
|
|
1
|
-
const { Plugin } = require(
|
|
1
|
+
const { Plugin } = require("@lumiastream/plugin");
|
|
2
2
|
|
|
3
3
|
const VARIABLE_NAMES = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
lastMessage: "last_message",
|
|
5
|
+
lastAlertColor: "last_alert_color",
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
const DEFAULTS = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
welcomeMessage: "Hello from Showcase Plugin!",
|
|
10
|
+
color: "#00c2ff",
|
|
11
|
+
alertDuration: 5,
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
class ShowcasePluginTemplate extends Plugin {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
15
|
+
async onload() {
|
|
16
|
+
const message = this._currentMessage();
|
|
17
|
+
await this._log("Plugin loaded");
|
|
18
|
+
await this._rememberMessage(message);
|
|
19
|
+
|
|
20
|
+
if (this.settings.autoAlert === "load") {
|
|
21
|
+
await this._triggerSampleAlert({
|
|
22
|
+
color: this.settings.favoriteColor,
|
|
23
|
+
duration: DEFAULTS.alertDuration,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async onunload() {
|
|
29
|
+
await this._log("Plugin unloaded");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async onsettingsupdate(settings, previous = {}) {
|
|
33
|
+
await this._log("Settings updated");
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
settings?.welcomeMessage &&
|
|
37
|
+
settings.welcomeMessage !== previous?.welcomeMessage
|
|
38
|
+
) {
|
|
39
|
+
await this._rememberMessage(settings.welcomeMessage);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (settings?.autoAlert === "load" && previous?.autoAlert !== "load") {
|
|
43
|
+
await this._log("Auto alert configured to fire on load");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async actions(config = {}) {
|
|
48
|
+
const actions = Array.isArray(config.actions) ? config.actions : [];
|
|
49
|
+
for (const action of actions) {
|
|
50
|
+
switch (action?.type) {
|
|
51
|
+
case "log_message":
|
|
52
|
+
await this._handleLogMessage(action.data);
|
|
53
|
+
break;
|
|
54
|
+
case "update_variable":
|
|
55
|
+
await this._handleUpdateVariable(action.data);
|
|
56
|
+
break;
|
|
57
|
+
case "trigger_alert":
|
|
58
|
+
await this._triggerSampleAlert(action.data);
|
|
59
|
+
break;
|
|
60
|
+
default:
|
|
61
|
+
await this._log(
|
|
62
|
+
`Unknown action type: ${action?.type ?? "undefined"}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_tag() {
|
|
69
|
+
return `[${this.manifest?.id ?? "showcase-plugin"}]`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_currentMessage() {
|
|
73
|
+
return (
|
|
74
|
+
this.settings?.welcomeMessage ||
|
|
75
|
+
`Hello from ${this.manifest?.name ?? "Showcase Plugin"}!`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async _log(message, severity = "info") {
|
|
80
|
+
const prefix = this._tag();
|
|
81
|
+
const decorated =
|
|
82
|
+
severity === "warn"
|
|
83
|
+
? `${prefix} ⚠️ ${message}`
|
|
84
|
+
: severity === "error"
|
|
85
|
+
? `${prefix} ❌ ${message}`
|
|
86
|
+
: `${prefix} ${message}`;
|
|
87
|
+
|
|
88
|
+
await this.lumia.addLog(decorated);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async _rememberMessage(value) {
|
|
92
|
+
await this.lumia.setVariable(VARIABLE_NAMES.lastMessage, value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async _handleLogMessage(data = {}) {
|
|
96
|
+
const message = data?.message || this._currentMessage();
|
|
97
|
+
const severity = data?.severity || "info";
|
|
98
|
+
|
|
99
|
+
await this._log(message, severity);
|
|
100
|
+
|
|
101
|
+
if (typeof this.lumia.showToast === "function") {
|
|
102
|
+
await this.lumia.showToast({
|
|
103
|
+
message: `${this.manifest?.name ?? "Plugin"}: ${message}`,
|
|
104
|
+
time: 4,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.settings.autoAlert === "after-log") {
|
|
109
|
+
await this._triggerSampleAlert({
|
|
110
|
+
color: this.settings.favoriteColor,
|
|
111
|
+
duration: DEFAULTS.alertDuration,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async _handleUpdateVariable(data = {}) {
|
|
117
|
+
const value = data?.value ?? new Date().toISOString();
|
|
118
|
+
await this._rememberMessage(value);
|
|
119
|
+
await this._log(`Stored variable value: ${value}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async _triggerSampleAlert(data = {}) {
|
|
123
|
+
const color = data?.color || this.settings?.favoriteColor || DEFAULTS.color;
|
|
124
|
+
const duration = Number(data?.duration) || DEFAULTS.alertDuration;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const success = await this.lumia.triggerAlert({
|
|
128
|
+
alert: "sample_light",
|
|
129
|
+
extraSettings: { color, duration },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!success) {
|
|
133
|
+
await this._log("Sample alert reported failure", "warn");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await this.lumia.setVariable(VARIABLE_NAMES.lastAlertColor, color);
|
|
138
|
+
await this._log(
|
|
139
|
+
`Triggered sample alert with color ${color} for ${duration}s`
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
await this._log(
|
|
143
|
+
`Failed to trigger sample alert: ${error.message ?? error}`,
|
|
144
|
+
"error"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
module.exports = ShowcasePluginTemplate;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"name": "lumia-showcase-plugin-template",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Internal template illustrating logging, variables, actions, and alerts for Lumia Stream plugins.",
|
|
6
|
+
"main": "main.js",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@lumiastream/plugin": "^0.1.0"
|
|
9
|
+
}
|
|
10
10
|
}
|
package/package.json
CHANGED
package/scripts/build-plugin.js
CHANGED
|
@@ -43,7 +43,7 @@ function parseArgs() {
|
|
|
43
43
|
function printHelp() {
|
|
44
44
|
console.log(`Build a .lumiaplugin archive for distribution.
|
|
45
45
|
|
|
46
|
-
Usage: npx
|
|
46
|
+
Usage: npx lumia-plugin build [options]
|
|
47
47
|
|
|
48
48
|
Options:
|
|
49
49
|
--dir, -d Plugin directory (defaults to cwd)
|
package/scripts/create-plugin.js
CHANGED
|
@@ -23,7 +23,7 @@ function toDisplayName(id) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function printHelp() {
|
|
26
|
-
console.log(`Scaffold a new Lumia Stream plugin directory using the showcase template.\n\nUsage: npx
|
|
26
|
+
console.log(`Scaffold a new Lumia Stream plugin directory using the showcase template.\n\nUsage: npx lumia-plugin create [target-directory]\n\nExamples:\n npx lumia-plugin create ./plugins/my-plugin\n npx lumia-plugin create my-awesome-plugin\n`);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function ensureEmptyDir(targetDir) {
|
package/scripts/utils.js
CHANGED
|
@@ -1,109 +1,129 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const JSZip = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const JSZip = require("jszip");
|
|
4
4
|
|
|
5
5
|
const DEFAULT_IGNORE = new Set([
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
".git",
|
|
7
|
+
".DS_Store",
|
|
8
|
+
"Thumbs.db",
|
|
9
|
+
"package-lock.json",
|
|
10
|
+
"yarn.lock",
|
|
11
|
+
".npmrc",
|
|
12
|
+
".gitignore",
|
|
13
13
|
]);
|
|
14
14
|
|
|
15
15
|
function toPosix(p) {
|
|
16
|
-
|
|
16
|
+
return p.split(path.sep).join("/");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async function readManifest(pluginDir) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const manifestPath = path.join(pluginDir, "manifest.json");
|
|
21
|
+
const raw = await fs.promises.readFile(manifestPath, "utf8");
|
|
22
|
+
return { manifest: JSON.parse(raw), manifestPath };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function validateManifest(manifest) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
const errors = [];
|
|
27
|
+
const requiredStringFields = [
|
|
28
|
+
"id",
|
|
29
|
+
"name",
|
|
30
|
+
"version",
|
|
31
|
+
"author",
|
|
32
|
+
"description",
|
|
33
|
+
"license",
|
|
34
|
+
"lumiaVersion",
|
|
35
|
+
];
|
|
36
|
+
for (const field of requiredStringFields) {
|
|
37
|
+
const value = manifest[field];
|
|
38
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
39
|
+
errors.push(`Missing or invalid manifest field: ${field}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
if (
|
|
44
|
+
!manifest.category ||
|
|
45
|
+
(typeof manifest.category !== "string" && !Array.isArray(manifest.category))
|
|
46
|
+
) {
|
|
47
|
+
errors.push("Manifest must declare a category string");
|
|
48
|
+
}
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
if (!manifest.config || typeof manifest.config !== "object") {
|
|
51
|
+
errors.push("Manifest config must be an object");
|
|
52
|
+
}
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (manifest.config) {
|
|
55
|
+
if (manifest.config.settings && !Array.isArray(manifest.config.settings)) {
|
|
56
|
+
errors.push("config.settings must be an array when provided");
|
|
57
|
+
}
|
|
58
|
+
if (manifest.config.actions && !Array.isArray(manifest.config.actions)) {
|
|
59
|
+
errors.push("config.actions must be an array when provided");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
manifest.config.variables &&
|
|
63
|
+
!Array.isArray(manifest.config.variables)
|
|
64
|
+
) {
|
|
65
|
+
errors.push("config.variables must be an array when provided");
|
|
66
|
+
}
|
|
67
|
+
if (manifest.config.alerts && !Array.isArray(manifest.config.alerts)) {
|
|
68
|
+
errors.push("config.alerts must be an array when provided");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
return errors;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
async function collectFiles(pluginDir, ignore = DEFAULT_IGNORE) {
|
|
62
|
-
|
|
76
|
+
const entries = [];
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
async function walk(currentDir) {
|
|
79
|
+
const list = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
80
|
+
for (const entry of list) {
|
|
81
|
+
if (ignore.has(entry.name)) continue;
|
|
82
|
+
const absolute = path.join(currentDir, entry.name);
|
|
83
|
+
const relative = path.relative(pluginDir, absolute);
|
|
84
|
+
if (!relative || relative.startsWith("..")) continue;
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
// Skip bundled Lumia packages in node_modules
|
|
87
|
+
if (
|
|
88
|
+
relative === "node_modules/@lumiastream" ||
|
|
89
|
+
relative.startsWith("node_modules/@lumiastream/plugin") ||
|
|
90
|
+
relative.startsWith("node_modules/lumia-plugin")
|
|
91
|
+
) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
await walk(absolute);
|
|
97
|
+
} else if (entry.isFile()) {
|
|
98
|
+
entries.push({ absolute, relative: toPosix(relative) });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
await walk(pluginDir);
|
|
104
|
+
return entries;
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
async function createPluginArchive(pluginDir, outputFile, files) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
const zip = new JSZip();
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
const data = await fs.promises.readFile(file.absolute);
|
|
111
|
+
zip.file(file.relative, data);
|
|
112
|
+
}
|
|
113
|
+
const content = await zip.generateAsync({
|
|
114
|
+
type: "nodebuffer",
|
|
115
|
+
compression: "DEFLATE",
|
|
116
|
+
compressionOptions: { level: 9 },
|
|
117
|
+
});
|
|
118
|
+
await fs.promises.mkdir(path.dirname(outputFile), { recursive: true });
|
|
119
|
+
await fs.promises.writeFile(outputFile, content);
|
|
120
|
+
return outputFile;
|
|
101
121
|
}
|
|
102
122
|
|
|
103
123
|
module.exports = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
readManifest,
|
|
125
|
+
validateManifest,
|
|
126
|
+
collectFiles,
|
|
127
|
+
createPluginArchive,
|
|
128
|
+
DEFAULT_IGNORE,
|
|
109
129
|
};
|