lumia-plugin 0.1.5 → 0.1.7
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/examples/base-plugin/GETTING_STARTED.md +243 -0
- package/package.json +2 -1
- package/scripts/create-plugin.js +228 -171
- package/scripts/utils.js +29 -50
|
@@ -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! 🚀
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumia-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Command-line tools for creating, building, and validating Lumia Stream plugins.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lumia-plugin": "scripts/cli.js"
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"author": "Lumia Stream",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@lumiastream/plugin": "latest",
|
|
23
24
|
"jszip": "3.10.1"
|
|
24
25
|
}
|
|
25
26
|
}
|
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
|
@@ -2,6 +2,35 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const JSZip = require("jszip");
|
|
4
4
|
|
|
5
|
+
function loadSharedValidator() {
|
|
6
|
+
try {
|
|
7
|
+
const shared = require("@lumiastream/plugin");
|
|
8
|
+
return (
|
|
9
|
+
shared.validatePluginManifest ||
|
|
10
|
+
shared.validateManifest
|
|
11
|
+
);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error.code !== "MODULE_NOT_FOUND") {
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const fallback = require("../../dist/manifest-validation");
|
|
20
|
+
return fallback.validatePluginManifest || fallback.validateManifest;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error.code !== "MODULE_NOT_FOUND") {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Unable to locate manifest validation helpers. Ensure @lumiastream/plugin is installed."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const validateManifest = loadSharedValidator();
|
|
33
|
+
|
|
5
34
|
const DEFAULT_IGNORE = new Set([
|
|
6
35
|
".git",
|
|
7
36
|
".DS_Store",
|
|
@@ -22,56 +51,6 @@ async function readManifest(pluginDir) {
|
|
|
22
51
|
return { manifest: JSON.parse(raw), manifestPath };
|
|
23
52
|
}
|
|
24
53
|
|
|
25
|
-
function validateManifest(manifest) {
|
|
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
|
-
}
|
|
42
|
-
|
|
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
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!manifest.config || typeof manifest.config !== "object") {
|
|
51
|
-
errors.push("Manifest config must be an object");
|
|
52
|
-
}
|
|
53
|
-
|
|
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
|
-
}
|
|
71
|
-
|
|
72
|
-
return errors;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
54
|
async function collectFiles(pluginDir, ignore = DEFAULT_IGNORE) {
|
|
76
55
|
const entries = [];
|
|
77
56
|
|