yllaw 1.4.0 → 1.5.0

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/plugin.js +117 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yllaw",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
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');
@@ -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 .rbxm using Rojo.
74
+ * Generates a temporary project.json, runs `rojo build`, returns the result.
75
+ * @returns {{ rbxmPath: string, name: string } | null}
76
+ */
77
+ async function buildToRbxm(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 rbxmPath = path.join(parentDir, `${pluginName}.rbxm`);
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', rbxmPath], {
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 { rbxmPath, name: `${pluginName}.rbxm` };
117
+ } finally {
118
+ await fs.remove(projectPath);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Install plugin entries to the plugins directory.
124
+ * For .rbxm files: copy directly.
125
+ * For .server.lua(u) / folders: build to .rbxm 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('.rbxm')) {
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 buildToRbxm(entry);
139
+ if (result) {
140
+ const dest = path.join(pluginsDir, result.name);
141
+ await fs.copy(result.rbxmPath, 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.rbxmPath);
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
  }