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.
@@ -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.trim().length === 0) {
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
- * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
17
- * @typedef {import('./config/types.js').Options} Options
18
- * @typedef {import('./config/types.js').Lockfile} Lockfile
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 default async function promptUserForInfo(defaults = {}) {
27
- let name = await prompts({
28
- type: 'text',
29
- name: 'name',
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
- type: 'text',
72
- name: 'projectUrl',
73
- message: 'Modpack URL',
74
- initial: defaults.projectUrl || config.DEFAULT_PROJECT_URL(id.id),
75
- },
76
- {
77
- type: 'text',
78
- name: 'sourceUrl',
79
- message: 'Modpack source code URL',
80
- initial: defaults.sourceUrl || config.DEFAULT_SOURCE_URL(id.id, author.author),
81
- },
82
- {
83
- type: 'text',
84
- name: 'license',
85
- message: 'Modpack license',
86
- initial: defaults.license || config.DEFAULT_MODPACK_LICENSE,
87
- },
88
- {
89
- type: 'autocomplete',
90
- name: 'modloader',
91
- message: 'Modpack modloader',
92
- initial: defaults.modloader,
93
- choices: [
94
- { title: 'fabric' },
95
- { title: 'forge' },
96
- { title: 'quilt' },
97
- { title: 'neoforge' },
98
- { title: 'sponge' },
99
- { title: 'paper' },
100
- { title: 'velocity' },
101
- { title: 'bungeecord' },
102
- { title: 'waterfall' },
103
- { title: 'travertia' },
104
- { title: 'nukkit' },
105
- { title: 'pufferfish' },
106
- { title: 'purpur' },
107
- ],
108
- validate: (value) => {
109
- return validateNotEmpty(value, 'Modloader');
110
- },
111
- },
112
- {
113
- type: 'text',
114
- name: 'targetModloaderVersion',
115
- message: 'Target modloader version',
116
- initial: defaults.targetModloaderVersion,
117
- },
118
- {
119
- type: 'text',
120
- name: 'targetMinecraftVersion',
121
- message: 'Target Minecraft version',
122
- initial: defaults.targetMinecraftVersion,
123
- validate: (value) => {
124
- return validateNotEmpty(value, 'Minecraft Version');
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
- let modpackInfo = {...name, ...version, ...id, ...description, ...author, ...answers};
130
- if (Object.keys(modpackInfo).length < 11) {
131
- console.warn('Modpack initialization was interrupted');
132
- process.exit(1);
133
- }
134
- return modpackInfo;
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 querying Modrinth API: ${error.message}`);
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
+ }