@bf6mods/cli 1.0.0 → 1.0.2
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 +72 -0
- package/dist/cli/index.js +541 -76
- package/dist/cli/index.js.map +1 -1
- package/dist/resources/prepare/bf6.d.ts +2 -0
- package/dist/resources/prepare/tsconfig.json +14 -15
- package/dist/resources/prepare/types/config.ts +139 -4
- package/dist/resources/templates/All/package.json +1 -1
- package/dist/resources/templates/Basic/bf6.config.ts +5 -1
- package/package.json +48 -41
- package/dist/resources/templates/AcePursuit/src/index.ts +0 -3421
- package/dist/resources/templates/AcePursuit/src/levels.tscn +0 -6922
- package/dist/resources/templates/AcePursuit/src/strings.json +0 -191
- package/dist/resources/templates/All/bf6.config.ts +0 -6
- package/dist/resources/templates/BombSquad/src/index.ts +0 -3683
- package/dist/resources/templates/BombSquad/src/levels.tscn +0 -3212
- package/dist/resources/templates/BombSquad/src/strings.json +0 -123
- package/dist/resources/templates/Exfil/src/index.ts +0 -2393
- package/dist/resources/templates/Exfil/src/levels.tscn +0 -5600
- package/dist/resources/templates/Exfil/src/strings.json +0 -185
- package/dist/resources/templates/Vertigo/src/index.ts +0 -1948
- package/dist/resources/templates/Vertigo/src/levels.tscn +0 -6387
- package/dist/resources/templates/Vertigo/src/strings.json +0 -308
- /package/dist/resources/templates/{AcePursuit/src/config.json → AcePursuit.json} +0 -0
- /package/dist/resources/templates/{BombSquad/src/config.json → BombSquad.json} +0 -0
- /package/dist/resources/templates/{Exfil/src/config.json → Exfil.json} +0 -0
- /package/dist/resources/templates/{Vertigo/src/config.json → Vertigo.json} +0 -0
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.1",
|
|
10
10
|
description: "CLI and library for bundling BF6 mods",
|
|
11
11
|
license: "MIT",
|
|
12
12
|
type: "module",
|
|
@@ -30,102 +30,522 @@ var package_default = {
|
|
|
30
30
|
],
|
|
31
31
|
dependencies: {
|
|
32
32
|
"@bf6mods/sdk": "1.0.1",
|
|
33
|
+
"@clack/prompts": "^0.11.0",
|
|
33
34
|
chokidar: "^4.0.3",
|
|
34
35
|
colors: "^1.4.0",
|
|
35
36
|
commander: "^14.0.1",
|
|
36
|
-
inquirer: "^12.9.6",
|
|
37
37
|
jiti: "^2.6.1",
|
|
38
|
-
knitwork: "^1.2.0"
|
|
38
|
+
knitwork: "^1.2.0",
|
|
39
|
+
rolldown: "^1.0.0-beta.43",
|
|
40
|
+
"stringify-object": "^6.0.0"
|
|
39
41
|
},
|
|
40
42
|
publishConfig: {
|
|
41
43
|
access: "public"
|
|
42
44
|
},
|
|
43
45
|
devDependencies: {
|
|
46
|
+
"@types/stringify-object": "^4.0.5",
|
|
44
47
|
"cross-env": "^10.1.0",
|
|
45
48
|
tsx: "^4.20.6"
|
|
46
49
|
}
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
// src/cli/
|
|
50
|
-
import
|
|
52
|
+
// src/cli/build.ts
|
|
53
|
+
import fs from "fs";
|
|
51
54
|
import path from "path";
|
|
55
|
+
import {
|
|
56
|
+
AttachmentType
|
|
57
|
+
} from "@bf6mods/sdk";
|
|
58
|
+
import { createJiti } from "jiti";
|
|
59
|
+
import { rolldown } from "rolldown";
|
|
60
|
+
|
|
61
|
+
// src/resources/prepare/types/config.ts
|
|
62
|
+
var MapId = /* @__PURE__ */ ((MapId2) => {
|
|
63
|
+
MapId2["FireStorm"] = "MP_FireStorm-ModBuilderCustom0";
|
|
64
|
+
MapId2["SiegeOfCairo"] = "MP_Abbasid-ModBuilderCustom0";
|
|
65
|
+
MapId2["EmpireState"] = "MP_Aftermath-ModBuilderCustom0";
|
|
66
|
+
MapId2["IberianOffensive"] = "MP_Battery-ModBuilderCustom0";
|
|
67
|
+
MapId2["LiberationPeak"] = "MP_Capstone-ModBuilderCustom0";
|
|
68
|
+
MapId2["ManhattanBridge"] = "MP_Dumbo-ModBuilderCustom0";
|
|
69
|
+
MapId2["SaintsQuarter"] = "MP_Limestone-ModBuilderCustom0";
|
|
70
|
+
MapId2["NewSobekCity"] = "MP_Outskirts-ModBuilderCustom0";
|
|
71
|
+
MapId2["MirakValley"] = "MP_Tungsten-ModBuilderCustom0";
|
|
72
|
+
return MapId2;
|
|
73
|
+
})(MapId || {});
|
|
74
|
+
|
|
75
|
+
// src/cli/build.ts
|
|
76
|
+
async function getBf6Config(rootDir) {
|
|
77
|
+
globalThis.defineBf6Config = (c) => c;
|
|
78
|
+
globalThis.MapId = MapId;
|
|
79
|
+
const jiti = createJiti(rootDir, { interopDefault: true });
|
|
80
|
+
const result = await jiti.import("./bf6.config", { default: true });
|
|
81
|
+
delete globalThis.defineBf6Config;
|
|
82
|
+
delete globalThis.MapId;
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
async function build() {
|
|
86
|
+
const workingDir = path.resolve(".");
|
|
87
|
+
const config = await getBf6Config(workingDir);
|
|
88
|
+
const outDir = path.resolve(workingDir, config.outDir);
|
|
89
|
+
if (fs.existsSync(outDir))
|
|
90
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
91
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
92
|
+
const minifyJson = typeof config.minify === "boolean" ? config.minify : config.minify?.json ?? false;
|
|
93
|
+
let tsAttachment;
|
|
94
|
+
if (config.entrypoint) {
|
|
95
|
+
const entryAbs = path.resolve(workingDir, config.entrypoint);
|
|
96
|
+
const compiled = await buildEntrypoint(
|
|
97
|
+
entryAbs,
|
|
98
|
+
outDir,
|
|
99
|
+
!!config.outputArtifacts
|
|
100
|
+
);
|
|
101
|
+
tsAttachment = createTsAttachment(entryAbs, compiled);
|
|
102
|
+
}
|
|
103
|
+
const { attachments, mapRotation } = await collectAttachments(
|
|
104
|
+
config,
|
|
105
|
+
workingDir,
|
|
106
|
+
tsAttachment
|
|
107
|
+
);
|
|
108
|
+
await writeModJson(config, outDir, attachments, mapRotation, minifyJson);
|
|
109
|
+
console.log(`\u2714 Built mod: ${config.name}`);
|
|
110
|
+
}
|
|
111
|
+
async function buildEntrypoint(entry, outDir, emit) {
|
|
112
|
+
const bundle = await rolldown({
|
|
113
|
+
input: entry
|
|
114
|
+
});
|
|
115
|
+
const result = await bundle.generate({
|
|
116
|
+
format: "esm",
|
|
117
|
+
inlineDynamicImports: true
|
|
118
|
+
});
|
|
119
|
+
const code = result.output[0].code;
|
|
120
|
+
if (emit) {
|
|
121
|
+
await fs.promises.writeFile(path.resolve(outDir, "index.js"), code);
|
|
122
|
+
}
|
|
123
|
+
return code;
|
|
124
|
+
}
|
|
125
|
+
async function collectAttachments(config, workingDir, tsAttachment) {
|
|
126
|
+
const attachments = [];
|
|
127
|
+
const mapRotation = [];
|
|
128
|
+
if (tsAttachment) attachments.push(tsAttachment);
|
|
129
|
+
if (config.strings) {
|
|
130
|
+
const strPath = path.resolve(workingDir, config.strings);
|
|
131
|
+
if (!fs.existsSync(strPath)) throw new Error("Cannot find strings file");
|
|
132
|
+
const raw = await fs.promises.readFile(strPath, "utf8");
|
|
133
|
+
attachments.push(createStringsAttachment(strPath, raw));
|
|
134
|
+
}
|
|
135
|
+
if (config.scenes) {
|
|
136
|
+
let mapIdx = 0;
|
|
137
|
+
for (const [mapId, scene] of config.scenes) {
|
|
138
|
+
const scenePath = path.resolve(workingDir, scene);
|
|
139
|
+
if (!fs.existsSync(scenePath))
|
|
140
|
+
throw new Error(`Cannot find spatial data file: ${scene}`);
|
|
141
|
+
const raw = await fs.promises.readFile(scenePath, "utf8");
|
|
142
|
+
const spatial = createSpatialAttachment(scenePath, raw, mapIdx++);
|
|
143
|
+
attachments.push(spatial);
|
|
144
|
+
mapRotation.push({ id: mapId, spatialAttachment: spatial });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { attachments, mapRotation };
|
|
148
|
+
}
|
|
149
|
+
async function writeModJson(config, outDir, attachments, mapRotation, minify) {
|
|
150
|
+
const baseGame = config.game;
|
|
151
|
+
const finalJson = {
|
|
152
|
+
name: config.name,
|
|
153
|
+
description: config.description,
|
|
154
|
+
gameMode: "ModBuilderCustom",
|
|
155
|
+
mutators: baseGame.mutators ?? {},
|
|
156
|
+
assetRestrictions: baseGame.assetRestrictions ?? {},
|
|
157
|
+
teamComposition: baseGame.teamComposition ?? [],
|
|
158
|
+
mapRotation,
|
|
159
|
+
attachments
|
|
160
|
+
};
|
|
161
|
+
const jsonOutput = minify ? JSON.stringify(finalJson) : JSON.stringify(finalJson, null, 2);
|
|
162
|
+
await fs.promises.writeFile(path.resolve(outDir, "mod.json"), jsonOutput);
|
|
163
|
+
}
|
|
164
|
+
function createTsAttachment(filePath, compiled) {
|
|
165
|
+
return {
|
|
166
|
+
id: crypto.randomUUID(),
|
|
167
|
+
version: "1.0",
|
|
168
|
+
filename: `${path.parse(filePath).name}.js`,
|
|
169
|
+
isProcessable: true,
|
|
170
|
+
processingStatus: 2,
|
|
171
|
+
attachmentType: AttachmentType.TypeScript,
|
|
172
|
+
attachmentData: { original: toBase64(compiled), compiled: "" },
|
|
173
|
+
errors: []
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function createStringsAttachment(filePath, raw) {
|
|
177
|
+
return {
|
|
178
|
+
id: crypto.randomUUID(),
|
|
179
|
+
version: "1.0",
|
|
180
|
+
filename: path.basename(filePath),
|
|
181
|
+
isProcessable: true,
|
|
182
|
+
processingStatus: 2,
|
|
183
|
+
attachmentType: AttachmentType.Strings,
|
|
184
|
+
attachmentData: { original: toBase64(raw), compiled: "" },
|
|
185
|
+
errors: []
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function createSpatialAttachment(filePath, raw, mapIdx) {
|
|
189
|
+
return {
|
|
190
|
+
id: crypto.randomUUID(),
|
|
191
|
+
version: "1.0",
|
|
192
|
+
filename: path.basename(filePath),
|
|
193
|
+
isProcessable: true,
|
|
194
|
+
processingStatus: 2,
|
|
195
|
+
attachmentType: AttachmentType.SpatialData,
|
|
196
|
+
attachmentData: { original: toBase64(raw), compiled: "" },
|
|
197
|
+
metadata: `mapIdx=${mapIdx}`,
|
|
198
|
+
errors: []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function toBase64(input) {
|
|
202
|
+
return Buffer.isBuffer(input) ? input.toString("base64") : Buffer.from(input, "utf8").toString("base64");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/cli/dev.ts
|
|
206
|
+
import fs2 from "fs";
|
|
207
|
+
import { glob } from "fs/promises";
|
|
208
|
+
import path2 from "path";
|
|
209
|
+
import chokidar from "chokidar";
|
|
210
|
+
import colors from "colors";
|
|
211
|
+
async function dev() {
|
|
212
|
+
const workingDir = path2.resolve(".");
|
|
213
|
+
let config = await getBf6Config(workingDir);
|
|
214
|
+
const outDir = path2.resolve(workingDir, config.outDir);
|
|
215
|
+
if (!fs2.existsSync(outDir)) fs2.mkdirSync(outDir, { recursive: true });
|
|
216
|
+
console.log(colors.cyan(`\u25B6 Starting dev for ${config.name}`));
|
|
217
|
+
let watcher;
|
|
218
|
+
async function rebuild(trigger) {
|
|
219
|
+
console.log(
|
|
220
|
+
colors.yellow(
|
|
221
|
+
`\u21BB Change detected in ${path2.basename(trigger)}, rebuilding...`
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
const start = performance.now();
|
|
225
|
+
try {
|
|
226
|
+
await build();
|
|
227
|
+
const end = performance.now();
|
|
228
|
+
const duration = ((end - start) / 1e3).toFixed(2);
|
|
229
|
+
console.log(colors.green(`\u2714 Updated mod.json (${duration}s)`));
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error(colors.red(`\u2716 Rebuild failed: ${err.message}`));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function collectWatchTargets() {
|
|
235
|
+
const targets = [];
|
|
236
|
+
if (config.entrypoint)
|
|
237
|
+
targets.push(path2.resolve(workingDir, config.entrypoint));
|
|
238
|
+
if (config.scenes) {
|
|
239
|
+
for (const [, scene] of config.scenes) {
|
|
240
|
+
targets.push(path2.resolve(workingDir, scene));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (config.strings) targets.push(path2.resolve(workingDir, config.strings));
|
|
244
|
+
const srcDir = path2.resolve(workingDir, "src");
|
|
245
|
+
for await (const entry of glob(`${srcDir}/**/*`)) targets.push(entry);
|
|
246
|
+
for await (const entry of glob("bf6.config.*")) targets.push(entry);
|
|
247
|
+
return targets;
|
|
248
|
+
}
|
|
249
|
+
async function setupWatcher() {
|
|
250
|
+
if (watcher) {
|
|
251
|
+
await watcher.close();
|
|
252
|
+
console.log(colors.grey("\u267B Reloading watcher due to config change..."));
|
|
253
|
+
}
|
|
254
|
+
const watchTargets = await collectWatchTargets();
|
|
255
|
+
watcher = chokidar.watch(watchTargets, {
|
|
256
|
+
persistent: true,
|
|
257
|
+
ignoreInitial: true,
|
|
258
|
+
awaitWriteFinish: true,
|
|
259
|
+
ignored: [path2.resolve(outDir), `${path2.resolve(outDir)}/**`]
|
|
260
|
+
});
|
|
261
|
+
watcher.on("change", async (file) => {
|
|
262
|
+
if (file.includes("bf6.config.")) {
|
|
263
|
+
console.log(
|
|
264
|
+
colors.magenta(
|
|
265
|
+
"\u2699 Config changed \u2014 reloading and rebuilding watcher..."
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
try {
|
|
269
|
+
config = await getBf6Config(workingDir);
|
|
270
|
+
await setupWatcher();
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(
|
|
273
|
+
colors.red(`\u2716 Failed to reload config: ${err.message}`)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
await rebuild(file);
|
|
279
|
+
});
|
|
280
|
+
watcher.on("add", async (file) => await rebuild(file));
|
|
281
|
+
await rebuild("initial");
|
|
282
|
+
}
|
|
283
|
+
await setupWatcher();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/cli/import.ts
|
|
287
|
+
import fs4 from "fs";
|
|
288
|
+
import path4 from "path";
|
|
289
|
+
import { AttachmentType as AttachmentType2 } from "@bf6mods/sdk";
|
|
290
|
+
import stringifyObject from "stringify-object";
|
|
291
|
+
|
|
292
|
+
// src/cli/init.ts
|
|
293
|
+
import child_process from "child_process";
|
|
294
|
+
import fs3 from "fs";
|
|
295
|
+
import path3 from "path";
|
|
52
296
|
import { fileURLToPath } from "url";
|
|
53
|
-
import
|
|
297
|
+
import * as prompts from "@clack/prompts";
|
|
54
298
|
var __filename = fileURLToPath(import.meta.url);
|
|
55
|
-
var __dirname =
|
|
56
|
-
var
|
|
299
|
+
var __dirname = path3.dirname(__filename);
|
|
300
|
+
var templatesDir = path3.resolve(__dirname, "../resources/templates");
|
|
57
301
|
function renameFilesRecursively(dir, modName) {
|
|
58
|
-
const entries =
|
|
302
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
59
303
|
for (const entry of entries) {
|
|
60
|
-
const oldPath =
|
|
304
|
+
const oldPath = path3.join(dir, entry.name);
|
|
61
305
|
let newPath = oldPath;
|
|
62
306
|
if (entry.name.includes("{name}")) {
|
|
63
307
|
const newName = entry.name.replace("{name}", modName);
|
|
64
|
-
newPath =
|
|
65
|
-
|
|
308
|
+
newPath = path3.join(dir, newName);
|
|
309
|
+
fs3.renameSync(oldPath, newPath);
|
|
310
|
+
}
|
|
311
|
+
if (entry.name === "bf6.config.ts") {
|
|
312
|
+
let content = fs3.readFileSync(newPath, "utf-8");
|
|
313
|
+
content = content.replace(/{bf6ConfigName}/g, modName);
|
|
314
|
+
fs3.writeFileSync(newPath, content, "utf-8");
|
|
66
315
|
}
|
|
67
316
|
if (entry.name === "package.json") {
|
|
68
|
-
const pkg = JSON.parse(
|
|
317
|
+
const pkg = JSON.parse(fs3.readFileSync(newPath, "utf-8"));
|
|
69
318
|
pkg.name = modName;
|
|
70
|
-
|
|
319
|
+
fs3.writeFileSync(newPath, JSON.stringify(pkg, null, 2));
|
|
71
320
|
}
|
|
72
|
-
if (
|
|
321
|
+
if (fs3.statSync(newPath).isDirectory()) {
|
|
73
322
|
renameFilesRecursively(newPath, modName);
|
|
74
323
|
}
|
|
75
324
|
}
|
|
76
325
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
326
|
+
var templates = [
|
|
327
|
+
"Basic",
|
|
328
|
+
"AcePursuit",
|
|
329
|
+
"BombSquad",
|
|
330
|
+
"Exfil",
|
|
331
|
+
"Vertigo"
|
|
332
|
+
];
|
|
333
|
+
async function startProject(destination, template, name) {
|
|
334
|
+
if (["AcePursuit", "BombSquad", "Exfil", "Vertigo"].includes(template)) {
|
|
335
|
+
const importPath = path3.resolve(templatesDir, `${template}.json`);
|
|
336
|
+
await importFile(importPath, destination, name);
|
|
337
|
+
} else if (template === "None") {
|
|
338
|
+
} else {
|
|
339
|
+
const templateDir = path3.resolve(templatesDir, template);
|
|
340
|
+
fs3.cpSync(templateDir, destination, { recursive: true });
|
|
341
|
+
}
|
|
342
|
+
fs3.cpSync(path3.resolve(templatesDir, "All"), destination, {
|
|
343
|
+
recursive: true
|
|
344
|
+
});
|
|
345
|
+
if (name) renameFilesRecursively(destination, name);
|
|
346
|
+
}
|
|
347
|
+
function installDependencies(projectDir) {
|
|
348
|
+
try {
|
|
349
|
+
child_process.execSync(`npm install`, {
|
|
350
|
+
stdio: "inherit",
|
|
351
|
+
cwd: projectDir
|
|
352
|
+
});
|
|
353
|
+
return true;
|
|
354
|
+
} catch (_err) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
var cancel2 = () => prompts.cancel("Operation cancelled");
|
|
359
|
+
function isEmpty(path6) {
|
|
360
|
+
const files = fs3.readdirSync(path6);
|
|
361
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
362
|
+
}
|
|
363
|
+
function emptyDir(dir) {
|
|
364
|
+
if (!fs3.existsSync(dir)) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (const file of fs3.readdirSync(dir)) {
|
|
368
|
+
if (file === ".git") {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
fs3.rmSync(path3.resolve(dir, file), { recursive: true, force: true });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function init(argTargetDir) {
|
|
375
|
+
prompts.intro("Initialize Bf6 Mod");
|
|
376
|
+
const path6 = argTargetDir ? argTargetDir : await prompts.text({
|
|
377
|
+
message: "Where should we create your project?",
|
|
378
|
+
placeholder: "./ace-pursuit",
|
|
379
|
+
validate: (value) => {
|
|
380
|
+
if (!value) return "Please enter a path.";
|
|
381
|
+
if (value[0] !== ".") return "Please enter a relative path.";
|
|
107
382
|
}
|
|
108
383
|
});
|
|
384
|
+
if (prompts.isCancel(path6)) return cancel2();
|
|
385
|
+
let name = await prompts.text({
|
|
386
|
+
message: "What is the name of your mod?",
|
|
387
|
+
placeholder: "Ace Pursuit",
|
|
388
|
+
validate: (value) => {
|
|
389
|
+
if (!value.trim()) return "Please enter a name.";
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
if (prompts.isCancel(name)) return cancel2();
|
|
393
|
+
name = name.trim();
|
|
394
|
+
if (fs3.existsSync(path6) && !isEmpty(path6)) {
|
|
395
|
+
let overwrite;
|
|
396
|
+
const res = await prompts.select({
|
|
397
|
+
message: (path6 === "." ? "Current directory" : `Target directory "${path6}"`) + ` is not empty. Please choose how to proceed:`,
|
|
398
|
+
options: [
|
|
399
|
+
{
|
|
400
|
+
label: "Cancel operation",
|
|
401
|
+
value: "no"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
label: "Remove existing files and continue",
|
|
405
|
+
value: "yes"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
label: "Ignore files and continue",
|
|
409
|
+
value: "ignore"
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
});
|
|
413
|
+
if (prompts.isCancel(res)) return cancel2();
|
|
414
|
+
overwrite = res;
|
|
415
|
+
switch (overwrite) {
|
|
416
|
+
case "yes":
|
|
417
|
+
emptyDir(path6);
|
|
418
|
+
break;
|
|
419
|
+
case "no":
|
|
420
|
+
cancel2();
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const template = await prompts.select({
|
|
425
|
+
message: "Select a template:",
|
|
426
|
+
options: templates.map((template2) => {
|
|
427
|
+
return {
|
|
428
|
+
label: template2,
|
|
429
|
+
value: template2
|
|
430
|
+
};
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
if (prompts.isCancel(template)) return cancel2();
|
|
434
|
+
await startProject(path6, template, name);
|
|
435
|
+
const s = prompts.spinner();
|
|
436
|
+
s.start("Installing via npm");
|
|
437
|
+
const installed = installDependencies(path6);
|
|
438
|
+
if (installed) s.stop("Installed via npm");
|
|
439
|
+
else s.stop("Failed to install via npm", 1);
|
|
440
|
+
const nextSteps = `cd ${path6}
|
|
441
|
+
npm run build`;
|
|
442
|
+
prompts.note(nextSteps, "Next steps.");
|
|
109
443
|
}
|
|
110
444
|
|
|
111
|
-
// src/cli/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
445
|
+
// src/cli/import.ts
|
|
446
|
+
async function writeFileSafe(filePath, data) {
|
|
447
|
+
const dir = path4.dirname(filePath);
|
|
448
|
+
await fs4.promises.mkdir(dir, { recursive: true });
|
|
449
|
+
await fs4.promises.writeFile(filePath, data);
|
|
450
|
+
}
|
|
451
|
+
function getMapKeyByValue(value) {
|
|
452
|
+
return Object.keys(MapId).find(
|
|
453
|
+
(k) => MapId[k] === value
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
async function importFile(input, output, name) {
|
|
457
|
+
const workingDir = path4.resolve(".");
|
|
458
|
+
const entrypoint = path4.resolve(workingDir, input);
|
|
459
|
+
const outDir = path4.resolve(workingDir, output);
|
|
460
|
+
if (!fs4.existsSync(entrypoint)) throw new Error("Cannot find strings file");
|
|
461
|
+
const config = JSON.parse(
|
|
462
|
+
await fs4.promises.readFile(entrypoint, { encoding: "utf8" })
|
|
463
|
+
);
|
|
464
|
+
if (!fs4.existsSync(outDir))
|
|
465
|
+
await fs4.promises.mkdir(outDir, { recursive: true });
|
|
466
|
+
await startProject(outDir, "None", name ?? config.name);
|
|
467
|
+
let typescriptFile;
|
|
468
|
+
let stringsFile;
|
|
469
|
+
const scenes = [];
|
|
470
|
+
const promises = [];
|
|
471
|
+
if (config.attachments) {
|
|
472
|
+
for (const attachment of config.attachments) {
|
|
473
|
+
if (attachment.attachmentType === AttachmentType2.TypeScript)
|
|
474
|
+
typescriptFile = attachment.filename;
|
|
475
|
+
if (attachment.attachmentType === AttachmentType2.Strings)
|
|
476
|
+
stringsFile = attachment.filename;
|
|
477
|
+
if (attachment.attachmentType === AttachmentType2.SpatialData) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
promises.push(
|
|
481
|
+
writeFileSafe(
|
|
482
|
+
path4.resolve(outDir, "src", attachment.filename),
|
|
483
|
+
atob(attachment.attachmentData.original)
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (config.mapRotation?.length) {
|
|
489
|
+
for (const map of config.mapRotation) {
|
|
490
|
+
promises.push(
|
|
491
|
+
writeFileSafe(
|
|
492
|
+
path4.resolve(outDir, "src", "scenes", map.spatialAttachment.filename),
|
|
493
|
+
atob(map.spatialAttachment.attachmentData.original)
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
scenes.push([map.id, `src/scenes/${map.spatialAttachment.filename}`]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const bf6Config = {
|
|
500
|
+
name: name ?? config.name,
|
|
501
|
+
description: config.description,
|
|
502
|
+
outDir: "dist",
|
|
503
|
+
entrypoint: typescriptFile ? `src/${typescriptFile}` : void 0,
|
|
504
|
+
scenes: scenes ? scenes.map(([id, path6]) => [
|
|
505
|
+
`MapId.${getMapKeyByValue(id) ?? id}`,
|
|
506
|
+
path6
|
|
507
|
+
]) : void 0,
|
|
508
|
+
strings: stringsFile ? `src/${stringsFile}` : void 0,
|
|
509
|
+
game: {
|
|
510
|
+
mutators: config.mutators,
|
|
511
|
+
assetRestrictions: config.assetRestrictions,
|
|
512
|
+
gameMode: config.gameMode,
|
|
513
|
+
teamComposition: config.teamComposition
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const bf6ConfigContent = `export default defineBf6Config(${stringifyWithRaw(
|
|
517
|
+
bf6Config
|
|
518
|
+
)});
|
|
519
|
+
`;
|
|
520
|
+
promises.push(
|
|
521
|
+
writeFileSafe(path4.resolve(outDir, "bf6.config.ts"), bf6ConfigContent)
|
|
522
|
+
);
|
|
523
|
+
await Promise.all(promises);
|
|
524
|
+
}
|
|
525
|
+
function stringifyWithRaw(value, options = {}) {
|
|
526
|
+
return stringifyObject(value, {
|
|
527
|
+
indent: " ",
|
|
528
|
+
// 4 spaces
|
|
529
|
+
singleQuotes: true,
|
|
530
|
+
transform: (_obj, _prop, originalResult) => {
|
|
531
|
+
if (/^['"]MapId\.[A-Za-z0-9_]+['"]$/.test(originalResult)) {
|
|
532
|
+
return originalResult.slice(1, -1);
|
|
533
|
+
}
|
|
534
|
+
return originalResult;
|
|
535
|
+
},
|
|
536
|
+
...options
|
|
537
|
+
});
|
|
122
538
|
}
|
|
123
539
|
|
|
124
540
|
// src/cli/prepare.ts
|
|
125
|
-
import
|
|
541
|
+
import fs5 from "fs";
|
|
542
|
+
import path5 from "path";
|
|
126
543
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
127
|
-
import
|
|
128
|
-
import { genExport } from "knitwork";
|
|
544
|
+
import colors3 from "colors";
|
|
545
|
+
import { genExport, genInlineTypeImport } from "knitwork";
|
|
546
|
+
|
|
547
|
+
// src/cli/utils.ts
|
|
548
|
+
import colors2 from "colors";
|
|
129
549
|
|
|
130
550
|
// ../../node_modules/ansi-regex/index.js
|
|
131
551
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
@@ -146,42 +566,80 @@ function stripAnsi(string) {
|
|
|
146
566
|
}
|
|
147
567
|
|
|
148
568
|
// src/cli/utils.ts
|
|
149
|
-
import colors from "colors";
|
|
150
569
|
var printToConsole = (message) => {
|
|
151
570
|
const now = /* @__PURE__ */ new Date();
|
|
152
|
-
const formattedTime =
|
|
571
|
+
const formattedTime = colors2.grey(now.toLocaleTimeString());
|
|
153
572
|
const terminalWidth = process.stdout.columns || 80;
|
|
154
573
|
const timeLength = stripAnsi(formattedTime).length;
|
|
155
574
|
const messageLength = stripAnsi(message).length;
|
|
156
|
-
|
|
575
|
+
const available = terminalWidth - (messageLength + timeLength);
|
|
576
|
+
const spacing = available > 1 ? " ".repeat(available) : " ";
|
|
577
|
+
console.log(`${message}${spacing}${formattedTime}`);
|
|
157
578
|
};
|
|
158
579
|
|
|
159
580
|
// src/cli/prepare.ts
|
|
160
581
|
async function prepare() {
|
|
161
582
|
try {
|
|
162
583
|
const __filename2 = fileURLToPath2(import.meta.url);
|
|
163
|
-
const __dirname2 =
|
|
164
|
-
const
|
|
165
|
-
const buildDir =
|
|
166
|
-
const resources =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
584
|
+
const __dirname2 = path5.dirname(__filename2);
|
|
585
|
+
const _workingDir = path5.resolve(".");
|
|
586
|
+
const buildDir = path5.resolve(".bf6");
|
|
587
|
+
const resources = path5.resolve(__dirname2, "../resources/prepare");
|
|
588
|
+
fs5.cpSync(
|
|
589
|
+
path5.resolve(resources, "tsconfig.json"),
|
|
590
|
+
path5.resolve(buildDir, "tsconfig.json")
|
|
591
|
+
);
|
|
592
|
+
fs5.cpSync(
|
|
593
|
+
path5.resolve(resources, "bf6.d.ts"),
|
|
594
|
+
path5.resolve(buildDir, "bf6.d.ts")
|
|
595
|
+
);
|
|
596
|
+
fs5.cpSync(
|
|
597
|
+
path5.resolve(resources, "types", "config.ts"),
|
|
598
|
+
path5.resolve(buildDir, "types", "config.ts")
|
|
599
|
+
);
|
|
600
|
+
const ConfigFileExports = genExport("./types/config.ts", [
|
|
601
|
+
"defineBf6Config"
|
|
602
|
+
]);
|
|
603
|
+
fs5.writeFileSync(
|
|
604
|
+
path5.resolve(buildDir, "imports.d.ts"),
|
|
605
|
+
`${ConfigFileExports}
|
|
606
|
+
`
|
|
607
|
+
);
|
|
608
|
+
const augmentations = {
|
|
609
|
+
defineBf6Config: genInlineTypeImport(
|
|
610
|
+
"./types/config.ts",
|
|
611
|
+
`defineBf6Config`
|
|
612
|
+
),
|
|
613
|
+
MapId: genInlineTypeImport("./types/config.ts", `MapId`)
|
|
614
|
+
};
|
|
615
|
+
const args = genNamespaceAugmentation("global", augmentations);
|
|
616
|
+
fs5.writeFileSync(
|
|
617
|
+
path5.resolve(buildDir, "globals.d.ts"),
|
|
618
|
+
`export {}
|
|
619
|
+
|
|
620
|
+
${args}
|
|
621
|
+
`
|
|
622
|
+
);
|
|
623
|
+
printToConsole(`${colors3.green("\u2714")} Types generated in .bf6`);
|
|
174
624
|
} catch (error) {
|
|
175
625
|
console.error(error);
|
|
176
|
-
printToConsole(`${"\u2717"
|
|
626
|
+
printToConsole(`${colors3.red("\u2717")} Types failed to generate in .bf6`);
|
|
177
627
|
}
|
|
178
628
|
}
|
|
629
|
+
var genNamespaceAugmentation = (name, contents) => {
|
|
630
|
+
if (!contents || Object.keys(contents).length === 0)
|
|
631
|
+
return `declare ${name} {}`;
|
|
632
|
+
const decls = Object.entries(contents).map(([k, v]) => ` const ${k}: ${v};`).join("\n");
|
|
633
|
+
return `declare ${name} {
|
|
634
|
+
${decls}
|
|
635
|
+
}`;
|
|
636
|
+
};
|
|
179
637
|
|
|
180
638
|
// src/cli/index.ts
|
|
181
639
|
var program = new Command();
|
|
182
640
|
program.name(Object.keys(package_default.bin)[0]).description(package_default.description).version(package_default.version);
|
|
183
|
-
program.command("init").description("Create a new bf6 mod").action(async () => {
|
|
184
|
-
await init();
|
|
641
|
+
program.command("init").argument("[directory]").description("Create a new bf6 mod").action(async (directory) => {
|
|
642
|
+
await init(directory);
|
|
185
643
|
});
|
|
186
644
|
program.command("build").description("build the bf6 mod").action(async () => {
|
|
187
645
|
await build();
|
|
@@ -189,6 +647,13 @@ program.command("build").description("build the bf6 mod").action(async () => {
|
|
|
189
647
|
program.command("prepare").description("prepare the types for bf6 mod").action(async () => {
|
|
190
648
|
await prepare();
|
|
191
649
|
});
|
|
650
|
+
program.command("dev").description("watch the changes in src, and recompile as needed").action(async () => {
|
|
651
|
+
await dev();
|
|
652
|
+
});
|
|
653
|
+
program.command("import").argument("<input>").argument("<output>").description("decompiles the json config of a mod into a new project").action(async (input, output) => {
|
|
654
|
+
await importFile(input, output);
|
|
655
|
+
installDependencies(output);
|
|
656
|
+
});
|
|
192
657
|
program.exitOverride((_err) => {
|
|
193
658
|
if (process.env.EXIT_CODE === "none") process.exit(0);
|
|
194
659
|
});
|