modpack-lock 0.2.0 → 0.3.1
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 +111 -17
- package/package.json +16 -9
- package/src/cli.js +166 -0
- package/src/config/api.js +14 -0
- package/src/config/constants.js +20 -0
- package/src/config/files.js +5 -0
- package/src/config/index.js +4 -0
- package/src/config/options.js +2 -0
- package/src/config/types.js +45 -0
- package/src/directory_scanning.js +118 -0
- package/src/generate_json.js +105 -0
- package/src/generate_lockfile.js +327 -0
- package/src/modpack-lock.js +27 -28
- package/src/modpack_info.js +136 -0
- package/src/modrinth_interactions.js +108 -0
- package/src/generate-lockfile.js +0 -517
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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
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/
|
|
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/modpack-lock-tests.yml?logo=github&label=tests&logoColor=white&style=for-the-badge&labelColor=%23505050"></a>
|
|
4
4
|
|
|
5
5
|
# modpack-lock
|
|
6
6
|
|
|
@@ -8,12 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
Creates a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks).
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
## Overview
|
|
13
12
|
|
|
14
13
|
Many mod and pack authors request that modpack creators link to Modrinth or CurseForge downloads rather than re-hosting files. This makes it difficult to track content files in version control when pushing to a remote server.
|
|
15
14
|
|
|
16
|
-
This script generates a `modpack.lock` file in the current directory containing a
|
|
15
|
+
This script generates a `modpack.lock` file in the current directory containing a plaintext representation of the modpack's contents. This object contains the metadata for the content available on Modrinth, including hashes, versions, names, download URLs and more. An optional `modpack.json` file can also be created to store your modpack's metadata (name, version, modloader, dependencies, etc.) alongside the lockfile. This setup allows for easy diffing and clear version history.
|
|
17
16
|
|
|
18
17
|
> While an `.mrpack` file could be used to track changes to the modpack, it is a large, binary file that cannot be diffed and can contain large amounts of duplicate data from the rest of the repository.
|
|
19
18
|
|
|
@@ -33,38 +32,88 @@ Alternatively, you can run it using `npx`:
|
|
|
33
32
|
npx modpack-lock
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
##
|
|
35
|
+
## CLI
|
|
37
36
|
|
|
38
37
|
Navigate to your Minecraft profile directory (the folder containing `mods`, `resourcepacks`, `datapacks`, and `shaderpacks` folders) and run:
|
|
39
38
|
|
|
39
|
+
```bash
|
|
40
|
+
modpack-lock
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The script will:
|
|
44
|
+
|
|
45
|
+
1. Scan the `mods`, `resourcepacks`, `datapacks`, and `shaderpacks` directories for `.jar` and `.zip` files
|
|
46
|
+
2. Calculate SHA1 hashes for each file
|
|
47
|
+
3. Query the Modrinth API to find version information
|
|
48
|
+
4. Generate a `modpack.lock` file in the current directory
|
|
49
|
+
|
|
50
|
+
If a `modpack.json` file exists in the directory, the lockfile's dependency list will also be written to it. Run `modpack-lock init` to create this file.
|
|
51
|
+
|
|
52
|
+
Then, commit the `modpack.lock` (and `modpack.json`) to your repository and push it to your remote.
|
|
53
|
+
|
|
40
54
|
```text
|
|
41
|
-
Usage: modpack-lock [options]
|
|
55
|
+
Usage: modpack-lock [options] [command]
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
Creates a modpack lockfile for files hosted on Modrinth (mods, resource packs, shaders and datapacks)
|
|
44
58
|
|
|
45
59
|
Options:
|
|
60
|
+
-p, --path <path> Path to the modpack directory
|
|
46
61
|
-d, --dry-run Dry-run mode - no files will be written
|
|
62
|
+
|
|
63
|
+
GENERATION
|
|
47
64
|
-g, --gitignore Print .gitignore rules for files not hosted on Modrinth
|
|
48
65
|
-r, --readme Generate README.md files for each category
|
|
49
|
-
-p, --path <path> Path to the modpack directory
|
|
50
66
|
|
|
51
67
|
LOGGING
|
|
52
68
|
-q, --quiet Quiet mode - only show errors and warnings
|
|
53
69
|
-s, --silent Silent mode - no output
|
|
54
70
|
|
|
55
71
|
INFORMATION
|
|
56
|
-
-V
|
|
57
|
-
--help
|
|
72
|
+
-V output the version number
|
|
73
|
+
-h, --help display help for modpack-lock
|
|
74
|
+
|
|
75
|
+
Commands:
|
|
76
|
+
init [options] This utility will walk you through creating a modpack.json file. It only covers the most common items, and tries to guess sensible defaults.
|
|
58
77
|
```
|
|
59
78
|
|
|
60
|
-
The script will:
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
### Initialization
|
|
81
|
+
|
|
82
|
+
To initialize a new modpack, run:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
modpack-lock init
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This will create a `modpack.json` file that stores your modpack's metadata (name, version, author, etc.), including a list of dependency slugs. This file is optional, but when present, the main command will also write the lockfile dependencies to `modpack.json`. It will also regenerate the lockfile.
|
|
66
89
|
|
|
67
|
-
|
|
90
|
+
The interactive mode will prompt you for each field. Set their initial values using the available option flags. Use `--noninteractive` with the required options (`--author`, `--modloader`, `--targetMinecraftVersion`) to skip prompts.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Usage: modpack-lock init [options]
|
|
94
|
+
|
|
95
|
+
This utility will walk you through creating a modpack.json file. It only covers the most common items, and tries to guess sensible defaults.
|
|
96
|
+
|
|
97
|
+
Options:
|
|
98
|
+
-f, --folder <path> Path to the modpack directory
|
|
99
|
+
-n, --noninteractive Non-interactive mode - must provide options for required fields
|
|
100
|
+
|
|
101
|
+
MODPACK INFORMATION
|
|
102
|
+
--name <name> Modpack name; defaults to the directory name; required
|
|
103
|
+
--version <version> Modpack version; defaults to 1.0.0; required
|
|
104
|
+
--id <id> Modpack slug/ID; defaults to the directory name slugified; required
|
|
105
|
+
--description <description> Modpack description
|
|
106
|
+
--author <author> Modpack author; required
|
|
107
|
+
--projectUrl <projectUrl> Modpack URL
|
|
108
|
+
--sourceUrl <sourceUrl> Modpack source code URL
|
|
109
|
+
--license <license> Modpack license
|
|
110
|
+
--modloader <modloader> Modpack modloader; required
|
|
111
|
+
--targetModloaderVersion <targetModloaderVersion> Target modloader version
|
|
112
|
+
--targetMinecraftVersion <targetMinecraftVersion> Target Minecraft version; required
|
|
113
|
+
|
|
114
|
+
INFORMATION
|
|
115
|
+
--help display help for modpack-lock init
|
|
116
|
+
```
|
|
68
117
|
|
|
69
118
|
> [!TIP]
|
|
70
119
|
>
|
|
@@ -82,9 +131,26 @@ Then, commit the `modpack.lock` file to your repository and push it to your remo
|
|
|
82
131
|
> # !mods/example.jar
|
|
83
132
|
> ```
|
|
84
133
|
|
|
85
|
-
##
|
|
134
|
+
## API
|
|
135
|
+
|
|
136
|
+
For programmatic usage, `modpack-lock` exports these functions:
|
|
137
|
+
|
|
138
|
+
- `getModpackInfo()`
|
|
139
|
+
- `getLockfile()`
|
|
140
|
+
- `generateJson()`
|
|
141
|
+
- `generateGitignoreRules()`
|
|
142
|
+
- `generateReadmeFiles()`
|
|
143
|
+
- `generateLockfile()`
|
|
144
|
+
- `generateModpackFiles()`
|
|
145
|
+
- `promptUserForInfo()`
|
|
146
|
+
|
|
147
|
+
See the [API documentation](https://nickesc.github.io/modpack-lock) for full details.
|
|
148
|
+
|
|
149
|
+
## File Formats
|
|
150
|
+
|
|
151
|
+
### `modpack.lock`
|
|
86
152
|
|
|
87
|
-
The
|
|
153
|
+
The lockfile contains metadata about Modrinth-hosted files found in your modpack directories:
|
|
88
154
|
|
|
89
155
|
```json
|
|
90
156
|
{
|
|
@@ -111,6 +177,34 @@ The `modpack.lock` file has the following structure:
|
|
|
111
177
|
}
|
|
112
178
|
```
|
|
113
179
|
|
|
180
|
+
### `modpack.json`
|
|
181
|
+
|
|
182
|
+
If created via `modpack-lock init`, the JSON file combines your modpack metadata with the lockfile's dependency list:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"name": "My Modpack",
|
|
187
|
+
"version": "1.0.0",
|
|
188
|
+
"id": "my-modpack",
|
|
189
|
+
"description": "",
|
|
190
|
+
"author": "name",
|
|
191
|
+
"projectUrl": "",
|
|
192
|
+
"sourceUrl": "",
|
|
193
|
+
"license": "",
|
|
194
|
+
"modloader": "modloader",
|
|
195
|
+
"targetModloaderVersion": "",
|
|
196
|
+
"targetMinecraftVersion": "x.y.z",
|
|
197
|
+
"dependencies": {
|
|
198
|
+
"mods": [ ... ],
|
|
199
|
+
"resourcepacks": [ ... ],
|
|
200
|
+
"datapacks": [ ... ],
|
|
201
|
+
"shaderpacks": [ ... ]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
114
206
|
## License
|
|
115
207
|
|
|
116
208
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.
|
|
209
|
+
|
|
210
|
+
<a href="https://github.com/nickesc/modpack-lock/blob/main/LICENSE"><img class="badge-img" alt="GitHub License" src="https://img.shields.io/github/license/nickesc/modpack-lock?style=for-the-badge&labelColor=%23333&color=%230070ff"></a>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modpack-lock",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Creates 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"
|
|
7
7
|
},
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/nickesc/modpack-lock.git"
|
|
11
11
|
},
|
|
12
|
+
"homepage": "https://nickesc.github.io/modpack-lock",
|
|
12
13
|
"license": "MIT",
|
|
13
14
|
"author": "N. Escobar <nick@nescobar.media> (https://nickesc.github.io/)",
|
|
14
15
|
"type": "module",
|
|
15
|
-
"main": "src/
|
|
16
|
+
"main": "src/modpack-lock.js",
|
|
16
17
|
"bin": {
|
|
17
|
-
"modpack-lock": "src/
|
|
18
|
+
"modpack-lock": "src/cli.js"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"modrinth",
|
|
@@ -23,23 +24,29 @@
|
|
|
23
24
|
"lockfile"
|
|
24
25
|
],
|
|
25
26
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
27
|
+
"node": ">=22.0.0"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
30
|
"test": "vitest --run",
|
|
30
|
-
"start": "node src/
|
|
31
|
-
"
|
|
31
|
+
"start": "node src/cli.js",
|
|
32
|
+
"docs": "typedoc",
|
|
33
|
+
"modpack-lock": "node src/cli.js"
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
36
|
"README.md",
|
|
35
37
|
"LICENSE",
|
|
36
38
|
"package.json",
|
|
37
|
-
"src
|
|
39
|
+
"src/**/*.js"
|
|
38
40
|
],
|
|
39
41
|
"dependencies": {
|
|
40
|
-
"commander": "^14.0.2"
|
|
42
|
+
"commander": "^14.0.2",
|
|
43
|
+
"prompts": "^2.4.2",
|
|
44
|
+
"slugify": "^1.6.6"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
48
|
+
"typedoc": "^0.28.16",
|
|
49
|
+
"typedoc-github-theme": "^0.3.1",
|
|
43
50
|
"unzipper": "^0.12.3",
|
|
44
51
|
"vitest": "^4.0.16"
|
|
45
52
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env NODE_OPTIONS=--no-warnings node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import slugify from 'slugify';
|
|
6
|
+
import {generateLockfile} from './generate_lockfile.js';
|
|
7
|
+
import generateJson from './generate_json.js';
|
|
8
|
+
import { generateModpackFiles } from './modpack-lock.js';
|
|
9
|
+
import promptUserForInfo from './modpack_info.js';
|
|
10
|
+
import { getModpackInfo } from './directory_scanning.js';
|
|
11
|
+
import * as config from './config/index.js';
|
|
12
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const modpackLock = new Command('modpack-lock');
|
|
16
|
+
|
|
17
|
+
const originalLogs = {
|
|
18
|
+
log: console.log,
|
|
19
|
+
info: console.info,
|
|
20
|
+
warn: console.warn,
|
|
21
|
+
error: console.error,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Silence all console.log output
|
|
26
|
+
*/
|
|
27
|
+
function quietConsole(silent = false) {
|
|
28
|
+
console.log = () => { };
|
|
29
|
+
console.info = () => { };
|
|
30
|
+
if (silent) {
|
|
31
|
+
console.warn = () => { };
|
|
32
|
+
console.error = () => { };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Restore the console's original functions
|
|
38
|
+
*/
|
|
39
|
+
function restoreConsole() {
|
|
40
|
+
console.log = originalLogs.log;
|
|
41
|
+
console.info = originalLogs.info;
|
|
42
|
+
console.warn = originalLogs.warn;
|
|
43
|
+
console.error = originalLogs.error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
modpackLock
|
|
47
|
+
.name(pkg.name)
|
|
48
|
+
.description(pkg.description)
|
|
49
|
+
.summary("Create a modpack lockfile")
|
|
50
|
+
.optionsGroup("Options:")
|
|
51
|
+
.option('-p, --path <path>', 'Path to the modpack directory')
|
|
52
|
+
.option('-d, --dry-run', 'Dry-run mode - no files will be written')
|
|
53
|
+
.optionsGroup("GENERATION")
|
|
54
|
+
.option('-g, --gitignore', 'Print .gitignore rules for files not hosted on Modrinth')
|
|
55
|
+
.option('-r, --readme', 'Generate README.md files for each category')
|
|
56
|
+
.optionsGroup("LOGGING")
|
|
57
|
+
.option('-q, --quiet', 'Quiet mode - only show errors and warnings')
|
|
58
|
+
.option('-s, --silent', 'Silent mode - no output')
|
|
59
|
+
.optionsGroup("INFORMATION")
|
|
60
|
+
.helpOption("-h, --help", `display help for ${pkg.name}`)
|
|
61
|
+
.version(pkg.version, '-V')
|
|
62
|
+
.action(async (options) => {
|
|
63
|
+
try {
|
|
64
|
+
const currDir = options.path || process.cwd();
|
|
65
|
+
|
|
66
|
+
if (options.quiet) {
|
|
67
|
+
quietConsole();
|
|
68
|
+
} else if (options.silent) {
|
|
69
|
+
quietConsole(true);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const modpackInfo = await getModpackInfo(currDir);
|
|
73
|
+
if (modpackInfo) {
|
|
74
|
+
await generateModpackFiles(modpackInfo, currDir, options);
|
|
75
|
+
} else {
|
|
76
|
+
await generateLockfile(currDir, options);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Error:', error);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const jsonDescription = `This utility will walk you through creating a ${config.MODPACK_JSON_NAME} file. It only covers the most common items, and tries to guess sensible defaults.`;
|
|
85
|
+
|
|
86
|
+
modpackLock.command('init')
|
|
87
|
+
.description(jsonDescription)
|
|
88
|
+
.optionsGroup("Options:")
|
|
89
|
+
.option('-f, --folder <path>', 'Path to the modpack directory')
|
|
90
|
+
.option("-n, --noninteractive", 'Non-interactive mode - must provide options for required fields')
|
|
91
|
+
.optionsGroup("MODPACK INFORMATION")
|
|
92
|
+
.option('--name <name>', 'Modpack name; defaults to the directory name')
|
|
93
|
+
.option('--version <version>', 'Modpack version; defaults to 1.0.0')
|
|
94
|
+
.option('--id <id>', 'Modpack slug/ID; defaults to the directory name slugified')
|
|
95
|
+
.option('--description <description>', 'Modpack description')
|
|
96
|
+
.option('--author <author>', 'Modpack author; required')
|
|
97
|
+
.option('--projectUrl <projectUrl>', 'Modpack URL')
|
|
98
|
+
.option('--sourceUrl <sourceUrl>', 'Modpack source code URL')
|
|
99
|
+
.option('--license <license>', 'Modpack license')
|
|
100
|
+
.option('--modloader <modloader>', 'Modpack modloader; required')
|
|
101
|
+
.option('--targetModloaderVersion <targetModloaderVersion>', 'Target modloader version')
|
|
102
|
+
.option('--targetMinecraftVersion <targetMinecraftVersion>', 'Target Minecraft version; required')
|
|
103
|
+
.optionsGroup("INFORMATION")
|
|
104
|
+
.helpOption("--help", `display help for ${pkg.name} init`)
|
|
105
|
+
.action(async (options) => {
|
|
106
|
+
const currDir = options.folder || process.cwd();
|
|
107
|
+
|
|
108
|
+
if (options.noninteractive) {
|
|
109
|
+
quietConsole();
|
|
110
|
+
if (!options.author || !options.modloader || !options.targetMinecraftVersion) {
|
|
111
|
+
console.error('Error: Must provide options for required fields');
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
} else {
|
|
115
|
+
const name = options.name || path.basename(currDir);
|
|
116
|
+
const modpackInfo = {
|
|
117
|
+
name: name,
|
|
118
|
+
version: options.version || '1.0.0',
|
|
119
|
+
id: slugify(options.id || name, config.SLUGIFY_OPTIONS),
|
|
120
|
+
description: options.description || '',
|
|
121
|
+
author: options.author,
|
|
122
|
+
projectUrl: options.projectUrl || '',
|
|
123
|
+
sourceUrl: options.sourceUrl || '',
|
|
124
|
+
license: options.license || '',
|
|
125
|
+
modloader: options.modloader,
|
|
126
|
+
targetModloaderVersion: options.targetModloaderVersion || '',
|
|
127
|
+
targetMinecraftVersion: options.targetMinecraftVersion,
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
await generateModpackFiles(modpackInfo, currDir, { dryRun: false });
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Error:', error);
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.log(jsonDescription);
|
|
138
|
+
console.log("\nSee `modpack-lock init --help` for definitive documentation on these fields and exactly what they do.\n");
|
|
139
|
+
console.log("Press ^C at any time to quit.\n");
|
|
140
|
+
try {
|
|
141
|
+
const modpackInfo = await promptUserForInfo({
|
|
142
|
+
name: options.name || path.basename(currDir),
|
|
143
|
+
version: options.version,
|
|
144
|
+
id: options.id,
|
|
145
|
+
description: options.description,
|
|
146
|
+
author: options.author,
|
|
147
|
+
projectUrl: options.projectUrl,
|
|
148
|
+
sourceUrl: options.sourceUrl,
|
|
149
|
+
license: options.license,
|
|
150
|
+
modloader: options.modloader,
|
|
151
|
+
targetModloaderVersion: options.targetModloaderVersion,
|
|
152
|
+
targetMinecraftVersion: options.targetMinecraftVersion,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await generateModpackFiles(modpackInfo, currDir, { dryRun: false });
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error('Error:', error);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
modpackLock.parseAsync().catch((error) => {
|
|
164
|
+
console.error('Error:', error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Modrinth API base URL */
|
|
2
|
+
export const MODRINTH_API_BASE = 'https://api.modrinth.com/v2';
|
|
3
|
+
|
|
4
|
+
/** Modrinth version files endpoint */
|
|
5
|
+
export const MODRINTH_VERSION_FILES_ENDPOINT = `${MODRINTH_API_BASE}/version_files`;
|
|
6
|
+
|
|
7
|
+
/** Modrinth projects endpoint */
|
|
8
|
+
export const MODRINTH_PROJECTS_ENDPOINT = `${MODRINTH_API_BASE}/projects`;
|
|
9
|
+
|
|
10
|
+
/** Modrinth users endpoint */
|
|
11
|
+
export const MODRINTH_USERS_ENDPOINT = `${MODRINTH_API_BASE}/users`;
|
|
12
|
+
|
|
13
|
+
/** Batch size for Modrinth API requests */
|
|
14
|
+
export const BATCH_SIZE = 100;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Lockfile format version -- increment on changes to the format */
|
|
2
|
+
export const LOCKFILE_VERSION = "1.0.1";
|
|
3
|
+
|
|
4
|
+
/** Required fields for the modpack information */
|
|
5
|
+
export const MODPACK_INFO_REQUIRED_FIELDS = [
|
|
6
|
+
"name",
|
|
7
|
+
"version",
|
|
8
|
+
"id",
|
|
9
|
+
"author",
|
|
10
|
+
"modloader",
|
|
11
|
+
"targetMinecraftVersion"
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Dependency categories, corresponds to folders in Minecraft profile */
|
|
15
|
+
export const DEPENDENCY_CATEGORIES = [
|
|
16
|
+
"mods",
|
|
17
|
+
"resourcepacks",
|
|
18
|
+
"shaderpacks",
|
|
19
|
+
"datapacks"
|
|
20
|
+
];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ModpackInfo
|
|
3
|
+
* Contains information about the modpack that is not dependent on the lockfile.
|
|
4
|
+
* @property {string} name - The name of the modpack (Required)
|
|
5
|
+
* @property {string} version - The version of the modpack (Required)
|
|
6
|
+
* @property {string} description - The description of the modpack
|
|
7
|
+
* @property {string} id - The slug/ID of the modpack (Required)
|
|
8
|
+
* @property {string} author - The author of the modpack (Required)
|
|
9
|
+
* @property {string} projectUrl - The project URL of the modpack
|
|
10
|
+
* @property {string} sourceUrl - The source code URL of the modpack
|
|
11
|
+
* @property {string} license - The license of the modpack
|
|
12
|
+
* @property {string} modloader - The modloader of the modpack (Required)
|
|
13
|
+
* @property {string} targetModloaderVersion - The target modloader version of the modpack
|
|
14
|
+
* @property {string} targetMinecraftVersion - The target Minecraft version of the modpack (Required)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} Lockfile
|
|
19
|
+
* Contains information about the modpack dependencies and their versions.
|
|
20
|
+
* @property {string} version - The version of the modpack
|
|
21
|
+
* @property {string} generated - The date and time the lockfile was generated
|
|
22
|
+
* @property {number} total - The total number of files in the modpack
|
|
23
|
+
* @property {Object} counts - The counts object
|
|
24
|
+
* @property {number} counts.mods - The mods count
|
|
25
|
+
* @property {number} counts.resourcepacks - The resourcepacks count
|
|
26
|
+
* @property {number} counts.datapacks - The datapacks count
|
|
27
|
+
* @property {number} counts.shaderpacks - The shaderpacks count
|
|
28
|
+
* @property {Object} dependencies - The dependencies object
|
|
29
|
+
* @property {Array<Object>} dependencies.mods - The mods object
|
|
30
|
+
* @property {Array<Object>} dependencies.resourcepacks - The resourcepacks object
|
|
31
|
+
* @property {Array<Object>} dependencies.datapacks - The datapacks object
|
|
32
|
+
* @property {Array<Object>} dependencies.shaderpacks - The shaderpacks object
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} Options
|
|
37
|
+
* Contains options for the generation of the modpack files.
|
|
38
|
+
* @property {boolean} dryRun - Whether to dry run the generation
|
|
39
|
+
* @property {boolean} quiet - Whether to quiet the console output
|
|
40
|
+
* @property {boolean} silent - Whether to silent the console output
|
|
41
|
+
* @property {boolean} gitignore - Whether to generate a .gitignore file
|
|
42
|
+
* @property {boolean} readme - Whether to generate README.md files
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import * as files from './config/files.js';
|
|
5
|
+
import * as constants from './config/constants.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
|
|
9
|
+
* @typedef {import('./config/types.js').Lockfile} Lockfile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the directories to scan for modpack files
|
|
14
|
+
* @param {string} directoryPath - The path to the directory to scan
|
|
15
|
+
* @returns {Array<Object>} The directories to scan
|
|
16
|
+
*/
|
|
17
|
+
export function getScanDirectories(directoryPath) {
|
|
18
|
+
const scanDirectories = [];
|
|
19
|
+
for (const category of constants.DEPENDENCY_CATEGORIES) {
|
|
20
|
+
scanDirectories.push({ name: category, path: path.join(directoryPath, category) });
|
|
21
|
+
}
|
|
22
|
+
return scanDirectories;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate SHA1 hash of a file
|
|
27
|
+
*/
|
|
28
|
+
async function calculateSHA1(filePath) {
|
|
29
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
30
|
+
return crypto.createHash('sha1').update(fileBuffer).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find all files in a directory
|
|
35
|
+
*/
|
|
36
|
+
async function findFiles(dirPath) {
|
|
37
|
+
const files = [];
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.isFile() && (entry.name.endsWith('.jar') || entry.name.endsWith('.zip'))) {
|
|
44
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
45
|
+
files.push(fullPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error.code !== 'ENOENT') {
|
|
50
|
+
console.warn(`Warning: Could not read directory ${dirPath}: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
files.sort((a, b) => a.localeCompare(b, 'en', { numeric: true, sensitivity: 'base' }));
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Scan a directory and return file info with hashes
|
|
60
|
+
*/
|
|
61
|
+
export async function scanDirectory(dirInfo, workspaceRoot) {
|
|
62
|
+
const files = await findFiles(dirInfo.path);
|
|
63
|
+
const fileEntries = [];
|
|
64
|
+
|
|
65
|
+
for (const filePath of files) {
|
|
66
|
+
try {
|
|
67
|
+
const hash = await calculateSHA1(filePath);
|
|
68
|
+
const relativePath = path.relative(workspaceRoot, filePath);
|
|
69
|
+
|
|
70
|
+
fileEntries.push({
|
|
71
|
+
path: relativePath,
|
|
72
|
+
fullPath: filePath,
|
|
73
|
+
hash: hash,
|
|
74
|
+
category: dirInfo.name,
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.warn(`Warning: Could not hash file ${filePath}: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return fileEntries;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Scan for existing JSON file and return the JSON object if it exists
|
|
86
|
+
*/
|
|
87
|
+
async function getJsonFile(directoryPath, filename) {
|
|
88
|
+
const jsonPath = path.join(directoryPath, filename);
|
|
89
|
+
// try to read the file
|
|
90
|
+
try {
|
|
91
|
+
const fileContent = await fs.readFile(jsonPath, 'utf-8');
|
|
92
|
+
return JSON.parse(fileContent);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.code !== 'ENOENT') {
|
|
95
|
+
throw new Error(`Error: Could not read file ${jsonPath}: ${error.message}`);
|
|
96
|
+
} else {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the modpack info from the JSON file if it exists
|
|
104
|
+
* @param {string} directoryPath - The path to the directory to scan
|
|
105
|
+
* @returns {Promise<ModpackInfo|null>} The modpack info JSON object if the file exists, otherwise null
|
|
106
|
+
*/
|
|
107
|
+
export async function getModpackInfo(directoryPath) {
|
|
108
|
+
return getJsonFile(directoryPath, files.MODPACK_JSON_NAME);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the lockfile file if it exists
|
|
113
|
+
* @param {string} directoryPath - The path to the directory to scan
|
|
114
|
+
* @returns {Lockfile|null} The JSON object if the file exists, otherwise null
|
|
115
|
+
*/
|
|
116
|
+
export async function getLockfile(directoryPath) {
|
|
117
|
+
return getJsonFile(directoryPath, files.MODPACK_LOCKFILE_NAME);
|
|
118
|
+
}
|