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.
@@ -0,0 +1,85 @@
1
+ import * as defaults from './defaults.js';
2
+ import pkg from '../../package.json' with { type: 'json' };
3
+
4
+ export const infoFields = {
5
+ name: {
6
+ prompt: 'modpack name',
7
+ option: 'Modpack name; defaults to the directory name'
8
+ },
9
+ version: {
10
+ prompt: 'modpack version',
11
+ option: `Modpack version; defaults to ${defaults.DEFAULT_MODPACK_VERSION}`
12
+ },
13
+ id: {
14
+ prompt: 'modpack slug/ID',
15
+ option: 'Modpack slug/ID; defaults to the directory name slugified'
16
+ },
17
+ description: {
18
+ prompt: 'modpack description',
19
+ option: 'Modpack description'
20
+ },
21
+ author: {
22
+ prompt: 'modpack author',
23
+ option: 'Modpack author; required'
24
+ },
25
+ projectUrl: {
26
+ prompt: 'modpack URL',
27
+ option: 'Modpack URL; defaults to a guessed Modrinth project URL'
28
+ },
29
+ sourceUrl: {
30
+ prompt: 'modpack source code URL',
31
+ option: 'Modpack source code URL; defaults to a guessed GitHub repository URL'
32
+ },
33
+ license: {
34
+ prompt: 'modpack license',
35
+ option: `Modpack license, popular licenses fetched from GitHub; defaults to ${defaults.DEFAULT_MODPACK_LICENSE} in interactive mode`
36
+ },
37
+ modloader: {
38
+ prompt: 'modpack modloader',
39
+ option: 'Modpack modloader, list of loaders fetched from Modrinth; required'
40
+ },
41
+ targetModloaderVersion: {
42
+ prompt: 'target modloader version',
43
+ option: 'Target modloader version'
44
+ },
45
+ targetMinecraftVersion: {
46
+ prompt: 'target Minecraft version',
47
+ option: 'Target Minecraft version, list of versions fetched from Modrinth; required'
48
+ }
49
+ }
50
+
51
+ export const fileFields = {
52
+ addLicense: {
53
+ prompt: 'Add the LICENSE file',
54
+ option: 'Add the LICENSE file to the modpack'
55
+ },
56
+ addGitignore: {
57
+ prompt: 'Update the .gitignore file',
58
+ option: 'Update the .gitignore file to ignore content hosted on Modrinth'
59
+ },
60
+ addReadme: {
61
+ prompt: 'Generate README.md files',
62
+ option: 'Generate README.md files for each category'
63
+ }
64
+ }
65
+
66
+ export const headings = {
67
+ options: "Options:",
68
+ generation: "GENERATION",
69
+ logging: "LOGGING",
70
+ packInfo: "MODPACK INFORMATION",
71
+ information: "INFORMATION"
72
+ };
73
+
74
+ export const dryRunText = (filename, location) => {
75
+ return `[DRY RUN] Would write ${filename} to: ${location}`;
76
+ }
77
+
78
+ /** All-Rights-Reserved license text */
79
+ export const ARR_LICENSE_TEXT =
80
+ "Copyright (c) [year] [fullname]\n" +
81
+ "\n" +
82
+ "All rights reserved.\n";
83
+
84
+
85
+ export {pkg};
@@ -42,4 +42,34 @@
42
42
  * @property {boolean} readme - Whether to generate README.md files
43
43
  */
44
44
 
