modpack-lock 0.4.0 → 0.5.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 +82 -59
- package/package.json +1 -1
- package/src/cli.js +56 -27
- package/src/config/api.js +18 -0
- package/src/config/constants.js +18 -0
- package/src/config/defaults.js +58 -0
- package/src/config/files.js +9 -0
- package/src/config/index.js +1 -0
- package/src/config/options.js +15 -1
- package/src/config/strings.js +85 -0
- package/src/config/types.js +30 -0
- package/src/directory_scanning.js +4 -5
- package/src/generate_json.js +3 -3
- package/src/generate_license.js +50 -0
- package/src/generate_lockfile.js +100 -19
- package/src/github_interactions.js +73 -0
- package/src/modpack-lock.js +5 -3
- package/src/modpack_info.js +191 -111
- package/src/modrinth_interactions.js +57 -1
package/src/modpack_info.js
CHANGED
|
@@ -1,135 +1,215 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import slugify from 'slugify';
|
|
3
3
|
import * as config from './config/index.js';
|
|
4
|
+
import { getLicenseList, getLicenseText } from './github_interactions.js';
|
|
5
|
+
import { getMinecraftVersions, getModloaders } from './modrinth_interactions.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Capitalizes a string
|
|
13
|
+
*/
|
|
14
|
+
function capitalize(string) {
|
|
15
|
+
return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
|
|
16
|
+
}
|
|
4
17
|
|
|
5
18
|
/**
|
|
6
19
|
* Validate that a value is not empty
|
|
7
20
|
*/
|
|
8
21
|
function validateNotEmpty(value, field) {
|
|
9
|
-
if (value
|
|
22
|
+
if (value === undefined || value?.trim().length === 0) {
|
|
10
23
|
return `${field} cannot be empty`;
|
|
11
24
|
}
|
|
12
25
|
return true;
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
* Returns a required text prompt
|
|
30
|
+
*/
|
|
31
|
+
function requiredText(name, message, initial) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'text',
|
|
34
|
+
name: name,
|
|
35
|
+
message: `${capitalize(message)}`,
|
|
36
|
+
initial: initial,
|
|
37
|
+
validate: (value) => {
|
|
38
|
+
return validateNotEmpty(value, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns an optional text prompt
|
|
45
|
+
*/
|
|
46
|
+
function optionalText(name, message, initial) {
|
|
47
|
+
return {
|
|
48
|
+
type: 'text',
|
|
49
|
+
name: name,
|
|
50
|
+
message: `${capitalize(message)}`,
|
|
51
|
+
initial: initial,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get an other answer from the user
|
|
19
57
|
*/
|
|
58
|
+
async function getOtherAnswer(value, message, initial) {
|
|
59
|
+
if (value && value !== config.OTHER_OPTION.value) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
const question = await prompts(
|
|
63
|
+
requiredText(
|
|
64
|
+
'other',
|
|
65
|
+
message,
|
|
66
|
+
initial
|
|
67
|
+
),
|
|
68
|
+
config.PROMPTS_OPTIONS
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return question.other || config.OTHER_OPTION.value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a required autocomplete prompt with a fallback to the other option
|
|
76
|
+
*/
|
|
77
|
+
function requiredAutocomplete(name, message, initial, choices, defaultValue) {
|
|
78
|
+
initial = initial || defaultValue || config.OTHER_OPTION.value;
|
|
79
|
+
if (initial && !choices.some(choice => choice.value === initial)) {
|
|
80
|
+
choices.push({ title: initial, value: initial });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
type: 'autocomplete',
|
|
85
|
+
name: name,
|
|
86
|
+
message: `${capitalize(message)}`,
|
|
87
|
+
initial: initial,
|
|
88
|
+
choices: choices,
|
|
89
|
+
fallback: config.OTHER_OPTION.value,
|
|
90
|
+
format: async (value) => {
|
|
91
|
+
return await getOtherAnswer(value, ` └─ Other ${message}`, initial);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns an confirmation prompt to generate an optional file
|
|
98
|
+
*/
|
|
99
|
+
function fileGenerationConfirm(name, message, showPrompt) {
|
|
100
|
+
return {
|
|
101
|
+
type: showPrompt ? 'confirm' : null,
|
|
102
|
+
name: name,
|
|
103
|
+
message: `${capitalize(message)}`,
|
|
104
|
+
initial: true,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
20
107
|
|
|
21
108
|
/**
|
|
22
109
|
* Get user input for modpack information
|
|
23
110
|
* @param {ModpackInfo} defaults - The initial/default modpack information
|
|
24
111
|
* @returns {Promise<ModpackInfo>} The modpack information from the user
|
|
25
112
|
*/
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
message: 'Modpack name',
|
|
31
|
-
initial: defaults.name,
|
|
32
|
-
validate: (value) => {
|
|
33
|
-
return validateNotEmpty(value, 'Name');
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
let version = await prompts({
|
|
37
|
-
type: 'text',
|
|
38
|
-
name: 'version',
|
|
39
|
-
message: 'Modpack version',
|
|
40
|
-
initial: defaults.version || config.DEFAULT_MODPACK_VERSION,
|
|
41
|
-
validate: (value) => {
|
|
42
|
-
return validateNotEmpty(value, 'Version');
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
let id = await prompts({
|
|
46
|
-
type: 'text',
|
|
47
|
-
name: 'id',
|
|
48
|
-
message: 'Modpack slug/ID',
|
|
49
|
-
initial: slugify(defaults.id || name.name, config.SLUGIFY_OPTIONS),
|
|
50
|
-
validate: (value) => {
|
|
51
|
-
return validateNotEmpty(value, 'ID');
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
let description = await prompts({
|
|
55
|
-
type: 'text',
|
|
56
|
-
name: 'description',
|
|
57
|
-
message: 'Modpack description',
|
|
58
|
-
initial: defaults.description,
|
|
59
|
-
});
|
|
60
|
-
let author = await prompts({
|
|
61
|
-
type: 'text',
|
|
62
|
-
name: 'author',
|
|
63
|
-
message: 'Modpack author',
|
|
64
|
-
initial: defaults.author,
|
|
65
|
-
validate: (value) => {
|
|
66
|
-
return validateNotEmpty(value, 'Author');
|
|
67
|
-
},
|
|
68
|
-
});
|
|
113
|
+
export async function promptUserForInfo(defaults = {}) {
|
|
114
|
+
const licenseList = await getLicenseList();
|
|
115
|
+
const minecraftVersions = await getMinecraftVersions();
|
|
116
|
+
const modloaders = await getModloaders();
|
|
69
117
|
let answers = await prompts([
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
name
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
requiredText(
|
|
119
|
+
'name',
|
|
120
|
+
config.infoFields.name.prompt,
|
|
121
|
+
defaults.name
|
|
122
|
+
),
|
|
123
|
+
requiredText(
|
|
124
|
+
'version',
|
|
125
|
+
config.infoFields.version.prompt,
|
|
126
|
+
defaults.version || config.DEFAULT_MODPACK_VERSION
|
|
127
|
+
),
|
|
128
|
+
requiredText(
|
|
129
|
+
'id',
|
|
130
|
+
config.infoFields.id.prompt,
|
|
131
|
+
(prev, values) => slugify(defaults.id || values.name, config.SLUGIFY_OPTIONS)
|
|
132
|
+
),
|
|
133
|
+
optionalText(
|
|
134
|
+
'description',
|
|
135
|
+
config.infoFields.description.prompt,
|
|
136
|
+
defaults.description
|
|
137
|
+
),
|
|
138
|
+
requiredText(
|
|
139
|
+
'author',
|
|
140
|
+
config.infoFields.author.prompt,
|
|
141
|
+
defaults.author
|
|
142
|
+
),
|
|
143
|
+
optionalText(
|
|
144
|
+
'projectUrl',
|
|
145
|
+
config.infoFields.projectUrl.prompt,
|
|
146
|
+
(prev, values) => defaults.projectUrl || config.DEFAULT_PROJECT_URL(values.id)
|
|
147
|
+
),
|
|
148
|
+
optionalText(
|
|
149
|
+
'sourceUrl',
|
|
150
|
+
config.infoFields.sourceUrl.prompt,
|
|
151
|
+
(prev, values) => defaults.sourceUrl || config.DEFAULT_SOURCE_URL(values.id, values.author)
|
|
152
|
+
),
|
|
153
|
+
requiredAutocomplete(
|
|
154
|
+
'license',
|
|
155
|
+
config.infoFields.license.prompt,
|
|
156
|
+
defaults.license,
|
|
157
|
+
licenseList,
|
|
158
|
+
config.DEFAULT_MODPACK_LICENSE
|
|
159
|
+
),
|
|
160
|
+
requiredAutocomplete(
|
|
161
|
+
'modloader',
|
|
162
|
+
config.infoFields.modloader.prompt,
|
|
163
|
+
defaults.modloader,
|
|
164
|
+
modloaders,
|
|
165
|
+
config.FALLBACK_MODLOADERS[0].value
|
|
166
|
+
),
|
|
167
|
+
optionalText(
|
|
168
|
+
'targetModloaderVersion',
|
|
169
|
+
config.infoFields.targetModloaderVersion.prompt,
|
|
170
|
+
defaults.targetModloaderVersion
|
|
171
|
+
),
|
|
172
|
+
requiredAutocomplete(
|
|
173
|
+
'targetMinecraftVersion',
|
|
174
|
+
config.infoFields.targetMinecraftVersion.prompt,
|
|
175
|
+
defaults.targetMinecraftVersion,
|
|
176
|
+
minecraftVersions,
|
|
177
|
+
minecraftVersions[0].value
|
|
178
|
+
)
|
|
179
|
+
], config.PROMPTS_OPTIONS);
|
|
128
180
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
181
|
+
return answers;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Prompt the user about adding the license text to the modpack
|
|
186
|
+
* @param {ModpackInfo} modpackInfo - The modpack information
|
|
187
|
+
* @returns {Promise<Object>} The answers from the user
|
|
188
|
+
*/
|
|
189
|
+
export async function promptUserAboutOptionalFiles(modpackInfo, defaults = {}) {
|
|
190
|
+
|
|
191
|
+
const licenseText = await getLicenseText(modpackInfo.license);
|
|
192
|
+
const answers = await (prompts([
|
|
193
|
+
fileGenerationConfirm(
|
|
194
|
+
'addLicense',
|
|
195
|
+
`${config.fileFields.addLicense.prompt}?`,
|
|
196
|
+
licenseText && defaults.addLicense === undefined
|
|
197
|
+
),
|
|
198
|
+
fileGenerationConfirm(
|
|
199
|
+
'addReadme',
|
|
200
|
+
`${config.fileFields.addReadme.prompt}?`,
|
|
201
|
+
defaults.addReadme === undefined
|
|
202
|
+
),
|
|
203
|
+
fileGenerationConfirm(
|
|
204
|
+
'addGitignore',
|
|
205
|
+
`${config.fileFields.addGitignore.prompt}?`,
|
|
206
|
+
defaults.addGitignore === undefined
|
|
207
|
+
),
|
|
208
|
+
], config.PROMPTS_OPTIONS));
|
|
209
|
+
|
|
210
|
+
answers.addLicense = answers.addLicense === undefined ? (licenseText ? defaults.addLicense : false) : answers.addLicense;
|
|
211
|
+
answers.addReadme = answers.addReadme === undefined ? defaults.addReadme : answers.addReadme;
|
|
212
|
+
answers.addGitignore = answers.addGitignore === undefined ? defaults.addGitignore : answers.addGitignore;
|
|
213
|
+
|
|
214
|
+
return answers;
|
|
135
215
|
}
|
|
@@ -38,7 +38,7 @@ export async function getVersionsFromHashes(hashes) {
|
|
|
38
38
|
|
|
39
39
|
return await response.json();
|
|
40
40
|
} catch (error) {
|
|
41
|
-
console.error(`Error
|
|
41
|
+
console.error(`Error fetching version information from hashes: ${error.message}`);
|
|
42
42
|
throw error;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -106,3 +106,59 @@ export async function getUsers(userIds) {
|
|
|
106
106
|
|
|
107
107
|
return results;
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fetch Minecraft versions from Modrinth
|
|
112
|
+
* @returns {Promise<Array<Object>>} The Minecraft versions
|
|
113
|
+
*/
|
|
114
|
+
export async function getMinecraftVersions() {
|
|
115
|
+
try {
|
|
116
|
+
const url = config.MODRINTH_MINECRAFT_VERSIONS_ENDPOINT;
|
|
117
|
+
const response = await fetch(url);
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const errorText = await response.text();
|
|
120
|
+
throw new Error(`Modrinth API error (${response.status}): ${errorText}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const json = await response.json();
|
|
124
|
+
if (json) {
|
|
125
|
+
//sort by version type (in the order of the MINECRAFT_VERSION_TYPES array)
|
|
126
|
+
json.sort((a, b) => {
|
|
127
|
+
return config.MINECRAFT_VERSION_TYPES.indexOf(a.version_type) - config.MINECRAFT_VERSION_TYPES.indexOf(b.version_type);
|
|
128
|
+
});
|
|
129
|
+
return json.map(version => ({ title: version.version, value: version.version }));
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error();
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.warn(`Warning: could not fetch Minecraft versions. Using fallbacks.`, error);
|
|
136
|
+
return config.FALLBACK_TARGET_MINECRAFT_VERSIONS;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fetch Modloaders from Modrinth
|
|
142
|
+
* @returns {Promise<Array<Object>>} The Modloaders
|
|
143
|
+
*/
|
|
144
|
+
export async function getModloaders() {
|
|
145
|
+
try {
|
|
146
|
+
const url = config.MODRINTH_MODLOADERS_ENDPOINT;
|
|
147
|
+
const response = await fetch(url);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const errorText = await response.text();
|
|
150
|
+
throw new Error(`Modrinth API error (${response.status}): ${errorText}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const json = await response.json();
|
|
154
|
+
if (json) {
|
|
155
|
+
return json.map(loader => ({ title: loader.name, value: loader.name }));
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error();
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn(`Warning: could not fetch Modloaders. Using fallbacks.`, error);
|
|
162
|
+
return config.FALLBACK_MODLOADERS;
|
|
163
|
+
}
|
|
164
|
+
}
|