@bobschlowinskii/clicraft 0.4.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/CHANGELOG.md +191 -0
- package/CONTRIBUTING.md +261 -0
- package/LICENSE.md +7 -0
- package/README.md +55 -0
- package/commands/auth.js +427 -0
- package/commands/config.js +356 -0
- package/commands/create.js +534 -0
- package/commands/info.js +113 -0
- package/commands/install.js +130 -0
- package/commands/launch.js +296 -0
- package/commands/search.js +50 -0
- package/commands/uninstall.js +94 -0
- package/commands/upgrade.js +343 -0
- package/commands/version.js +21 -0
- package/docs/README.md +119 -0
- package/docs/_config.yml +63 -0
- package/docs/commands/auth.md +376 -0
- package/docs/commands/config.md +294 -0
- package/docs/commands/create.md +276 -0
- package/docs/commands/info.md +460 -0
- package/docs/commands/install.md +320 -0
- package/docs/commands/launch.md +427 -0
- package/docs/commands/search.md +276 -0
- package/docs/commands/uninstall.md +128 -0
- package/docs/commands/upgrade.md +515 -0
- package/docs/commands.md +266 -0
- package/docs/configuration.md +410 -0
- package/docs/contributing.md +114 -0
- package/docs/index.md +82 -0
- package/docs/installation.md +162 -0
- package/helpers/auth/index.js +20 -0
- package/helpers/auth/microsoft.js +329 -0
- package/helpers/auth/storage.js +260 -0
- package/helpers/config.js +258 -0
- package/helpers/constants.js +38 -0
- package/helpers/getv.js +5 -0
- package/helpers/minecraft.js +308 -0
- package/helpers/modrinth.js +141 -0
- package/helpers/utils.js +334 -0
- package/helpers/versions.js +21 -0
- package/index.js +89 -0
- package/package.json +30 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { fetchJson, downloadFile, mavenToPath } from './utils.js';
|
|
5
|
+
import {
|
|
6
|
+
MOJANG_VERSION_MANIFEST,
|
|
7
|
+
MOJANG_RESOURCES,
|
|
8
|
+
FABRIC_META,
|
|
9
|
+
FABRIC_MAVEN,
|
|
10
|
+
FORGE_PROMOS,
|
|
11
|
+
FORGE_MAVEN,
|
|
12
|
+
USER_AGENT
|
|
13
|
+
} from './constants.js';
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Mojang/Minecraft APIs
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch available Minecraft versions (releases only)
|
|
21
|
+
* @returns {Promise<Array>} - Array of version objects
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchMinecraftVersions() {
|
|
24
|
+
const data = await fetchJson(MOJANG_VERSION_MANIFEST);
|
|
25
|
+
return data.versions.filter(v => v.type === 'release');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get version manifest for a specific Minecraft version
|
|
30
|
+
* @param {string} mcVersion - Minecraft version
|
|
31
|
+
* @returns {Promise<object>} - Version manifest data
|
|
32
|
+
*/
|
|
33
|
+
export async function getVersionManifest(mcVersion) {
|
|
34
|
+
const manifest = await fetchJson(MOJANG_VERSION_MANIFEST);
|
|
35
|
+
const versionEntry = manifest.versions.find(v => v.id === mcVersion);
|
|
36
|
+
if (!versionEntry) {
|
|
37
|
+
throw new Error(`Minecraft version ${mcVersion} not found`);
|
|
38
|
+
}
|
|
39
|
+
return await fetchJson(versionEntry.url);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// Fabric APIs
|
|
44
|
+
// ============================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch Fabric loader versions
|
|
48
|
+
* @returns {Promise<Array<string>>} - Array of version strings
|
|
49
|
+
*/
|
|
50
|
+
export async function fetchFabricLoaderVersions() {
|
|
51
|
+
const data = await fetchJson(`${FABRIC_META}/versions/loader`);
|
|
52
|
+
return data.map(v => v.version);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fetch Fabric-supported game versions
|
|
57
|
+
* @returns {Promise<Array<string>>} - Array of stable Minecraft versions
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchFabricGameVersions() {
|
|
60
|
+
const data = await fetchJson(`${FABRIC_META}/versions/game`);
|
|
61
|
+
return data.filter(v => v.stable).map(v => v.version);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fetch Fabric profile JSON for a version
|
|
66
|
+
* @param {string} mcVersion - Minecraft version
|
|
67
|
+
* @param {string} loaderVersion - Fabric loader version
|
|
68
|
+
* @returns {Promise<object>} - Fabric profile data
|
|
69
|
+
*/
|
|
70
|
+
export async function fetchFabricProfile(mcVersion, loaderVersion) {
|
|
71
|
+
return await fetchJson(`${FABRIC_META}/versions/loader/${mcVersion}/${loaderVersion}/profile/json`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fetch Fabric installer versions
|
|
76
|
+
* @returns {Promise<Array>} - Array of installer version objects
|
|
77
|
+
*/
|
|
78
|
+
export async function fetchFabricInstallerVersions() {
|
|
79
|
+
return await fetchJson(`${FABRIC_META}/versions/installer`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================
|
|
83
|
+
// Forge APIs
|
|
84
|
+
// ============================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fetch Forge versions for a specific Minecraft version
|
|
88
|
+
* @param {string} mcVersion - Minecraft version
|
|
89
|
+
* @returns {Promise<Array>} - Array of Forge version objects
|
|
90
|
+
*/
|
|
91
|
+
export async function fetchForgeVersions(mcVersion) {
|
|
92
|
+
const data = await fetchJson(FORGE_PROMOS);
|
|
93
|
+
const versions = [];
|
|
94
|
+
for (const [key, value] of Object.entries(data.promos)) {
|
|
95
|
+
if (key.startsWith(mcVersion)) {
|
|
96
|
+
versions.push({ label: key, version: value });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return versions;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Fetch all Forge-supported Minecraft versions
|
|
104
|
+
* @returns {Promise<Array<string>>} - Array of Minecraft versions
|
|
105
|
+
*/
|
|
106
|
+
export async function fetchForgeGameVersions() {
|
|
107
|
+
const data = await fetchJson(FORGE_PROMOS);
|
|
108
|
+
const mcVersions = new Set();
|
|
109
|
+
for (const key of Object.keys(data.promos)) {
|
|
110
|
+
const mcVersion = key.split('-')[0];
|
|
111
|
+
mcVersions.add(mcVersion);
|
|
112
|
+
}
|
|
113
|
+
return Array.from(mcVersions).sort((a, b) => {
|
|
114
|
+
const aParts = a.split('.').map(Number);
|
|
115
|
+
const bParts = b.split('.').map(Number);
|
|
116
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
117
|
+
const aVal = aParts[i] || 0;
|
|
118
|
+
const bVal = bParts[i] || 0;
|
|
119
|
+
if (aVal !== bVal) return bVal - aVal;
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================
|
|
126
|
+
// Library Download
|
|
127
|
+
// ============================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if library applies to current OS
|
|
131
|
+
* @param {object} lib - Library object with rules
|
|
132
|
+
* @returns {boolean} - Whether library should be used
|
|
133
|
+
*/
|
|
134
|
+
function checkLibraryRules(lib) {
|
|
135
|
+
if (!lib.rules) return true;
|
|
136
|
+
|
|
137
|
+
const osName = process.platform === 'darwin' ? 'osx' :
|
|
138
|
+
process.platform === 'win32' ? 'windows' : 'linux';
|
|
139
|
+
|
|
140
|
+
return lib.rules.some(rule => {
|
|
141
|
+
if (rule.os && rule.os.name) {
|
|
142
|
+
return rule.action === 'allow' && rule.os.name === osName;
|
|
143
|
+
}
|
|
144
|
+
return rule.action === 'allow';
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Download libraries from version data
|
|
150
|
+
* @param {object} versionData - Version manifest data
|
|
151
|
+
* @param {string} instancePath - Instance directory path
|
|
152
|
+
*/
|
|
153
|
+
export async function downloadLibraries(versionData, instancePath) {
|
|
154
|
+
const librariesPath = path.join(instancePath, 'libraries');
|
|
155
|
+
fs.mkdirSync(librariesPath, { recursive: true });
|
|
156
|
+
|
|
157
|
+
const libraries = versionData.libraries || [];
|
|
158
|
+
const applicableLibs = libraries.filter(checkLibraryRules);
|
|
159
|
+
let downloadCount = 0;
|
|
160
|
+
|
|
161
|
+
for (const lib of applicableLibs) {
|
|
162
|
+
let libPath = null;
|
|
163
|
+
let downloadUrl = null;
|
|
164
|
+
|
|
165
|
+
// Format 1: downloads.artifact (vanilla Minecraft)
|
|
166
|
+
if (lib.downloads?.artifact) {
|
|
167
|
+
const artifact = lib.downloads.artifact;
|
|
168
|
+
libPath = path.join(librariesPath, artifact.path);
|
|
169
|
+
downloadUrl = artifact.url;
|
|
170
|
+
}
|
|
171
|
+
// Format 2: name with Maven coordinates + url (Fabric/Forge)
|
|
172
|
+
else if (lib.name && lib.url) {
|
|
173
|
+
const relativePath = mavenToPath(lib.name);
|
|
174
|
+
if (relativePath) {
|
|
175
|
+
libPath = path.join(librariesPath, relativePath);
|
|
176
|
+
downloadUrl = lib.url + relativePath;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Format 3: name only (defaults to Fabric Maven)
|
|
180
|
+
else if (lib.name) {
|
|
181
|
+
const relativePath = mavenToPath(lib.name);
|
|
182
|
+
if (relativePath) {
|
|
183
|
+
libPath = path.join(librariesPath, relativePath);
|
|
184
|
+
downloadUrl = `${FABRIC_MAVEN}/${relativePath}`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (libPath && downloadUrl) {
|
|
189
|
+
// Skip if already downloaded
|
|
190
|
+
if (fs.existsSync(libPath)) {
|
|
191
|
+
downloadCount++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fs.mkdirSync(path.dirname(libPath), { recursive: true });
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await downloadFile(downloadUrl, libPath, `Library ${++downloadCount}/${applicableLibs.length}`);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.log(chalk.yellow(` Warning: Failed to download ${lib.name}`));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Download assets from version data
|
|
208
|
+
* @param {object} versionData - Version manifest data
|
|
209
|
+
* @param {string} instancePath - Instance directory path
|
|
210
|
+
*/
|
|
211
|
+
export async function downloadAssets(versionData, instancePath) {
|
|
212
|
+
const assetsPath = path.join(instancePath, 'assets');
|
|
213
|
+
const indexesPath = path.join(assetsPath, 'indexes');
|
|
214
|
+
const objectsPath = path.join(assetsPath, 'objects');
|
|
215
|
+
|
|
216
|
+
fs.mkdirSync(indexesPath, { recursive: true });
|
|
217
|
+
fs.mkdirSync(objectsPath, { recursive: true });
|
|
218
|
+
|
|
219
|
+
// Download asset index
|
|
220
|
+
const assetIndex = versionData.assetIndex;
|
|
221
|
+
const indexPath = path.join(indexesPath, `${assetIndex.id}.json`);
|
|
222
|
+
|
|
223
|
+
if (!fs.existsSync(indexPath)) {
|
|
224
|
+
await downloadFile(assetIndex.url, indexPath, 'Asset index');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const assets = await fetchJson(assetIndex.url);
|
|
228
|
+
const objects = Object.entries(assets.objects);
|
|
229
|
+
|
|
230
|
+
console.log(chalk.gray(` Downloading ${objects.length} assets...`));
|
|
231
|
+
|
|
232
|
+
let downloaded = 0;
|
|
233
|
+
for (const [name, obj] of objects) {
|
|
234
|
+
const hash = obj.hash;
|
|
235
|
+
const prefix = hash.substring(0, 2);
|
|
236
|
+
const objectDir = path.join(objectsPath, prefix);
|
|
237
|
+
const objectPath = path.join(objectDir, hash);
|
|
238
|
+
|
|
239
|
+
if (fs.existsSync(objectPath)) {
|
|
240
|
+
downloaded++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fs.mkdirSync(objectDir, { recursive: true });
|
|
245
|
+
|
|
246
|
+
const url = `${MOJANG_RESOURCES}/${prefix}/${hash}`;
|
|
247
|
+
try {
|
|
248
|
+
const response = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
|
249
|
+
if (response.ok) {
|
|
250
|
+
const buffer = await response.arrayBuffer();
|
|
251
|
+
fs.writeFileSync(objectPath, Buffer.from(buffer));
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
// Skip failed assets
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
downloaded++;
|
|
258
|
+
process.stdout.write(`\r${chalk.gray(` Assets: ${downloaded}/${objects.length}`)}`);
|
|
259
|
+
}
|
|
260
|
+
process.stdout.write(`\r${chalk.gray(` Assets: ${downloaded}/${objects.length}`)}\n`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Build classpath from version data
|
|
265
|
+
* @param {string} instancePath - Instance directory path
|
|
266
|
+
* @param {object} versionData - Version manifest data
|
|
267
|
+
* @returns {string} - Classpath string
|
|
268
|
+
*/
|
|
269
|
+
export function buildClasspath(instancePath, versionData) {
|
|
270
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
271
|
+
const librariesPath = path.join(instancePath, 'libraries');
|
|
272
|
+
const classpath = [];
|
|
273
|
+
|
|
274
|
+
for (const lib of versionData.libraries || []) {
|
|
275
|
+
if (!checkLibraryRules(lib)) continue;
|
|
276
|
+
|
|
277
|
+
let libPath = null;
|
|
278
|
+
|
|
279
|
+
if (lib.downloads?.artifact) {
|
|
280
|
+
libPath = path.join(librariesPath, lib.downloads.artifact.path);
|
|
281
|
+
} else if (lib.name) {
|
|
282
|
+
const relativePath = mavenToPath(lib.name);
|
|
283
|
+
if (relativePath) {
|
|
284
|
+
libPath = path.join(librariesPath, relativePath);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (libPath && fs.existsSync(libPath)) {
|
|
289
|
+
classpath.push(libPath);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return classpath.join(sep);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export default {
|
|
297
|
+
fetchMinecraftVersions,
|
|
298
|
+
getVersionManifest,
|
|
299
|
+
fetchFabricLoaderVersions,
|
|
300
|
+
fetchFabricGameVersions,
|
|
301
|
+
fetchFabricProfile,
|
|
302
|
+
fetchFabricInstallerVersions,
|
|
303
|
+
fetchForgeVersions,
|
|
304
|
+
fetchForgeGameVersions,
|
|
305
|
+
downloadLibraries,
|
|
306
|
+
downloadAssets,
|
|
307
|
+
buildClasspath
|
|
308
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fetchJson, downloadFile } from './utils.js';
|
|
4
|
+
import { MODRINTH_API, USER_AGENT } from './constants.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get project info from Modrinth
|
|
8
|
+
* @param {string} slugOrId - Project slug or ID
|
|
9
|
+
* @returns {Promise<object|null>} - Project data or null if not found
|
|
10
|
+
*/
|
|
11
|
+
export async function getProject(slugOrId) {
|
|
12
|
+
try {
|
|
13
|
+
return await fetchJson(`${MODRINTH_API}/project/${slugOrId}`);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.message.includes('404')) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get project versions from Modrinth
|
|
24
|
+
* @param {string} slugOrId - Project slug or ID
|
|
25
|
+
* @param {string} [mcVersion] - Filter by Minecraft version
|
|
26
|
+
* @param {string} [loader] - Filter by mod loader
|
|
27
|
+
* @returns {Promise<Array>} - Array of version objects
|
|
28
|
+
*/
|
|
29
|
+
export async function getProjectVersions(slugOrId, mcVersion = null, loader = null) {
|
|
30
|
+
const params = new URLSearchParams();
|
|
31
|
+
if (mcVersion) {
|
|
32
|
+
params.set('game_versions', JSON.stringify([mcVersion]));
|
|
33
|
+
}
|
|
34
|
+
if (loader) {
|
|
35
|
+
params.set('loaders', JSON.stringify([loader]));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const query = params.toString();
|
|
39
|
+
const url = `${MODRINTH_API}/project/${slugOrId}/version${query ? '?' + query : ''}`;
|
|
40
|
+
return await fetchJson(url);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Search for mods on Modrinth
|
|
45
|
+
* @param {string} query - Search query
|
|
46
|
+
* @param {object} options - Search options
|
|
47
|
+
* @param {number} [options.limit=10] - Max results
|
|
48
|
+
* @param {string} [options.version] - Minecraft version filter
|
|
49
|
+
* @param {string} [options.loader] - Mod loader filter
|
|
50
|
+
* @returns {Promise<object>} - Search results
|
|
51
|
+
*/
|
|
52
|
+
export async function searchMods(query, options = {}) {
|
|
53
|
+
const params = new URLSearchParams({
|
|
54
|
+
query: query,
|
|
55
|
+
limit: options.limit || 10,
|
|
56
|
+
facets: JSON.stringify([['project_type:mod']])
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Add version filter if specified
|
|
60
|
+
if (options.version) {
|
|
61
|
+
const facets = JSON.parse(params.get('facets'));
|
|
62
|
+
facets.push([`versions:${options.version}`]);
|
|
63
|
+
params.set('facets', JSON.stringify(facets));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add loader filter if specified
|
|
67
|
+
if (options.loader) {
|
|
68
|
+
const facets = JSON.parse(params.get('facets'));
|
|
69
|
+
facets.push([`categories:${options.loader}`]);
|
|
70
|
+
params.set('facets', JSON.stringify(facets));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return await fetchJson(`${MODRINTH_API}/search?${params}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Download a mod from Modrinth
|
|
78
|
+
* @param {string} projectId - Modrinth project ID
|
|
79
|
+
* @param {string} mcVersion - Minecraft version
|
|
80
|
+
* @param {string} loader - Mod loader (fabric, forge, etc.)
|
|
81
|
+
* @param {string} modsPath - Path to mods directory
|
|
82
|
+
* @returns {Promise<object|null>} - Mod info object or null if failed
|
|
83
|
+
*/
|
|
84
|
+
export async function downloadMod(projectId, mcVersion, loader, modsPath) {
|
|
85
|
+
// Get project info
|
|
86
|
+
const project = await getProject(projectId);
|
|
87
|
+
if (!project) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get compatible versions
|
|
92
|
+
const versions = await getProjectVersions(projectId, mcVersion, loader);
|
|
93
|
+
if (versions.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Use the latest compatible version
|
|
98
|
+
const modVersion = versions[0];
|
|
99
|
+
const file = modVersion.files.find(f => f.primary) || modVersion.files[0];
|
|
100
|
+
|
|
101
|
+
if (!file) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Download the file
|
|
106
|
+
const destPath = path.join(modsPath, file.filename);
|
|
107
|
+
await downloadFile(file.url, destPath, project.title);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
projectId: project.id,
|
|
111
|
+
slug: project.slug,
|
|
112
|
+
name: project.title,
|
|
113
|
+
versionId: modVersion.id,
|
|
114
|
+
versionNumber: modVersion.version_number,
|
|
115
|
+
fileName: file.filename,
|
|
116
|
+
installedAt: new Date().toISOString()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find mod in config by slug, name, or project ID
|
|
122
|
+
* @param {Array} mods - Array of mod objects
|
|
123
|
+
* @param {string} query - Search query
|
|
124
|
+
* @returns {object|undefined} - Found mod or undefined
|
|
125
|
+
*/
|
|
126
|
+
export function findMod(mods, query) {
|
|
127
|
+
const lowerQuery = query.toLowerCase();
|
|
128
|
+
return mods.find(m =>
|
|
129
|
+
m.slug.toLowerCase() === lowerQuery ||
|
|
130
|
+
m.name.toLowerCase() === lowerQuery ||
|
|
131
|
+
m.projectId === query
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default {
|
|
136
|
+
getProject,
|
|
137
|
+
getProjectVersions,
|
|
138
|
+
searchMods,
|
|
139
|
+
downloadMod,
|
|
140
|
+
findMod
|
|
141
|
+
};
|