lumia-plugin 0.1.4 → 0.1.6
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/GETTING_STARTED.md +243 -0
- 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 +228 -171
- 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.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Getting Started with Showcase Plugin Template
|
|
2
|
+
|
|
3
|
+
Welcome to your new Lumia Stream plugin! This guide will help you understand the plugin structure and start building your custom functionality.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
your-plugin/
|
|
9
|
+
├── manifest.json # Plugin metadata and configuration
|
|
10
|
+
├── main.js # Your plugin's main code
|
|
11
|
+
├── package.json # npm dependencies (optional)
|
|
12
|
+
└── README.md # Plugin documentation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Understanding the Files
|
|
16
|
+
|
|
17
|
+
### 📋 manifest.json
|
|
18
|
+
|
|
19
|
+
This file defines your plugin's metadata and capabilities:
|
|
20
|
+
|
|
21
|
+
- **id**: Unique identifier (kebab-case, e.g., "my-awesome-plugin")
|
|
22
|
+
- **name**: Display name shown to users
|
|
23
|
+
- **description**: Short description for the plugin marketplace
|
|
24
|
+
- **main**: Entry point file (usually "main.js")
|
|
25
|
+
- **config**: Defines settings, actions, and variables
|
|
26
|
+
|
|
27
|
+
**Key sections:**
|
|
28
|
+
- `settings`: User-configurable options for your plugin
|
|
29
|
+
- `actions`: Functions users can trigger (e.g., send a message, change color)
|
|
30
|
+
- `variables`: Data your plugin exposes to Lumia Stream
|
|
31
|
+
|
|
32
|
+
### 🔧 main.js
|
|
33
|
+
|
|
34
|
+
Your plugin's logic lives here. The main class extends the `Plugin` base class:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
class YourPlugin extends Plugin {
|
|
38
|
+
constructor(props) {
|
|
39
|
+
super(props);
|
|
40
|
+
// Initialize your plugin
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async onload() {
|
|
44
|
+
// Called when plugin is loaded
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async onsettingsupdate() {
|
|
48
|
+
// Called when settings change
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async onunload() {
|
|
52
|
+
// Called when plugin is unloaded
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Common Tasks
|
|
58
|
+
|
|
59
|
+
### Adding a New Setting
|
|
60
|
+
|
|
61
|
+
1. Edit `manifest.json` in the `config.settings` array:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"key": "myNewSetting",
|
|
66
|
+
"label": "My New Setting",
|
|
67
|
+
"type": "text",
|
|
68
|
+
"defaultValue": "default value",
|
|
69
|
+
"required": false
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
2. Access in `main.js`:
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const value = this.getSetting('myNewSetting');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Adding a New Action
|
|
80
|
+
|
|
81
|
+
1. Define in `manifest.json` under `config.actions`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"type": "my_custom_action",
|
|
86
|
+
"label": "Do Something Cool",
|
|
87
|
+
"fields": [
|
|
88
|
+
{
|
|
89
|
+
"key": "message",
|
|
90
|
+
"label": "Message",
|
|
91
|
+
"type": "text",
|
|
92
|
+
"required": true
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
2. Handle in `main.js`:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
async onAction(action) {
|
|
102
|
+
if (action.type === 'my_custom_action') {
|
|
103
|
+
const message = action.fields.message;
|
|
104
|
+
// Your logic here
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Creating Variables
|
|
110
|
+
|
|
111
|
+
Variables let other Lumia features access your plugin's data:
|
|
112
|
+
|
|
113
|
+
1. Define in `manifest.json` under `config.variables`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"name": "my_variable",
|
|
118
|
+
"key": "myVariable",
|
|
119
|
+
"origin": "your-plugin-id",
|
|
120
|
+
"type": "string",
|
|
121
|
+
"example": "Sample value"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
2. Update in `main.js`:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
this.setVariable('myVariable', 'new value');
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Testing Your Plugin
|
|
132
|
+
|
|
133
|
+
1. **Install dependencies** (if using package.json):
|
|
134
|
+
```bash
|
|
135
|
+
npm install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
2. **Load in Lumia Stream**:
|
|
139
|
+
- Open Lumia Stream
|
|
140
|
+
- Go to Plugins section
|
|
141
|
+
- Load your plugin directory
|
|
142
|
+
- Enable your plugin
|
|
143
|
+
|
|
144
|
+
3. **Check logs**:
|
|
145
|
+
```javascript
|
|
146
|
+
this.log('Debug message');
|
|
147
|
+
this.error('Error message');
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Next Steps
|
|
151
|
+
|
|
152
|
+
- [ ] Customize `manifest.json` with your plugin details
|
|
153
|
+
- [ ] Update the plugin name and description
|
|
154
|
+
- [ ] Add your custom settings
|
|
155
|
+
- [ ] Implement your actions
|
|
156
|
+
- [ ] Test your plugin in Lumia Stream
|
|
157
|
+
- [ ] Write tests (optional)
|
|
158
|
+
- [ ] Update README.md with usage instructions
|
|
159
|
+
|
|
160
|
+
## API Reference
|
|
161
|
+
|
|
162
|
+
### Plugin Methods
|
|
163
|
+
|
|
164
|
+
- `this.getSetting(key)` - Get a setting value
|
|
165
|
+
- `this.setVariable(key, value)` - Update a variable
|
|
166
|
+
- `this.log(message)` - Log info message
|
|
167
|
+
- `this.error(message)` - Log error message
|
|
168
|
+
- `this.sendAlert(type, data)` - Trigger an alert
|
|
169
|
+
|
|
170
|
+
### Lifecycle Hooks
|
|
171
|
+
|
|
172
|
+
- `onload()` - Plugin initialization
|
|
173
|
+
- `onunload()` - Cleanup when plugin is disabled
|
|
174
|
+
- `onsettingsupdate()` - Respond to setting changes
|
|
175
|
+
- `onAction(action)` - Handle triggered actions
|
|
176
|
+
|
|
177
|
+
## Common Patterns
|
|
178
|
+
|
|
179
|
+
### Making HTTP Requests
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const response = await fetch('https://api.example.com/data');
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Using Timers
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
async onload() {
|
|
190
|
+
this.interval = setInterval(() => {
|
|
191
|
+
// Do something periodically
|
|
192
|
+
}, 5000);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async onunload() {
|
|
196
|
+
if (this.interval) {
|
|
197
|
+
clearInterval(this.interval);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Error Handling
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
try {
|
|
206
|
+
// Your code
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.error(`Something went wrong: ${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Troubleshooting
|
|
213
|
+
|
|
214
|
+
**Plugin not loading?**
|
|
215
|
+
- Check manifest.json syntax (use a JSON validator)
|
|
216
|
+
- Ensure the `main` field points to the correct file
|
|
217
|
+
- Check Lumia Stream logs for errors
|
|
218
|
+
|
|
219
|
+
**Settings not showing?**
|
|
220
|
+
- Verify the `settings` array in manifest.json
|
|
221
|
+
- Ensure required fields are filled
|
|
222
|
+
|
|
223
|
+
**Actions not working?**
|
|
224
|
+
- Check the action type matches your handler
|
|
225
|
+
- Verify field keys match what you're accessing
|
|
226
|
+
|
|
227
|
+
## Resources
|
|
228
|
+
|
|
229
|
+
- [Lumia Stream Documentation](https://docs.lumiastream.com)
|
|
230
|
+
- [Plugin API Reference](https://docs.lumiastream.com/plugins/api)
|
|
231
|
+
- [Example Plugins](https://github.com/lumiastream/plugins)
|
|
232
|
+
- [Community Discord](https://discord.gg/lumiastream)
|
|
233
|
+
|
|
234
|
+
## Need Help?
|
|
235
|
+
|
|
236
|
+
- Join the Lumia Stream Discord community
|
|
237
|
+
- Check the documentation
|
|
238
|
+
- Review example plugins
|
|
239
|
+
- Ask questions in the developer channel
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
Happy coding! 🚀
|
|
@@ -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
|
@@ -5,207 +5,264 @@ const fs = require("fs");
|
|
|
5
5
|
const TEMPLATE_DIR = path.resolve(__dirname, "..", "examples", "base-plugin");
|
|
6
6
|
|
|
7
7
|
function toKebabCase(value) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
return (
|
|
9
|
+
value
|
|
10
|
+
.trim()
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
13
|
+
.replace(/^-+|-+$/g, "") || "my-plugin"
|
|
14
|
+
);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function toDisplayName(id) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
return id
|
|
19
|
+
.split("-")
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
22
|
+
.join(" ");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function printHelp() {
|
|
26
|
-
|
|
26
|
+
console.log(
|
|
27
|
+
`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`
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
async function ensureEmptyDir(targetDir) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
32
|
+
try {
|
|
33
|
+
const stats = await fs.promises.stat(targetDir);
|
|
34
|
+
if (!stats.isDirectory()) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Path '${path.basename(
|
|
37
|
+
targetDir
|
|
38
|
+
)}' exists and is not a directory.\n Tip: Choose a different name or remove the existing file.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const contents = await fs.promises.readdir(targetDir);
|
|
42
|
+
if (contents.length > 0) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Directory '${path.basename(
|
|
45
|
+
targetDir
|
|
46
|
+
)}' already exists and is not empty.\n Tip: Use a different name or remove existing files first.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.code === "ENOENT") {
|
|
51
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
async function copyTemplate(src, dest) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const stats = await fs.promises.stat(src);
|
|
60
|
+
if (stats.isDirectory()) {
|
|
61
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
62
|
+
const entries = await fs.promises.readdir(src);
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
await copyTemplate(path.join(src, entry), path.join(dest, entry));
|
|
65
|
+
}
|
|
66
|
+
} else if (stats.isFile()) {
|
|
67
|
+
await fs.promises.copyFile(src, dest);
|
|
68
|
+
}
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
function ensureArray(value) {
|
|
62
|
-
|
|
72
|
+
return Array.isArray(value) ? value : [];
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
async function updateManifest(manifestPath, pluginId, displayName) {
|
|
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
|
-
|
|
76
|
+
const raw = await fs.promises.readFile(manifestPath, "utf8");
|
|
77
|
+
const manifest = JSON.parse(raw);
|
|
78
|
+
|
|
79
|
+
manifest.id = pluginId;
|
|
80
|
+
manifest.name = displayName;
|
|
81
|
+
manifest.description = manifest.description?.trim()
|
|
82
|
+
? manifest.description
|
|
83
|
+
: `Describe what ${displayName} does.`;
|
|
84
|
+
manifest.longDescription = manifest.longDescription || "";
|
|
85
|
+
manifest.repository = manifest.repository || "";
|
|
86
|
+
manifest.website = manifest.website || "";
|
|
87
|
+
manifest.email = manifest.email || "";
|
|
88
|
+
manifest.icon = manifest.icon || "";
|
|
89
|
+
manifest.screenshots = ensureArray(manifest.screenshots);
|
|
90
|
+
manifest.changelog = manifest.changelog || "";
|
|
91
|
+
manifest.keywords = ensureArray(manifest.keywords);
|
|
92
|
+
|
|
93
|
+
if (!manifest.keywords.includes(pluginId)) {
|
|
94
|
+
manifest.keywords.push(pluginId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!manifest.main) {
|
|
98
|
+
manifest.main = "main.js";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (manifest.config && typeof manifest.config === "object") {
|
|
102
|
+
const { settings, actions, variables } = manifest.config;
|
|
103
|
+
|
|
104
|
+
const welcomeSetting = ensureArray(settings).find(
|
|
105
|
+
(setting) => setting?.key === "welcomeMessage"
|
|
106
|
+
);
|
|
107
|
+
if (welcomeSetting) {
|
|
108
|
+
welcomeSetting.defaultValue = `Hello from ${displayName}!`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const logAction = ensureArray(actions).find(
|
|
112
|
+
(action) => action?.type === "log_message"
|
|
113
|
+
);
|
|
114
|
+
if (logAction && Array.isArray(logAction.fields)) {
|
|
115
|
+
const messageField = logAction.fields.find(
|
|
116
|
+
(field) => field?.key === "message"
|
|
117
|
+
);
|
|
118
|
+
if (messageField) {
|
|
119
|
+
messageField.defaultValue = `Hello from ${displayName}!`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const variable of ensureArray(variables)) {
|
|
124
|
+
if (!variable || typeof variable !== "object") continue;
|
|
125
|
+
if (variable.origin) {
|
|
126
|
+
variable.origin = pluginId;
|
|
127
|
+
}
|
|
128
|
+
if (variable.name === "last_message" && "example" in variable) {
|
|
129
|
+
variable.example = `Hello from ${displayName}!`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
async function updateMain(mainPath, pluginId, className, displayName) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
let source = await fs.promises.readFile(mainPath, "utf8");
|
|
139
|
+
source = source.replace(
|
|
140
|
+
/class\s+ShowcasePluginTemplate\s+extends\s+Plugin/,
|
|
141
|
+
`class ${className} extends Plugin`
|
|
142
|
+
);
|
|
143
|
+
source = source.replace(
|
|
144
|
+
/module\.exports\s*=\s*ShowcasePluginTemplate;/,
|
|
145
|
+
`module.exports = ${className};`
|
|
146
|
+
);
|
|
147
|
+
source = source.replace(
|
|
148
|
+
/Hello from Showcase Plugin!/g,
|
|
149
|
+
`Hello from ${displayName}!`
|
|
150
|
+
);
|
|
151
|
+
source = source.replace(/Showcase Plugin/g, displayName);
|
|
152
|
+
source = source.replace(/showcase-plugin/g, pluginId);
|
|
153
|
+
await fs.promises.writeFile(mainPath, source);
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
async function updatePackageJson(packagePath, pluginId, displayName) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
if (!fs.existsSync(packagePath)) return;
|
|
158
|
+
const pkg = JSON.parse(await fs.promises.readFile(packagePath, "utf8"));
|
|
159
|
+
pkg.name = `lumia-plugin-${pluginId}`;
|
|
160
|
+
pkg.description = pkg.description || `${displayName} for Lumia Stream`;
|
|
161
|
+
await fs.promises.writeFile(packagePath, JSON.stringify(pkg, null, 2));
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
async function updateReadme(readmePath, displayName) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
if (!fs.existsSync(readmePath)) return;
|
|
166
|
+
let content = await fs.promises.readFile(readmePath, "utf8");
|
|
167
|
+
content = content.replace(/^# .*$/m, `# ${displayName}`);
|
|
168
|
+
await fs.promises.writeFile(readmePath, content);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function updateGettingStarted(gettingStartedPath, displayName) {
|
|
172
|
+
if (!fs.existsSync(gettingStartedPath)) return;
|
|
173
|
+
let content = await fs.promises.readFile(gettingStartedPath, "utf8");
|
|
174
|
+
content = content.replace(
|
|
175
|
+
/^# Getting Started with .*$/m,
|
|
176
|
+
`# Getting Started with ${displayName}`
|
|
177
|
+
);
|
|
178
|
+
content = content.replace(/Showcase Plugin Template/g, displayName);
|
|
179
|
+
await fs.promises.writeFile(gettingStartedPath, content);
|
|
154
180
|
}
|
|
155
181
|
|
|
156
182
|
async function main() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
183
|
+
const args = process.argv.slice(2);
|
|
184
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
185
|
+
printHelp();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(TEMPLATE_DIR)) {
|
|
190
|
+
console.error("✖ Template directory not found:", TEMPLATE_DIR);
|
|
191
|
+
console.error(" Tip: Make sure lumia-plugin is properly installed.");
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const targetDir = path.resolve(args[0] || "my-plugin");
|
|
196
|
+
const pluginId = toKebabCase(path.basename(targetDir));
|
|
197
|
+
const displayName = toDisplayName(pluginId) || "My Plugin";
|
|
198
|
+
const className = displayName.replace(/[^a-zA-Z0-9]/g, "") || "MyPlugin";
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
console.log("⏳ Creating plugin scaffold...\n");
|
|
202
|
+
|
|
203
|
+
console.log(" 📁 Creating directory...");
|
|
204
|
+
await ensureEmptyDir(targetDir);
|
|
205
|
+
|
|
206
|
+
console.log(" 📋 Copying template files...");
|
|
207
|
+
await copyTemplate(TEMPLATE_DIR, targetDir);
|
|
208
|
+
|
|
209
|
+
console.log(" ✏️ Updating manifest.json...");
|
|
210
|
+
await updateManifest(
|
|
211
|
+
path.join(targetDir, "manifest.json"),
|
|
212
|
+
pluginId,
|
|
213
|
+
displayName
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
console.log(" ✏️ Updating main.js...");
|
|
217
|
+
await updateMain(
|
|
218
|
+
path.join(targetDir, "main.js"),
|
|
219
|
+
pluginId,
|
|
220
|
+
className,
|
|
221
|
+
displayName
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
console.log(" ✏️ Updating package.json...");
|
|
225
|
+
await updatePackageJson(
|
|
226
|
+
path.join(targetDir, "package.json"),
|
|
227
|
+
pluginId,
|
|
228
|
+
displayName
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
console.log(" ✏️ Updating README.md...");
|
|
232
|
+
await updateReadme(path.join(targetDir, "README.md"), displayName);
|
|
233
|
+
|
|
234
|
+
console.log(" ✏️ Updating GETTING_STARTED.md...");
|
|
235
|
+
await updateGettingStarted(
|
|
236
|
+
path.join(targetDir, "GETTING_STARTED.md"),
|
|
237
|
+
displayName
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
console.log("\n✅ Plugin scaffold created successfully!\n");
|
|
241
|
+
console.log("📦 Created files:");
|
|
242
|
+
console.log(` - ${targetDir}/`);
|
|
243
|
+
console.log(" - manifest.json");
|
|
244
|
+
console.log(" - main.js");
|
|
245
|
+
if (fs.existsSync(path.join(targetDir, "README.md"))) {
|
|
246
|
+
console.log(" - README.md");
|
|
247
|
+
}
|
|
248
|
+
if (fs.existsSync(path.join(targetDir, "GETTING_STARTED.md"))) {
|
|
249
|
+
console.log(" - GETTING_STARTED.md");
|
|
250
|
+
}
|
|
251
|
+
if (fs.existsSync(path.join(targetDir, "package.json"))) {
|
|
252
|
+
console.log(" - package.json");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log("\n🚀 Next steps:");
|
|
256
|
+
console.log(` 1. cd ${path.basename(targetDir)}`);
|
|
257
|
+
console.log(" 2. npm install");
|
|
258
|
+
console.log(" 3. Read GETTING_STARTED.md for a complete guide");
|
|
259
|
+
console.log(" 4. Edit manifest.json to configure your plugin");
|
|
260
|
+
console.log(" 5. Start coding in main.js");
|
|
261
|
+
console.log("\n📖 Learn more: https://docs.lumiastream.com");
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error(`\n✖ Failed to create plugin: ${error.message}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
209
266
|
}
|
|
210
267
|
|
|
211
268
|
main();
|
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
|
};
|