@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.
Files changed (26) hide show
  1. package/README.md +72 -0
  2. package/dist/cli/index.js +541 -76
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/resources/prepare/bf6.d.ts +2 -0
  5. package/dist/resources/prepare/tsconfig.json +14 -15
  6. package/dist/resources/prepare/types/config.ts +139 -4
  7. package/dist/resources/templates/All/package.json +1 -1
  8. package/dist/resources/templates/Basic/bf6.config.ts +5 -1
  9. package/package.json +48 -41
  10. package/dist/resources/templates/AcePursuit/src/index.ts +0 -3421
  11. package/dist/resources/templates/AcePursuit/src/levels.tscn +0 -6922
  12. package/dist/resources/templates/AcePursuit/src/strings.json +0 -191
  13. package/dist/resources/templates/All/bf6.config.ts +0 -6
  14. package/dist/resources/templates/BombSquad/src/index.ts +0 -3683
  15. package/dist/resources/templates/BombSquad/src/levels.tscn +0 -3212
  16. package/dist/resources/templates/BombSquad/src/strings.json +0 -123
  17. package/dist/resources/templates/Exfil/src/index.ts +0 -2393
  18. package/dist/resources/templates/Exfil/src/levels.tscn +0 -5600
  19. package/dist/resources/templates/Exfil/src/strings.json +0 -185
  20. package/dist/resources/templates/Vertigo/src/index.ts +0 -1948
  21. package/dist/resources/templates/Vertigo/src/levels.tscn +0 -6387
  22. package/dist/resources/templates/Vertigo/src/strings.json +0 -308
  23. /package/dist/resources/templates/{AcePursuit/src/config.json → AcePursuit.json} +0 -0
  24. /package/dist/resources/templates/{BombSquad/src/config.json → BombSquad.json} +0 -0
  25. /package/dist/resources/templates/{Exfil/src/config.json → Exfil.json} +0 -0
  26. /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.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/init.ts
50
- import inquirer from "inquirer";
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 fs from "fs";
297
+ import * as prompts from "@clack/prompts";
54
298
  var __filename = fileURLToPath(import.meta.url);
55
- var __dirname = path.dirname(__filename);
56
- var templates = path.resolve(__dirname, "../resources/templates");
299
+ var __dirname = path3.dirname(__filename);
300
+ var templatesDir = path3.resolve(__dirname, "../resources/templates");
57
301
  function renameFilesRecursively(dir, modName) {
58
- const entries = fs.readdirSync(dir, { withFileTypes: true });
302
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
59
303
  for (const entry of entries) {
60
- const oldPath = path.join(dir, entry.name);
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 = path.join(dir, newName);
65
- fs.renameSync(oldPath, newPath);
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(fs.readFileSync(newPath, "utf-8"));
317
+ const pkg = JSON.parse(fs3.readFileSync(newPath, "utf-8"));
69
318
  pkg.name = modName;
70
- fs.writeFileSync(newPath, JSON.stringify(pkg, null, 2));
319
+ fs3.writeFileSync(newPath, JSON.stringify(pkg, null, 2));
71
320
  }
72
- if (fs.statSync(newPath).isDirectory()) {
321
+ if (fs3.statSync(newPath).isDirectory()) {
73
322
  renameFilesRecursively(newPath, modName);
74
323
  }
75
324
  }
76
325
  }
77
- async function init() {
78
- inquirer.prompt([
79
- {
80
- type: "input",
81
- name: "mod_name",
82
- message: "What's your mod's name",
83
- validate: ((value) => {
84
- if (value.trim() === "") return "Please provide a value";
85
- return true;
86
- })
87
- },
88
- {
89
- type: "list",
90
- name: "template",
91
- message: "Which template should be used",
92
- choices: ["Basic", "AcePursuit", "BombSquad", "Exfil", "Vertigo"]
93
- }
94
- ]).then((answers) => {
95
- const template = path.resolve(templates, answers.template);
96
- const newProject = path.resolve(".", answers.mod_name);
97
- console.log("template:", template);
98
- console.log("newProject:", newProject);
99
- fs.cpSync(path.resolve(templates, "All"), newProject, { recursive: true });
100
- fs.cpSync(template, newProject, { recursive: true });
101
- renameFilesRecursively(newProject, answers.mod_name);
102
- }).catch((error) => {
103
- if (error.isTtyError) {
104
- console.error("TTY Error:", error);
105
- } else {
106
- console.error("Error occurred:", error);
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/build.ts
112
- import { createJiti } from "jiti";
113
- import path2 from "path";
114
- var getBf6Config = async (rootDir) => {
115
- const jiti = createJiti(rootDir, { interopDefault: true });
116
- const result = await jiti.import("./bf6.config", { default: true });
117
- return result;
118
- };
119
- async function build() {
120
- const workingDir = path2.resolve(".");
121
- getBf6Config(workingDir);
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 path3 from "path";
541
+ import fs5 from "fs";
542
+ import path5 from "path";
126
543
  import { fileURLToPath as fileURLToPath2 } from "url";
127
- import fs2 from "fs";
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 = colors.grey(now.toLocaleTimeString());
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
- console.log(`${message}${" ".repeat(terminalWidth - (messageLength + timeLength))}${formattedTime}`);
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 = path3.dirname(__filename2);
164
- const workingDir = path3.resolve(".");
165
- const buildDir = path3.resolve(".bf6");
166
- const resources = path3.resolve(__dirname2, "../../resources/prepare");
167
- fs2.cpSync(path3.resolve(resources, "tsconfig.json"), path3.resolve(buildDir, "tsconfig.json"));
168
- fs2.cpSync(path3.resolve(resources, "bf6.d.ts"), path3.resolve(buildDir, "bf6.d.ts"));
169
- fs2.cpSync(path3.resolve(resources, "types", "config.ts"), path3.resolve(buildDir, "types", "config.ts"));
170
- const ConfigFileExports = genExport("./types/config", ["defineBf6Config"]);
171
- fs2.writeFileSync(path3.resolve(buildDir, "imports.d.ts"), `${ConfigFileExports}
172
- `);
173
- printToConsole(`${"\u2714".green} Types generated in .bf6`);
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".red} Types failed to generate in .bf6`);
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
  });