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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. 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
- 插件文件(`*.rbxm`、`*.server.luau`、`*.server.lua`)会被安装到 `%LOCALAPPDATA%/Roblox/Plugins/`。
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 # 或 .rbxm 文件
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 会自动查找 `*.rbxm`、`*.server.luau`、`*.server.lua` 文件,以及包含 `init.server.luau` / `init.server.lua` 的文件夹。
262
+ yllaw 会自动查找 `*.rbxmx`、`*.server.luau`、`*.server.lua` 文件,以及包含 `init.server.luau` / `init.server.lua` 的文件夹。
263
263
 
264
264
  ## 与 Wally 的关系
265
265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yllaw",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Package manager for Roblox - supports Wally packages and private Git repositories",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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: *.rbxm, *.server.luau, *.server.lua
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('.rbxm') ||
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 (latestTag === entry.version) {
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
- log.ok(`${name}: ${entry.version} -> ${latestTag}`);
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 at new version
498
+ // Reinstall
430
499
  const spec = `${entry.url}@${latestTag}`;
431
500
  return await installPluginFromGitSpec(name, spec);
432
501
  }