@bf6mods/cli 1.0.1 → 1.1.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 +10 -3
- package/dist/cli/index.js +217 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/resources/maps/spatial/MP_Abbasid-ModBuilderCustom0.spatial.json +218 -0
- package/dist/resources/maps/spatial/MP_Aftermath-ModBuilderCustom0.spatial.json +194 -0
- package/dist/resources/maps/spatial/MP_Battery-ModBuilderCustom0.spatial.json +249 -0
- package/dist/resources/maps/spatial/MP_Capstone-ModBuilderCustom0.spatial.json +189 -0
- package/dist/resources/maps/spatial/MP_Dumbo-ModBuilderCustom0.spatial.json +222 -0
- package/dist/resources/maps/spatial/MP_FireStorm-ModBuilderCustom0.spatial.json +197 -0
- package/dist/resources/maps/spatial/MP_Limestone-ModBuilderCustom0.spatial.json +226 -0
- package/dist/resources/maps/spatial/MP_Outskirts-ModBuilderCustom0.spatial.json +190 -0
- package/dist/resources/maps/spatial/MP_Tungsten-ModBuilderCustom0.spatial.json +206 -0
- package/dist/resources/prepare/types/config.ts +144 -145
- package/dist/resources/templates/All/package.json +2 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -7,15 +7,17 @@ Create Battlefield 6 mods quickly with a vite like development experience.
|
|
|
7
7
|
To create a new project, simply run the following command and answer the questions
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
npx bf6mods init
|
|
10
|
+
npx @bf6mods/cli init
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+

