-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
build.ts
233 lines (190 loc) · 6.42 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
228
229
230
231
232
233
#!/usr/bin/env bun
import { readFile } from "node:fs/promises";
import { parseArgs } from "node:util";
import { sys } from "typescript";
// @ts-ignore
import txtScriptHeader from "./src/assets/header_script.txt" with { type: "text" };
// @ts-ignore
import txtScriptHeaderLite from "./src/assets/header_script.lite.txt" with { type: "text" };
// @ts-ignore
import txtMetaHeader from "./src/assets/header_meta.txt" with { type: "text" };
import { assert } from "node:console";
import { ESLint } from "eslint";
enum BuildTarget {
ALL = 'all',
ANDROID_APP = 'android-app',
MOBILE = 'mobile',
WEBOS = 'webos',
}
type BuildVariant = 'full' | 'lite';
const postProcess = (str: string): string => {
// Unescape unicode charaters
str = unescape((str.replace(/\\u/g, '%u')));
// Replace \x00 to normal character
str = str.replaceAll(/\\x[A-F0-9]{2}/g, (e) => String.fromCharCode(parseInt(e.substring(2), 16)));
// Replace "globalThis." with "var";
str = str.replaceAll('globalThis.', 'var ');
// Remove enum's inlining comments
str = str.replaceAll(/ \/\* [A-Z0-9_]+ \*\//g, '');
str = str.replaceAll('/* @__PURE__ */ ', '');
// Remove comments from import
str = str.replaceAll(/\/\/ src.*\n/g, '');
// Add ADDITIONAL CODE block
str = str.replace('var DEFAULT_FLAGS', '\n/* ADDITIONAL CODE */\n\nvar DEFAULT_FLAGS');
str = str.replaceAll('(e) => `', 'e => `');
// Simplify object definitions
// {[1]: "a"} => {1: "a"}
str = str.replaceAll(/\[(\d+)\]: /g, '$1: ');
// {["a"]: 1, ["b-c"]: 2} => {a: 1, "b-c": 2}
str = str.replaceAll(/\["([^"]+)"\]: /g, function(match, p1) {
if (p1.includes('-') || p1.match(/^\d/)) {
p1 = `"${p1}"`;
}
return p1 + ': ';
});
// Minify SVG import code
const svgMap = {}
str = str.replaceAll(/var ([\w_]+) = ("<svg.*?");\n\n/g, (match, p1, p2) => {
// Remove new lines in SVG
p2 = p2.replaceAll(/\\n*\s*/g, '');
svgMap[p1] = p2;
return '';
});
for (const name in svgMap) {
str = str.replace(`: ${name}`, `: ${svgMap[name]}`);
}
// Collapse empty brackets
str = str.replaceAll(/\{[\s\n]+\}/g, '{}');
// Collapse if/else blocks without curly braces
str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
// Remove blank lines
str = str.replaceAll(/\n([\s]*)\n/g, "\n");
// Minify WebGL shaders & JS strings
// Replace "\n " with "\n"
str = str.replaceAll(/\\n+\s*/g, '\\n');
// Remove comment line
str = str.replaceAll(/\\n\/\/.*?(?=\\n)/g, '');
// Replace ${"time".toUpperCase()} with "TIME"
str = str.replaceAll(/\$\{"([^"]+)"\.toUpperCase\(\)\}/g, (match, p1) => {
return p1.toUpperCase();
});
assert(str.includes('/* ADDITIONAL CODE */'));
assert(str.includes('window.BX_EXPOSED = BxExposed'));
assert(str.includes('window.BxEvent = BxEvent'));
assert(str.includes('window.BX_FETCH = window.fetch'));
return str;
}
const build = async (target: BuildTarget, version: string, variant: BuildVariant, config: any={}) => {
console.log('-- Target:', target);
const startTime = performance.now();
let outputScriptName = 'better-xcloud';
if (target !== BuildTarget.ALL) {
outputScriptName += `.${target}`;
}
if (variant !== 'full') {
outputScriptName += `.${variant}`;
}
let outputMetaName = outputScriptName;
outputScriptName += '.user.js';
outputMetaName += '.meta.js';
const outDir = './dist';
let output = await Bun.build({
entrypoints: ['src/index.ts'],
outdir: outDir,
naming: outputScriptName,
minify: {
syntax: true,
},
define: {
'Bun.env.BUILD_TARGET': JSON.stringify(target),
'Bun.env.BUILD_VARIANT': JSON.stringify(variant),
'Bun.env.SCRIPT_VERSION': JSON.stringify(version),
},
});
if (!output.success) {
console.log(output);
process.exit(1);
}
const {path} = output.outputs[0];
// Get generated file
let result = postProcess(await readFile(path, 'utf-8'));
// Replace [[VERSION]] with real value
let scriptHeader: string;
if (variant === 'full') {
scriptHeader = txtScriptHeader;
} else {
scriptHeader = txtScriptHeaderLite;
}
scriptHeader = scriptHeader.replace('[[VERSION]]', version);
// Save to script
await Bun.write(path, scriptHeader + result);
// Create meta file (don't build if it's beta version)
if (!version.includes('beta') && variant === 'full') {
await Bun.write(outDir + '/' + outputMetaName, txtMetaHeader.replace('[[VERSION]]', version));
}
// Check with ESLint
const eslint = new ESLint();
const results = await eslint.lintFiles([path]);
results[0].messages.forEach((msg: any) => {
console.error(`${path}#${msg.line}: ${msg.message}`);
});
console.log(`---- [${target}] done in ${performance.now() - startTime} ms`);
console.log(`---- [${target}] ${new Date()}`);
}
const buildTargets = [
BuildTarget.ALL,
// BuildTarget.ANDROID_APP,
// BuildTarget.MOBILE,
// BuildTarget.WEBOS,
];
const { values, positionals } = parseArgs({
args: Bun.argv,
options: {
version: {
type: 'string',
},
variant: {
type: 'string',
default: 'full',
},
},
strict: true,
allowPositionals: true,
}) as {
values: {
version: string,
variant: BuildVariant,
},
positionals: string[],
};
if (!values['version']) {
console.log('Missing --version param');
sys.exit(-1);
}
if (values['variant'] !== 'full' && values['variant'] !== 'lite') {
console.log('--variant param must be either "full" or "lite"');
sys.exit(-1);
}
async function main() {
const config = {};
console.log(`Building: VERSION=${values['version']}, VARIANT=${values['variant']}`);
for (const target of buildTargets) {
await build(target, values['version']!!, values['variant'], config);
}
console.log('')
// console.log('\n** Press Enter to build or Esc to exit');
}
function onKeyPress(data: any) {
const keyCode = data[0];
if (keyCode === 13) { // Enter key
main();
} else if (keyCode === 27) { // Esc key
process.exit(0);
}
}
main();
/*
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', onKeyPress);
*/