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 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
- ```bash
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
- Flags:
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
- - `--dry-run` or `-d`: Print the files that would be scanned, but don't actually scan them
47
- - `--quiet` or `-q`: Print only errors and warnings
48
- - `--silent` or `-s`: Print nothing
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
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.1.3",
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": "index.js",
15
+ "main": "src/generate-lockfile.js",
16
16
  "bin": {
17
- "modpack-lock": "index.js"
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": "node index.js",
30
- "start": "node index.js"
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; // Safe limit for URL length
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 () => {}; // No-op function when quiet
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
- const DIRECTORIES_TO_SCAN = [
53
- { name: 'mods', path: path.join(WORKSPACE_ROOT, 'mods') },
54
- { name: 'resourcepacks', path: path.join(WORKSPACE_ROOT, 'resourcepacks') },
55
- { name: 'datapacks', path: path.join(WORKSPACE_ROOT, 'datapacks') },
56
- { name: 'shaderpacks', path: path.join(WORKSPACE_ROOT, 'shaderpacks') },
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(WORKSPACE_ROOT, filePath);
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 (batched to avoid URL length limits)
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 (batched to avoid URL length limits)
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 main() {
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 DIRECTORIES_TO_SCAN) {
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(WORKSPACE_ROOT, MODPACK_LOCKFILE_NAME);
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(WORKSPACE_ROOT, MODPACK_LOCKFILE_NAME);
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 if requested
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 if requested
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
- // Create maps for easy lookup
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; // Skip empty categories
490
+ continue;
505
491
  }
506
492
 
507
493
  const readmeContent = generateCategoryReadme(category, entries, projectsMap, usersMap);
508
- const categoryDir = DIRECTORIES_TO_SCAN.find(d => d.name === category);
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
- main().catch(error => {
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
+