modpack-lock 0.1.1 → 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 +9 -1
- package/index.js +229 -1
- 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
|
|
|
@@ -79,8 +80,15 @@ The `modpack.lock` file has the following structure:
|
|
|
79
80
|
|
|
80
81
|
```json
|
|
81
82
|
{
|
|
82
|
-
"version": "1.0.
|
|
83
|
+
"version": "1.0.1",
|
|
83
84
|
"generated": "2026-01-06T03:00:00.000Z",
|
|
85
|
+
"total": 7,
|
|
86
|
+
"counts": {
|
|
87
|
+
"mods": 1,
|
|
88
|
+
"resourcepacks": 3,
|
|
89
|
+
"datapacks": 1,
|
|
90
|
+
"shaderpacks": 2
|
|
91
|
+
},
|
|
84
92
|
"dependencies": {
|
|
85
93
|
"mods": [
|
|
86
94
|
{
|
package/index.js
CHANGED
|
@@ -4,10 +4,13 @@ import fs from 'fs/promises';
|
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
|
|
7
|
-
const LOCKFILE_VERSION = '1.0.
|
|
7
|
+
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
|
|
@@ -151,6 +230,8 @@ function createEmptyLockfile() {
|
|
|
151
230
|
return {
|
|
152
231
|
version: LOCKFILE_VERSION,
|
|
153
232
|
generated: new Date().toISOString(),
|
|
233
|
+
total: 0,
|
|
234
|
+
counts: {},
|
|
154
235
|
dependencies: {},
|
|
155
236
|
};
|
|
156
237
|
}
|
|
@@ -179,6 +260,14 @@ function createLockfile(fileEntries, versionData) {
|
|
|
179
260
|
lockfile.dependencies[fileInfo.category].push(entry);
|
|
180
261
|
}
|
|
181
262
|
|
|
263
|
+
// Calculate counts for each category
|
|
264
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
265
|
+
lockfile.counts[category] = entries.length;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Calculate total count
|
|
269
|
+
lockfile.total = fileEntries.length;
|
|
270
|
+
|
|
182
271
|
return lockfile;
|
|
183
272
|
}
|
|
184
273
|
|
|
@@ -191,6 +280,79 @@ async function writeLockfile(lockfile, outputPath, log) {
|
|
|
191
280
|
log(`Lockfile written to: ${outputPath}`);
|
|
192
281
|
}
|
|
193
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
|
+
|
|
194
356
|
/**
|
|
195
357
|
* Generate .gitignore rules for files not hosted on Modrinth
|
|
196
358
|
*/
|
|
@@ -297,6 +459,72 @@ async function main() {
|
|
|
297
459
|
log('\n=== .gitignore Rules ===');
|
|
298
460
|
log(generateGitignoreRules(lockfile));
|
|
299
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
|
+
}
|
|
300
528
|
}
|
|
301
529
|
|
|
302
530
|
main().catch(error => {
|
package/package.json
CHANGED