modpack-lock 0.1.3 → 0.2.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.
- package/README.md +19 -11
- package/package.json +16 -8
- package/{index.js → src/generate-lockfile.js} +30 -46
- package/src/modpack-lock.js +32 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
<a href="https://github.com/nickesc/modpack-lock"><img alt="Source: Github" src="https://img.shields.io/badge/source-github-brightgreen?style=for-the-badge&logo=github&labelColor=%23505050"></a>
|
|
3
1
|
<a href="https://www.npmjs.com/package/modpack-lock"><img alt="NPM: npmjs.com/package/modpack-lock" src="https://img.shields.io/npm/v/modpack-lock?style=for-the-badge&logo=npm&logoColor=white&label=npm&color=%23C12127&labelColor=%23505050"></a>
|
|
2
|
+
<a href="https://github.com/nickesc/modpack-lock"><img alt="Source: Github" src="https://img.shields.io/badge/source-github-brightgreen?style=for-the-badge&logo=github&labelColor=%23505050"></a>
|
|
3
|
+
<a href="https://github.com/nickesc/modpack-lock/actions/workflows/generateLockfile-tests.yml"><img alt="Tests: github.com/nickesc/modpack-lock/actions/workflows/generateLockfile-tests.yml" src="https://img.shields.io/github/actions/workflow/status/nickesc/modpack-lock/generateLockfile-tests.yml?logo=github&label=tests&logoColor=white&style=for-the-badge&labelColor=%23505050"></a>
|
|
4
4
|
|
|
5
5
|
# modpack-lock
|
|
6
6
|
|
|
@@ -37,17 +37,25 @@ npx modpack-lock
|
|
|
37
37
|
|
|
38
38
|
Navigate to your Minecraft profile directory (the folder containing `mods`, `resourcepacks`, `datapacks`, and `shaderpacks` folders) and run:
|
|
39
39
|
|
|
40
|
-
```
|
|
41
|
-
modpack-lock
|
|
42
|
-
|
|
40
|
+
```text
|
|
41
|
+
Usage: modpack-lock [options]
|
|
42
|
+
|
|
43
|
+
Create a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks)
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Options:
|
|
46
|
+
-d, --dry-run Dry-run mode - no files will be written
|
|
47
|
+
-g, --gitignore Print .gitignore rules for files not hosted on Modrinth
|
|
48
|
+
-r, --readme Generate README.md files for each category
|
|
49
|
+
-p, --path <path> Path to the modpack directory
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
LOGGING
|
|
52
|
+
-q, --quiet Quiet mode - only show errors and warnings
|
|
53
|
+
-s, --silent Silent mode - no output
|
|
54
|
+
|
|
55
|
+
INFORMATION
|
|
56
|
+
-V, --version output the version number
|
|
57
|
+
--help display help for modpack-lock
|
|
58
|
+
```
|
|
51
59
|
|
|
52
60
|
The script will:
|
|
53
61
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modpack-lock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Create a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks)",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/nickesc/modpack-lock/issues"
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"author": "N. Escobar <nick@nescobar.media> (https://nickesc.github.io/)",
|
|
14
14
|
"type": "module",
|
|
15
|
-
"main": "
|
|
15
|
+
"main": "src/generate-lockfile.js",
|
|
16
16
|
"bin": {
|
|
17
|
-
"modpack-lock": "
|
|
17
|
+
"modpack-lock": "src/modpack-lock.js"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"modrinth",
|
|
@@ -26,13 +26,21 @@
|
|
|
26
26
|
"node": ">=18.0.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
-
"test": "
|
|
30
|
-
"start": "node
|
|
29
|
+
"test": "vitest --run",
|
|
30
|
+
"start": "node src/modpack-lock.js",
|
|
31
|
+
"modpack-lock": "node src/modpack-lock.js"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
33
|
-
"index.js",
|
|
34
34
|
"README.md",
|
|
35
35
|
"LICENSE",
|
|
36
|
-
"package.json"
|
|
37
|
-
|
|
36
|
+
"package.json",
|
|
37
|
+
"src/*.js"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^14.0.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"unzipper": "^0.12.3",
|
|
44
|
+
"vitest": "^4.0.16"
|
|
45
|
+
}
|
|
38
46
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import fs from 'fs/promises';
|
|
4
2
|
import crypto from 'crypto';
|
|
5
3
|
import path from 'path';
|
|
@@ -10,31 +8,17 @@ const MODRINTH_API_BASE = 'https://api.modrinth.com/v2';
|
|
|
10
8
|
const MODRINTH_VERSION_FILES_ENDPOINT = `${MODRINTH_API_BASE}/version_files`;
|
|
11
9
|
const MODRINTH_PROJECTS_ENDPOINT = `${MODRINTH_API_BASE}/projects`;
|
|
12
10
|
const MODRINTH_USERS_ENDPOINT = `${MODRINTH_API_BASE}/users`;
|
|
13
|
-
const BATCH_SIZE = 100;
|
|
11
|
+
const BATCH_SIZE = 100;
|
|
14
12
|
|
|
15
13
|
// Get the workspace root from the current working directory
|
|
16
|
-
const WORKSPACE_ROOT = process.cwd();
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse command-line arguments
|
|
20
|
-
*/
|
|
21
|
-
function parseArgs() {
|
|
22
|
-
const args = process.argv.slice(2);
|
|
23
|
-
return {
|
|
24
|
-
dryRun: args.includes('--dry-run') || args.includes('-d'),
|
|
25
|
-
quiet: args.includes('--quiet') || args.includes('-q'),
|
|
26
|
-
silent: args.includes('--silent') || args.includes('-s'),
|
|
27
|
-
gitignore: args.includes('--gitignore') || args.includes('-g'),
|
|
28
|
-
readme: args.includes('--readme') || args.includes('-r'),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
14
|
+
//const WORKSPACE_ROOT = process.cwd();
|
|
31
15
|
|
|
32
16
|
/**
|
|
33
17
|
* Create a logger function that respects quiet mode
|
|
34
18
|
*/
|
|
35
19
|
function createLogger(quiet) {
|
|
36
20
|
if (quiet) {
|
|
37
|
-
return () => {};
|
|
21
|
+
return () => {};
|
|
38
22
|
}
|
|
39
23
|
return (...args) => console.log(...args);
|
|
40
24
|
}
|
|
@@ -49,12 +33,17 @@ function silenceConsole() {
|
|
|
49
33
|
console.info = () => {};
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Get the directories to scan for modpack files
|
|
38
|
+
*/
|
|
39
|
+
function getScanDirectories(directoryPath) {
|
|
40
|
+
return [
|
|
41
|
+
{ name: 'mods', path: path.join(directoryPath, 'mods') },
|
|
42
|
+
{ name: 'resourcepacks', path: path.join(directoryPath, 'resourcepacks') },
|
|
43
|
+
{ name: 'datapacks', path: path.join(directoryPath, 'datapacks') },
|
|
44
|
+
{ name: 'shaderpacks', path: path.join(directoryPath, 'shaderpacks') },
|
|
45
|
+
];
|
|
46
|
+
}
|
|
58
47
|
|
|
59
48
|
/**
|
|
60
49
|
* Calculate SHA1 hash of a file
|
|
@@ -80,7 +69,6 @@ async function findFiles(dirPath) {
|
|
|
80
69
|
}
|
|
81
70
|
}
|
|
82
71
|
} catch (error) {
|
|
83
|
-
// Directory doesn't exist or can't be read - skip it
|
|
84
72
|
if (error.code !== 'ENOENT') {
|
|
85
73
|
console.warn(`Warning: Could not read directory ${dirPath}: ${error.message}`);
|
|
86
74
|
}
|
|
@@ -92,14 +80,14 @@ async function findFiles(dirPath) {
|
|
|
92
80
|
/**
|
|
93
81
|
* Scan a directory and return file info with hashes
|
|
94
82
|
*/
|
|
95
|
-
async function scanDirectory(dirInfo) {
|
|
83
|
+
async function scanDirectory(dirInfo, workspaceRoot) {
|
|
96
84
|
const files = await findFiles(dirInfo.path);
|
|
97
85
|
const fileEntries = [];
|
|
98
86
|
|
|
99
87
|
for (const filePath of files) {
|
|
100
88
|
try {
|
|
101
89
|
const hash = await calculateSHA1(filePath);
|
|
102
|
-
const relativePath = path.relative(
|
|
90
|
+
const relativePath = path.relative(workspaceRoot, filePath);
|
|
103
91
|
|
|
104
92
|
fileEntries.push({
|
|
105
93
|
path: relativePath,
|
|
@@ -159,7 +147,7 @@ function chunkArray(array, size) {
|
|
|
159
147
|
}
|
|
160
148
|
|
|
161
149
|
/**
|
|
162
|
-
* Fetch multiple projects by their IDs
|
|
150
|
+
* Fetch multiple projects by their IDs in batches
|
|
163
151
|
*/
|
|
164
152
|
async function getProjects(projectIds) {
|
|
165
153
|
if (projectIds.length === 0) {
|
|
@@ -191,7 +179,7 @@ async function getProjects(projectIds) {
|
|
|
191
179
|
}
|
|
192
180
|
|
|
193
181
|
/**
|
|
194
|
-
* Fetch multiple users by their IDs
|
|
182
|
+
* Fetch multiple users by their IDs in batches
|
|
195
183
|
*/
|
|
196
184
|
async function getUsers(userIds) {
|
|
197
185
|
if (userIds.length === 0) {
|
|
@@ -265,7 +253,6 @@ function createLockfile(fileEntries, versionData) {
|
|
|
265
253
|
lockfile.counts[category] = entries.length;
|
|
266
254
|
}
|
|
267
255
|
|
|
268
|
-
// Calculate total count
|
|
269
256
|
lockfile.total = fileEntries.length;
|
|
270
257
|
|
|
271
258
|
return lockfile;
|
|
@@ -390,8 +377,7 @@ function generateGitignoreRules(lockfile) {
|
|
|
390
377
|
/**
|
|
391
378
|
* Main execution function
|
|
392
379
|
*/
|
|
393
|
-
async function
|
|
394
|
-
const config = parseArgs();
|
|
380
|
+
async function generateLockfile(config) {
|
|
395
381
|
const log = createLogger(config.quiet);
|
|
396
382
|
|
|
397
383
|
if (config.silent) {
|
|
@@ -406,16 +392,16 @@ async function main() {
|
|
|
406
392
|
|
|
407
393
|
// Scan all directories
|
|
408
394
|
const allFileEntries = [];
|
|
409
|
-
for (const dirInfo of
|
|
395
|
+
for (const dirInfo of getScanDirectories(config.path)) {
|
|
410
396
|
log(`Scanning ${dirInfo.name}...`);
|
|
411
|
-
const fileEntries = await scanDirectory(dirInfo);
|
|
397
|
+
const fileEntries = await scanDirectory(dirInfo, config.path);
|
|
412
398
|
log(` Found ${fileEntries.length} file(s)`);
|
|
413
399
|
allFileEntries.push(...fileEntries);
|
|
414
400
|
}
|
|
415
401
|
|
|
416
402
|
if (allFileEntries.length === 0) {
|
|
417
403
|
log('No files found. Creating empty lockfile.');
|
|
418
|
-
const outputPath = path.join(
|
|
404
|
+
const outputPath = path.join(config.path, MODPACK_LOCKFILE_NAME);
|
|
419
405
|
if (config.dryRun) {
|
|
420
406
|
log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
|
|
421
407
|
} else {
|
|
@@ -439,7 +425,7 @@ async function main() {
|
|
|
439
425
|
const lockfile = createLockfile(allFileEntries, versionData);
|
|
440
426
|
|
|
441
427
|
// Write lockfile
|
|
442
|
-
const outputPath = path.join(
|
|
428
|
+
const outputPath = path.join(config.path, MODPACK_LOCKFILE_NAME);
|
|
443
429
|
if (config.dryRun) {
|
|
444
430
|
log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
|
|
445
431
|
} else {
|
|
@@ -454,13 +440,13 @@ async function main() {
|
|
|
454
440
|
log(`${category}: ${entries.length} file(s) (${withVersion} found on Modrinth, ${withoutVersion} unknown)`);
|
|
455
441
|
}
|
|
456
442
|
|
|
457
|
-
// Generate .gitignore rules
|
|
443
|
+
// Generate .gitignore rules
|
|
458
444
|
if (config.gitignore) {
|
|
459
445
|
log('\n=== .gitignore Rules ===');
|
|
460
446
|
log(generateGitignoreRules(lockfile));
|
|
461
447
|
}
|
|
462
448
|
|
|
463
|
-
// Generate README files
|
|
449
|
+
// Generate README files
|
|
464
450
|
if (config.readme) {
|
|
465
451
|
log('\nGenerating README files...');
|
|
466
452
|
|
|
@@ -487,7 +473,7 @@ async function main() {
|
|
|
487
473
|
getUsers(Array.from(authorIds)),
|
|
488
474
|
]);
|
|
489
475
|
|
|
490
|
-
//
|
|
476
|
+
// Map projects and users to their IDs
|
|
491
477
|
const projectsMap = {};
|
|
492
478
|
for (const project of projects) {
|
|
493
479
|
projectsMap[project.id] = project;
|
|
@@ -501,11 +487,11 @@ async function main() {
|
|
|
501
487
|
// Generate README for each category
|
|
502
488
|
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
503
489
|
if (entries.length === 0) {
|
|
504
|
-
continue;
|
|
490
|
+
continue;
|
|
505
491
|
}
|
|
506
492
|
|
|
507
493
|
const readmeContent = generateCategoryReadme(category, entries, projectsMap, usersMap);
|
|
508
|
-
const categoryDir =
|
|
494
|
+
const categoryDir = getScanDirectories(config.path).find(d => d.name === category);
|
|
509
495
|
|
|
510
496
|
if (categoryDir) {
|
|
511
497
|
const readmePath = path.join(categoryDir.path, 'README.md');
|
|
@@ -525,9 +511,7 @@ async function main() {
|
|
|
525
511
|
|
|
526
512
|
log('README generation complete.');
|
|
527
513
|
}
|
|
514
|
+
return true;
|
|
528
515
|
}
|
|
529
516
|
|
|
530
|
-
|
|
531
|
-
console.error('Error:', error);
|
|
532
|
-
process.exit(1);
|
|
533
|
-
});
|
|
517
|
+
export default generateLockfile;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env NODE_OPTIONS=--no-warnings node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import generateLockfile from './generate-lockfile.js';
|
|
5
|
+
|
|
6
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
7
|
+
const modpackLock = new Command('modpack-lock');
|
|
8
|
+
|
|
9
|
+
modpackLock
|
|
10
|
+
.name(pkg.name)
|
|
11
|
+
.description(pkg.description)
|
|
12
|
+
.summary("Create a modpack lockfile")
|
|
13
|
+
.optionsGroup("Options:")
|
|
14
|
+
.option('-d, --dry-run', 'Dry-run mode - no files will be written')
|
|
15
|
+
.option('-g, --gitignore', 'Print .gitignore rules for files not hosted on Modrinth')
|
|
16
|
+
.option('-r, --readme', 'Generate README.md files for each category')
|
|
17
|
+
.option('-p, --path <path>', 'Path to the modpack directory')
|
|
18
|
+
.optionsGroup("LOGGING")
|
|
19
|
+
.option('-q, --quiet', 'Quiet mode - only show errors and warnings')
|
|
20
|
+
.option('-s, --silent', 'Silent mode - no output')
|
|
21
|
+
.optionsGroup("INFORMATION")
|
|
22
|
+
.helpOption("--help", `display help for ${pkg.name}`)
|
|
23
|
+
.version(pkg.version)
|
|
24
|
+
.action((options) => {
|
|
25
|
+
generateLockfile({ ...options, path: options.path || process.cwd() }).catch(error => {
|
|
26
|
+
console.error('Error:', error);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
modpackLock.parse()
|
|
32
|
+
|