|
|
14
|
+
|
|
13
15
|
## Import from JSON
|
|
14
16
|
|
|
15
17
|
Already have a project? Just export your currently existing project in [portal.battlefield.com](https://portal.battlefield.com), and run...
|
|
16
18
|
|
|
17
19
|
```
|
|
18
|
-
npx bf6mods import <input> <output>
|
|
20
|
+
npx @bf6mods/cli import <input> <output>
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
## Deploying Project to Portal
|
|
@@ -27,8 +29,9 @@ Just run `npm run build` in your project dir, open [portal.battlefield.com](http
|
|
|
27
29
|
- Use multiple different files, instead of just one large TypeScript file!
|
|
28
30
|
- Use a programatic interface for defining your gamemode.
|
|
29
31
|
- Extensive documentation
|
|
30
|
-
- Extended standard library
|
|
32
|
+
- Extended standard library (still in progress)
|
|
31
33
|
- Hot reload
|
|
34
|
+
- Automatic string injection
|
|
32
35
|
|
|
33
36
|
## Structure
|
|
34
37
|
|
|
@@ -68,3 +71,7 @@ export default defineBf6Config({
|
|
|
68
71
|
## @bf6mods/sdk
|
|
69
72
|
|
|
70
73
|
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.
|
|
74
|
+
|
|
75
|
+
## Join the discord with fellow modders!
|
|
76
|
+
|
|
77
|
+
You can join the discord by clicking this [link](https://discord.gg/2gJ9fheYYK)!
|
package/dist/cli/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@bf6mods/cli",
|
|
9
|
-
version: "1.0.
|
|
9
|
+
version: "1.0.2",
|
|
10
10
|
description: "CLI and library for bundling BF6 mods",
|
|
11
11
|
license: "MIT",
|
|
12
12
|
type: "module",
|
|
@@ -28,8 +28,12 @@ var package_default = {
|
|
|
28
28
|
files: [
|
|
29
29
|
"dist/"
|
|
30
30
|
],
|
|
31
|
+
repository: {
|
|
32
|
+
type: "git",
|
|
33
|
+
url: "git+https://github.com/bf6mods/bf6mods.git"
|
|
34
|
+
},
|
|
31
35
|
dependencies: {
|
|
32
|
-
"@bf6mods/sdk": "1.0.
|
|
36
|
+
"@bf6mods/sdk": "1.0.3",
|
|
33
37
|
"@clack/prompts": "^0.11.0",
|
|
34
38
|
chokidar: "^4.0.3",
|
|
35
39
|
colors: "^1.4.0",
|
|
@@ -43,15 +47,17 @@ var package_default = {
|
|
|
43
47
|
access: "public"
|
|
44
48
|
},
|
|
45
49
|
devDependencies: {
|
|
50
|
+
"@oxc-project/types": "^0.95.0",
|
|
46
51
|
"@types/stringify-object": "^4.0.5",
|
|
47
52
|
"cross-env": "^10.1.0",
|
|
48
53
|
tsx: "^4.20.6"
|
|
49
54
|
}
|
|
50
55
|
};
|
|
51
56
|
|
|
52
|
-
// src/cli/build.ts
|
|
57
|
+
// src/cli/build/index.ts
|
|
53
58
|
import fs from "fs";
|
|
54
59
|
import path from "path";
|
|
60
|
+
import { fileURLToPath } from "url";
|
|
55
61
|
import {
|
|
56
62
|
AttachmentType
|
|
57
63
|
} from "@bf6mods/sdk";
|
|
@@ -72,7 +78,156 @@ var MapId = /* @__PURE__ */ ((MapId2) => {
|
|
|
72
78
|
return MapId2;
|
|
73
79
|
})(MapId || {});
|
|
74
80
|
|
|
75
|
-
// src/cli/build.ts
|
|
81
|
+
// src/cli/build/generated-strings.ts
|
|
82
|
+
function extractBf6Strings(bf6Strings, generateFromLiterals) {
|
|
83
|
+
return {
|
|
84
|
+
name: "extract-bf6-strings",
|
|
85
|
+
generateBundle(_options, bundle) {
|
|
86
|
+
if (!generateFromLiterals) return;
|
|
87
|
+
let id = 0;
|
|
88
|
+
const existingStrings = new Set(Object.values(bf6Strings));
|
|
89
|
+
for (const [_file, output] of Object.entries(bundle)) {
|
|
90
|
+
let walk2 = function(node) {
|
|
91
|
+
if (node.type === "Literal") {
|
|
92
|
+
if (typeof node.value !== "string" || node.value === "" || node.value === "----uniquename----")
|
|
93
|
+
return;
|
|
94
|
+
if (!(node.value in bf6Strings) && !existingStrings.has(node.value)) {
|
|
95
|
+
existingStrings.add(node.value);
|
|
96
|
+
bf6Strings[`__auto__${id++}`] = node.value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const value of Object.values(node)) {
|
|
100
|
+
if (!value) continue;
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
for (const v of value) {
|
|
103
|
+
if (v && typeof v === "object" && "type" in v) walk2(v);
|
|
104
|
+
}
|
|
105
|
+
} else if (typeof value === "object" && "type" in value) {
|
|
106
|
+
walk2(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var walk = walk2;
|
|
111
|
+
if (output.type !== "chunk") continue;
|
|
112
|
+
const code = output.code;
|
|
113
|
+
const program2 = this.parse(code, {
|
|
114
|
+
lang: "ts",
|
|
115
|
+
astType: "js",
|
|
116
|
+
range: true
|
|
117
|
+
});
|
|
118
|
+
for (const item of program2.body) {
|
|
119
|
+
walk2(item);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
moduleParsed(moduleInfo) {
|
|
124
|
+
if (moduleInfo.code && moduleInfo.exports.includes("bf6Strings")) {
|
|
125
|
+
const code = moduleInfo.code;
|
|
126
|
+
const getSnippet = (start, end, linesBefore = 2, linesAfter = 2) => {
|
|
127
|
+
const lines = code.split(/\r?\n/);
|
|
128
|
+
const startLine = Math.max(
|
|
129
|
+
0,
|
|
130
|
+
code.slice(0, start).split(/\r?\n/).length - 1 - linesBefore
|
|
131
|
+
);
|
|
132
|
+
const endLine = Math.min(
|
|
133
|
+
lines.length,
|
|
134
|
+
code.slice(0, end).split(/\r?\n/).length - 1 + linesAfter
|
|
135
|
+
);
|
|
136
|
+
return lines.slice(startLine, endLine).map((line, i) => {
|
|
137
|
+
const actualLine = startLine + i + 1;
|
|
138
|
+
return `${actualLine.toString().padStart(3)} | ${line}`;
|
|
139
|
+
}).join("\n");
|
|
140
|
+
};
|
|
141
|
+
const program2 = this.parse(code, {
|
|
142
|
+
lang: "ts",
|
|
143
|
+
astType: "js",
|
|
144
|
+
range: true
|
|
145
|
+
});
|
|
146
|
+
const extractObjectLiteral = (declaration) => {
|
|
147
|
+
if (declaration.init?.type !== "ObjectExpression") {
|
|
148
|
+
const snippet = getSnippet(...declaration.range ?? [0, 0]);
|
|
149
|
+
this.error(
|
|
150
|
+
`\u274C Invalid bf6Strings export: expected an object literal.
|
|
151
|
+
|
|
152
|
+
${snippet}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
for (const property of declaration.init.properties) {
|
|
156
|
+
if (property.type !== "Property") {
|
|
157
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
158
|
+
this.error(
|
|
159
|
+
`\u274C Invalid bf6Strings property: expected a key/value pair.
|
|
160
|
+
Found type: ${property.type}
|
|
161
|
+
|
|
162
|
+
${snippet}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
let key = void 0;
|
|
166
|
+
if (property.key.type === "Literal") {
|
|
167
|
+
key = property.key.value;
|
|
168
|
+
} else if (property.key.type === "Identifier") {
|
|
169
|
+
key = property.key.name;
|
|
170
|
+
} else {
|
|
171
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
172
|
+
this.error(
|
|
173
|
+
`\u274C Invalid bf6Strings key: expected a string literal or identifier.
|
|
174
|
+
Found type: ${property.key.type}
|
|
175
|
+
|
|
176
|
+
${snippet}`
|
|
177
|
+
);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (property.value.type !== "Literal") {
|
|
181
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
182
|
+
this.error(
|
|
183
|
+
`\u274C Invalid bf6Strings value: expected a string literal.
|
|
184
|
+
Found type: ${property.value.type}
|
|
185
|
+
|
|
186
|
+
${snippet}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const value = property.value.value;
|
|
190
|
+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "bigint" && typeof key !== "boolean") {
|
|
191
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
192
|
+
this.error(
|
|
193
|
+
`\u274C bf6Strings key must be a string, found: ${typeof key}
|
|
194
|
+
|
|
195
|
+
${snippet}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint" && typeof value !== "boolean") {
|
|
199
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
200
|
+
this.error(
|
|
201
|
+
`\u274C bf6Strings value must be a string, found: ${typeof value}
|
|
202
|
+
|
|
203
|
+
${snippet}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
bf6Strings[`${key}`] = `${value}`;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
for (const item of program2.body) {
|
|
210
|
+
if (item.type === "VariableDeclaration") {
|
|
211
|
+
for (const declaration of item.declarations) {
|
|
212
|
+
if (declaration.type === "VariableDeclarator" && declaration.id.type === "Identifier" && declaration.id.name === "bf6Strings") {
|
|
213
|
+
extractObjectLiteral(declaration);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (item.type === "ExportNamedDeclaration" && item.declaration?.type === "VariableDeclaration") {
|
|
218
|
+
for (const declaration of item.declaration.declarations) {
|
|
219
|
+
if (declaration.type === "VariableDeclarator" && declaration.id.type === "Identifier" && declaration.id.name === "bf6Strings") {
|
|
220
|
+
extractObjectLiteral(declaration);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/cli/build/index.ts
|
|
76
231
|
async function getBf6Config(rootDir) {
|
|
77
232
|
globalThis.defineBf6Config = (c) => c;
|
|
78
233
|
globalThis.MapId = MapId;
|
|
@@ -90,39 +245,43 @@ async function build() {
|
|
|
90
245
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
91
246
|
fs.mkdirSync(outDir, { recursive: true });
|
|
92
247
|
const minifyJson = typeof config.minify === "boolean" ? config.minify : config.minify?.json ?? false;
|
|
248
|
+
const generatedStrings = {};
|
|
93
249
|
let tsAttachment;
|
|
94
250
|
if (config.entrypoint) {
|
|
95
251
|
const entryAbs = path.resolve(workingDir, config.entrypoint);
|
|
96
252
|
const compiled = await buildEntrypoint(
|
|
97
253
|
entryAbs,
|
|
98
|
-
|
|
99
|
-
|
|
254
|
+
generatedStrings,
|
|
255
|
+
config.generateStrings ?? true
|
|
100
256
|
);
|
|
101
257
|
tsAttachment = createTsAttachment(entryAbs, compiled);
|
|
102
258
|
}
|
|
103
259
|
const { attachments, mapRotation } = await collectAttachments(
|
|
104
260
|
config,
|
|
105
261
|
workingDir,
|
|
106
|
-
tsAttachment
|
|
262
|
+
tsAttachment,
|
|
263
|
+
generatedStrings
|
|
107
264
|
);
|
|
108
265
|
await writeModJson(config, outDir, attachments, mapRotation, minifyJson);
|
|
109
266
|
console.log(`\u2714 Built mod: ${config.name}`);
|
|
110
267
|
}
|
|
111
|
-
async function buildEntrypoint(entry,
|
|
268
|
+
async function buildEntrypoint(entry, bf6Strings, generateStringsFromLiterals) {
|
|
112
269
|
const bundle = await rolldown({
|
|
113
|
-
input: entry
|
|
270
|
+
input: entry,
|
|
271
|
+
plugins: [extractBf6Strings(bf6Strings, generateStringsFromLiterals)],
|
|
272
|
+
logLevel: "debug"
|
|
114
273
|
});
|
|
115
274
|
const result = await bundle.generate({
|
|
116
275
|
format: "esm",
|
|
117
276
|
inlineDynamicImports: true
|
|
118
277
|
});
|
|
119
278
|
const code = result.output[0].code;
|
|
120
|
-
if (emit) {
|
|
121
|
-
await fs.promises.writeFile(path.resolve(outDir, "index.js"), code);
|
|
122
|
-
}
|
|
123
279
|
return code;
|
|
124
280
|
}
|
|
125
|
-
|
|
281
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
282
|
+
var __dirname = path.dirname(__filename);
|
|
283
|
+
var spatialsDir = path.resolve(__dirname, "../resources/maps/spatial");
|
|
284
|
+
async function collectAttachments(config, workingDir, tsAttachment, generatedStrings) {
|
|
126
285
|
const attachments = [];
|
|
127
286
|
const mapRotation = [];
|
|
128
287
|
if (tsAttachment) attachments.push(tsAttachment);
|
|
@@ -130,11 +289,20 @@ async function collectAttachments(config, workingDir, tsAttachment) {
|
|
|
130
289
|
const strPath = path.resolve(workingDir, config.strings);
|
|
131
290
|
if (!fs.existsSync(strPath)) throw new Error("Cannot find strings file");
|
|
132
291
|
const raw = await fs.promises.readFile(strPath, "utf8");
|
|
133
|
-
|
|
292
|
+
const attachment = createStringsAttachment(strPath, raw, generatedStrings);
|
|
293
|
+
attachments.push(attachment);
|
|
134
294
|
}
|
|
135
295
|
if (config.scenes) {
|
|
136
296
|
let mapIdx = 0;
|
|
137
|
-
for (const
|
|
297
|
+
for (const map of config.scenes) {
|
|
298
|
+
let mapId;
|
|
299
|
+
let scene;
|
|
300
|
+
if (Array.isArray(map)) {
|
|
301
|
+
[mapId, scene] = map;
|
|
302
|
+
} else {
|
|
303
|
+
mapId = map;
|
|
304
|
+
scene = path.resolve(spatialsDir, `${mapId}.spatial.json`);
|
|
305
|
+
}
|
|
138
306
|
const scenePath = path.resolve(workingDir, scene);
|
|
139
307
|
if (!fs.existsSync(scenePath))
|
|
140
308
|
throw new Error(`Cannot find spatial data file: ${scene}`);
|
|
@@ -160,6 +328,19 @@ async function writeModJson(config, outDir, attachments, mapRotation, minify) {
|
|
|
160
328
|
};
|
|
161
329
|
const jsonOutput = minify ? JSON.stringify(finalJson) : JSON.stringify(finalJson, null, 2);
|
|
162
330
|
await fs.promises.writeFile(path.resolve(outDir, "mod.json"), jsonOutput);
|
|
331
|
+
if (config.outputArtifacts && finalJson?.attachments) {
|
|
332
|
+
for (const attachment of finalJson.attachments) {
|
|
333
|
+
const attachmentsDir = path.resolve(outDir, "attachments");
|
|
334
|
+
if (!fs.existsSync(attachmentsDir))
|
|
335
|
+
fs.mkdirSync(attachmentsDir, {
|
|
336
|
+
recursive: true
|
|
337
|
+
});
|
|
338
|
+
await fs.promises.writeFile(
|
|
339
|
+
path.resolve(outDir, "attachments", attachment.filename),
|
|
340
|
+
atob(attachment.attachmentData.original)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
163
344
|
}
|
|
164
345
|
function createTsAttachment(filePath, compiled) {
|
|
165
346
|
return {
|
|
@@ -173,7 +354,18 @@ function createTsAttachment(filePath, compiled) {
|
|
|
173
354
|
errors: []
|
|
174
355
|
};
|
|
175
356
|
}
|
|
176
|
-
function createStringsAttachment(filePath, raw) {
|
|
357
|
+
function createStringsAttachment(filePath, raw, generatedStrings) {
|
|
358
|
+
let result = raw;
|
|
359
|
+
if (generatedStrings) {
|
|
360
|
+
result = JSON.stringify(
|
|
361
|
+
{
|
|
362
|
+
...generatedStrings,
|
|
363
|
+
...JSON.parse(raw)
|
|
364
|
+
},
|
|
365
|
+
null,
|
|
366
|
+
4
|
|
367
|
+
);
|
|
368
|
+
}
|
|
177
369
|
return {
|
|
178
370
|
id: crypto.randomUUID(),
|
|
179
371
|
version: "1.0",
|
|
@@ -181,7 +373,7 @@ function createStringsAttachment(filePath, raw) {
|
|
|
181
373
|
isProcessable: true,
|
|
182
374
|
processingStatus: 2,
|
|
183
375
|
attachmentType: AttachmentType.Strings,
|
|
184
|
-
attachmentData: { original: toBase64(
|
|
376
|
+
attachmentData: { original: toBase64(result), compiled: "" },
|
|
185
377
|
errors: []
|
|
186
378
|
};
|
|
187
379
|
}
|
|
@@ -293,11 +485,11 @@ import stringifyObject from "stringify-object";
|
|
|
293
485
|
import child_process from "child_process";
|
|
294
486
|
import fs3 from "fs";
|
|
295
487
|
import path3 from "path";
|
|
296
|
-
import { fileURLToPath } from "url";
|
|
488
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
297
489
|
import * as prompts from "@clack/prompts";
|
|
298
|
-
var
|
|
299
|
-
var
|
|
300
|
-
var templatesDir = path3.resolve(
|
|
490
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
491
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
492
|
+
var templatesDir = path3.resolve(__dirname2, "../resources/templates");
|
|
301
493
|
function renameFilesRecursively(dir, modName) {
|
|
302
494
|
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
303
495
|
for (const entry of entries) {
|
|
@@ -540,7 +732,7 @@ function stringifyWithRaw(value, options = {}) {
|
|
|
540
732
|
// src/cli/prepare.ts
|
|
541
733
|
import fs5 from "fs";
|
|
542
734
|
import path5 from "path";
|
|
543
|
-
import { fileURLToPath as
|
|
735
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
544
736
|
import colors3 from "colors";
|
|
545
737
|
import { genExport, genInlineTypeImport } from "knitwork";
|
|
546
738
|
|
|
@@ -580,11 +772,11 @@ var printToConsole = (message) => {
|
|
|
580
772
|
// src/cli/prepare.ts
|
|
581
773
|
async function prepare() {
|
|
582
774
|
try {
|
|
583
|
-
const
|
|
584
|
-
const
|
|
775
|
+
const __filename3 = fileURLToPath3(import.meta.url);
|
|
776
|
+
const __dirname3 = path5.dirname(__filename3);
|
|
585
777
|
const _workingDir = path5.resolve(".");
|
|
586
778
|
const buildDir = path5.resolve(".bf6");
|
|
587
|
-
const resources = path5.resolve(
|
|
779
|
+
const resources = path5.resolve(__dirname3, "../resources/prepare");
|
|
588
780
|
fs5.cpSync(
|
|
589
781
|
path5.resolve(resources, "tsconfig.json"),
|
|
590
782
|
path5.resolve(buildDir, "tsconfig.json")
|