@bf6mods/cli 1.1.2 → 1.2.1
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 +2 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +681 -782
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -1
- package/dist/resources/templates/All/package.json +3 -2
- package/package.json +5 -4
- package/dist/index.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,853 +1,752 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli/index.ts
|
|
4
2
|
import { Command } from "commander";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
description: "CLI and library for bundling BF6 mods",
|
|
11
|
-
license: "MIT",
|
|
12
|
-
type: "module",
|
|
13
|
-
bin: {
|
|
14
|
-
bf6mods: "./dist/cli/index.js"
|
|
15
|
-
},
|
|
16
|
-
main: "./dist/index.js",
|
|
17
|
-
types: "./dist/index.d.ts",
|
|
18
|
-
scripts: {
|
|
19
|
-
build: "tsup",
|
|
20
|
-
start: "cross-env EXIT_CODE=none tsx ./src/cli/index.ts"
|
|
21
|
-
},
|
|
22
|
-
exports: {
|
|
23
|
-
".": {
|
|
24
|
-
types: "./dist/index.d.ts",
|
|
25
|
-
import: "./dist/index.js"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
files: [
|
|
29
|
-
"dist/"
|
|
30
|
-
],
|
|
31
|
-
repository: {
|
|
32
|
-
type: "git",
|
|
33
|
-
url: "git+https://github.com/bf6mods/bf6mods.git"
|
|
34
|
-
},
|
|
35
|
-
dependencies: {
|
|
36
|
-
"@bf6mods/sdk": "1.0.3",
|
|
37
|
-
"@clack/prompts": "^0.11.0",
|
|
38
|
-
chokidar: "^4.0.3",
|
|
39
|
-
colors: "^1.4.0",
|
|
40
|
-
commander: "^14.0.1",
|
|
41
|
-
jiti: "^2.6.1",
|
|
42
|
-
knitwork: "^1.2.0",
|
|
43
|
-
rolldown: "^1.0.0-beta.43",
|
|
44
|
-
"stringify-object": "^6.0.0"
|
|
45
|
-
},
|
|
46
|
-
publishConfig: {
|
|
47
|
-
access: "public"
|
|
48
|
-
},
|
|
49
|
-
devDependencies: {
|
|
50
|
-
"@oxc-project/types": "^0.95.0",
|
|
51
|
-
"@types/stringify-object": "^4.0.5",
|
|
52
|
-
"cross-env": "^10.1.0",
|
|
53
|
-
tsx: "^4.20.6"
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// src/cli/build/index.ts
|
|
58
|
-
import fs from "fs";
|
|
59
|
-
import path from "path";
|
|
60
|
-
import { fileURLToPath } from "url";
|
|
61
|
-
import {
|
|
62
|
-
AttachmentType
|
|
63
|
-
} from "@bf6mods/sdk";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { AttachmentType } from "@bf6mods/sdk";
|
|
7
|
+
import colors from "colors";
|
|
64
8
|
import { createJiti } from "jiti";
|
|
65
9
|
import { rolldown } from "rolldown";
|
|
10
|
+
import stripAnsi from "strip-ansi";
|
|
11
|
+
import { glob } from "node:fs/promises";
|
|
12
|
+
import chokidar from "chokidar";
|
|
13
|
+
import stringifyObject from "stringify-object";
|
|
14
|
+
import child_process from "node:child_process";
|
|
15
|
+
import * as prompts from "@clack/prompts";
|
|
16
|
+
import { genExport, genInlineTypeImport } from "knitwork";
|
|
66
17
|
|
|
67
|
-
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
MapId2["EmpireState"] = "MP_Aftermath-ModBuilderCustom0";
|
|
72
|
-
MapId2["IberianOffensive"] = "MP_Battery-ModBuilderCustom0";
|
|
73
|
-
MapId2["LiberationPeak"] = "MP_Capstone-ModBuilderCustom0";
|
|
74
|
-
MapId2["ManhattanBridge"] = "MP_Dumbo-ModBuilderCustom0";
|
|
75
|
-
MapId2["SaintsQuarter"] = "MP_Limestone-ModBuilderCustom0";
|
|
76
|
-
MapId2["NewSobekCity"] = "MP_Outskirts-ModBuilderCustom0";
|
|
77
|
-
MapId2["MirakValley"] = "MP_Tungsten-ModBuilderCustom0";
|
|
78
|
-
return MapId2;
|
|
79
|
-
})(MapId || {});
|
|
80
|
-
|
|
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}
|
|
18
|
+
//#region package.json
|
|
19
|
+
var version = "1.2.1";
|
|
20
|
+
var description = "CLI and library for bundling BF6 mods";
|
|
21
|
+
var bin = { "bf6mods": "./dist/cli/index.js" };
|
|
185
22
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/resources/prepare/types/config.ts
|
|
25
|
+
let MapId = /* @__PURE__ */ function(MapId$1) {
|
|
26
|
+
MapId$1["FireStorm"] = "MP_FireStorm-ModBuilderCustom0";
|
|
27
|
+
MapId$1["SiegeOfCairo"] = "MP_Abbasid-ModBuilderCustom0";
|
|
28
|
+
MapId$1["EmpireState"] = "MP_Aftermath-ModBuilderCustom0";
|
|
29
|
+
MapId$1["IberianOffensive"] = "MP_Battery-ModBuilderCustom0";
|
|
30
|
+
MapId$1["LiberationPeak"] = "MP_Capstone-ModBuilderCustom0";
|
|
31
|
+
MapId$1["ManhattanBridge"] = "MP_Dumbo-ModBuilderCustom0";
|
|
32
|
+
MapId$1["SaintsQuarter"] = "MP_Limestone-ModBuilderCustom0";
|
|
33
|
+
MapId$1["NewSobekCity"] = "MP_Outskirts-ModBuilderCustom0";
|
|
34
|
+
MapId$1["MirakValley"] = "MP_Tungsten-ModBuilderCustom0";
|
|
35
|
+
return MapId$1;
|
|
36
|
+
}({});
|
|
194
37
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/cli/utils.ts
|
|
40
|
+
/**
|
|
41
|
+
* Safely prints a message with a right-aligned timestamp.
|
|
42
|
+
*/
|
|
43
|
+
const printToConsole = (message, error = false) => {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const formattedTime = colors.grey(now.toLocaleTimeString());
|
|
46
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
47
|
+
const timeLength = stripAnsi(formattedTime).length;
|
|
48
|
+
const available = terminalWidth - (stripAnsi(message).length + timeLength);
|
|
49
|
+
const spacing = available > 1 ? " ".repeat(available) : " ";
|
|
50
|
+
if (error) console.error(`${message}${spacing}${formattedTime}`);
|
|
51
|
+
else console.log(`${message}${spacing}${formattedTime}`);
|
|
52
|
+
};
|
|
202
53
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/cli/build/generated-strings.ts
|
|
56
|
+
function extractBf6Strings(bf6Strings, generateFromLiterals) {
|
|
57
|
+
return {
|
|
58
|
+
name: "extract-bf6-strings",
|
|
59
|
+
generateBundle(_options, bundle) {
|
|
60
|
+
if (!generateFromLiterals) return;
|
|
61
|
+
let id = 0;
|
|
62
|
+
const existingStrings = new Set(Object.values(bf6Strings));
|
|
63
|
+
for (const [_file, output] of Object.entries(bundle)) {
|
|
64
|
+
if (output.type !== "chunk") continue;
|
|
65
|
+
const code = output.code;
|
|
66
|
+
const program$1 = this.parse(code, {
|
|
67
|
+
lang: "ts",
|
|
68
|
+
astType: "js",
|
|
69
|
+
range: true
|
|
70
|
+
});
|
|
71
|
+
function walk(node) {
|
|
72
|
+
if (node.type === "Literal") {
|
|
73
|
+
if (typeof node.value !== "string" || node.value === "" || node.value === "----uniquename----") return;
|
|
74
|
+
if (!(node.value in bf6Strings) && !existingStrings.has(node.value)) {
|
|
75
|
+
existingStrings.add(node.value);
|
|
76
|
+
bf6Strings[`__auto__${id++}`] = node.value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const value of Object.values(node)) {
|
|
80
|
+
if (!value) continue;
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
for (const v of value) if (v && typeof v === "object" && "type" in v) walk(v);
|
|
83
|
+
} else if (typeof value === "object" && "type" in value) walk(value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const item of program$1.body) walk(item);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
moduleParsed(moduleInfo) {
|
|
90
|
+
if (moduleInfo.code && moduleInfo.exports.includes("bf6Strings")) {
|
|
91
|
+
const code = moduleInfo.code;
|
|
92
|
+
const getSnippet = (start, end, linesBefore = 2, linesAfter = 2) => {
|
|
93
|
+
const lines = code.split(/\r?\n/);
|
|
94
|
+
const startLine = Math.max(0, code.slice(0, start).split(/\r?\n/).length - 1 - linesBefore);
|
|
95
|
+
const endLine = Math.min(lines.length, code.slice(0, end).split(/\r?\n/).length - 1 + linesAfter);
|
|
96
|
+
return lines.slice(startLine, endLine).map((line, i) => {
|
|
97
|
+
return `${(startLine + i + 1).toString().padStart(3)} | ${line}`;
|
|
98
|
+
}).join("\n");
|
|
99
|
+
};
|
|
100
|
+
const program$1 = this.parse(code, {
|
|
101
|
+
lang: "ts",
|
|
102
|
+
astType: "js",
|
|
103
|
+
range: true
|
|
104
|
+
});
|
|
105
|
+
const extractObjectLiteral = (declaration) => {
|
|
106
|
+
if (declaration.init?.type !== "ObjectExpression") {
|
|
107
|
+
const snippet = getSnippet(...declaration.range ?? [0, 0]);
|
|
108
|
+
this.error(`${colors.red.bold("✗")} Invalid bf6Strings export: expected an object literal.\n\n${snippet}`);
|
|
109
|
+
}
|
|
110
|
+
for (const property of declaration.init.properties) {
|
|
111
|
+
if (property.type !== "Property") {
|
|
112
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
113
|
+
this.error(`${colors.red.bold("✗")} Invalid bf6Strings property: expected a key/value pair.\nFound type: ${property.type}\n\n${snippet}`);
|
|
114
|
+
}
|
|
115
|
+
let key;
|
|
116
|
+
if (property.key.type === "Literal") key = property.key.value;
|
|
117
|
+
else if (property.key.type === "Identifier") key = property.key.name;
|
|
118
|
+
else {
|
|
119
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
120
|
+
this.error(`${colors.red.bold("✗")} Invalid bf6Strings key: expected a string literal or identifier.\nFound type: ${property.key.type}\n\n${snippet}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (property.value.type !== "Literal") {
|
|
124
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
125
|
+
this.error(`${colors.red.bold("✗")} Invalid bf6Strings value: expected a string literal.\nFound type: ${property.value.type}\n\n${snippet}`);
|
|
126
|
+
}
|
|
127
|
+
const value = property.value.value;
|
|
128
|
+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "bigint" && typeof key !== "boolean") {
|
|
129
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
130
|
+
this.error(`${colors.red.bold("✗")} bf6Strings key must be a string, found: ${typeof key}\n\n${snippet}`);
|
|
131
|
+
}
|
|
132
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint" && typeof value !== "boolean") {
|
|
133
|
+
const snippet = getSnippet(...property.range ?? [0, 0]);
|
|
134
|
+
this.error(`${colors.red.bold("✗")} bf6Strings value must be a string, found: ${typeof value}\n\n${snippet}`);
|
|
135
|
+
}
|
|
136
|
+
bf6Strings[`${key}`] = `${value}`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
for (const item of program$1.body) {
|
|
140
|
+
if (item.type === "VariableDeclaration") {
|
|
141
|
+
for (const declaration of item.declarations) if (declaration.type === "VariableDeclarator" && declaration.id.type === "Identifier" && declaration.id.name === "bf6Strings") extractObjectLiteral(declaration);
|
|
142
|
+
}
|
|
143
|
+
if (item.type === "ExportNamedDeclaration" && item.declaration?.type === "VariableDeclaration") {
|
|
144
|
+
for (const declaration of item.declaration.declarations) if (declaration.type === "VariableDeclarator" && declaration.id.type === "Identifier" && declaration.id.name === "bf6Strings") extractObjectLiteral(declaration);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
228
150
|
}
|
|
229
151
|
|
|
230
|
-
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/cli/build/index.ts
|
|
154
|
+
/**
|
|
155
|
+
* Dynamically imports and validates bf6.config.ts
|
|
156
|
+
*/
|
|
231
157
|
async function getBf6Config(rootDir) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return result;
|
|
158
|
+
globalThis.defineBf6Config = (c) => c;
|
|
159
|
+
globalThis.MapId = MapId;
|
|
160
|
+
const result = await createJiti(rootDir, { interopDefault: true }).import("./bf6.config", { default: true });
|
|
161
|
+
delete globalThis.defineBf6Config;
|
|
162
|
+
delete globalThis.MapId;
|
|
163
|
+
return result;
|
|
239
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Builds the entire mod project once (for production or single run).
|
|
167
|
+
*/
|
|
240
168
|
async function build() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const { attachments, mapRotation } = await collectAttachments(
|
|
260
|
-
config,
|
|
261
|
-
workingDir,
|
|
262
|
-
tsAttachment,
|
|
263
|
-
generatedStrings
|
|
264
|
-
);
|
|
265
|
-
await writeModJson(config, outDir, attachments, mapRotation, minifyJson);
|
|
266
|
-
console.log(`\u2714 Built mod: ${config.name}`);
|
|
169
|
+
const workingDir = path.resolve(".");
|
|
170
|
+
const config = await getBf6Config(workingDir);
|
|
171
|
+
const outDir = path.resolve(workingDir, config.outDir);
|
|
172
|
+
if (fs.existsSync(outDir)) fs.rmSync(outDir, {
|
|
173
|
+
recursive: true,
|
|
174
|
+
force: true
|
|
175
|
+
});
|
|
176
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
177
|
+
const minifyJson = typeof config.minify === "boolean" ? config.minify : config.minify?.json ?? false;
|
|
178
|
+
const generatedStrings = {};
|
|
179
|
+
let tsAttachment;
|
|
180
|
+
if (config.entrypoint) {
|
|
181
|
+
const entryAbs = path.resolve(workingDir, config.entrypoint);
|
|
182
|
+
tsAttachment = createTsAttachment(entryAbs, await buildEntrypoint(entryAbs, generatedStrings, config.generateStrings ?? true));
|
|
183
|
+
}
|
|
184
|
+
const { attachments, mapRotation } = await collectAttachments(config, workingDir, tsAttachment, generatedStrings);
|
|
185
|
+
await writeModJson(config, outDir, attachments, mapRotation, minifyJson);
|
|
186
|
+
printToConsole(`${colors.green.bold("✓")} Built mod: ${config.name}`);
|
|
267
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Compiles the TypeScript entrypoint using rolldown and returns the compiled code.
|
|
190
|
+
*/
|
|
268
191
|
async function buildEntrypoint(entry, bf6Strings, generateStringsFromLiterals) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
const code = result.output[0].code;
|
|
279
|
-
return code;
|
|
192
|
+
return (await (await rolldown({
|
|
193
|
+
input: entry,
|
|
194
|
+
plugins: [extractBf6Strings(bf6Strings, generateStringsFromLiterals)],
|
|
195
|
+
logLevel: "debug"
|
|
196
|
+
})).generate({
|
|
197
|
+
format: "esm",
|
|
198
|
+
inlineDynamicImports: true
|
|
199
|
+
})).output[0].code;
|
|
280
200
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
201
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
202
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
203
|
+
const spatialsDir = path.resolve(__dirname$1, "../resources/maps/spatial");
|
|
204
|
+
/**
|
|
205
|
+
* Collects all attachments (TS, scenes, strings) and returns them + mapRotation.
|
|
206
|
+
*/
|
|
284
207
|
async function collectAttachments(config, workingDir, tsAttachment, generatedStrings) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
208
|
+
const attachments = [];
|
|
209
|
+
const mapRotation = [];
|
|
210
|
+
if (tsAttachment) attachments.push(tsAttachment);
|
|
211
|
+
if (config.strings || Object.keys(generatedStrings ?? {}).length > 0) {
|
|
212
|
+
const strPath = path.resolve(workingDir, config.strings ?? "strings.json");
|
|
213
|
+
let raw = "{}";
|
|
214
|
+
if (fs.existsSync(strPath)) raw = await fs.promises.readFile(strPath, "utf8");
|
|
215
|
+
else printToConsole(colors.yellow(`⚠️ No strings.json found, generating from literals only.`));
|
|
216
|
+
const attachment = createStringsAttachment(strPath, raw, generatedStrings);
|
|
217
|
+
attachments.push(attachment);
|
|
218
|
+
}
|
|
219
|
+
if (config.scenes) {
|
|
220
|
+
let mapIdx = 0;
|
|
221
|
+
for (const map of config.scenes) {
|
|
222
|
+
let mapId;
|
|
223
|
+
let scene;
|
|
224
|
+
if (Array.isArray(map)) [mapId, scene] = map;
|
|
225
|
+
else {
|
|
226
|
+
mapId = map;
|
|
227
|
+
scene = path.resolve(spatialsDir, `${mapId}.spatial.json`);
|
|
228
|
+
}
|
|
229
|
+
const scenePath = path.resolve(workingDir, scene);
|
|
230
|
+
if (!fs.existsSync(scenePath)) throw new Error(`Cannot find spatial data file: ${scene}`);
|
|
231
|
+
const spatial = createSpatialAttachment(scenePath, await fs.promises.readFile(scenePath, "utf8"), mapIdx++);
|
|
232
|
+
attachments.push(spatial);
|
|
233
|
+
mapRotation.push({
|
|
234
|
+
id: mapId,
|
|
235
|
+
spatialAttachment: spatial
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
attachments,
|
|
241
|
+
mapRotation
|
|
242
|
+
};
|
|
316
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Writes the mod.json output to disk.
|
|
246
|
+
*/
|
|
317
247
|
async function writeModJson(config, outDir, attachments, mapRotation, minify) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
recursive: true
|
|
337
|
-
});
|
|
338
|
-
await fs.promises.writeFile(
|
|
339
|
-
path.resolve(outDir, "attachments", attachment.filename),
|
|
340
|
-
atob(attachment.attachmentData.original)
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
248
|
+
const baseGame = config.game;
|
|
249
|
+
const finalJson = {
|
|
250
|
+
name: config.name,
|
|
251
|
+
description: config.description,
|
|
252
|
+
gameMode: "ModBuilderCustom",
|
|
253
|
+
mutators: baseGame.mutators ?? {},
|
|
254
|
+
assetRestrictions: baseGame.assetRestrictions ?? {},
|
|
255
|
+
teamComposition: baseGame.teamComposition ?? [],
|
|
256
|
+
mapRotation,
|
|
257
|
+
attachments
|
|
258
|
+
};
|
|
259
|
+
const jsonOutput = minify ? JSON.stringify(finalJson) : JSON.stringify(finalJson, null, 2);
|
|
260
|
+
await fs.promises.writeFile(path.resolve(outDir, "mod.json"), jsonOutput);
|
|
261
|
+
if (config.outputArtifacts && finalJson?.attachments) for (const attachment of finalJson.attachments) {
|
|
262
|
+
const attachmentsDir = path.resolve(outDir, "attachments");
|
|
263
|
+
if (!fs.existsSync(attachmentsDir)) fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
264
|
+
await fs.promises.writeFile(path.resolve(outDir, "attachments", attachment.filename), atob(attachment.attachmentData.original));
|
|
265
|
+
}
|
|
344
266
|
}
|
|
345
267
|
function createTsAttachment(filePath, compiled) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
268
|
+
return {
|
|
269
|
+
id: crypto.randomUUID(),
|
|
270
|
+
version: "1.0",
|
|
271
|
+
filename: `${path.parse(filePath).name}.js`,
|
|
272
|
+
isProcessable: true,
|
|
273
|
+
processingStatus: 2,
|
|
274
|
+
attachmentType: AttachmentType.TypeScript,
|
|
275
|
+
attachmentData: {
|
|
276
|
+
original: toBase64(compiled),
|
|
277
|
+
compiled: ""
|
|
278
|
+
},
|
|
279
|
+
errors: []
|
|
280
|
+
};
|
|
356
281
|
}
|
|
357
282
|
function createStringsAttachment(filePath, raw, generatedStrings) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
attachmentData: { original: toBase64(result), compiled: "" },
|
|
377
|
-
errors: []
|
|
378
|
-
};
|
|
283
|
+
let result = raw;
|
|
284
|
+
if (generatedStrings) result = JSON.stringify({
|
|
285
|
+
...generatedStrings,
|
|
286
|
+
...JSON.parse(raw)
|
|
287
|
+
}, null, 4);
|
|
288
|
+
return {
|
|
289
|
+
id: crypto.randomUUID(),
|
|
290
|
+
version: "1.0",
|
|
291
|
+
filename: path.basename(filePath),
|
|
292
|
+
isProcessable: true,
|
|
293
|
+
processingStatus: 2,
|
|
294
|
+
attachmentType: AttachmentType.Strings,
|
|
295
|
+
attachmentData: {
|
|
296
|
+
original: toBase64(result),
|
|
297
|
+
compiled: ""
|
|
298
|
+
},
|
|
299
|
+
errors: []
|
|
300
|
+
};
|
|
379
301
|
}
|
|
380
302
|
function createSpatialAttachment(filePath, raw, mapIdx) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
303
|
+
return {
|
|
304
|
+
id: crypto.randomUUID(),
|
|
305
|
+
version: "1.0",
|
|
306
|
+
filename: path.basename(filePath),
|
|
307
|
+
isProcessable: true,
|
|
308
|
+
processingStatus: 2,
|
|
309
|
+
attachmentType: AttachmentType.SpatialData,
|
|
310
|
+
attachmentData: {
|
|
311
|
+
original: toBase64(raw),
|
|
312
|
+
compiled: ""
|
|
313
|
+
},
|
|
314
|
+
metadata: `mapIdx=${mapIdx}`,
|
|
315
|
+
errors: []
|
|
316
|
+
};
|
|
392
317
|
}
|
|
393
318
|
function toBase64(input) {
|
|
394
|
-
|
|
319
|
+
return Buffer.isBuffer(input) ? input.toString("base64") : Buffer.from(input, "utf8").toString("base64");
|
|
395
320
|
}
|
|
396
321
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/cli/log.ts
|
|
324
|
+
var Bf6Logger = class Bf6Logger {
|
|
325
|
+
constructor(input) {
|
|
326
|
+
if (Bf6Logger.instance) {
|
|
327
|
+
printToConsole(colors.yellow("⚠ Existing logger found — closing it first..."));
|
|
328
|
+
Bf6Logger.instance.stop();
|
|
329
|
+
}
|
|
330
|
+
if (input) this.watchTarget = path.resolve(input);
|
|
331
|
+
else if (process.env.LOCALAPPDATA) this.watchTarget = path.resolve(process.env.LOCALAPPDATA, "temp", "Battlefieldâ„¢ 6", "PortalLog.txt");
|
|
332
|
+
else throw new Error(`${colors.red.bold("✗")} Env variable LOCALAPPDATA not defined, and input file to watch was not provided!`);
|
|
333
|
+
if (!fs.existsSync(this.watchTarget)) throw new Error(`${colors.red.bold("✗")} Cannot find file '${this.watchTarget}' to watch for changes`);
|
|
334
|
+
this.lastSize = fs.statSync(this.watchTarget).size;
|
|
335
|
+
Bf6Logger.instance = this;
|
|
336
|
+
}
|
|
337
|
+
async start() {
|
|
338
|
+
printToConsole(colors.cyan(`👀 Watching Battlefield logs at: ${colors.yellow(this.watchTarget)}`));
|
|
339
|
+
this.watcher = chokidar.watch(this.watchTarget, {
|
|
340
|
+
persistent: true,
|
|
341
|
+
ignoreInitial: true
|
|
342
|
+
});
|
|
343
|
+
this.watcher.on("change", this.handleChange.bind(this));
|
|
344
|
+
}
|
|
345
|
+
async stop() {
|
|
346
|
+
if (this.watcher) {
|
|
347
|
+
this.watcher.close();
|
|
348
|
+
this.watcher = void 0;
|
|
349
|
+
printToConsole(colors.yellow(`${colors.red.bold("✗")} Stopped watching: ${colors.gray(this.watchTarget)}`));
|
|
350
|
+
}
|
|
351
|
+
Bf6Logger.instance = void 0;
|
|
352
|
+
}
|
|
353
|
+
handleChange(file, stats) {
|
|
354
|
+
if (!stats) return;
|
|
355
|
+
if (stats.size > this.lastSize) {
|
|
356
|
+
const newBytes = stats.size - this.lastSize;
|
|
357
|
+
const fd = fs.openSync(file, "r");
|
|
358
|
+
const buffer = Buffer.alloc(newBytes);
|
|
359
|
+
fs.readSync(fd, buffer, 0, newBytes, this.lastSize);
|
|
360
|
+
fs.closeSync(fd);
|
|
361
|
+
const newContent = buffer.toString("utf8").split("QuickJS: ").filter((debug) => debug.trim() !== "").map((debug) => debug.trim()).map((debug) => {
|
|
362
|
+
if (debug.startsWith("console.log: ")) return {
|
|
363
|
+
type: "console.log",
|
|
364
|
+
text: debug.replace("console.log: ", "")
|
|
365
|
+
};
|
|
366
|
+
else if (debug.startsWith("Exception:")) return {
|
|
367
|
+
type: "exception",
|
|
368
|
+
text: debug.replace("Exception:", "")
|
|
369
|
+
};
|
|
370
|
+
return {
|
|
371
|
+
type: "unknown",
|
|
372
|
+
text: debug
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
for (const content of newContent) if (content.type === "console.log") printToConsole(colors.gray(content.text));
|
|
376
|
+
else if (content.type === "exception") printToConsole(colors.red(content.text), true);
|
|
377
|
+
else printToConsole(colors.bold(content.text));
|
|
378
|
+
}
|
|
379
|
+
this.lastSize = stats.size;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/cli/dev.ts
|
|
403
385
|
async function dev() {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
386
|
+
const workingDir = path.resolve(".");
|
|
387
|
+
let config = await getBf6Config(workingDir);
|
|
388
|
+
const outDir = path.resolve(workingDir, config.outDir);
|
|
389
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
390
|
+
printToConsole(colors.cyan(`▶ Starting dev for ${config.name}`));
|
|
391
|
+
let watcher;
|
|
392
|
+
function debounce(fn, delay) {
|
|
393
|
+
let timer;
|
|
394
|
+
return (...args) => {
|
|
395
|
+
if (timer) clearTimeout(timer);
|
|
396
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const rebuild = async (trigger) => {
|
|
400
|
+
printToConsole(colors.yellow(`↻ Change detected in ${path.basename(trigger)}, rebuilding...`));
|
|
401
|
+
const start = performance.now();
|
|
402
|
+
try {
|
|
403
|
+
await build();
|
|
404
|
+
const duration = ((performance.now() - start) / 1e3).toFixed(2);
|
|
405
|
+
printToConsole(`${colors.green.bold("✓")} Updated mod.json (${duration}s)`);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
printToConsole(colors.red(`${colors.red.bold("✗")} Rebuild failed: ${err.message}`), true);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const debouncedRebuild = debounce(rebuild, 200);
|
|
411
|
+
async function collectWatchTargets() {
|
|
412
|
+
const targets = [];
|
|
413
|
+
if (config.entrypoint) targets.push(path.resolve(workingDir, config.entrypoint));
|
|
414
|
+
if (config.scenes) for (const [, scene] of config.scenes) targets.push(path.resolve(workingDir, scene));
|
|
415
|
+
if (config.strings) targets.push(path.resolve(workingDir, config.strings));
|
|
416
|
+
for await (const entry of glob("bf6.config.*")) targets.push(entry);
|
|
417
|
+
for await (const entry of glob("src/**/*")) targets.push(entry);
|
|
418
|
+
return targets;
|
|
419
|
+
}
|
|
420
|
+
async function setupWatcher() {
|
|
421
|
+
if (watcher) {
|
|
422
|
+
await watcher.close();
|
|
423
|
+
printToConsole(colors.grey("♻ Reloading watcher due to config change..."));
|
|
424
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
425
|
+
}
|
|
426
|
+
const watchTargets = await collectWatchTargets();
|
|
427
|
+
printToConsole(colors.cyan(`👀 Watching ${watchTargets.length} files...`));
|
|
428
|
+
watcher = chokidar.watch(watchTargets, {
|
|
429
|
+
persistent: true,
|
|
430
|
+
ignoreInitial: true,
|
|
431
|
+
awaitWriteFinish: {
|
|
432
|
+
stabilityThreshold: 250,
|
|
433
|
+
pollInterval: 100
|
|
434
|
+
},
|
|
435
|
+
ignored: [
|
|
436
|
+
outDir,
|
|
437
|
+
`${outDir}/**`,
|
|
438
|
+
"node_modules/**",
|
|
439
|
+
".git/**"
|
|
440
|
+
]
|
|
441
|
+
});
|
|
442
|
+
watcher.on("error", (err) => {
|
|
443
|
+
printToConsole(colors.red(`⚠ Watcher error: ${err.message}`), true);
|
|
444
|
+
setTimeout(setupWatcher, 1e3);
|
|
445
|
+
});
|
|
446
|
+
watcher.on("change", async (file) => {
|
|
447
|
+
if (file.includes("bf6.config.")) {
|
|
448
|
+
printToConsole(colors.magenta("⚙ Config changed — reloading watcher..."));
|
|
449
|
+
config = await getBf6Config(workingDir);
|
|
450
|
+
await setupWatcher();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
debouncedRebuild(file);
|
|
454
|
+
});
|
|
455
|
+
watcher.on("add", debouncedRebuild);
|
|
456
|
+
await rebuild("initial");
|
|
457
|
+
}
|
|
458
|
+
process.on("SIGINT", async () => {
|
|
459
|
+
printToConsole(colors.red(`\n${colors.red.bold("✗")} Exiting dev mode...`));
|
|
460
|
+
await watcher?.close();
|
|
461
|
+
process.exit(0);
|
|
462
|
+
});
|
|
463
|
+
await setupWatcher();
|
|
464
|
+
let logger;
|
|
465
|
+
try {
|
|
466
|
+
logger = new Bf6Logger();
|
|
467
|
+
logger.start();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
printToConsole(colors.grey("Failed to start logging for the following reason (building will still work)"), true);
|
|
470
|
+
printToConsole(error.message, true);
|
|
471
|
+
}
|
|
476
472
|
}
|
|
477
473
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// src/cli/init.ts
|
|
485
|
-
import child_process from "child_process";
|
|
486
|
-
import fs3 from "fs";
|
|
487
|
-
import path3 from "path";
|
|
488
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
489
|
-
import * as prompts from "@clack/prompts";
|
|
490
|
-
var __filename2 = fileURLToPath2(import.meta.url);
|
|
491
|
-
var __dirname2 = path3.dirname(__filename2);
|
|
492
|
-
var templatesDir = path3.resolve(__dirname2, "../resources/templates");
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/cli/init.ts
|
|
476
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
477
|
+
const __dirname = path.dirname(__filename);
|
|
478
|
+
const templatesDir = path.resolve(__dirname, "../resources/templates");
|
|
493
479
|
function renameFilesRecursively(dir, modName) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
}
|
|
480
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
481
|
+
for (const entry of entries) {
|
|
482
|
+
const oldPath = path.join(dir, entry.name);
|
|
483
|
+
let newPath = oldPath;
|
|
484
|
+
if (entry.name.includes("{name}")) {
|
|
485
|
+
const newName = entry.name.replace("{name}", modName);
|
|
486
|
+
newPath = path.join(dir, newName);
|
|
487
|
+
fs.renameSync(oldPath, newPath);
|
|
488
|
+
}
|
|
489
|
+
if (entry.name === "bf6.config.ts") {
|
|
490
|
+
let content = fs.readFileSync(newPath, "utf-8");
|
|
491
|
+
content = content.replace(/{bf6ConfigName}/g, modName);
|
|
492
|
+
fs.writeFileSync(newPath, content, "utf-8");
|
|
493
|
+
}
|
|
494
|
+
if (entry.name === "package.json") {
|
|
495
|
+
const pkg = JSON.parse(fs.readFileSync(newPath, "utf-8"));
|
|
496
|
+
pkg.name = modName;
|
|
497
|
+
fs.writeFileSync(newPath, JSON.stringify(pkg, null, 2));
|
|
498
|
+
}
|
|
499
|
+
if (fs.statSync(newPath).isDirectory()) renameFilesRecursively(newPath, modName);
|
|
500
|
+
}
|
|
517
501
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
502
|
+
const templates = [
|
|
503
|
+
"Basic",
|
|
504
|
+
"AcePursuit",
|
|
505
|
+
"BombSquad",
|
|
506
|
+
"Exfil",
|
|
507
|
+
"Vertigo"
|
|
524
508
|
];
|
|
525
509
|
async function startProject(destination, template, name) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
510
|
+
if ([
|
|
511
|
+
"AcePursuit",
|
|
512
|
+
"BombSquad",
|
|
513
|
+
"Exfil",
|
|
514
|
+
"Vertigo"
|
|
515
|
+
].includes(template)) await importFile(path.resolve(templatesDir, `${template}.json`), destination, name);
|
|
516
|
+
else if (template === "None") {} else {
|
|
517
|
+
const templateDir = path.resolve(templatesDir, template);
|
|
518
|
+
fs.cpSync(templateDir, destination, { recursive: true });
|
|
519
|
+
}
|
|
520
|
+
fs.cpSync(path.resolve(templatesDir, "All"), destination, { recursive: true });
|
|
521
|
+
if (name) renameFilesRecursively(destination, name);
|
|
538
522
|
}
|
|
539
523
|
function installDependencies(projectDir) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
524
|
+
try {
|
|
525
|
+
child_process.execSync(`npm install`, {
|
|
526
|
+
stdio: "inherit",
|
|
527
|
+
cwd: projectDir
|
|
528
|
+
});
|
|
529
|
+
return true;
|
|
530
|
+
} catch (_err) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
549
533
|
}
|
|
550
|
-
|
|
551
|
-
function isEmpty(
|
|
552
|
-
|
|
553
|
-
|
|
534
|
+
const cancel = () => prompts.cancel("Operation cancelled");
|
|
535
|
+
function isEmpty(path$1) {
|
|
536
|
+
const files = fs.readdirSync(path$1);
|
|
537
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
554
538
|
}
|
|
555
539
|
function emptyDir(dir) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
540
|
+
if (!fs.existsSync(dir)) return;
|
|
541
|
+
for (const file of fs.readdirSync(dir)) {
|
|
542
|
+
if (file === ".git") continue;
|
|
543
|
+
fs.rmSync(path.resolve(dir, file), {
|
|
544
|
+
recursive: true,
|
|
545
|
+
force: true
|
|
546
|
+
});
|
|
547
|
+
}
|
|
565
548
|
}
|
|
566
549
|
async function init(argTargetDir) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
npm run build`;
|
|
634
|
-
prompts.note(nextSteps, "Next steps.");
|
|
550
|
+
prompts.intro("Initialize Bf6 Mod");
|
|
551
|
+
const path$1 = argTargetDir ? argTargetDir : await prompts.text({
|
|
552
|
+
message: "Where should we create your project?",
|
|
553
|
+
placeholder: "./ace-pursuit",
|
|
554
|
+
validate: (value) => {
|
|
555
|
+
if (!value) return "Please enter a path.";
|
|
556
|
+
if (value[0] !== ".") return "Please enter a relative path.";
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
if (prompts.isCancel(path$1)) return cancel();
|
|
560
|
+
let name = await prompts.text({
|
|
561
|
+
message: "What is the name of your mod?",
|
|
562
|
+
placeholder: "Ace Pursuit",
|
|
563
|
+
validate: (value) => {
|
|
564
|
+
if (!value.trim()) return "Please enter a name.";
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
if (prompts.isCancel(name)) return cancel();
|
|
568
|
+
name = name.trim();
|
|
569
|
+
if (fs.existsSync(path$1) && !isEmpty(path$1)) {
|
|
570
|
+
let overwrite;
|
|
571
|
+
const res = await prompts.select({
|
|
572
|
+
message: (path$1 === "." ? "Current directory" : `Target directory "${path$1}"`) + ` is not empty. Please choose how to proceed:`,
|
|
573
|
+
options: [
|
|
574
|
+
{
|
|
575
|
+
label: "Cancel operation",
|
|
576
|
+
value: "no"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
label: "Remove existing files and continue",
|
|
580
|
+
value: "yes"
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
label: "Ignore files and continue",
|
|
584
|
+
value: "ignore"
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
});
|
|
588
|
+
if (prompts.isCancel(res)) return cancel();
|
|
589
|
+
overwrite = res;
|
|
590
|
+
switch (overwrite) {
|
|
591
|
+
case "yes":
|
|
592
|
+
emptyDir(path$1);
|
|
593
|
+
break;
|
|
594
|
+
case "no":
|
|
595
|
+
cancel();
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const template = await prompts.select({
|
|
600
|
+
message: "Select a template:",
|
|
601
|
+
options: templates.map((template$1) => {
|
|
602
|
+
return {
|
|
603
|
+
label: template$1,
|
|
604
|
+
value: template$1
|
|
605
|
+
};
|
|
606
|
+
})
|
|
607
|
+
});
|
|
608
|
+
if (prompts.isCancel(template)) return cancel();
|
|
609
|
+
await startProject(path$1, template, name);
|
|
610
|
+
const s = prompts.spinner();
|
|
611
|
+
s.start("Installing via npm");
|
|
612
|
+
if (installDependencies(path$1)) s.stop("Installed via npm");
|
|
613
|
+
else s.stop("Failed to install via npm", 1);
|
|
614
|
+
const nextSteps = `cd ${path$1}\nnpm run build`;
|
|
615
|
+
prompts.note(nextSteps, "Next steps.");
|
|
635
616
|
}
|
|
636
617
|
|
|
637
|
-
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/cli/import.ts
|
|
638
620
|
async function writeFileSafe(filePath, data) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
621
|
+
const dir = path.dirname(filePath);
|
|
622
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
623
|
+
await fs.promises.writeFile(filePath, data);
|
|
642
624
|
}
|
|
643
625
|
function getMapKeyByValue(value) {
|
|
644
|
-
|
|
645
|
-
(k) => MapId[k] === value
|
|
646
|
-
);
|
|
626
|
+
return Object.keys(MapId).find((k) => MapId[k] === value);
|
|
647
627
|
}
|
|
648
|
-
async function importFile(input, output, name) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
]) : void 0,
|
|
700
|
-
strings: stringsFile ? `src/${stringsFile}` : void 0,
|
|
701
|
-
game: {
|
|
702
|
-
mutators: config.mutators,
|
|
703
|
-
assetRestrictions: config.assetRestrictions,
|
|
704
|
-
gameMode: config.gameMode,
|
|
705
|
-
teamComposition: config.teamComposition
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
const bf6ConfigContent = `export default defineBf6Config(${stringifyWithRaw(
|
|
709
|
-
bf6Config
|
|
710
|
-
)});
|
|
711
|
-
`;
|
|
712
|
-
promises.push(
|
|
713
|
-
writeFileSafe(path4.resolve(outDir, "bf6.config.ts"), bf6ConfigContent)
|
|
714
|
-
);
|
|
715
|
-
await Promise.all(promises);
|
|
628
|
+
async function importFile(input, output, name, logging = false) {
|
|
629
|
+
const workingDir = path.resolve(".");
|
|
630
|
+
const entrypoint = path.resolve(workingDir, input);
|
|
631
|
+
const outDir = path.resolve(workingDir, output);
|
|
632
|
+
if (!fs.existsSync(entrypoint)) throw new Error("Cannot find strings file");
|
|
633
|
+
const config = JSON.parse(await fs.promises.readFile(entrypoint, { encoding: "utf8" }));
|
|
634
|
+
if (!fs.existsSync(outDir)) await fs.promises.mkdir(outDir, { recursive: true });
|
|
635
|
+
await startProject(outDir, "None", name ?? config.name);
|
|
636
|
+
let typescriptFile;
|
|
637
|
+
let stringsFile;
|
|
638
|
+
const scenes = [];
|
|
639
|
+
const promises = [];
|
|
640
|
+
if (config.attachments) {
|
|
641
|
+
let unamedAttachmentIndex = 0;
|
|
642
|
+
for (const attachment of config.attachments) {
|
|
643
|
+
let filename = attachment.filename?.trim();
|
|
644
|
+
if (!filename) {
|
|
645
|
+
unamedAttachmentIndex++;
|
|
646
|
+
let ext;
|
|
647
|
+
if (attachment.attachmentType === AttachmentType.TypeScript) ext = ".ts";
|
|
648
|
+
else if (attachment.attachmentType === AttachmentType.Strings) ext = ".strings.json";
|
|
649
|
+
else if (attachment.attachmentType === AttachmentType.SpatialData) ext = ".spatial.json";
|
|
650
|
+
if (unamedAttachmentIndex > 1) filename = `attachment_${unamedAttachmentIndex}${ext}`;
|
|
651
|
+
else filename = `attachment${ext}`;
|
|
652
|
+
}
|
|
653
|
+
if (attachment.attachmentType === AttachmentType.TypeScript) typescriptFile = filename;
|
|
654
|
+
if (attachment.attachmentType === AttachmentType.Strings) stringsFile = filename;
|
|
655
|
+
if (attachment.attachmentType === AttachmentType.SpatialData) continue;
|
|
656
|
+
promises.push(writeFileSafe(path.resolve(outDir, "src", filename), atob(attachment.attachmentData.original)));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (config.mapRotation?.length) for (const map of config.mapRotation) {
|
|
660
|
+
promises.push(writeFileSafe(path.resolve(outDir, "src", "scenes", map.spatialAttachment.filename), atob(map.spatialAttachment.attachmentData.original)));
|
|
661
|
+
scenes.push([map.id, `src/scenes/${map.spatialAttachment.filename}`]);
|
|
662
|
+
}
|
|
663
|
+
const bf6ConfigContent = `export default defineBf6Config(${stringifyWithRaw({
|
|
664
|
+
name: name ?? config.name,
|
|
665
|
+
description: config.description,
|
|
666
|
+
outDir: "dist",
|
|
667
|
+
entrypoint: typescriptFile ? `src/${typescriptFile}` : void 0,
|
|
668
|
+
scenes: scenes ? scenes.map(([id, path$1]) => [`MapId.${getMapKeyByValue(id) ?? id}`, path$1]) : void 0,
|
|
669
|
+
strings: stringsFile ? `src/${stringsFile}` : void 0,
|
|
670
|
+
game: {
|
|
671
|
+
mutators: config.mutators,
|
|
672
|
+
assetRestrictions: config.assetRestrictions,
|
|
673
|
+
gameMode: config.gameMode,
|
|
674
|
+
teamComposition: config.teamComposition
|
|
675
|
+
}
|
|
676
|
+
})});\n`;
|
|
677
|
+
promises.push(writeFileSafe(path.resolve(outDir, "bf6.config.ts"), bf6ConfigContent));
|
|
678
|
+
await Promise.all(promises);
|
|
716
679
|
}
|
|
717
680
|
function stringifyWithRaw(value, options = {}) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
},
|
|
728
|
-
...options
|
|
729
|
-
});
|
|
681
|
+
return stringifyObject(value, {
|
|
682
|
+
indent: " ",
|
|
683
|
+
singleQuotes: true,
|
|
684
|
+
transform: (_obj, _prop, originalResult) => {
|
|
685
|
+
if (/^['"]MapId\.[A-Za-z0-9_]+['"]$/.test(originalResult)) return originalResult.slice(1, -1);
|
|
686
|
+
return originalResult;
|
|
687
|
+
},
|
|
688
|
+
...options
|
|
689
|
+
});
|
|
730
690
|
}
|
|
731
691
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
import path5 from "path";
|
|
735
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
736
|
-
import colors3 from "colors";
|
|
737
|
-
import { genExport, genInlineTypeImport } from "knitwork";
|
|
738
|
-
|
|
739
|
-
// src/cli/utils.ts
|
|
740
|
-
import colors2 from "colors";
|
|
741
|
-
|
|
742
|
-
// ../../node_modules/ansi-regex/index.js
|
|
743
|
-
function ansiRegex({ onlyFirst = false } = {}) {
|
|
744
|
-
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
|
|
745
|
-
const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
|
|
746
|
-
const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
|
|
747
|
-
const pattern = `${osc}|${csi}`;
|
|
748
|
-
return new RegExp(pattern, onlyFirst ? void 0 : "g");
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// ../../node_modules/strip-ansi/index.js
|
|
752
|
-
var regex = ansiRegex();
|
|
753
|
-
function stripAnsi(string) {
|
|
754
|
-
if (typeof string !== "string") {
|
|
755
|
-
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
|
|
756
|
-
}
|
|
757
|
-
return string.replace(regex, "");
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// src/cli/utils.ts
|
|
761
|
-
var printToConsole = (message) => {
|
|
762
|
-
const now = /* @__PURE__ */ new Date();
|
|
763
|
-
const formattedTime = colors2.grey(now.toLocaleTimeString());
|
|
764
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
765
|
-
const timeLength = stripAnsi(formattedTime).length;
|
|
766
|
-
const messageLength = stripAnsi(message).length;
|
|
767
|
-
const available = terminalWidth - (messageLength + timeLength);
|
|
768
|
-
const spacing = available > 1 ? " ".repeat(available) : " ";
|
|
769
|
-
console.log(`${message}${spacing}${formattedTime}`);
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
// src/cli/prepare.ts
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/cli/prepare.ts
|
|
773
694
|
async function prepare() {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
fs5.writeFileSync(
|
|
796
|
-
path5.resolve(buildDir, "imports.d.ts"),
|
|
797
|
-
`${ConfigFileExports}
|
|
798
|
-
`
|
|
799
|
-
);
|
|
800
|
-
const augmentations = {
|
|
801
|
-
defineBf6Config: genInlineTypeImport(
|
|
802
|
-
"./types/config.ts",
|
|
803
|
-
`defineBf6Config`
|
|
804
|
-
),
|
|
805
|
-
MapId: genInlineTypeImport("./types/config.ts", `MapId`)
|
|
806
|
-
};
|
|
807
|
-
const args = genNamespaceAugmentation("global", augmentations);
|
|
808
|
-
fs5.writeFileSync(
|
|
809
|
-
path5.resolve(buildDir, "globals.d.ts"),
|
|
810
|
-
`export {}
|
|
811
|
-
|
|
812
|
-
${args}
|
|
813
|
-
`
|
|
814
|
-
);
|
|
815
|
-
printToConsole(`${colors3.green("\u2714")} Types generated in .bf6`);
|
|
816
|
-
} catch (error) {
|
|
817
|
-
console.error(error);
|
|
818
|
-
printToConsole(`${colors3.red("\u2717")} Types failed to generate in .bf6`);
|
|
819
|
-
}
|
|
695
|
+
try {
|
|
696
|
+
const __filename$2 = fileURLToPath(import.meta.url);
|
|
697
|
+
const __dirname$2 = path.dirname(__filename$2);
|
|
698
|
+
path.resolve(".");
|
|
699
|
+
const buildDir = path.resolve(".bf6");
|
|
700
|
+
const resources = path.resolve(__dirname$2, "../resources/prepare");
|
|
701
|
+
fs.cpSync(path.resolve(resources, "tsconfig.json"), path.resolve(buildDir, "tsconfig.json"));
|
|
702
|
+
fs.cpSync(path.resolve(resources, "bf6.d.ts"), path.resolve(buildDir, "bf6.d.ts"));
|
|
703
|
+
fs.cpSync(path.resolve(resources, "types", "config.ts"), path.resolve(buildDir, "types", "config.ts"));
|
|
704
|
+
const ConfigFileExports = genExport("./types/config.ts", ["defineBf6Config"]);
|
|
705
|
+
fs.writeFileSync(path.resolve(buildDir, "imports.d.ts"), `${ConfigFileExports}\n`);
|
|
706
|
+
const args = genNamespaceAugmentation("global", {
|
|
707
|
+
defineBf6Config: genInlineTypeImport("./types/config.ts", `defineBf6Config`),
|
|
708
|
+
MapId: genInlineTypeImport("./types/config.ts", `MapId`)
|
|
709
|
+
});
|
|
710
|
+
fs.writeFileSync(path.resolve(buildDir, "globals.d.ts"), `export {}\n\n${args}\n`);
|
|
711
|
+
printToConsole(`${colors.green.bold("✓")} Types generated in .bf6`);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
console.error(error);
|
|
714
|
+
printToConsole(`${colors.red.bold("✗")} Types failed to generate in .bf6`, true);
|
|
715
|
+
}
|
|
820
716
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const decls = Object.entries(contents).map(([k, v]) => ` const ${k}: ${v};`).join("\n");
|
|
825
|
-
return `declare ${name} {
|
|
826
|
-
${decls}
|
|
827
|
-
}`;
|
|
717
|
+
const genNamespaceAugmentation = (name, contents) => {
|
|
718
|
+
if (!contents || Object.keys(contents).length === 0) return `declare ${name} {}`;
|
|
719
|
+
return `declare ${name} {\n${Object.entries(contents).map(([k, v]) => `\tconst ${k}: ${v};`).join("\n")}\n}`;
|
|
828
720
|
};
|
|
829
721
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
program
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/cli/index.ts
|
|
724
|
+
const program = new Command();
|
|
725
|
+
program.name(Object.keys(bin)[0]).description(description).version(version);
|
|
833
726
|
program.command("init").argument("[directory]").description("Create a new bf6 mod").action(async (directory) => {
|
|
834
|
-
|
|
727
|
+
await init(directory);
|
|
835
728
|
});
|
|
836
729
|
program.command("build").description("build the bf6 mod").action(async () => {
|
|
837
|
-
|
|
730
|
+
await build();
|
|
838
731
|
});
|
|
839
732
|
program.command("prepare").description("prepare the types for bf6 mod").action(async () => {
|
|
840
|
-
|
|
733
|
+
await prepare();
|
|
841
734
|
});
|
|
842
735
|
program.command("dev").description("watch the changes in src, and recompile as needed").action(async () => {
|
|
843
|
-
|
|
736
|
+
await dev();
|
|
844
737
|
});
|
|
845
738
|
program.command("import").argument("<input>").argument("<output>").description("decompiles the json config of a mod into a new project").action(async (input, output) => {
|
|
846
|
-
|
|
847
|
-
|
|
739
|
+
await importFile(input, output, void 0, true);
|
|
740
|
+
installDependencies(output);
|
|
741
|
+
});
|
|
742
|
+
program.command("log").argument("[input]").description("logs the output from a locally running server").action(async (input) => {
|
|
743
|
+
new Bf6Logger(input).start();
|
|
848
744
|
});
|
|
849
745
|
program.exitOverride((_err) => {
|
|
850
|
-
|
|
746
|
+
if (process.env.EXIT_CODE === "none") process.exit(0);
|
|
851
747
|
});
|
|
852
748
|
program.parse();
|
|
749
|
+
|
|
750
|
+
//#endregion
|
|
751
|
+
export { };
|
|
853
752
|
//# sourceMappingURL=index.js.map
|