45
+ /**
46
+ * @typedef {Object} InitOptions
47
+ * Contains options for the initialization of the modpack files.
48
+ * @property {string} folder - The folder to generate the modpack files in
49
+ * @property {boolean} noninteractive - Whether to run the interactive mode
50
+ * @property {boolean} addLicense - Whether to add the license file to the modpack
51
+ * @property {boolean} gitignore - Whether to generate .gitignore rules
52
+ * @property {boolean} readme - Whether to generate README.md files
53
+ * @property {string} name - The name of the modpack
54
+ * @property {string} version - The version of the modpack
55
+ * @property {string} id - The slug/ID of the modpack
56
+ * @property {string} description - The description of the modpack
57
+ * @property {string} author - The author of the modpack
58
+ * @property {string} projectUrl - The modpack's project URL
59
+ * @property {string} sourceUrl - The modpack's source code URL
60
+ * @property {string} license - The modpack's license
61
+ * @property {string} modloader - The modpack's modloader
62
+ * @property {string} targetModloaderVersion - The target modloader version
63
+ * @property {string} targetMinecraftVersion - The target Minecraft version
64
+ * @property {boolean} _init - Internal boolean added to indicate options come from the `init` command.
65
+ */
66
+
67
+ /**
68
+ * @typedef {Object} RunOptions
69
+ * Contains options for the running scripts defined in modpack.json.
70
+ * @property {string} folder - The folder to look for modpack.json in
71
+ * @property {boolean} debug - Whether to print debug information about the executed script
72
+ * @property {boolean} _run - Internal boolean added to indicate options come from the `run` command.
73
+ */
74
+
45
75
  export {};
@@ -1,8 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import crypto from 'crypto';
3
3
  import path from 'path';
4
- import * as files from './config/files.js';
5
- import * as constants from './config/constants.js';
4
+ import * as config from './config/index.js';
6
5
 
7
6
  /**
8
7
  * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
@@ -16,7 +15,7 @@ import * as constants from './config/constants.js';
16
15
  */
