@steambrew/ttc 3.0.1 → 3.1.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.
@@ -3,7 +3,8 @@
3
3
  "allow": [
4
4
  "Bash(npm show:*)",
5
5
  "Bash(bun remove:*)",
6
- "Bash(bun run:*)"
6
+ "Bash(bun run:*)",
7
+ "Bash(node:*)"
7
8
  ]
8
9
  }
9
10
  }
package/dist/index.js CHANGED
@@ -13,18 +13,18 @@ import terser from '@rollup/plugin-terser';
13
13
  import typescript from '@rollup/plugin-typescript';
14
14
  import url from '@rollup/plugin-url';
15
15
  import nodePolyfills from 'rollup-plugin-polyfill-node';
16
- import { rollup } from 'rollup';
16
+ import { watch, rollup } from 'rollup';
17
17
  import { minify_sync } from 'terser';
18
18
  import scss from 'rollup-plugin-scss';
19
19
  import * as sass from 'sass';
20
20
  import dotenv from 'dotenv';
21
21
  import injectProcessEnv from 'rollup-plugin-inject-process-env';
22
+ import { performance } from 'perf_hooks';
22
23
  import * as parser from '@babel/parser';
23
24
  import { createFilter } from '@rollup/pluginutils';
24
25
  import * as glob from 'glob';
25
26
  import MagicString from 'magic-string';
26
27
  import _traverse from '@babel/traverse';
27
- import { performance as performance$1 } from 'perf_hooks';
28
28
 
29
29
  const version = JSON.parse(readFileSync(path.resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')).version;
30
30
  const Logger = {
@@ -59,6 +59,10 @@ const Logger = {
59
59
  meta.push(`${envCount} env var${envCount > 1 ? 's' : ''}`);
60
60
  console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim('(' + meta.join(', ') + ')'));
61
61
  },
62
+ failed({ elapsedMs, buildType }) {
63
+ const elapsed = `${(elapsedMs / 1000).toFixed(2)}s`;
64
+ console.error(`${chalk.red('Failed')} ${buildType} in ${elapsed} ` + chalk.dim(`(ttc v${version})`));
65
+ },
62
66
  };
63
67
 
64
68
  const PrintParamHelp = () => {
@@ -69,6 +73,7 @@ const PrintParamHelp = () => {
69
73
  'options:',
70
74
  ' --build <dev|prod> build type (prod enables minification)',
71
75
  ' --target <path> plugin directory (default: current directory)',
76
+ ' --watch enable watch mode for continuous rebuilding',
72
77
  ' --no-update skip update check',
73
78
  ' --help show this message',
74
79
  '',
@@ -80,7 +85,7 @@ var BuildType;
80
85
  BuildType[BuildType["ProdBuild"] = 1] = "ProdBuild";
81
86
  })(BuildType || (BuildType = {}));
82
87
  const ValidateParameters = (args) => {
83
- let typeProp = BuildType.DevBuild, targetProp = process.cwd(), isMillennium = false;
88
+ let typeProp = BuildType.DevBuild, targetProp = process.cwd(), isMillennium = false, watch = false;
84
89
  if (args.includes('--help')) {
85
90
  PrintParamHelp();
86
91
  process.exit();
@@ -116,11 +121,15 @@ const ValidateParameters = (args) => {
116
121
  if (args[i] == '--millennium-internal') {
117
122
  isMillennium = true;
118
123
  }
124
+ if (args[i] === '--watch') {
125
+ watch = true;
126
+ }
119
127
  }
120
128
  return {
121
129
  type: typeProp,
122
130
  targetPlugin: targetProp,
123
- isMillennium: isMillennium,
131
+ isMillennium,
132
+ watch,
124
133
  };
125
134
  };
126
135
 
@@ -415,6 +424,7 @@ function constSysfsExpr(options = {}) {
415
424
  fileName: path.relative(searchBasePath, singleFilePath),
416
425
  };
417
426
  embeddedContent = JSON.stringify(fileInfo);
427
+ this.addWatchFile(singleFilePath);
418
428
  }
419
429
  catch (fileError) {
420
430
  let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
@@ -438,6 +448,7 @@ function constSysfsExpr(options = {}) {
438
448
  filePath: fullPath,
439
449
  fileName: path.relative(searchBasePath, fullPath),
440
450
  });
451
+ this.addWatchFile(fullPath);
441
452
  }
