modpack-lock 0.1.2 → 0.1.3
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 +1 -0
- package/index.js +218 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ Flags:
|
|
|
47
47
|
- `--quiet` or `-q`: Print only errors and warnings
|
|
48
48
|
- `--silent` or `-s`: Print nothing
|
|
49
49
|
- `--gitignore` or `-g`: Print the rules to add to your `.gitignore` file
|
|
50
|
+
- `--readme` or `-r`: Generate README.md files with lists of all content for each category inside their directories
|
|
50
51
|
|
|
51
52
|
The script will:
|
|
52
53
|
|
package/index.js
CHANGED
|
@@ -8,6 +8,9 @@ const LOCKFILE_VERSION = '1.0.1';
|
|
|
8
8
|
const MODPACK_LOCKFILE_NAME = 'modpack.lock';
|
|
9
9
|
const MODRINTH_API_BASE = 'https://api.modrinth.com/v2';
|
|
10
10
|
const MODRINTH_VERSION_FILES_ENDPOINT = `${MODRINTH_API_BASE}/version_files`;
|
|
11
|
+
const MODRINTH_PROJECTS_ENDPOINT = `${MODRINTH_API_BASE}/projects`;
|
|
12
|
+
const MODRINTH_USERS_ENDPOINT = `${MODRINTH_API_BASE}/users`;
|
|
13
|
+
const BATCH_SIZE = 100; // Safe limit for URL length
|
|
11
14
|
|
|
12
15
|
// Get the workspace root from the current working directory
|
|
13
16
|
const WORKSPACE_ROOT = process.cwd();
|
|
@@ -22,6 +25,7 @@ function parseArgs() {
|
|
|
22
25
|
quiet: args.includes('--quiet') || args.includes('-q'),
|
|
23
26
|
silent: args.includes('--silent') || args.includes('-s'),
|
|
24
27
|
gitignore: args.includes('--gitignore') || args.includes('-g'),
|
|
28
|
+
readme: args.includes('--readme') || args.includes('-r'),
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -143,6 +147,81 @@ async function getVersionsFromHashes(hashes) {
|
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Split an array into chunks of specified size
|
|
152
|
+
*/
|
|
153
|
+
function chunkArray(array, size) {
|
|
154
|
+
const chunks = [];
|
|
155
|
+
for (let i = 0; i < array.length; i += size) {
|
|
156
|
+
chunks.push(array.slice(i, i + size));
|
|
157
|
+
}
|
|
158
|
+
return chunks;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Fetch multiple projects by their IDs (batched to avoid URL length limits)
|
|
163
|
+
*/
|
|
164
|
+
async function getProjects(projectIds) {
|
|
165
|
+
if (projectIds.length === 0) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const chunks = chunkArray(projectIds, BATCH_SIZE);
|
|
170
|
+
const results = [];
|
|
171
|
+
|
|
172
|
+
for (const chunk of chunks) {
|
|
173
|
+
try {
|
|
174
|
+
const url = `${MODRINTH_PROJECTS_ENDPOINT}?ids=${encodeURIComponent(JSON.stringify(chunk))}`;
|
|
175
|
+
const response = await fetch(url);
|
|
176
|
+
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const errorText = await response.text();
|
|
179
|
+
throw new Error(`Modrinth API error (${response.status}): ${errorText}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const data = await response.json();
|
|
183
|
+
results.push(...data);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(`Error fetching projects: ${error.message}`);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Fetch multiple users by their IDs (batched to avoid URL length limits)
|
|
195
|
+
*/
|
|
196
|
+
async function getUsers(userIds) {
|
|
197
|
+
if (userIds.length === 0) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const chunks = chunkArray(userIds, BATCH_SIZE);
|
|
202
|
+
const results = [];
|
|
203
|
+
|
|
204
|
+
for (const chunk of chunks) {
|
|
205
|
+
try {
|
|
206
|
+
const url = `${MODRINTH_USERS_ENDPOINT}?ids=${encodeURIComponent(JSON.stringify(chunk))}`;
|
|
207
|
+
const response = await fetch(url);
|
|
208
|
+
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
const errorText = await response.text();
|
|
211
|
+
throw new Error(`Modrinth API error (${response.status}): ${errorText}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
results.push(...data);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`Error fetching users: ${error.message}`);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
|
|
146
225
|
|
|
147
226
|
/**
|
|
148
227
|
* Create empty lockfile structure
|
|
@@ -201,6 +280,79 @@ async function writeLockfile(lockfile, outputPath, log) {
|
|
|
201
280
|
log(`Lockfile written to: ${outputPath}`);
|
|
202
281
|
}
|
|
203
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Generate README.md content for a category
|
|
285
|
+
*/
|
|
286
|
+
function generateCategoryReadme(category, entries, projectsMap, usersMap) {
|
|
287
|
+
const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1);
|
|
288
|
+
const lines = [`# ${categoryTitle}`, '', '| Name | Author | Version |', '|-|-|-|'];
|
|
289
|
+
|
|
290
|
+
// Map category to Modrinth URL path segment
|
|
291
|
+
const categoryPathMap = {
|
|
292
|
+
mods: 'mod',
|
|
293
|
+
resourcepacks: 'resourcepack',
|
|
294
|
+
shaderpacks: 'shader',
|
|
295
|
+
datapacks: 'datapack',
|
|
296
|
+
};
|
|
297
|
+
const categoryPath = categoryPathMap[category] || 'project';
|
|
298
|
+
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
const version = entry.version;
|
|
301
|
+
let nameCell = '';
|
|
302
|
+
let authorCell = '';
|
|
303
|
+
let versionCell = '';
|
|
304
|
+
|
|
305
|
+
if (version && version.project_id) {
|
|
306
|
+
const project = projectsMap[version.project_id];
|
|
307
|
+
const author = version.author_id ? usersMap[version.author_id] : null;
|
|
308
|
+
|
|
309
|
+
// Name column with icon and link
|
|
310
|
+
if (project) {
|
|
311
|
+
const projectName = project.title || project.slug || 'Unknown';
|
|
312
|
+
const projectSlug = project.slug || project.id;
|
|
313
|
+
const projectUrl = `https://modrinth.com/${categoryPath}/${projectSlug}`;
|
|
314
|
+
|
|
315
|
+
if (project.icon_url) {
|
|
316
|
+
nameCell = `<img alt="Icon" src="${project.icon_url}" height="20px"> [${projectName}](${projectUrl})`;
|
|
317
|
+
} else {
|
|
318
|
+
nameCell = `[${projectName}](${projectUrl})`;
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
// Project not found, use filename
|
|
322
|
+
const fileName = path.basename(entry.path);
|
|
323
|
+
nameCell = fileName;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Author column with avatar and link
|
|
327
|
+
if (author) {
|
|
328
|
+
const authorName = author.username || 'Unknown';
|
|
329
|
+
const authorUrl = `https://modrinth.com/user/${authorName}`;
|
|
330
|
+
|
|
331
|
+
if (author.avatar_url) {
|
|
332
|
+
authorCell = `<img alt="Avatar" src="${author.avatar_url}" height="20px"> [${authorName}](${authorUrl})`;
|
|
333
|
+
} else {
|
|
334
|
+
authorCell = `[${authorName}](${authorUrl})`;
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
authorCell = 'Unknown';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Version column
|
|
341
|
+
versionCell = version.version_number || 'Unknown';
|
|
342
|
+
} else {
|
|
343
|
+
// File not found on Modrinth
|
|
344
|
+
const fileName = path.basename(entry.path);
|
|
345
|
+
nameCell = fileName;
|
|
346
|
+
authorCell = 'Unknown';
|
|
347
|
+
versionCell = '-';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
lines.push(`| ${nameCell} | ${authorCell} | ${versionCell} |`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return lines.join('\n') + '\n';
|
|
354
|
+
}
|
|
355
|
+
|
|
204
356
|
/**
|
|
205
357
|
* Generate .gitignore rules for files not hosted on Modrinth
|
|
206
358
|
*/
|
|
@@ -307,6 +459,72 @@ async function main() {
|
|
|
307
459
|
log('\n=== .gitignore Rules ===');
|
|
308
460
|
log(generateGitignoreRules(lockfile));
|
|
309
461
|
}
|
|
462
|
+
|
|
463
|
+
// Generate README files if requested
|
|
464
|
+
if (config.readme) {
|
|
465
|
+
log('\nGenerating README files...');
|
|
466
|
+
|
|
467
|
+
// Collect unique project IDs and author IDs from version data
|
|
468
|
+
const projectIds = new Set();
|
|
469
|
+
const authorIds = new Set();
|
|
470
|
+
|
|
471
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
if (entry.version && entry.version.project_id) {
|
|
474
|
+
projectIds.add(entry.version.project_id);
|
|
475
|
+
}
|
|
476
|
+
if (entry.version && entry.version.author_id) {
|
|
477
|
+
authorIds.add(entry.version.author_id);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Fetch projects and users in parallel
|
|
483
|
+
log(`Fetching data for ${projectIds.size} project(s) and ${authorIds.size} user(s)...`);
|
|
484
|
+
|
|
485
|
+
const [projects, users] = await Promise.all([
|
|
486
|
+
getProjects(Array.from(projectIds)),
|
|
487
|
+
getUsers(Array.from(authorIds)),
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
// Create maps for easy lookup
|
|
491
|
+
const projectsMap = {};
|
|
492
|
+
for (const project of projects) {
|
|
493
|
+
projectsMap[project.id] = project;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const usersMap = {};
|
|
497
|
+
for (const user of users) {
|
|
498
|
+
usersMap[user.id] = user;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Generate README for each category
|
|
502
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
503
|
+
if (entries.length === 0) {
|
|
504
|
+
continue; // Skip empty categories
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const readmeContent = generateCategoryReadme(category, entries, projectsMap, usersMap);
|
|
508
|
+
const categoryDir = DIRECTORIES_TO_SCAN.find(d => d.name === category);
|
|
509
|
+
|
|
510
|
+
if (categoryDir) {
|
|
511
|
+
const readmePath = path.join(categoryDir.path, 'README.md');
|
|
512
|
+
|
|
513
|
+
if (config.dryRun) {
|
|
514
|
+
log(`[DRY RUN] Would write README to: ${readmePath}`);
|
|
515
|
+
} else {
|
|
516
|
+
try {
|
|
517
|
+
await fs.writeFile(readmePath, readmeContent, 'utf-8');
|
|
518
|
+
log(`Generated README: ${readmePath}`);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.warn(`Warning: Could not write README to ${readmePath}: ${error.message}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
log('README generation complete.');
|
|
527
|
+
}
|
|
310
528
|
}
|
|
311
529
|
|
|
312
530
|
main().catch(error => {
|
package/package.json
CHANGED