17
16
  export function getScanDirectories(directoryPath) {
18
17
  const scanDirectories = [];
19
- for (const category of constants.DEPENDENCY_CATEGORIES) {
18
+ for (const category of config.DEPENDENCY_CATEGORIES) {
20
19
  scanDirectories.push({ name: category, path: path.join(directoryPath, category) });
21
20
  }
22
21
  return scanDirectories;
@@ -105,7 +104,7 @@ async function getJsonFile(directoryPath, filename) {
105
104
  * @returns {Promise<ModpackInfo|null>} The modpack info JSON object if the file exists, otherwise null
106
105
  */
107
106
  export async function getModpackInfo(directoryPath) {
108
- return getJsonFile(directoryPath, files.MODPACK_JSON_NAME);
107
+ return getJsonFile(directoryPath, config.MODPACK_JSON_NAME);
109
108
  }
110
109
 
111
110
  /**
@@ -114,5 +113,5 @@ export async function getModpackInfo(directoryPath) {
114
113
  * @returns {Lockfile|null} The JSON object if the file exists, otherwise null
115
114
  */
116
115
  export async function getLockfile(directoryPath) {
117
- return getJsonFile(directoryPath, files.MODPACK_LOCKFILE_NAME);
116
+ return getJsonFile(directoryPath, config.MODPACK_LOCKFILE_NAME);
118
117
  }
@@ -6,6 +6,7 @@ import * as config from './config/index.js';
6
6
  /**
7
7
  * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
8
8
  * @typedef {import('./config/types.js').Options} Options
9
+ * @typedef {import('./config/types.js').InitOptions} InitOptions
9
10
  * @typedef {import('./config/types.js').Lockfile} Lockfile
10
11
  */
11
12
 
@@ -33,7 +34,7 @@ async function writeJson(jsonObject, outputPath) {
33
34
  * @param {ModpackInfo} modpackInfo - The modpack information
34
35
  * @param {Lockfile} lockfile - The lockfile
35
36
  * @param {string} outputDir - The path to write the JSON object to
36
- * @param {Options} options - The options object
37
+ * @param {Options | InitOptions} options - The options object
37
38
  * @returns {Promise<Lockfile>} The JSON file's object
38
39
  */
39
40
  export default async function generateJson(modpackInfo, lockfile, outputDir, options = {}) {
@@ -84,7 +85,6 @@ export default async function generateJson(modpackInfo, lockfile, outputDir, opt
84
85
  packDependencies[category].push(projectSlug);
85
86
  }
86
87
  }
87
- //packDependencies[category].push(...packDependencies[category].map(item => item.path));
88
88
  }
89
89
  }
90
90
 
@@ -93,7 +93,7 @@ export default async function generateJson(modpackInfo, lockfile, outputDir, opt
93
93
 
94
94
  // Write modpack JSON object to disk
95
95
  if (options.dryRun) {
96
- console.log(`[DRY RUN] Would write ${config.MODPACK_JSON_NAME} to: ${path.join(outputDir, config.MODPACK_JSON_NAME)}`);
96
+ console.log(config.dryRunText(config.MODPACK_JSON_NAME, path.join(outputDir, config.MODPACK_JSON_NAME)));
97
97
  } else {
98
98
  await writeJson(jsonObject, outputDir);
99
99
  }
@@ -0,0 +1,50 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { getLicenseText } from './github_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').InitOptions} InitOptions
10
+ */
11
+
12
+ async function writeLicense(licenseText, outputPath) {
13
+ await fs.writeFile(path.join(outputPath, config.MODPACK_LICENSE_NAME), licenseText, 'utf-8');
14
+ console.log(`${config.MODPACK_LICENSE_NAME} written to: ${path.join(outputPath, config.MODPACK_LICENSE_NAME)}`);
15
+ }
16
+
17
+ /**
18
+ * Write a license to a file
19
+ * @param {ModpackInfo} modpackInfo - The modpack information
20
+ * @param {string} outputPath - The path to write the license to
21
+ * @param {InitOptions} options - The initialization options object
22
+ * @param {string} licenseTextOverride - The license text to override the default license text with
23
+ * @returns {Promise<string> | null} The license text or null if the license text could not be generated
24
+ */
25
+ export default async function generateLicense(modpackInfo, outputPath, options = {}, licenseTextOverride = null) {
26
+ try {
27
+ const spdxId = modpackInfo.license;
28
+ console.log(`Generating license for: ${spdxId}`);
29
+
30
+ let licenseText = licenseTextOverride || await getLicenseText(spdxId);
31
+ licenseText = licenseText.replace('[year]', new Date().getFullYear());
32
+ licenseText = licenseText.replace('{{year}}', new Date().getFullYear());
33
+ licenseText = licenseText.replace('[fullname]', modpackInfo.author);
34
+ licenseText = licenseText.replace('{{fullname}}', modpackInfo.author);
35
+ licenseText = licenseText.replace('[organization]', modpackInfo.author);
36
+ licenseText = licenseText.replace('{{organization}}', modpackInfo.author);
37
+ licenseText = licenseText.replace('[project]', modpackInfo.name);
38
+ licenseText = licenseText.replace('{{project}}', modpackInfo.name);
39
+
40
+ if (options.dryRun) {
41
+ console.log(config.dryRunText(config.MODPACK_LICENSE_NAME, path.join(outputPath, config.MODPACK_LICENSE_NAME)));
42
+ } else {
43
+ await writeLicense(licenseText, outputPath);
44
+ }
45
+ return licenseText;
46
+ } catch (error) {
47
+ console.warn(`Warning: unable to generate license for: ${modpackInfo.license}`);
48
+ return null;
49
+ }
50
+ }
@@ -6,6 +6,7 @@ import * as config from './config/index.js';
6
6
 
7
7
  /**
8
8
  * @typedef {import('./config/types.js').Options} Options
9
+ * @typedef {import('./config/types.js').InitOptions} InitOptions
9
10
  * @typedef {import('./config/types.js').Lockfile} Lockfile
10
11
  */
11
12
 
@@ -137,11 +138,12 @@ function generateCategoryReadme(category, entries, projectsMap, usersMap) {
137
138
  }
138
139
 
139
140
  /**
140
- * Generate .gitignore rules for files not hosted on Modrinth
141
+ * Generate .gitignore rules for files not hosted on Modrinth and write them to .gitignore file
141
142
  * @param {Lockfile} lockfile - The lockfile object
142
- * @returns {string} The .gitignore rules
143
+ * @param {string} workingDir - The working directory
144
+ * @param {Options | InitOptions} options - The options object
143
145
  */
144
- export function generateGitignoreRules(lockfile) {
146
+ export async function generateGitignoreRules(lockfile, workingDir, options = {}) {
145
147
  const rules = [];
146
148
  const exceptions = [];
147
149
 
@@ -149,7 +151,7 @@ export function generateGitignoreRules(lockfile) {
149
151
  for (const category of config.DEPENDENCY_CATEGORIES) {
150
152
  rules.push(`${category}/*.${category === "mods" ? "jar" : "zip"}`);
151
153
  }
152
- rules.push('\n## Exceptions');
154
+ rules.push(`*/**/*.disabled`);
153
155
 
154
156
  // Find files not hosted on Modrinth
155
157
  for (const [category, entries] of Object.entries(lockfile.dependencies)) {
@@ -162,19 +164,104 @@ export function generateGitignoreRules(lockfile) {
162
164
 
163
165
  // Add exceptions if any
164
166
  if (exceptions.length > 0) {
167
+ rules.push('\n## Exceptions');
165
168
  rules.push(...exceptions);
169
+ }
170
+
171
+ const rulesContent = rules.join('\n');
172
+ const gitignorePath = path.join(workingDir, config.GITIGNORE_NAME);
173
+
174
+ // Read existing .gitignore file if it exists
175
+ let existingContent = '';
176
+ try {
177
+ existingContent = await fs.readFile(gitignorePath, 'utf-8');
178
+ } catch (error) {
179
+ // File doesn't exist, that's okay - we'll create it
180
+ if (error.code !== 'ENOENT') {
181
+ console.warn(`Warning: Could not read .gitignore file: ${error.message}`);
182
+ return;
183
+ }
184
+ }
185
+
186
+ // Find markers in existing content
187
+ const startMarkerIndex = existingContent.indexOf(config.GITIGNORE_START_MARKER);
188
+ const endMarkerIndex = existingContent.indexOf(config.GITIGNORE_END_MARKER);
189
+
190
+ let newContent = '';
191
+
192
+ if (startMarkerIndex !== -1 && endMarkerIndex !== -1 && endMarkerIndex > startMarkerIndex) {
193
+ // Both markers exist, replace content between them
194
+ const beforeSection = existingContent.substring(0, startMarkerIndex);
195
+ const afterSection = existingContent.substring(endMarkerIndex + config.GITIGNORE_END_MARKER.length);
196
+
197
+ // Remove trailing newlines from before section and leading newlines from after section
198
+ const beforeTrimmed = beforeSection.replace(/\n+$/, '');
199
+ const afterTrimmed = afterSection.replace(/^\n+/, '');
200
+
201
+ const parts = [beforeTrimmed];
202
+ if (beforeTrimmed) parts.push(''); // Add separator if there's content before
203
+ parts.push(
204
+ config.GITIGNORE_START_MARKER,
205
+ rulesContent,
206
+ config.GITIGNORE_END_MARKER
207
+ );
208
+ if (afterTrimmed) {
209
+ parts.push(''); // Add separator if there's content after
210
+ parts.push(afterTrimmed);
211
+ }
212
+
213
+ newContent = parts.join('\n');
214
+ } else if (startMarkerIndex !== -1 || endMarkerIndex !== -1) {
215
+ // Only one marker exists, append to end
216
+ const trimmed = existingContent.replace(/\n+$/, '');
217
+ newContent = [
218
+ trimmed,
219
+ '',
220
+ config.GITIGNORE_START_MARKER,
221
+ rulesContent,
222
+ config.GITIGNORE_END_MARKER
223
+ ].join('\n');
166
224
  } else {
167
- rules.push('# No exceptions needed - all files are hosted on Modrinth');
225
+ // No markers exist, append to end
226
+ if (existingContent.trim() === '') {
227
+ // File is empty or only whitespace
228
+ newContent = [
229
+ config.GITIGNORE_START_MARKER,
230
+ rulesContent,
231
+ config.GITIGNORE_END_MARKER
232
+ ].join('\n');
233
+ } else {
234
+ // File has content, append with newline
235
+ const trimmed = existingContent.replace(/\n+$/, '');
236
+ newContent = [
237
+ trimmed,
238
+ '',
239
+ config.GITIGNORE_START_MARKER,
240
+ rulesContent,
241
+ config.GITIGNORE_END_MARKER
242
+ ].join('\n');
243
+ }
168
244
  }
169
245
 
170
- return rules.join('\n');
246
+ // Write the updated content
247
+ if (options.dryRun) {
248
+ console.log(config.dryRunText(config.GITIGNORE_NAME, gitignorePath));
249
+ console.log();
250
+ } else {
251
+ try {
252
+ await fs.writeFile(gitignorePath, newContent, 'utf-8');
253
+ console.log(`Updated .gitignore: ${gitignorePath}`);
254
+ } catch (error) {
255
+ console.warn(`Warning: Could not write .gitignore file: ${error.message}`);
256
+ }
257
+ }
171
258
  }
172
259
 
173
260
  /**
174
261
  * Generate the README.md files for each category
175
262
  * @param {Lockfile} lockfile - The lockfile object
176
263
  * @param {string} workingDir - The working directory
177
- * @param {Options} options - The options object
264
+ * @param {Options | InitOptions} options - The options object
178
265
  */
179
266
  export async function generateReadmeFiles(lockfile, workingDir, options = {}) {
180
267
  // Collect unique project IDs and author IDs from version data
@@ -221,10 +308,10 @@ export async function generateReadmeFiles(lockfile, workingDir, options = {}) {
221
308
  const categoryDir = getScanDirectories(workingDir).find(d => d.name === category);
222
309
 
223
310
  if (categoryDir) {
224
- const readmePath = path.join(categoryDir.path, 'README.md');
311
+ const readmePath = path.join(categoryDir.path, config.README_NAME);
225
312
 
226
313
  if (options.dryRun) {
227
- console.log(`[DRY RUN] Would write README to: ${readmePath}`);
314
+ console.log(config.dryRunText(config.README_NAME, readmePath));
228
315
  } else {
229
316
  try {
230
317
  await fs.writeFile(readmePath, readmeContent, 'utf-8');
@@ -246,10 +333,6 @@ export async function generateReadmeFiles(lockfile, workingDir, options = {}) {
246
333
  * @returns {Lockfile} The lockfile object
247
334
  */
248
335
  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
336
  console.log('Scanning directories for modpack files...');
254
337
 
255
338
  // Scan all directories
@@ -273,7 +356,7 @@ export async function generateLockfile(workingDir, options = {}) {
273
356
  console.log('No files found. Creating empty lockfile.');
274
357
  const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME);
275
358
  if (options.dryRun) {
276
- console.log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
359
+ console.log(config.dryRunText(config.MODPACK_LOCKFILE_NAME, outputPath));
277
360
  } else {
278
361
  await writeLockfile(createEmptyLockfile(), outputPath);
279
362
  }
@@ -297,7 +380,7 @@ export async function generateLockfile(workingDir, options = {}) {
297
380
  // Write lockfile
298
381
  const outputPath = path.join(workingDir, config.MODPACK_LOCKFILE_NAME);
299
382
  if (options.dryRun) {
300
- console.log(`[DRY RUN] Would write lockfile to: ${outputPath}`);
383
+ console.log(config.dryRunText(config.MODPACK_LOCKFILE_NAME, outputPath));
301
384
  } else {
302
385
  await writeLockfile(lockfile, outputPath);
303
386
  }
@@ -312,15 +395,13 @@ export async function generateLockfile(workingDir, options = {}) {
312
395
 
313
396
  // Generate .gitignore rules
314
397
  if (options.gitignore) {
315
- console.log('\n=== .gitignore Rules ===\n');
316
- console.log(generateGitignoreRules(lockfile));
317
- console.log();
398
+ await generateGitignoreRules(lockfile, workingDir, options);
318
399
  }
319
400
 
320
401
  // Generate README files
321
402
  if (options.readme) {
322
403
  console.log('\nGenerating README files...');
323
- await generateReadmeFiles(lockfile, workingDir, options = {});
404
+ await generateReadmeFiles(lockfile, workingDir, options);
324
405
  }
325
406
 
326
407
  return lockfile;
@@ -0,0 +1,73 @@
1
+ import * as config from './config/index.js';
2
+
3
+
4
+ /**
5
+ * Fetch a list of the most popular licenses from GitHub
6
+ * @param {boolean} featured - If the fetch should be limited to featured licenses
7
+ * @returns {Promise<Array<Object>>} The list of licenses for use in a prompt
8
+ */
9
+ export async function getLicenseList(featured = false) {
10
+ try {
11
+ const url = featured ? config.GITHUB_FEATURED_LICENSES_ENDPOINT : config.GITHUB_LICENSES_ENDPOINT;
12
+ const response = await fetch(url);
13
+ if (!response.ok) {
14
+ const errorText = await response.text();
15
+ throw new Error(`GitHub API error (${response.status}): ${errorText}`);
16
+ }
17
+ let licenseList = await response.json();
18
+
19
+ let licenseSpdxIds = licenseList.map(license => ({ title: license.spdx_id, value: license.key }));
20
+
21
+
22
+ if (!featured) {
23
+ // get featured licenses and place them at the beginning of the list, removing them from the original list
24
+ licenseSpdxIds.unshift(config.ALL_RIGHTS_RESERVED_LICENSE);
25
+ licenseSpdxIds.push(config.OTHER_OPTION);
26
+ const featuredLicenseList = await getLicenseList(true);
27
+ for (const license of featuredLicenseList) {
28
+ licenseSpdxIds= licenseSpdxIds.filter( id => id !== license );
29
+ licenseSpdxIds.unshift(license);
30
+ };
31
+ }
32
+
33
+ return licenseSpdxIds;
34
+ } catch (error) {
35
+ console.warn(`Warning: could not fetch license list. Using fallbacks.`);
36
+ const licenses = config.FALLBACK_LICENSES.push(config.ALL_RIGHTS_RESERVED_LICENSE)
37
+ licenses.push(config.OTHER_OPTION);
38
+ return licenses;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Fetch specific license information from GitHub
44
+ * @param {string} spdxId - The SPDX ID of the license
45
+ * @returns {Promise<string> | null} The license text
46
+ */
47
+ export async function getLicenseText(spdxId) {
48
+ if (spdxId === 'all-rights-reserved') {
49
+ return config.ARR_LICENSE_TEXT;
50
+ }
51
+ try {
52
+ const url = config.GITHUB_LICENSE_ENDPOINT(spdxId.toLowerCase());
53
+ const response = await fetch(url);
54
+ if (!response.ok) {
55
+ const errorText = await response.text();
56
+ throw new Error(`GitHub API error (${response.status}): ${errorText}`);
57
+ }
58
+
59
+ const json = await response.json();
60
+ if (json.body) {
61
+ return json.body;
62
+ } else {
63
+ throw new Error();
64
+ }
65
+ return null;
66
+ } catch (error) {
67
+ console.warn(`Warning: could not find license text for: ${spdxId}`);
68
+ return null;
69
+ }
70
+ }
71
+
72
+
73
+
@@ -1,11 +1,13 @@
1
1
  import { generateLockfile, generateReadmeFiles, generateGitignoreRules } from './generate_lockfile.js';
2
2
  import generateJson from './generate_json.js';
3
- import promptUserForInfo from './modpack_info.js';
3
+ import generateLicense from './generate_license.js';
4
+ import { promptUserForInfo } from './modpack_info.js';
4
5
  import { getModpackInfo, getLockfile } from './directory_scanning.js';
5
6
 
6
7
  /**
7
8
  * @typedef {import('./config/types.js').ModpackInfo} ModpackInfo
8
9
  * @typedef {import('./config/types.js').Options} Options
10
+ * @typedef {import('./config/types.js').InitOptions} InitOptions
9
11
  * @typedef {import('./config/types.js').Lockfile} Lockfile
10
12
  */
11
13
 
@@ -19,7 +21,7 @@ import { getModpackInfo, getLockfile } from './directory_scanning.js';
19
21
  * Generate the modpack files (lockfile and JSON)
20
22
  * @param {ModpackInfo} modpackInfo - The modpack information
21
23
  * @param {string} directory - The directory to generate the files in
22
- * @param {Options} options - The options object
24
+ * @param {Options | InitOptions } options - The options object
23
25
  * @returns {Promise<Lockfile>} The lockfile object
24
26
  */
25
27
  async function generateModpackFiles(modpackInfo, directory, options = {}) {
@@ -28,4 +30,4 @@ async function generateModpackFiles(modpackInfo, directory, options = {}) {
28
30
  return lockfile;
29
31
  }
30
32
 
31
- export { generateModpackFiles, generateJson, generateLockfile, generateGitignoreRules, generateReadmeFiles, getModpackInfo, getLockfile, promptUserForInfo };
33
+ export { generateModpackFiles, generateJson, generateLockfile, generateGitignoreRules, generateReadmeFiles, generateLicense, getModpackInfo, getLockfile, promptUserForInfo };