yllaw 1.4.0 → 1.5.1
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 +3 -3
- package/package.json +1 -1
- package/src/plugin.js +119 -50
package/README.md
CHANGED
|
@@ -204,7 +204,7 @@ SomePlugin = "https://gitlab.example.com/group/some-plugin.git@1.0.0"
|
|
|
204
204
|
TagEditor = "https://gitlab.example.com/group/tag-editor.git@2.1.0"
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
插件文件(`*.
|
|
207
|
+
插件文件(`*.rbxmx`、`*.server.luau`、`*.server.lua`)会被安装到 `%LOCALAPPDATA%/Roblox/Plugins/`。
|
|
208
208
|
|
|
209
209
|
### 全局插件管理
|
|
210
210
|
|
|
@@ -246,7 +246,7 @@ yllaw plugin update SomePlugin # 更新指定插件
|
|
|
246
246
|
```
|
|
247
247
|
your-plugin/
|
|
248
248
|
├── README.md
|
|
249
|
-
└── YourPlugin.server.luau # 或 .
|
|
249
|
+
└── YourPlugin.server.luau # 或 .rbxmx 文件
|
|
250
250
|
```
|
|
251
251
|
|
|
252
252
|
**文件夹插件(推荐,可包含子模块):**
|
|
@@ -259,7 +259,7 @@ your-plugin/
|
|
|
259
259
|
└── Utils.luau
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
yllaw 会自动查找 `*.
|
|
262
|
+
yllaw 会自动查找 `*.rbxmx`、`*.server.luau`、`*.server.lua` 文件,以及包含 `init.server.luau` / `init.server.lua` 的文件夹。
|
|
263
263
|
|
|
264
264
|
## 与 Wally 的关系
|
|
265
265
|
|
package/package.json
CHANGED
package/src/plugin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { spawn } = require('child_process');
|
|
3
4
|
const simpleGit = require('simple-git');
|
|
4
5
|
const chalk = require('chalk');
|
|
5
6
|
const { getPluginsDir, loadConfig, loadInstalled, saveInstalled } = require('./config');
|
|
@@ -16,7 +17,7 @@ const log = {
|
|
|
16
17
|
/**
|
|
17
18
|
* Find plugin files in a directory.
|
|
18
19
|
* Supports:
|
|
19
|
-
* - Single file plugins: *.
|
|
20
|
+
* - Single file plugins: *.rbxmx, *.server.luau, *.server.lua
|
|
20
21
|
* - Folder plugins: directories containing init.server.luau or init.server.lua
|
|
21
22
|
*
|
|
22
23
|
* Returns structured results:
|
|
@@ -44,7 +45,7 @@ async function findPluginFiles(dir) {
|
|
|
44
45
|
for (const entry of entries) {
|
|
45
46
|
if (entry.isFile()) {
|
|
46
47
|
const name = entry.name;
|
|
47
|
-
if (name.endsWith('.
|
|
48
|
+
if (name.endsWith('.rbxmx') ||
|
|
48
49
|
name.endsWith('.server.luau') ||
|
|
49
50
|
name.endsWith('.server.lua')) {
|
|
50
51
|
results.push({ type: 'file', path: path.join(searchDir, name), name });
|
|
@@ -68,6 +69,99 @@ async function findPluginFiles(dir) {
|
|
|
68
69
|
return results;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Build a plugin entry (.server.lua(u) or folder) to .rbxmx using Rojo.
|
|
74
|
+
* Generates a temporary project.json, runs `rojo build`, returns the result.
|
|
75
|
+
* @returns {{ rbxmxPath: string, name: string } | null}
|
|
76
|
+
*/
|
|
77
|
+
async function buildToRbxmx(entry) {
|
|
78
|
+
let pluginName;
|
|
79
|
+
if (entry.type === 'folder') {
|
|
80
|
+
pluginName = entry.name;
|
|
81
|
+
} else {
|
|
82
|
+
pluginName = entry.name
|
|
83
|
+
.replace(/\.server\.luau$/, '')
|
|
84
|
+
.replace(/\.server\.lua$/, '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parentDir = path.dirname(entry.path);
|
|
88
|
+
const entryBasename = path.basename(entry.path);
|
|
89
|
+
const projectPath = path.join(parentDir, `_yllaw_build.project.json`);
|
|
90
|
+
const rbxmxPath = path.join(parentDir, `${pluginName}.rbxmx`);
|
|
91
|
+
|
|
92
|
+
await fs.writeJson(projectPath, {
|
|
93
|
+
name: pluginName,
|
|
94
|
+
tree: { "$path": entryBasename },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const success = await new Promise((resolve) => {
|
|
99
|
+
const rojo = spawn('rojo', ['build', projectPath, '-o', rbxmxPath], {
|
|
100
|
+
stdio: 'pipe',
|
|
101
|
+
shell: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
let stderr = '';
|
|
105
|
+
rojo.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
106
|
+
rojo.on('close', (code) => {
|
|
107
|
+
if (code !== 0 && stderr) {
|
|
108
|
+
log.error(`Rojo build failed: ${stderr.trim()}`);
|
|
109
|
+
}
|
|
110
|
+
resolve(code === 0);
|
|
111
|
+
});
|
|
112
|
+
rojo.on('error', () => resolve(false));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!success) return null;
|
|
116
|
+
return { rbxmxPath, name: `${pluginName}.rbxmx` };
|
|
117
|
+
} finally {
|
|
118
|
+
await fs.remove(projectPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Install plugin entries to the plugins directory.
|
|
124
|
+
* For .rbxmx files: copy directly.
|
|
125
|
+
* For .server.lua(u) / folders: build to .rbxmx via Rojo, fallback to direct copy.
|
|
126
|
+
* @returns {Array} installed file records
|
|
127
|
+
*/
|
|
128
|
+
async function installEntries(pluginEntries, pluginsDir, logPrefix = '') {
|
|
129
|
+
const installedFiles = [];
|
|
130
|
+
|
|
131
|
+
for (const entry of pluginEntries) {
|
|
132
|
+
if (entry.type === 'file' && entry.name.endsWith('.rbxmx')) {
|
|
133
|
+
const dest = path.join(pluginsDir, entry.name);
|
|
134
|
+
await fs.copy(entry.path, dest, { overwrite: true });
|
|
135
|
+
installedFiles.push({ type: 'file', name: entry.name });
|
|
136
|
+
log.ok(`${logPrefix}${entry.name} -> ${pluginsDir}`);
|
|
137
|
+
} else {
|
|
138
|
+
const result = await buildToRbxmx(entry);
|
|
139
|
+
if (result) {
|
|
140
|
+
const dest = path.join(pluginsDir, result.name);
|
|
141
|
+
await fs.copy(result.rbxmxPath, dest, { overwrite: true });
|
|
142
|
+
installedFiles.push({ type: 'file', name: result.name });
|
|
143
|
+
log.ok(`${logPrefix}${entry.name} -> ${result.name} -> ${pluginsDir}`);
|
|
144
|
+
await fs.remove(result.rbxmxPath);
|
|
145
|
+
} else {
|
|
146
|
+
log.warn(`Rojo build failed for ${entry.name}, copying as-is`);
|
|
147
|
+
if (entry.type === 'folder') {
|
|
148
|
+
const dest = path.join(pluginsDir, entry.name);
|
|
149
|
+
await fs.copy(entry.path, dest, { overwrite: true });
|
|
150
|
+
await fs.remove(path.join(dest, '.git'));
|
|
151
|
+
installedFiles.push({ type: 'folder', name: entry.name });
|
|
152
|
+
} else {
|
|
153
|
+
const dest = path.join(pluginsDir, entry.name);
|
|
154
|
+
await fs.copy(entry.path, dest, { overwrite: true });
|
|
155
|
+
installedFiles.push({ type: 'file', name: entry.name });
|
|
156
|
+
}
|
|
157
|
+
log.ok(`${logPrefix}${entry.name} -> ${pluginsDir}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return installedFiles;
|
|
163
|
+
}
|
|
164
|
+
|
|
71
165
|
/**
|
|
72
166
|
* Parse git spec: "https://example.com/repo.git@version"
|
|
73
167
|
*/
|
|
@@ -180,21 +274,7 @@ async function installPlugin(name, version, sources) {
|
|
|
180
274
|
const pluginsDir = getPluginsDir();
|
|
181
275
|
await fs.ensureDir(pluginsDir);
|
|
182
276
|
|
|
183
|
-
const installedFiles =
|
|
184
|
-
for (const entry of pluginEntries) {
|
|
185
|
-
if (entry.type === 'folder') {
|
|
186
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
187
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
188
|
-
await fs.remove(path.join(dest, '.git'));
|
|
189
|
-
installedFiles.push({ type: 'folder', name: entry.name });
|
|
190
|
-
log.ok(`${entry.name}/ -> ${pluginsDir}`);
|
|
191
|
-
} else {
|
|
192
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
193
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
194
|
-
installedFiles.push({ type: 'file', name: entry.name });
|
|
195
|
-
log.ok(`${entry.name} -> ${pluginsDir}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
277
|
+
const installedFiles = await installEntries(pluginEntries, pluginsDir);
|
|
198
278
|
|
|
199
279
|
// Update installed.json
|
|
200
280
|
const installed = await loadInstalled();
|
|
@@ -237,21 +317,7 @@ async function installPluginFromGitSpec(name, spec, options = {}) {
|
|
|
237
317
|
if (pluginEntries.length > 0) {
|
|
238
318
|
const pluginsDir = getPluginsDir();
|
|
239
319
|
await fs.ensureDir(pluginsDir);
|
|
240
|
-
const installedFiles = [];
|
|
241
|
-
for (const entry of pluginEntries) {
|
|
242
|
-
if (entry.type === 'folder') {
|
|
243
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
244
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
245
|
-
await fs.remove(path.join(dest, '.git'));
|
|
246
|
-
installedFiles.push({ type: 'folder', name: entry.name });
|
|
247
|
-
log.ok(`[DEV] ${entry.name}/ -> ${pluginsDir}`);
|
|
248
|
-
} else {
|
|
249
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
250
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
251
|
-
installedFiles.push({ type: 'file', name: entry.name });
|
|
252
|
-
log.ok(`[DEV] ${entry.name} -> ${pluginsDir}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
320
|
+
const installedFiles = await installEntries(pluginEntries, pluginsDir, '[DEV] ');
|
|
255
321
|
const installed = await loadInstalled();
|
|
256
322
|
installed[name] = {
|
|
257
323
|
version: displayRef,
|
|
@@ -278,21 +344,7 @@ async function installPluginFromGitSpec(name, spec, options = {}) {
|
|
|
278
344
|
const pluginsDir = getPluginsDir();
|
|
279
345
|
await fs.ensureDir(pluginsDir);
|
|
280
346
|
|
|
281
|
-
const installedFiles =
|
|
282
|
-
for (const entry of pluginEntries) {
|
|
283
|
-
if (entry.type === 'folder') {
|
|
284
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
285
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
286
|
-
await fs.remove(path.join(dest, '.git'));
|
|
287
|
-
installedFiles.push({ type: 'folder', name: entry.name });
|
|
288
|
-
log.ok(`${entry.name}/ -> ${pluginsDir}`);
|
|
289
|
-
} else {
|
|
290
|
-
const dest = path.join(pluginsDir, entry.name);
|
|
291
|
-
await fs.copy(entry.path, dest, { overwrite: true });
|
|
292
|
-
installedFiles.push({ type: 'file', name: entry.name });
|
|
293
|
-
log.ok(`${entry.name} -> ${pluginsDir}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
347
|
+
const installedFiles = await installEntries(pluginEntries, pluginsDir);
|
|
296
348
|
|
|
297
349
|
// Update installed.json
|
|
298
350
|
const installed = await loadInstalled();
|
|
@@ -419,14 +471,31 @@ async function updatePlugin(name) {
|
|
|
419
471
|
return false;
|
|
420
472
|
}
|
|
421
473
|
|
|
422
|
-
if
|
|
474
|
+
// Check if plugin files still exist on disk
|
|
475
|
+
const pluginsDir = getPluginsDir();
|
|
476
|
+
let missing = false;
|
|
477
|
+
if (entry.files) {
|
|
478
|
+
for (const fileEntry of entry.files) {
|
|
479
|
+
const fileName = typeof fileEntry === 'string' ? fileEntry : fileEntry.name;
|
|
480
|
+
if (!await fs.pathExists(path.join(pluginsDir, fileName))) {
|
|
481
|
+
missing = true;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (latestTag === entry.version && !missing) {
|
|
423
488
|
log.info(`${name}: up to date (${entry.version})`);
|
|
424
489
|
return true;
|
|
425
490
|
}
|
|
426
491
|
|
|
427
|
-
|
|
492
|
+
if (missing) {
|
|
493
|
+
log.info(`${name}: files missing, reinstalling (${latestTag})`);
|
|
494
|
+
} else {
|
|
495
|
+
log.ok(`${name}: ${entry.version} -> ${latestTag}`);
|
|
496
|
+
}
|
|
428
497
|
|
|
429
|
-
// Reinstall
|
|
498
|
+
// Reinstall
|
|
430
499
|
const spec = `${entry.url}@${latestTag}`;
|
|
431
500
|
return await installPluginFromGitSpec(name, spec);
|
|
432
501
|
}
|