@bf6mods/cli 1.6.0 → 1.7.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 +9 -51
- package/dist/cli/index.js +215 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/{puppeteer-u5zMrFnK.js → puppeteer-BzRJX4NM.js} +72 -48
- package/dist/puppeteer-BzRJX4NM.js.map +1 -0
- package/dist/resources/prepare/types/config.ts +17 -0
- package/dist/resources/templates/All/README.md +22 -0
- package/dist/resources/templates/All/placeholder.jpg +0 -0
- package/dist/resources/templates/Basic/bf6.config.ts +1 -0
- package/dist/resources/templates/Complete/bf6.config.ts +1 -0
- package/package.json +2 -1
- package/dist/puppeteer-u5zMrFnK.js.map +0 -1
- package/dist/resources/templates/BombSquad.json +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Create Battlefield 6 mods quickly with a vite like development experience.
|
|
|
4
4
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
7
|
-
To create a new project, simply run the following command and answer the questions, ensure that you are using Node v22 and above
|
|
7
|
+
To create a new project, simply run the following command and answer the questions, ensure that you are using Node v22 and above, and if on Linux install `libsecret-1-0`.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
npx @bf6mods/cli init
|
|
@@ -20,20 +20,9 @@ Already have a project? Just export your currently existing project in [portal.b
|
|
|
20
20
|
npx @bf6mods/cli import <export file> <output directory>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Full Documentation
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Manually Import
|
|
28
|
-
|
|
29
|
-
Just run `npm run build` in your project dir, open [portal.battlefield.com](https://portal.battlefield.com), click import, and select the `dist/mod.json` file.
|
|
30
|
-
|
|
31
|
-
### `npx @bf6mods/cli deploy`
|
|
32
|
-
|
|
33
|
-
To use this, you must first install puppeteer via `npm -g i puppeteer`, but after doing so you can just run this command, and you will
|
|
34
|
-
have your project deploy automatically for you.
|
|
35
|
-
|
|
36
|
-
Important note on this. The [portal.battlefield.com](https://portal.battlefield.com) will not update showing the changes from the deployed code. This is due to the browsers cache.
|
|
25
|
+
The full documentation can be found [here](https://bf6mods.github.io/bf6mods/guide/getting-started.html)!
|
|
37
26
|
|
|
38
27
|
## Features
|
|
39
28
|
|
|
@@ -43,47 +32,16 @@ Important note on this. The [portal.battlefield.com](https://portal.battlefield.
|
|
|
43
32
|
- Extended standard library (still in progress)
|
|
44
33
|
- Hot reload
|
|
45
34
|
- Automatic string injection
|
|
35
|
+
- **Automatic thumbnail resizing** - Add any image to your project and it will be automatically resized and optimized to meet BF6 Portal requirements (352x248, max 78KB)
|
|
46
36
|
- Logging from BF6! (Only when in hosting locally)
|
|
47
37
|
|
|
48
|
-
## Structure
|
|
49
|
-
|
|
50
|
-
`bf6mods` is very configurable, but the following structure is what can be expected from any mod.
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
my-mod/
|
|
54
|
-
├─ src/
|
|
55
|
-
│ ├─ index.ts
|
|
56
|
-
│ ├─ scenes/
|
|
57
|
-
│ │ └─ MyMap.spatial.json
|
|
58
|
-
├─ bf6.config.ts
|
|
59
|
-
├─ package.json
|
|
60
|
-
└─ dist/
|
|
61
|
-
└─ mod.json
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## `bf6.config.ts`
|
|
65
|
-
|
|
66
|
-
Here is a short example of a `bf6.config.ts`.
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
export default defineBf6Config({
|
|
70
|
-
name: "AcePursuit",
|
|
71
|
-
description: "A fast-paced race mod",
|
|
72
|
-
outDir: "dist",
|
|
73
|
-
entrypoint: "src/index.ts",
|
|
74
|
-
scenes: [[MapId.LiberationPeak, "src/scenes/AcePursuit.spatial.json"]],
|
|
75
|
-
game: {
|
|
76
|
-
mutators: {
|
|
77
|
-
// ...
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
````
|
|
82
|
-
|
|
83
38
|
## @bf6mods/sdk
|
|
84
39
|
|
|
85
40
|
This is a seperate library that exports the `PortalSdk`'s `mod` and `modlib`. Additionally it exports some stdlib helper functions and classes to help accelerate development.
|
|
86
41
|
|
|
87
|
-
##
|
|
42
|
+
## Links
|
|
88
43
|
|
|
89
|
-
|
|
44
|
+
- [Documentation](https://bf6mods.github.io/bf6mods/)
|
|
45
|
+
- [Contributing Guide](https://bf6mods.github.io/bf6mods/contributing/how-to-contribute.html)
|
|
46
|
+
- [Discord](https://discord.gg/2gJ9fheYYK)
|
|
47
|
+
- [Portal](https://portal.battlefield.com/bf6/en-gb/)
|
package/dist/cli/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { AttachmentType } from "@bf6mods/sdk";
|
|
|
8
8
|
import { createJiti } from "jiti";
|
|
9
9
|
import { rolldown } from "rolldown";
|
|
10
10
|
import stripAnsi from "strip-ansi";
|
|
11
|
+
import sharp from "sharp";
|
|
11
12
|
import { Clients, Generated_pb } from "@bf6mods/portal";
|
|
12
13
|
import os from "node:os";
|
|
13
14
|
import keytar from "keytar";
|
|
@@ -19,7 +20,7 @@ import * as prompts from "@clack/prompts";
|
|
|
19
20
|
import { genExport, genInlineTypeImport } from "knitwork";
|
|
20
21
|
|
|
21
22
|
//#region package.json
|
|
22
|
-
var version$1 = "1.
|
|
23
|
+
var version$1 = "1.7.0";
|
|
23
24
|
var description = "CLI and library for bundling BF6 mods";
|
|
24
25
|
var bin = { "bf6mods": "./dist/cli/index.js" };
|
|
25
26
|
|
|
@@ -68,6 +69,49 @@ const readableList = (initialValues) => {
|
|
|
68
69
|
return values[0];
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/cli/build/any-params.ts
|
|
74
|
+
function addAnyToParams() {
|
|
75
|
+
return {
|
|
76
|
+
name: "add-any-to-params",
|
|
77
|
+
generateBundle(_options, bundle) {
|
|
78
|
+
for (const [_file, output] of Object.entries(bundle)) {
|
|
79
|
+
if (output.type !== "chunk") continue;
|
|
80
|
+
const program$1 = this.parse(output.code, {
|
|
81
|
+
lang: "js",
|
|
82
|
+
astType: "js",
|
|
83
|
+
range: true
|
|
84
|
+
});
|
|
85
|
+
const edits = [];
|
|
86
|
+
function walk(node) {
|
|
87
|
+
if (!node || typeof node !== "object") return;
|
|
88
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") for (const param of node.params) {
|
|
89
|
+
if (param.type === "Identifier" && param.range) edits.push({
|
|
90
|
+
pos: param.range[1],
|
|
91
|
+
insert: ": any"
|
|
92
|
+
});
|
|
93
|
+
if (param.type === "AssignmentPattern" && param.left?.type === "Identifier" && param.left.range) edits.push({
|
|
94
|
+
pos: param.left.range[1],
|
|
95
|
+
insert: ": any"
|
|
96
|
+
});
|
|
97
|
+
if (param.type === "RestElement" && param.argument?.type === "Identifier" && param.argument.range) edits.push({
|
|
98
|
+
pos: param.argument.range[1],
|
|
99
|
+
insert: ": any"
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
for (const val of Object.values(node)) if (Array.isArray(val)) val.forEach(walk);
|
|
103
|
+
else if (val && typeof val === "object" && "type" in val) walk(val);
|
|
104
|
+
}
|
|
105
|
+
for (const item of program$1.body) walk(item);
|
|
106
|
+
edits.sort((a, b) => b.pos - a.pos);
|
|
107
|
+
let result = output.code;
|
|
108
|
+
for (const edit of edits) result = result.slice(0, edit.pos) + edit.insert + result.slice(edit.pos);
|
|
109
|
+
output.code = result;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
71
115
|
//#endregion
|
|
72
116
|
//#region src/cli/build/generated-strings.ts
|
|
73
117
|
function extractBf6Strings(bf6Strings, generateFromLiterals) {
|
|
@@ -166,6 +210,131 @@ function extractBf6Strings(bf6Strings, generateFromLiterals) {
|
|
|
166
210
|
};
|
|
167
211
|
}
|
|
168
212
|
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/cli/build/thumbnail.ts
|
|
215
|
+
/**
|
|
216
|
+
* BF6 Portal thumbnail requirements:
|
|
217
|
+
* - Max file size: 78KB
|
|
218
|
+
* - Dimensions: 352x248 pixels
|
|
219
|
+
* - Format: JPEG or PNG
|
|
220
|
+
*/
|
|
221
|
+
const THUMBNAIL_REQUIREMENTS = {
|
|
222
|
+
maxSizeBytes: 78 * 1024,
|
|
223
|
+
width: 352,
|
|
224
|
+
height: 248,
|
|
225
|
+
supportedFormats: [
|
|
226
|
+
".jpg",
|
|
227
|
+
".jpeg",
|
|
228
|
+
".png"
|
|
229
|
+
]
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Validates that an image buffer meets BF6 Portal requirements
|
|
233
|
+
*/
|
|
234
|
+
function validateThumbnail(buffer, filename) {
|
|
235
|
+
const errors = [];
|
|
236
|
+
if (buffer.length > THUMBNAIL_REQUIREMENTS.maxSizeBytes) errors.push(`File size is ${(buffer.length / 1024).toFixed(2)}KB, must be less than 78KB`);
|
|
237
|
+
const ext = path.extname(filename).toLowerCase();
|
|
238
|
+
if (!THUMBNAIL_REQUIREMENTS.supportedFormats.includes(ext)) errors.push(`File format ${ext} is not supported. Must be ${THUMBNAIL_REQUIREMENTS.supportedFormats.join(" or ")}`);
|
|
239
|
+
const dimensions = getImageDimensions(buffer, ext);
|
|
240
|
+
if (dimensions) {
|
|
241
|
+
if (dimensions.width !== THUMBNAIL_REQUIREMENTS.width || dimensions.height !== THUMBNAIL_REQUIREMENTS.height) errors.push(`Image dimensions are ${dimensions.width}x${dimensions.height}, must be ${THUMBNAIL_REQUIREMENTS.width}x${THUMBNAIL_REQUIREMENTS.height}`);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
valid: errors.length === 0,
|
|
245
|
+
errors
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Gets image dimensions from buffer by reading image headers
|
|
250
|
+
*/
|
|
251
|
+
function getImageDimensions(buffer, ext) {
|
|
252
|
+
try {
|
|
253
|
+
if (ext === ".png") {
|
|
254
|
+
if (buffer.length >= 24 && buffer.toString("ascii", 1, 4) === "PNG") return {
|
|
255
|
+
width: buffer.readUInt32BE(16),
|
|
256
|
+
height: buffer.readUInt32BE(20)
|
|
257
|
+
};
|
|
258
|
+
} else if (ext === ".jpg" || ext === ".jpeg") {
|
|
259
|
+
let offset = 2;
|
|
260
|
+
while (offset < buffer.length) {
|
|
261
|
+
if (buffer[offset] !== 255) break;
|
|
262
|
+
const marker = buffer[offset + 1];
|
|
263
|
+
offset += 2;
|
|
264
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
265
|
+
const height = buffer.readUInt16BE(offset + 3);
|
|
266
|
+
return {
|
|
267
|
+
width: buffer.readUInt16BE(offset + 5),
|
|
268
|
+
height
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const segmentLength = buffer.readUInt16BE(offset);
|
|
272
|
+
offset += segmentLength;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (_error) {}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Processes a thumbnail file for BF6 Portal
|
|
280
|
+
* Saves the resized thumbnail to the output directory
|
|
281
|
+
*/
|
|
282
|
+
async function processThumbnail(thumbnailPath, workingDir, outDir) {
|
|
283
|
+
const fullPath = path.resolve(workingDir, thumbnailPath);
|
|
284
|
+
if (!fs.existsSync(fullPath)) {
|
|
285
|
+
printToConsole(colors.yellow(`⚠️ Thumbnail file not found: ${thumbnailPath}`));
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
let buffer = await fs.promises.readFile(fullPath);
|
|
289
|
+
const filename = path.basename(fullPath);
|
|
290
|
+
const ext = path.extname(fullPath);
|
|
291
|
+
if (!validateThumbnail(buffer, filename).valid) {
|
|
292
|
+
printToConsole(colors.yellow(`⚠️ Thumbnail doesn't meet requirements, attempting to resize and optimize...`));
|
|
293
|
+
try {
|
|
294
|
+
buffer = Buffer.from(await resizeThumbnail(buffer, fullPath));
|
|
295
|
+
printToConsole(colors.green(`✓ Thumbnail resized and optimized: ${filename} (${(buffer.length / 1024).toFixed(2)}KB)`));
|
|
296
|
+
} catch (error) {
|
|
297
|
+
printToConsole(colors.red(`✗ Failed to resize thumbnail: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
298
|
+
printToConsole(colors.yellow(`\nThumbnail requirements:\n - Size: max 78KB\n - Dimensions: 352x248 pixels\n - Format: JPEG or PNG`));
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
} else printToConsole(`${colors.green.bold("✓")} Thumbnail validated: ${filename} (${(buffer.length / 1024).toFixed(2)}KB)`);
|
|
302
|
+
const outputPath = path.resolve(outDir, `thumbnail${ext}`);
|
|
303
|
+
await fs.promises.writeFile(outputPath, buffer);
|
|
304
|
+
printToConsole(colors.green(`✓ Thumbnail saved to: ${path.relative(workingDir, outputPath)}`));
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Resizes and optimizes an image to meet BF6 Portal thumbnail requirements
|
|
309
|
+
*/
|
|
310
|
+
async function resizeThumbnail(buffer, originalPath) {
|
|
311
|
+
const ext = path.extname(originalPath).toLowerCase();
|
|
312
|
+
const isJpeg = ext === ".jpg" || ext === ".jpeg";
|
|
313
|
+
let result = await sharp(buffer).resize(THUMBNAIL_REQUIREMENTS.width, THUMBNAIL_REQUIREMENTS.height, {
|
|
314
|
+
fit: "cover",
|
|
315
|
+
position: "center"
|
|
316
|
+
}).toBuffer();
|
|
317
|
+
if (isJpeg || result.length > THUMBNAIL_REQUIREMENTS.maxSizeBytes) for (let quality = 90; quality >= 60; quality -= 10) {
|
|
318
|
+
result = await sharp(buffer).resize(THUMBNAIL_REQUIREMENTS.width, THUMBNAIL_REQUIREMENTS.height, {
|
|
319
|
+
fit: "cover",
|
|
320
|
+
position: "center"
|
|
321
|
+
}).jpeg({
|
|
322
|
+
quality,
|
|
323
|
+
progressive: true
|
|
324
|
+
}).toBuffer();
|
|
325
|
+
if (result.length <= THUMBNAIL_REQUIREMENTS.maxSizeBytes) break;
|
|
326
|
+
}
|
|
327
|
+
else result = await sharp(buffer).resize(THUMBNAIL_REQUIREMENTS.width, THUMBNAIL_REQUIREMENTS.height, {
|
|
328
|
+
fit: "cover",
|
|
329
|
+
position: "center"
|
|
330
|
+
}).png({
|
|
331
|
+
compressionLevel: 9,
|
|
332
|
+
quality: 90
|
|
333
|
+
}).toBuffer();
|
|
334
|
+
if (result.length > THUMBNAIL_REQUIREMENTS.maxSizeBytes) throw new Error(`Unable to compress image below 78KB (current: ${(result.length / 1024).toFixed(2)}KB). Try using a simpler image or pre-compressing it.`);
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
|
|
169
338
|
//#endregion
|
|
170
339
|
//#region src/cli/build/index.ts
|
|
171
340
|
/**
|
|
@@ -204,6 +373,7 @@ async function build() {
|
|
|
204
373
|
tsAttachment = createTsAttachment(entryAbs, await buildEntrypoint(entryAbs, generatedStrings, config.generateStrings ?? true));
|
|
205
374
|
}
|
|
206
375
|
const { attachments, mapRotation } = await collectAttachments(config, workingDir, tsAttachment, generatedStrings);
|
|
376
|
+
if (config.thumbnail) await processThumbnail(config.thumbnail, workingDir, outDir);
|
|
207
377
|
await writeModJson(config, outDir, attachments, mapRotation, minifyJson);
|
|
208
378
|
printToConsole(`${colors.green.bold("✓")} Built mod: ${config.name}`);
|
|
209
379
|
}
|
|
@@ -213,7 +383,7 @@ async function build() {
|
|
|
213
383
|
async function buildEntrypoint(entry, bf6Strings, generateStringsFromLiterals) {
|
|
214
384
|
return (await (await rolldown({
|
|
215
385
|
input: entry,
|
|
216
|
-
plugins: [extractBf6Strings(bf6Strings, generateStringsFromLiterals)],
|
|
386
|
+
plugins: [extractBf6Strings(bf6Strings, generateStringsFromLiterals), addAnyToParams()],
|
|
217
387
|
logLevel: "debug",
|
|
218
388
|
resolve: { alias: { modlib: "@bf6mods/sdk" } }
|
|
219
389
|
})).generate({
|
|
@@ -291,7 +461,7 @@ function createTsAttachment(filePath, compiled) {
|
|
|
291
461
|
return {
|
|
292
462
|
id: crypto.randomUUID(),
|
|
293
463
|
version: "1.0",
|
|
294
|
-
filename: `${path.parse(filePath).name}.
|
|
464
|
+
filename: `${path.parse(filePath).name}.ts`,
|
|
295
465
|
isProcessable: true,
|
|
296
466
|
processingStatus: 2,
|
|
297
467
|
attachmentType: AttachmentType.TypeScript,
|
|
@@ -347,7 +517,7 @@ function toBase64(input) {
|
|
|
347
517
|
async function getSessionIdFromCookies() {
|
|
348
518
|
let puppeteer;
|
|
349
519
|
try {
|
|
350
|
-
puppeteer = await import("../puppeteer-
|
|
520
|
+
puppeteer = await import("../puppeteer-BzRJX4NM.js");
|
|
351
521
|
} catch (_error) {
|
|
352
522
|
printToConsole(`${colors.red.bold("✗")} Puppeteer could not be loaded.`);
|
|
353
523
|
console.error("It looks like Puppeteer is not installed in this environment.");
|
|
@@ -371,7 +541,7 @@ async function getSessionIdFromCookies() {
|
|
|
371
541
|
return document.cookie.includes("bf6sessionId");
|
|
372
542
|
}, { timeout: 0 });
|
|
373
543
|
console.log(`${colors.green.bold("✓")} Login detected!`);
|
|
374
|
-
const cookies = await
|
|
544
|
+
const cookies = await browser.cookies();
|
|
375
545
|
await browser.close();
|
|
376
546
|
return cookies.find((cookie) => cookie.name === "bf6sessionId")?.value;
|
|
377
547
|
}
|
|
@@ -413,15 +583,14 @@ async function authenticate(sessionIdParam) {
|
|
|
413
583
|
printToConsole(`${colors.red("✗")} Authentication failed. Session may have expired immediately.`, true);
|
|
414
584
|
return false;
|
|
415
585
|
}
|
|
416
|
-
async function alwaysAuthenticatedRequest(method,
|
|
417
|
-
const fn = clients.play[method];
|
|
586
|
+
async function alwaysAuthenticatedRequest(method, sessionId) {
|
|
418
587
|
try {
|
|
419
|
-
return await
|
|
588
|
+
return await method();
|
|
420
589
|
} catch (error) {
|
|
421
590
|
if (error instanceof Error && error.message === "[unauthenticated]") {
|
|
422
591
|
printToConsole("🔄 Session expired. Re-authenticating…");
|
|
423
592
|
if (!await authenticate(sessionId)) throw error;
|
|
424
|
-
return await
|
|
593
|
+
return await method();
|
|
425
594
|
}
|
|
426
595
|
throw error;
|
|
427
596
|
}
|
|
@@ -431,30 +600,28 @@ async function alwaysAuthenticatedRequest(method, request, sessionId, options) {
|
|
|
431
600
|
//#region src/cli/deploy/index.ts
|
|
432
601
|
const clients = new Clients();
|
|
433
602
|
async function deploy({ input, sessionIdParam, modId }) {
|
|
434
|
-
printToConsole("To be implemented in a future release");
|
|
435
|
-
process.exit(0);
|
|
436
603
|
const rootDir = path.resolve(".");
|
|
437
604
|
if (!input) input = path.resolve(rootDir, "dist", "mod.json");
|
|
438
605
|
printToConsole(`🚀 Starting deploy for ${colors.cyan(input)}…`);
|
|
439
606
|
if (!fs.existsSync(input)) return printToConsole(`${colors.red.bold("✗")} File ${colors.cyan(input)} does not exist!`, true);
|
|
440
607
|
const mod = JSON.parse(fs.readFileSync(input, { encoding: "utf-8" }));
|
|
441
|
-
const blueprints = await alwaysAuthenticatedRequest(
|
|
608
|
+
const blueprints = await alwaysAuthenticatedRequest(() => clients.play.getScheduledBlueprints({}), sessionIdParam);
|
|
442
609
|
console.log("blueprints", blueprints.blueprintIds);
|
|
443
|
-
await alwaysAuthenticatedRequest(
|
|
610
|
+
await alwaysAuthenticatedRequest(() => clients.play.getBlueprintsById({ blueprintIds: blueprints.blueprintIds }), sessionIdParam);
|
|
444
611
|
const config = await getBf6Config(rootDir);
|
|
445
612
|
let id = modId;
|
|
446
613
|
if (!config) printToConsole(`Cannot find bf6mods config in dir ${rootDir}, proceeding without it.`);
|
|
447
614
|
else if (config?.id) id = config.id;
|
|
448
615
|
else printToConsole(`No id specified in bf6.config.ts! Defaulting to experience with same name!`);
|
|
449
616
|
if (!id) {
|
|
450
|
-
const owned = await alwaysAuthenticatedRequest(
|
|
617
|
+
const owned = await alwaysAuthenticatedRequest(() => clients.play.getOwnedPlayElementsV2({
|
|
451
618
|
includeDenied: true,
|
|
452
619
|
publishStates: [
|
|
453
620
|
Generated_pb.PublishStateType.Draft,
|
|
454
621
|
Generated_pb.PublishStateType.Published,
|
|
455
622
|
Generated_pb.PublishStateType.Error
|
|
456
623
|
]
|
|
457
|
-
}, sessionIdParam);
|
|
624
|
+
}), sessionIdParam);
|
|
458
625
|
console.log("owned:", owned.playElements.map((owned$1) => owned$1.playElement));
|
|
459
626
|
const found = owned.playElements.find((element) => element?.playElement?.name === mod.name);
|
|
460
627
|
if (found?.playElement?.id) id = found.playElement.id;
|
|
@@ -463,13 +630,43 @@ async function deploy({ input, sessionIdParam, modId }) {
|
|
|
463
630
|
printToConsole(`${colors.red.bold("✗")} Cannot find an id for your mod to deploy with! Please specify the id explicitly with \`--id\` or by having a mod with the same name!`);
|
|
464
631
|
process.exit(0);
|
|
465
632
|
}
|
|
466
|
-
const playElementResponse = await alwaysAuthenticatedRequest(
|
|
633
|
+
const playElementResponse = await alwaysAuthenticatedRequest(() => clients.play.getPlayElement({
|
|
467
634
|
id,
|
|
468
635
|
includeDenied: true
|
|
469
|
-
}, sessionIdParam);
|
|
636
|
+
}), sessionIdParam);
|
|
470
637
|
if (!playElementResponse.playElement || !playElementResponse.playElementDesign || !playElementResponse.progressionMode) throw new Error("Cannot find essential attribute in mod, please report to GitHub!");
|
|
471
638
|
if (!mod.mapRotation) throw new Error("You must specify at least one map!");
|
|
472
639
|
console.log("playElementResponse:", playElementResponse);
|
|
640
|
+
const thumbnailExtensions = [
|
|
641
|
+
".jpg",
|
|
642
|
+
".jpeg",
|
|
643
|
+
".png"
|
|
644
|
+
];
|
|
645
|
+
let thumbnailPath;
|
|
646
|
+
let thumbnailExt;
|
|
647
|
+
for (const ext of thumbnailExtensions) {
|
|
648
|
+
const testPath = path.resolve(rootDir, "dist", `thumbnail${ext}`);
|
|
649
|
+
if (fs.existsSync(testPath)) {
|
|
650
|
+
thumbnailPath = testPath;
|
|
651
|
+
thumbnailExt = ext;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (thumbnailPath && thumbnailExt) try {
|
|
656
|
+
printToConsole(colors.blue("📸 Uploading thumbnail..."));
|
|
657
|
+
const thumbnailBuffer = await fs.promises.readFile(thumbnailPath);
|
|
658
|
+
const mimeType = thumbnailExt === ".png" ? "image/png" : "image/jpeg";
|
|
659
|
+
const uploadResponse = await alwaysAuthenticatedRequest(() => clients.play.uploadExperienceThumbnail({
|
|
660
|
+
image: thumbnailBuffer,
|
|
661
|
+
mimeType
|
|
662
|
+
}), sessionIdParam);
|
|
663
|
+
printToConsole(colors.green(`✓ Thumbnail uploaded successfully: ${uploadResponse.assetId}`));
|
|
664
|
+
printToConsole(colors.dim(` URL: ${uploadResponse.url}`));
|
|
665
|
+
} catch (error) {
|
|
666
|
+
printToConsole(colors.yellow(`⚠ Failed to upload thumbnail: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
667
|
+
printToConsole(colors.dim(" Continuing deployment without thumbnail"));
|
|
668
|
+
}
|
|
669
|
+
else printToConsole(colors.dim(" No thumbnail found in dist folder, skipping upload"));
|
|
473
670
|
}
|
|
474
671
|
|
|
475
672
|
//#endregion
|
|
@@ -628,7 +825,7 @@ async function dev() {
|
|
|
628
825
|
|
|
629
826
|
//#endregion
|
|
630
827
|
//#region ../sdk/package.json
|
|
631
|
-
var version = "1.
|
|
828
|
+
var version = "1.7.0";
|
|
632
829
|
|
|
633
830
|
//#endregion
|
|
634
831
|
//#region src/cli/init.ts
|
|
@@ -665,14 +862,12 @@ const templates = [
|
|
|
665
862
|
"Basic",
|
|
666
863
|
"Complete",
|
|
667
864
|
"AcePursuit",
|
|
668
|
-
"BombSquad",
|
|
669
865
|
"Exfil",
|
|
670
866
|
"Vertigo"
|
|
671
867
|
];
|
|
672
868
|
async function startProject(destination, template, name) {
|
|
673
869
|
if ([
|
|
674
870
|
"AcePursuit",
|
|
675
|
-
"BombSquad",
|
|
676
871
|
"Exfil",
|
|
677
872
|
"Vertigo"
|
|
678
873
|
].includes(template)) await importFile(path.resolve(templatesDir, `${template}.json`), destination, name);
|