442
453
  catch (fileError) {
443
454
  let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
@@ -554,6 +565,8 @@ function stripPluginPrefix(message) {
554
565
  message = message.replace(/^@?[\w-]+\/[\w-]+\s+/, '');
555
566
  return message;
556
567
  }
568
+ class BuildFailedError extends Error {
569
+ }
557
570
  class MillenniumBuild {
558
571
  isExternal(id) {
559
572
  const hint = this.forbidden.get(id);
@@ -563,6 +576,25 @@ class MillenniumBuild {
563
576
  }
564
577
  return this.externals.has(id);
565
578
  }
579
+ async watchConfig(input, sysfsPlugin, isMillennium) {
580
+ return {
581
+ input,
582
+ plugins: await this.plugins(sysfsPlugin),
583
+ onwarn: (warning) => {
584
+ const msg = stripPluginPrefix(warning.message);
585
+ const loc = logLocation(warning);
586
+ if (warning.plugin === 'typescript') {
587
+ Logger.error(msg, loc);
588
+ }
589
+ else {
590
+ Logger.warn(msg, loc);
591
+ }
592
+ },
593
+ context: 'window',
594
+ external: (id) => this.isExternal(id),
595
+ output: this.output(isMillennium),
596
+ };
597
+ }
566
598
  async build(input, sysfsPlugin, isMillennium) {
567
599
  let hasErrors = false;
568
600
  const config = {
@@ -585,7 +617,7 @@ class MillenniumBuild {
585
617
  };
586
618
  await (await rollup(config)).write(config.output);
587
619
  if (hasErrors)
588
- process.exit(1);
620
+ throw new BuildFailedError();
589
621
  }
590
622
  }
591
623
  class FrontendBuild extends MillenniumBuild {
@@ -679,10 +711,47 @@ class WebkitBuild extends MillenniumBuild {
679
711
  };
680
712
  }
681
713
  }
682
- const TranspilerPluginComponent = async (isMillennium, pluginJson, props) => {
714
+ function RunWatchMode(frontendConfig, webkitConfig) {
715
+ const configs = webkitConfig ? [frontendConfig, webkitConfig] : [frontendConfig];
716
+ const watcher = watch(configs);
717
+ console.log(chalk.blueBright.bold('watch'), 'watching for file changes...');
718
+ watcher.on('event', async (event) => {
719
+ if (event.code === 'BUNDLE_START') {
720
+ const label = event.output.some((f) => f.includes('index.js')) ? 'frontend' : 'webkit';
721
+ console.log(chalk.yellowBright.bold('watch'), `rebuilding ${label}...`);
722
+ }
723
+ else if (event.code === 'BUNDLE_END') {
724
+ const label = event.output.some((f) => f.includes('index.js')) ? 'frontend' : 'webkit';
725
+ console.log(chalk.greenBright.bold('watch'), `${label} built in ${chalk.green(`${event.duration}ms`)}`);
726
+ await event.result.close();
727
+ }
728
+ else if (event.code === 'ERROR') {
729
+ const err = event.error;
730
+ const msg = stripPluginPrefix(err?.message ?? String(err));
731
+ Logger.error(msg, logLocation(err));
732
+ if (event.result)
733
+ await event.result.close();
734
+ }
735
+ });
736
+ const shutdown = () => {
737
+ console.log(chalk.yellowBright.bold('watch'), 'stopping...');
738
+ watcher.close();
739
+ process.exit(0);
740
+ };
741
+ process.on('SIGINT', shutdown);
742
+ process.on('SIGUSR2', shutdown);
743
+ }
744
+ const TranspilerPluginComponent = async (pluginJson, props) => {
683
745
  const webkitDir = './webkit/index.tsx';
684
746
  const frontendDir = getFrontendDir(pluginJson);
685
747
  const sysfs = constSysfsExpr();
748
+ const isMillennium = props.isMillennium ?? false;
749
+ if (props.watch) {
750
+ const frontendConfig = await new FrontendBuild(frontendDir, props).watchConfig(resolveEntryFile(frontendDir), sysfs.plugin, isMillennium);
751
+ const webkitConfig = fs.existsSync(webkitDir) ? await new WebkitBuild(props).watchConfig(webkitDir, sysfs.plugin, isMillennium) : null;
752
+ RunWatchMode(frontendConfig, webkitConfig);
753
+ return;
754
+ }
686
755
  try {
687
756
  await new FrontendBuild(frontendDir, props).build(resolveEntryFile(frontendDir), sysfs.plugin, isMillennium);
688
757
  if (fs.existsSync(webkitDir)) {
@@ -696,7 +765,10 @@ const TranspilerPluginComponent = async (isMillennium, pluginJson, props) => {
696
765
  });
697
766
  }
698
767
  catch (exception) {
699
- Logger.error(stripPluginPrefix(exception?.message ?? String(exception)), logLocation(exception));
768
+ if (!(exception instanceof BuildFailedError)) {
769
+ Logger.error(stripPluginPrefix(exception?.message ?? String(exception)), logLocation(exception));
770
+ }
771
+ Logger.failed({ elapsedMs: performance.now() - global.PerfStartTime, buildType: props.minify ? 'prod' : 'dev' });
700
772
  process.exit(1);
701
773
  }
702
774
  };
@@ -717,16 +789,18 @@ const StartCompilerModule = () => {
717
789
  .then((json) => {
718
790
  const props = {
719
791
  minify: bTersePlugin,
720
- pluginName: json?.name,
792
+ pluginName: json.name,
793
+ watch: parameters.watch || false,
794
+ isMillennium: bIsMillennium,
721
795
  };
722
- TranspilerPluginComponent(bIsMillennium, json, props);
796
+ TranspilerPluginComponent(json, props);
723
797
  })
724
798
  .catch(() => {
725
799
  process.exit();
726
800
  });
727
801
  };
728
802
  const Initialize = () => {
729
- global.PerfStartTime = performance$1.now();
803
+ global.PerfStartTime = performance.now();
730
804
  // Check for --no-update flag
731
805
  if (process.argv.includes('--no-update')) {
732
806
  StartCompilerModule();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steambrew/ttc",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "rollup -c",
12
+ "dev": "rollup -c -w",
12
13
  "prepare": "bun run build"
13
14
  },
14
15
  "publishConfig": {
@@ -1,9 +1,10 @@
1
1
  import path from 'path';
2
2
  import { existsSync, readFile } from 'fs';
3
3
  import { Logger } from './logger';
4
+ import { PluginJson } from './plugin-json';
4
5
 
5
- export const ValidatePlugin = (bIsMillennium: boolean, target: string): Promise<any> => {
6
- return new Promise<any>((resolve, reject) => {
6
+ export const ValidatePlugin = (bIsMillennium: boolean, target: string): Promise<PluginJson> => {
7
+ return new Promise<PluginJson>((resolve, reject) => {
7
8
  if (!existsSync(target)) {
8
9
  Logger.error(`target path does not exist: ${target}`);
9
10
  reject();
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { BuildType, ValidateParameters } from './query-parser';
9
9
  import { CheckForUpdates } from './version-control';
10
10
  import { ValidatePlugin } from './check-health';
11
+ import { PluginJson } from './plugin-json';
11
12
  import { TranspilerPluginComponent, TranspilerProps } from './transpiler';
12
13
  import { performance } from 'perf_hooks';
13
14
 
@@ -25,13 +26,15 @@ const StartCompilerModule = () => {
25
26
  const bTersePlugin = parameters.type == BuildType.ProdBuild;
26
27
 
27
28
  ValidatePlugin(bIsMillennium, parameters.targetPlugin)
28
- .then((json: any) => {
29
+ .then((json: PluginJson) => {
29
30
  const props: TranspilerProps = {
30
31
  minify: bTersePlugin,
31
- pluginName: json?.name,
32
+ pluginName: json.name,
33
+ watch: parameters.watch || false,
34
+ isMillennium: bIsMillennium,
32
35
  };
33
36
 
34
- TranspilerPluginComponent(bIsMillennium, json, props);
37
+ TranspilerPluginComponent(json, props);
35
38
  })
36
39
  .catch(() => {
37
40
  process.exit();
package/src/logger.ts CHANGED
@@ -44,6 +44,11 @@ const Logger = {
44
44
  if (envCount) meta.push(`${envCount} env var${envCount > 1 ? 's' : ''}`);
45
45
  console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim('(' + meta.join(', ') + ')'));
46
46
  },
47
+
48
+ failed({ elapsedMs, buildType }: Pick<DoneOptions, 'elapsedMs' | 'buildType'>) {
49
+ const elapsed = `${(elapsedMs / 1000).toFixed(2)}s`;
50
+ console.error(`${chalk.red('Failed')} ${buildType} in ${elapsed} ` + chalk.dim(`(ttc v${version})`));
51
+ },
47
52
  };
48
53
 
49
54
  export { Logger };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * generated from https://raw.githubusercontent.com/SteamClientHomebrew/Millennium/main/src/sys/plugin-schema.json
3
+ */
4
+ export interface PluginJson {
5
+ backend?: string;
6
+ common_name?: string;
7
+ description?: string;
8
+ frontend?: string;
9
+ include?: string[];
10
+ name: string;
11
+ splash_image?: string;
12
+ thumbnail?: string;
13
+ useBackend?: boolean;
14
+ venv?: string;
15
+ version?: string;
16
+ }
@@ -9,6 +9,7 @@ export const PrintParamHelp = () => {
9
9
  'options:',
10
10
  ' --build <dev|prod> build type (prod enables minification)',
11
11
  ' --target <path> plugin directory (default: current directory)',
12
+ ' --watch enable watch mode for continuous rebuilding',
12
13
  ' --no-update skip update check',
13
14
  ' --help show this message',
14
15
  '',
@@ -25,12 +26,14 @@ export interface ParameterProps {
25
26
  type: BuildType;
26
27
  targetPlugin: string; // path
27
28
  isMillennium?: boolean;
29
+ watch?: boolean;
28
30
  }
29
31
 
30
32
  export const ValidateParameters = (args: Array<string>): ParameterProps => {
31
33
  let typeProp: BuildType = BuildType.DevBuild,
32
34
  targetProp: string = process.cwd(),
33
- isMillennium: boolean = false;
35
+ isMillennium: boolean = false,
36
+ watch: boolean = false;
34
37
 
35
38
  if (args.includes('--help')) {
36
39
  PrintParamHelp();
@@ -73,11 +76,16 @@ export const ValidateParameters = (args: Array<string>): ParameterProps => {
73
76
  if (args[i] == '--millennium-internal') {
74
77
  isMillennium = true;
75
78
  }
79
+
80
+ if (args[i] === '--watch') {
81
+ watch = true;
82
+ }
76
83
  }
77
84
 
78
85
  return {
79
86
  type: typeProp,
80
87
  targetPlugin: targetProp,
81
- isMillennium: isMillennium,
88
+ isMillennium,
89
+ watch,
82
90
  };
83
91
  };
@@ -206,7 +206,8 @@ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsP
206
206
  fileName: path.relative(searchBasePath, singleFilePath),
207
207
  };
208
208
  embeddedContent = JSON.stringify(fileInfo);
209
- } catch (fileError: unknown) {
209
+ this.addWatchFile(singleFilePath);
210
+ } catch (fileError: unknown) {
210
211
  let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
211
212
  this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
212
213
  return;
@@ -230,6 +231,7 @@ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsP
230
231
  filePath: fullPath,
231
232
  fileName: path.relative(searchBasePath, fullPath),
232
233
  });
234
+ this.addWatchFile(fullPath);
233
235
  } catch (fileError: unknown) {
234
236
  let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
235
237
  this.warn(`Error reading file ${fullPath}: ${message}`);
package/src/transpiler.ts CHANGED
@@ -7,7 +7,8 @@ import terser from '@rollup/plugin-terser';
7
7
  import typescript from '@rollup/plugin-typescript';
8
8
  import url from '@rollup/plugin-url';
9
9
  import nodePolyfills from 'rollup-plugin-polyfill-node';
10
- import { InputPluginOption, OutputBundle, OutputOptions, Plugin, RollupOptions, rollup } from 'rollup';
10
+ import chalk from 'chalk';
11
+ import { InputPluginOption, OutputBundle, OutputOptions, Plugin, RollupOptions, rollup, watch as rollupWatch } from 'rollup';
11
12
  import { minify_sync } from 'terser';
12
13
  import scss from 'rollup-plugin-scss';
13
14
  import * as sass from 'sass';
@@ -16,8 +17,10 @@ import path from 'path';
16
17
  import { pathToFileURL } from 'url';
17
18
  import dotenv from 'dotenv';
18
19
  import injectProcessEnv from 'rollup-plugin-inject-process-env';
20
+ import { performance } from 'perf_hooks';
19
21
  import { ExecutePluginModule, InitializePlugins } from './plugin-api';
20
22
  import { Logger } from './logger';
23
+ import { PluginJson } from './plugin-json';
21
24
  import constSysfsExpr from './static-embed';
22
25
 
23
26
  const env = dotenv.config().parsed ?? {};
@@ -41,6 +44,8 @@ declare const __call_server_method__: (methodName: string, kwargs: any) => any;
41
44
  export interface TranspilerProps {
42
45
  minify: boolean;
43
46
  pluginName: string;
47
+ watch?: boolean;
48
+ isMillennium?: boolean;
44
49
  }
45
50
 
46
51
  enum BuildTarget {
@@ -129,6 +134,8 @@ function stripPluginPrefix(message: string): string {
129
134
  return message;
130
135
  }
131
136
 
137
+ class BuildFailedError extends Error {}
138
+
132
139
  abstract class MillenniumBuild {
133
140
  protected abstract readonly externals: ReadonlySet<string>;
134
141
  protected abstract readonly forbidden: ReadonlyMap<string, string>;
@@ -144,6 +151,25 @@ abstract class MillenniumBuild {
144
151
  return this.externals.has(id);
145
152
  }
146
153
 
154
+ async watchConfig(input: string, sysfsPlugin: InputPluginOption, isMillennium: boolean): Promise<RollupOptions> {
155
+ return {
156
+ input,
157
+ plugins: await this.plugins(sysfsPlugin),
158
+ onwarn: (warning) => {
159
+ const msg = stripPluginPrefix(warning.message);
160
+ const loc = logLocation(warning);
161
+ if (warning.plugin === 'typescript') {
162
+ Logger.error(msg, loc);
163
+ } else {
164
+ Logger.warn(msg, loc);
165
+ }
166
+ },
167
+ context: 'window',
168
+ external: (id) => this.isExternal(id),
169
+ output: this.output(isMillennium),
170
+ };
171
+ }
172
+
147
173
  async build(input: string, sysfsPlugin: InputPluginOption, isMillennium: boolean): Promise<void> {
148
174
  let hasErrors = false;
149
175
 
@@ -167,7 +193,7 @@ abstract class MillenniumBuild {
167
193
 
168
194
  await (await rollup(config)).write(config.output as OutputOptions);
169
195
 
170
- if (hasErrors) process.exit(1);
196
+ if (hasErrors) throw new BuildFailedError();
171
197
  }
172
198
  }
173
199
 
@@ -273,10 +299,50 @@ class WebkitBuild extends MillenniumBuild {
273
299
  }
274
300
  }
275
301
 
276
- export const TranspilerPluginComponent = async (isMillennium: boolean, pluginJson: any, props: TranspilerProps) => {
302
+ function RunWatchMode(frontendConfig: RollupOptions, webkitConfig: RollupOptions | null): void {
303
+ const configs = webkitConfig ? [frontendConfig, webkitConfig] : [frontendConfig];
304
+ const watcher = rollupWatch(configs);
305
+
306
+ console.log(chalk.blueBright.bold('watch'), 'watching for file changes...');
307
+
308
+ watcher.on('event', async (event) => {
309
+ if (event.code === 'BUNDLE_START') {
310
+ const label = (event.output as readonly string[]).some((f) => f.includes('index.js')) ? 'frontend' : 'webkit';
311
+ console.log(chalk.yellowBright.bold('watch'), `rebuilding ${label}...`);
312
+ } else if (event.code === 'BUNDLE_END') {
313
+ const label = (event.output as readonly string[]).some((f) => f.includes('index.js')) ? 'frontend' : 'webkit';
314
+ console.log(chalk.greenBright.bold('watch'), `${label} built in ${chalk.green(`${event.duration}ms`)}`);
315
+ await event.result.close();
316
+ } else if (event.code === 'ERROR') {
317
+ const err = event.error;
318
+ const msg = stripPluginPrefix(err?.message ?? String(err));
319
+ Logger.error(msg, logLocation(err as any));
320
+ if (event.result) await event.result.close();
321
+ }
322
+ });
323
+
324
+ const shutdown = () => {
325
+ console.log(chalk.yellowBright.bold('watch'), 'stopping...');
326
+ watcher.close();
327
+ process.exit(0);
328
+ };
329
+
330
+ process.on('SIGINT', shutdown);
331
+ process.on('SIGUSR2', shutdown);
332
+ }
333
+
334
+ export const TranspilerPluginComponent = async (pluginJson: PluginJson, props: TranspilerProps) => {
277
335
  const webkitDir = './webkit/index.tsx';
278
336
  const frontendDir = getFrontendDir(pluginJson);
279
337
  const sysfs = constSysfsExpr();
338
+ const isMillennium = props.isMillennium ?? false;
339
+
340
+ if (props.watch) {
341
+ const frontendConfig = await new FrontendBuild(frontendDir, props).watchConfig(resolveEntryFile(frontendDir), sysfs.plugin, isMillennium);
342
+ const webkitConfig = fs.existsSync(webkitDir) ? await new WebkitBuild(props).watchConfig(webkitDir, sysfs.plugin, isMillennium) : null;
343
+ RunWatchMode(frontendConfig, webkitConfig);
344
+ return;
345
+ }
280
346
 
281
347
  try {
282
348
  await new FrontendBuild(frontendDir, props).build(resolveEntryFile(frontendDir), sysfs.plugin, isMillennium);
@@ -292,7 +358,10 @@ export const TranspilerPluginComponent = async (isMillennium: boolean, pluginJso
292
358
  envCount: Object.keys(env).length || undefined,
293
359
  });
294
360
  } catch (exception: any) {
295
- Logger.error(stripPluginPrefix(exception?.message ?? String(exception)), logLocation(exception));
361
+ if (!(exception instanceof BuildFailedError)) {
362
+ Logger.error(stripPluginPrefix(exception?.message ?? String(exception)), logLocation(exception));
363
+ }
364
+ Logger.failed({ elapsedMs: performance.now() - global.PerfStartTime, buildType: props.minify ? 'prod' : 'dev' });
296
365
  process.exit(1);
297
366
  }
298
367
  };