modpack-lock 0.2.0 → 0.3.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 +111 -17
- package/package.json +16 -9
- package/src/cli.js +165 -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 +134 -0
- package/src/modrinth_interactions.js +108 -0
- package/src/generate-lockfile.js +0 -517
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getProjects } from './modrinth_interactions.js';
|
|
4
|
+
import * as config from './config/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
|
|
8
|
+
* @typedef {import('./config/types.js').Options} Options
|
|
9
|
+
* @typedef {import('./config/types.js').Lockfile} Lockfile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a JSON object from the modpack information and dependencies
|
|
14
|
+
*/
|
|
15
|
+
function createModpackJson(modpackInfo, dependencies) {
|
|
16
|
+
return {
|
|
17
|
+
...modpackInfo,
|
|
18
|
+
dependencies: dependencies,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Write modpack.json to disk
|
|
24
|
+
*/
|
|
25
|
+
async function writeJson(jsonObject, outputPath) {
|
|
26
|
+
const content = JSON.stringify(jsonObject, null, 2);
|
|
27
|
+
await fs.writeFile(path.join(outputPath, config.MODPACK_JSON_NAME), content, 'utf-8');
|
|
28
|
+
console.log(`${config.MODPACK_JSON_NAME} written to: ${path.join(outputPath, config.MODPACK_JSON_NAME)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate a modpack.json file
|
|
33
|
+
* @param {ModpackInfo} modpackInfo - The modpack information
|
|
34
|
+
* @param {Lockfile} lockfile - The lockfile
|
|
35
|
+
* @param {string} outputDir - The path to write the JSON object to
|
|
36
|
+
* @param {Options} options - The options object
|
|
37
|
+
* @returns {Promise<Lockfile>} The JSON file's object
|
|
38
|
+
*/
|
|
39
|
+
export default async function generateJson(modpackInfo, lockfile, outputDir, options = {}) {
|
|
40
|
+
// Validate modpack info
|
|
41
|
+
for (const field of config.MODPACK_INFO_REQUIRED_FIELDS) {
|
|
42
|
+
if (!modpackInfo[field]) {
|
|
43
|
+
throw new Error(`Modpack info is missing required field: ${field}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectIds = {};
|
|
48
|
+
const packDependencies = {};
|
|
49
|
+
for (const category of config.DEPENDENCY_CATEGORIES) {
|
|
50
|
+
projectIds[category] = new Set();
|
|
51
|
+
packDependencies[category] = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Collect project IDs from lockfile
|
|
55
|
+
if (lockfile) if (lockfile.dependencies) {
|
|
56
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (entry.version && entry.version.project_id) {
|
|
59
|
+
projectIds[category].add(entry.version.project_id);
|
|
60
|
+
} else {
|
|
61
|
+
packDependencies[category].push(entry.path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const allProjectIds = new Set();
|
|
66
|
+
for (const category of config.DEPENDENCY_CATEGORIES) {
|
|
67
|
+
for (const projectId of projectIds[category]) {
|
|
68
|
+
allProjectIds.add(projectId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fetch projects from Modrinth
|
|
73
|
+
const projects = await getProjects(Array.from(allProjectIds));
|
|
74
|
+
const projectsMap = {};
|
|
75
|
+
for (const project of projects) {
|
|
76
|
+
projectsMap[project.id] = project.slug;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add projects to dependencies by category
|
|
80
|
+
for (const category of config.DEPENDENCY_CATEGORIES) {
|
|
81
|
+
for (const projectId of projectIds[category]) {
|
|
82
|
+
const projectSlug = projectsMap[projectId];
|
|
83
|
+
if (projectSlug) {
|
|
84
|
+
packDependencies[category].push(projectSlug);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//packDependencies[category].push(...packDependencies[category].map(item => item.path));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Create modpack JSON object
|
|
92
|
+
const jsonObject = createModpackJson(modpackInfo, packDependencies);
|
|
93
|
+
|
|
94
|
+
// Write modpack JSON object to disk
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
console.log(`[DRY RUN] Would write ${config.MODPACK_JSON_NAME} to: ${path.join(outputDir, config.MODPACK_JSON_NAME)}`);
|
|
97
|
+
} else {
|
|
98
|
+
await writeJson(jsonObject, outputDir);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return jsonObject;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getVersionsFromHashes, getProjects, getUsers } from './modrinth_interactions.js';
|
|
4
|
+
import { getScanDirectories, scanDirectory } from './directory_scanning.js';
|
|
5
|
+
import * as config from './config/index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./config/types.js').Options} Options
|
|
9
|
+
* @typedef {import('./config/types.js').Lockfile} Lockfile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create empty lockfile structure
|
|
14
|
+
*/
|
|
15
|
+
function createEmptyLockfile() {
|
|
16
|
+
return {
|
|
17
|
+
version: config.LOCKFILE_VERSION,
|
|
18
|
+
generated: new Date().toISOString(),
|
|
19
|
+
total: 0,
|
|
20
|
+
counts: {},
|
|
21
|
+
dependencies: {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create lockfile structure from file info and version data
|
|
27
|
+
*/
|
|
28
|
+
function createLockfile(fileEntries, versionData) {
|
|
29
|
+
const lockfile = createEmptyLockfile();
|
|
30
|
+
|
|
31
|
+
// Organize by category
|
|
32
|
+
for (const fileInfo of fileEntries) {
|
|
33
|
+
const version = versionData[fileInfo.hash];
|
|
34
|
+
|
|
35
|
+
lockfile.dependencies[fileInfo.category] ||= [];
|
|
36
|
+
|
|
37
|
+
const entry = {
|
|
38
|
+
path: fileInfo.path,
|
|
39
|
+
version: version || null,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!version) {
|
|
43
|
+
console.warn(`Warning: File ${fileInfo.path} not found on Modrinth`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lockfile.dependencies[fileInfo.category].push(entry);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Calculate counts for each category
|
|
50
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
51
|
+
lockfile.counts[category] = entries.length;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lockfile.total = fileEntries.length;
|
|
55
|
+
|
|
56
|
+
return lockfile;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Write lockfile to disk
|
|
61
|
+
*/
|
|
62
|
+
async function writeLockfile(lockfile, outputPath) {
|
|
63
|
+
const content = JSON.stringify(lockfile, null, 2);
|
|
64
|
+
await fs.writeFile(outputPath, content, 'utf-8');
|
|
65
|
+
console.log(`Lockfile written to: ${outputPath}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate README.md content for a category
|
|
70
|
+
*/
|
|
71
|
+
function generateCategoryReadme(category, entries, projectsMap, usersMap) {
|
|
72
|
+
const categoryTitle = category.charAt(0).toUpperCase() + category.slice(1);
|
|
73
|
+
const lines = [`# ${categoryTitle}`, '', '| Name | Author | Version |', '|-|-|-|'];
|
|
74
|
+
|
|
75
|
+
// Map category to Modrinth URL path segment
|
|
76
|
+
const categoryPathMap = {};
|
|
77
|
+
for (const category of config.DEPENDENCY_CATEGORIES) {
|
|
78
|
+
categoryPathMap[category] = category === 'shaderpacks' ? 'shader' : category.toLowerCase().slice(0, -1);
|
|
79
|
+
}
|
|
80
|
+
const categoryPath = categoryPathMap[category] || 'project';
|
|
81
|
+
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const version = entry.version;
|
|
84
|
+
let nameCell = '';
|
|
85
|
+
let authorCell = '';
|
|
86
|
+
let versionCell = '';
|
|
87
|
+
|
|
88
|
+
if (version && version.project_id) {
|
|
89
|
+
const project = projectsMap[version.project_id];
|
|
90
|
+
const author = version.author_id ? usersMap[version.author_id] : null;
|
|
91
|
+
|
|
92
|
+
// Name column with icon and link
|
|
93
|
+
if (project) {
|
|
94
|
+
const projectName = project.title || project.slug || 'Unknown';
|
|
95
|
+
const projectSlug = project.slug || project.id;
|
|
96
|
+
const projectUrl = `https://modrinth.com/${categoryPath}/${projectSlug}`;
|
|
97
|
+
|
|
98
|
+
if (project.icon_url) {
|
|
99
|
+
nameCell = `<img alt="Icon" src="${project.icon_url}" height="20px"> [${projectName}](${projectUrl})`;
|
|
100
|
+
} else {
|
|
101
|
+
nameCell = `[${projectName}](${projectUrl})`;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// Project not found, use filename
|
|
105
|
+
const fileName = path.basename(entry.path);
|
|
106
|
+
nameCell = fileName;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Author column with avatar and link
|
|
110
|
+
if (author) {
|
|
111
|
+
const authorName = author.username || 'Unknown';
|
|
112
|
+
const authorUrl = `https://modrinth.com/user/${authorName}`;
|
|
113
|
+
|
|
114
|
+
if (author.avatar_url) {
|
|
115
|
+
authorCell = `<img alt="Avatar" src="${author.avatar_url}" height="20px"> [${authorName}](${authorUrl})`;
|
|
116
|
+
} else {
|
|
117
|
+
authorCell = `[${authorName}](${authorUrl})`;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
authorCell = 'Unknown';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Version column
|
|
124
|
+
versionCell = version.version_number || 'Unknown';
|
|
125
|
+
} else {
|
|
126
|
+
// File not found on Modrinth
|
|
127
|
+
const fileName = path.basename(entry.path);
|
|
128
|
+
nameCell = fileName;
|
|
129
|
+
authorCell = 'Unknown';
|
|
130
|
+
versionCell = '-';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
lines.push(`| ${nameCell} | ${authorCell} | ${versionCell} |`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lines.join('\n') + '\n';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate .gitignore rules for files not hosted on Modrinth
|
|
141
|
+
* @param {Lockfile} lockfile - The lockfile object
|
|
142
|
+
* @returns {string} The .gitignore rules
|
|
143
|
+
*/
|
|
144
|
+
export function generateGitignoreRules(lockfile) {
|
|
145
|
+
const rules = [];
|
|
146
|
+
const exceptions = [];
|
|
147
|
+
|
|
148
|
+
// Base ignore patterns for each category
|
|
149
|
+
for (const category of config.DEPENDENCY_CATEGORIES) {
|
|
150
|
+
rules.push(`${category}/*.${category === "mods" ? "jar" : "zip"}`);
|
|
151
|
+
}
|
|
152
|
+
rules.push('\n## Exceptions');
|
|
153
|
+
|
|
154
|
+
// Find files not hosted on Modrinth
|
|
155
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (entry.version === null) {
|
|
158
|
+
exceptions.push(`!${entry.path}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add exceptions if any
|
|
164
|
+
if (exceptions.length > 0) {
|
|
165
|
+
rules.push(...exceptions);
|
|
166
|
+
} else {
|
|
167
|
+
rules.push('# No exceptions needed - all files are hosted on Modrinth');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return rules.join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Generate the README.md files for each category
|
|
175
|
+
* @param {Lockfile} lockfile - The lockfile object
|
|
176
|
+
* @param {string} workingDir - The working directory
|
|
177
|
+
* @param {Options} options - The options object
|
|
178
|
+
*/
|
|
179
|
+
export async function generateReadmeFiles(lockfile, workingDir, options = {}) {
|
|
180
|
+
// Collect unique project IDs and author IDs from version data
|
|
181
|
+
const projectIds = new Set();
|
|
182
|
+
const authorIds = new Set();
|
|
183
|
+
|
|
184
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
if (entry.version && entry.version.project_id) {
|
|
187
|
+
projectIds.add(entry.version.project_id);
|
|
188
|
+
}
|
|
189
|
+
if (entry.version && entry.version.author_id) {
|
|
190
|
+
authorIds.add(entry.version.author_id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fetch projects and users in parallel
|
|
196
|
+
console.log(`Fetching data for ${projectIds.size} project(s) and ${authorIds.size} user(s)...`);
|
|
197
|
+
|
|
198
|
+
const [projects, users] = await Promise.all([
|
|
199
|
+
getProjects(Array.from(projectIds)),
|
|
200
|
+
getUsers(Array.from(authorIds)),
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Map projects and users to their IDs
|
|
204
|
+
const projectsMap = {};
|
|
205
|
+
for (const project of projects) {
|
|
206
|
+
projectsMap[project.id] = project;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const usersMap = {};
|
|
210
|
+
for (const user of users) {
|
|
211
|
+
usersMap[user.id] = user;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generate README for each category
|
|
215
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
216
|
+
if (entries.length === 0) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const readmeContent = generateCategoryReadme(category, entries, projectsMap, usersMap);
|
|
221
|
+
const categoryDir = getScanDirectories(workingDir).find(d => d.name === category);
|
|
222
|
+
|
|
223
|
+
if (categoryDir) {
|
|
224
|
+
const readmePath = path.join(categoryDir.path, 'README.md');
|
|
225
|
+
|
|
226
|
+
if (options.dryRun) {
|
|
227
|
+
console.log(`[DRY RUN] Would write README to: ${readmePath}`);
|
|
228
|
+
} else {
|
|
229
|
+
try {
|
|
230
|
+
await fs.writeFile(readmePath, readmeContent, 'utf-8');
|
|
231
|
+
console.log(`Generated README: ${readmePath}`);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(`Warning: Could not write README to ${readmePath}: ${error.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log('README generation complete.');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Generate the lockfile
|
|
244
|
+
* @param {string} workingDir - The working directory
|
|
245
|
+
* @param {Options} options - The options object
|
|
246
|
+
* @returns {Lockfile} The lockfile object
|
|
247
|
+
*/
|
|
248
|
+
export async function generateLockfile(workingDir, options = {}) {
|
|
249
|
+
if (options.dryRun) {
|
|
250
|
+
console.log('[DRY RUN] Preview mode - no files will be written');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log('Scanning directories for modpack files...');
|
|
254
|
+
|
|
255
|
+
// Scan all directories
|
|
256
|
+
const allFileEntries = [];
|
|
257
|
+
for (const dirInfo of getScanDirectories(workingDir)) {
|
|
258
|
+
console.log(`Scanning ${dirInfo.name}...`);
|
|
259
|
+
const fileEntries = await scanDirectory(dirInfo, workingDir);
|
|
260
|
+
console.log(` Found ${fileEntries.length} file(s)`);
|
|
261
|
+
allFileEntries.push(...fileEntries);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Sort file entries
|
|
265
|
+
allFileEntries.sort((a, b) => {
|
|
266
|
+
if (a.category !== b.category) {
|
|
267
|
+
return a.category.localeCompare(b.category, 'en', { sensitivity: 'base' });
|
|
268
|
+
}
|
|
269
|
+
return a.path.localeCompare(b.path, 'en', { numeric: true, sensitivity: 'base' });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (allFileEntries.length === 0) {
|
|
273
|
+
console.log('No files found. Creating empty lockfile.');
|
|
274
|
+
const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME);
|
|
275
|
+
if (options.dryRun) {
|
|
276
|
+
console.log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
|
|
277
|
+
} else {
|
|
278
|
+
await writeLockfile(createEmptyLockfile(), outputPath);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(`\nTotal files found: ${allFileEntries.length}`);
|
|
284
|
+
console.log('\nQuerying Modrinth API...');
|
|
285
|
+
|
|
286
|
+
// Extract all hashes
|
|
287
|
+
const hashes = allFileEntries.map(info => info.hash);
|
|
288
|
+
|
|
289
|
+
// Query Modrinth API
|
|
290
|
+
const versionData = await getVersionsFromHashes(hashes);
|
|
291
|
+
|
|
292
|
+
console.log(`\nFound version information for ${Object.keys(versionData).length} out of ${hashes.length} files`);
|
|
293
|
+
|
|
294
|
+
// Create lockfile
|
|
295
|
+
const lockfile = createLockfile(allFileEntries, versionData);
|
|
296
|
+
|
|
297
|
+
// Write lockfile
|
|
298
|
+
const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME);
|
|
299
|
+
if (options.dryRun) {
|
|
300
|
+
console.log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
|
|
301
|
+
} else {
|
|
302
|
+
await writeLockfile(lockfile, outputPath);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Summary
|
|
306
|
+
console.log('\n=== Summary ===');
|
|
307
|
+
for (const [category, entries] of Object.entries(lockfile.dependencies)) {
|
|
308
|
+
const withVersion = entries.filter(e => e.version !== null).length;
|
|
309
|
+
const withoutVersion = entries.length - withVersion;
|
|
310
|
+
console.log(`${category}: ${entries.length} file(s) (${withVersion} found on Modrinth, ${withoutVersion} unknown)`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Generate .gitignore rules
|
|
314
|
+
if (options.gitignore) {
|
|
315
|
+
console.log('\n=== .gitignore Rules ===\n');
|
|
316
|
+
console.log(generateGitignoreRules(lockfile));
|
|
317
|
+
console.log();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Generate README files
|
|
321
|
+
if (options.readme) {
|
|
322
|
+
console.log('\nGenerating README files...');
|
|
323
|
+
await generateReadmeFiles(lockfile, workingDir, options = {});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return lockfile;
|
|
327
|
+
}
|
package/src/modpack-lock.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { generateLockfile, generateReadmeFiles, generateGitignoreRules } from './generate_lockfile.js';
|
|
2
|
+
import generateJson from './generate_json.js';
|
|
3
|
+
import promptUserForInfo from './modpack_info.js';
|
|
4
|
+
import { getModpackInfo, getLockfile } from './directory_scanning.js';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
|
|
8
|
+
* @typedef {import('./config/types.js').Options} Options
|
|
9
|
+
* @typedef {import('./config/types.js').Lockfile} Lockfile
|
|
10
|
+
*/
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @license MIT
|
|
14
|
+
* @author nickesc
|
|
15
|
+
* @module modpack-lock
|
|
16
|
+
*/
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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()
|
|
18
|
+
/**
|
|
19
|
+
* Generate the modpack files (lockfile and JSON)
|
|
20
|
+
* @param {ModpackInfo} modpackInfo - The modpack information
|
|
21
|
+
* @param {string} directory - The directory to generate the files in
|
|
22
|
+
* @param {Options} options - The options object
|
|
23
|
+
* @returns {Promise<Lockfile>} The lockfile object
|
|
24
|
+
*/
|
|
25
|
+
async function generateModpackFiles(modpackInfo, directory, options = {}) {
|
|
26
|
+
const lockfile = await generateLockfile(directory, options);
|
|
27
|
+
await generateJson(modpackInfo, lockfile, directory, options);
|
|
28
|
+
return lockfile;
|
|
29
|
+
}
|
|
32
30
|
|
|
31
|
+
export { generateModpackFiles, generateJson, generateLockfile, generateGitignoreRules, generateReadmeFiles, getModpackInfo, getLockfile, promptUserForInfo };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import slugify from 'slugify';
|
|
3
|
+
import * as config from './config/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate that a value is not empty
|
|
7
|
+
*/
|
|
8
|
+
function validateNotEmpty(value, field) {
|
|
9
|
+
if (value.trim().length === 0) {
|
|
10
|
+
return `${field} cannot be empty`;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
|
|
17
|
+
* @typedef {import('./config/types.js').Options} Options
|
|
18
|
+
* @typedef {import('./config/types.js').Lockfile} Lockfile
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get user input for modpack information
|
|
23
|
+
* @param {ModpackInfo} defaults - The initial/default modpack information
|
|
24
|
+
* @returns {Promise<ModpackInfo>} The modpack information from the user
|
|
25
|
+
*/
|
|
26
|
+
export default async function promptUserForInfo(defaults = {}) {
|
|
27
|
+
let answers = await prompts([
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
name: 'name',
|
|
31
|
+
message: 'Modpack name',
|
|
32
|
+
initial: defaults.name,
|
|
33
|
+
validate: (value) => {
|
|
34
|
+
return validateNotEmpty(value, 'Name');
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
name: 'version',
|
|
40
|
+
message: 'Modpack version',
|
|
41
|
+
initial: defaults.version || '1.0.0',
|
|
42
|
+
validate: (value) => {
|
|
43
|
+
return validateNotEmpty(value, 'Version');
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
name: 'id',
|
|
50
|
+
message: 'Modpack slug/ID',
|
|
51
|
+
initial: slugify(defaults.id || defaults.name, config.SLUGIFY_OPTIONS),
|
|
52
|
+
validate: (value) => {
|
|
53
|
+
return validateNotEmpty(value, 'ID');
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
name: 'description',
|
|
59
|
+
message: 'Modpack description',
|
|
60
|
+
initial: defaults.description || undefined,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: 'text',
|
|
64
|
+
name: 'author',
|
|
65
|
+
message: 'Modpack author',
|
|
66
|
+
initial: defaults.author || undefined,
|
|
67
|
+
validate: (value) => {
|
|
68
|
+
return validateNotEmpty(value, 'Author');
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
name: 'projectUrl',
|
|
74
|
+
message: 'Modpack URL',
|
|
75
|
+
initial: defaults.projectUrl || undefined,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
name: 'sourceUrl',
|
|
80
|
+
message: 'Modpack source code URL',
|
|
81
|
+
initial: defaults.sourceUrl || undefined,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
name: 'license',
|
|
86
|
+
message: 'Modpack license',
|
|
87
|
+
initial: defaults.license || undefined,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'autocomplete',
|
|
91
|
+
name: 'modloader',
|
|
92
|
+
message: 'Modpack modloader',
|
|
93
|
+
initial: defaults.modloader || undefined,
|
|
94
|
+
choices: [
|
|
95
|
+
{ title: 'fabric' },
|
|
96
|
+
{ title: 'forge' },
|
|
97
|
+
{ title: 'quilt' },
|
|
98
|
+
{ title: 'neoforge' },
|
|
99
|
+
{ title: 'sponge' },
|
|
100
|
+
{ title: 'paper' },
|
|
101
|
+
{ title: 'velocity' },
|
|
102
|
+
{ title: 'bungeecord' },
|
|
103
|
+
{ title: 'waterfall' },
|
|
104
|
+
{ title: 'travertia' },
|
|
105
|
+
{ title: 'nukkit' },
|
|
106
|
+
{ title: 'pufferfish' },
|
|
107
|
+
{ title: 'purpur' },
|
|
108
|
+
],
|
|
109
|
+
validate: (value) => {
|
|
110
|
+
return validateNotEmpty(value, 'Modloader');
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'text',
|
|
115
|
+
name: 'targetModloaderVersion',
|
|
116
|
+
message: 'Target modloader version',
|
|
117
|
+
initial: defaults.targetModloaderVersion || undefined,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
name: 'targetMinecraftVersion',
|
|
122
|
+
message: 'Target Minecraft version',
|
|
123
|
+
initial: defaults.targetMinecraftVersion || undefined,
|
|
124
|
+
validate: (value) => {
|
|
125
|
+
return validateNotEmpty(value, 'Minecraft Version');
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
]);
|
|
129
|
+
if (Object.keys(answers).length < 11) {
|
|
130
|
+
console.warn('Modpack initialization was interrupted');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
return answers;
|
|
134
|
+
}
|