@steambrew/ttc 3.2.2 → 3.2.4

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/bun.lock CHANGED
@@ -32,6 +32,7 @@
32
32
  "sass": "^1.89.1",
33
33
  "terser": "^5.43.1",
34
34
  "tslib": "^2.8.1",
35
+ "typescript": ">=5.0.0",
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/babel__traverse": "^7.20.7",
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import replace from '@rollup/plugin-replace';
12
12
  import terser from '@rollup/plugin-terser';
13
13
  import typescript from '@rollup/plugin-typescript';
14
14
  import esbuild from 'rollup-plugin-esbuild';
15
+ import ts from 'typescript';
15
16
  import url from '@rollup/plugin-url';
16
17
  import nodePolyfills from 'rollup-plugin-polyfill-node';
17
18
  import { watch, rollup } from 'rollup';
@@ -51,14 +52,14 @@ const Logger = {
51
52
  },
52
53
  done({ elapsedMs, buildType, sysfsCount, envCount }) {
53
54
  const elapsed = `${(elapsedMs / 1000).toFixed(2)}s`;
54
- const meta = [`ttc v${version}`];
55
+ const meta = [`ttc v${version} (${"fd773865"})`];
55
56
  if (buildType === 'dev')
56
57
  meta.push('no type checking');
57
58
  if (sysfsCount)
58
- meta.push(`${sysfsCount} constSysfsExpr`);
59
+ meta.push(`${sysfsCount} bundled file${sysfsCount > 1 ? 's' : ''}`);
59
60
  if (envCount)
60
61
  meta.push(`${envCount} env var${envCount > 1 ? 's' : ''}`);
61
- console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim('(' + meta.join(', ') + ')'));
62
+ console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim(meta.join(', ')));
62
63
  },
63
64
  failed({ elapsedMs, buildType }) {
64
65
  const elapsed = `${(elapsedMs / 1000).toFixed(2)}s`;
@@ -267,6 +268,7 @@ function constSysfsExpr(options = {}) {
267
268
  const filter = createFilter(options.include, options.exclude);
268
269
  const pluginName = 'millennium-const-sysfs-expr';
269
270
  let count = 0;
271
+ const globCache = new Map();
270
272
  const plugin = {
271
273
  name: pluginName,
272
274
  transform(code, id) {
@@ -310,8 +312,6 @@ function constSysfsExpr(options = {}) {
310
312
  }
311
313
  }
312
314
  },
313
- });
314
- traverse(ast, {
315
315
  CallExpression: (nodePath) => {
316
316
  const node = nodePath.node;
317
317
  if (node.callee.type === 'Identifier' && node.callee.name === 'constSysfsExpr') {
@@ -431,53 +431,60 @@ function constSysfsExpr(options = {}) {
431
431
  ? path.dirname(pathOrPattern)
432
432
  : path.resolve(path.dirname(id), path.dirname(pathOrPattern));
433
433
  let embeddedContent;
434
- const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
435
- if (!isPotentialPattern &&
436
- fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
437
- fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()) {
438
- const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
439
- try {
440
- const rawContent = fs.readFileSync(singleFilePath, callOptions.encoding);
441
- const contentString = rawContent.toString();
442
- const fileInfo = {
443
- content: contentString,
444
- filePath: singleFilePath,
445
- fileName: path.relative(searchBasePath, singleFilePath),
446
- };
447
- embeddedContent = JSON.stringify(fileInfo);
448
- this.addWatchFile(singleFilePath);
449
- }
450
- catch (fileError) {
451
- let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
452
- this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
453
- return;
454
- }
434
+ const cacheKey = `${searchBasePath}\0${pathOrPattern}\0${callOptions.encoding}`;
435
+ if (globCache.has(cacheKey)) {
436
+ embeddedContent = globCache.get(cacheKey);
455
437
  }
456
438
  else {
457
- const matchingFiles = glob.sync(pathOrPattern, {
458
- cwd: searchBasePath,
459
- nodir: true,
460
- absolute: true,
461
- });
462
- const fileInfoArray = [];
463
- for (const fullPath of matchingFiles) {
439
+ const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
440
+ if (!isPotentialPattern &&
441
+ fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
442
+ fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()) {
443
+ const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
464
444
  try {
465
- const rawContent = fs.readFileSync(fullPath, callOptions.encoding);
445
+ const rawContent = fs.readFileSync(singleFilePath, callOptions.encoding);
466
446
  const contentString = rawContent.toString();
467
- fileInfoArray.push({
447
+ const fileInfo = {
468
448
  content: contentString,
469
- filePath: fullPath,
470
- fileName: path.relative(searchBasePath, fullPath),
471
- });
472
- this.addWatchFile(fullPath);
449
+ filePath: singleFilePath,
450
+ fileName: path.relative(searchBasePath, singleFilePath),
451
+ };
452
+ embeddedContent = JSON.stringify(fileInfo);
453
+ this.addWatchFile(singleFilePath);
473
454
  }
474
455
  catch (fileError) {
475
456
  let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
476
- this.warn(`Error reading file ${fullPath}: ${message}`);
457
+ this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
458
+ return;
477
459
  }
478
460
  }
479
- embeddedContent = JSON.stringify(fileInfoArray);
480
- }
461
+ else {
462
+ const matchingFiles = glob.sync(pathOrPattern, {
463
+ cwd: searchBasePath,
464
+ nodir: true,
465
+ absolute: true,
466
+ });
467
+ const fileInfoArray = [];
468
+ for (const fullPath of matchingFiles) {
469
+ try {
470
+ const rawContent = fs.readFileSync(fullPath, callOptions.encoding);
471
+ const contentString = rawContent.toString();
472
+ fileInfoArray.push({
473
+ content: contentString,
474
+ filePath: fullPath,
475
+ fileName: path.relative(searchBasePath, fullPath),
476
+ });
477
+ this.addWatchFile(fullPath);
478
+ }
479
+ catch (fileError) {
480
+ let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
481
+ this.warn(`Error reading file ${fullPath}: ${message}`);
482
+ }
483
+ }
484
+ embeddedContent = JSON.stringify(fileInfoArray);
485
+ }
486
+ globCache.set(cacheKey, embeddedContent);
487
+ } // end cache miss
481
488
  // Replace the call expression with the generated content string
482
489
  magicString.overwrite(node.start, node.end, embeddedContent);
483
490
  hasReplaced = true;
@@ -605,99 +612,16 @@ function stripPluginPrefix(message) {
605
612
  }
606
613
  class BuildFailedError extends Error {
607
614
  }
608
- /**
609
- * tsconfig.json files use JSONC, not regular JSON.
610
- * JSONC supports comments and trailing commas, while JSON does not.
611
- * This function sanitizes JSONC into JSON.parse()-able content.
612
- *
613
- * @param text input text
614
- * @returns object
615
- */
616
- function parseJsonc(text) {
617
- let out = '';
618
- let i = 0;
619
- const n = text.length;
620
- while (i < n) {
621
- const ch = text[i];
622
- if (ch === '"') {
623
- out += ch;
624
- i++;
625
- while (i < n) {
626
- const c = text[i];
627
- out += c;
628
- if (c === '\\') {
629
- i++;
630
- if (i < n) {
631
- out += text[i];
632
- i++;
633
- }
634
- }
635
- else if (c === '"') {
636
- i++;
637
- break;
638
- }
639
- else
640
- i++;
641
- }
642
- }
643
- else if (ch === '/' && i + 1 < n && text[i + 1] === '/') {
644
- i += 2;
645
- while (i < n && text[i] !== '\n')
646
- i++;
647
- }
648
- else if (ch === '/' && i + 1 < n && text[i + 1] === '*') {
649
- i += 2;
650
- while (i < n - 1 && !(text[i] === '*' && text[i + 1] === '/'))
651
- i++;
652
- i += 2;
653
- }
654
- else if (ch === ',') {
655
- let j = i + 1;
656
- while (j < n && (text[j] === ' ' || text[j] === '\t' || text[j] === '\n' || text[j] === '\r'))
657
- j++;
658
- if (j < n && (text[j] === '}' || text[j] === ']')) {
659
- i++;
660
- }
661
- else {
662
- out += ch;
663
- i++;
664
- }
665
- }
666
- else {
667
- out += ch;
668
- i++;
669
- }
670
- }
671
- return JSON.parse(out);
672
- }
673
615
  function tsconfigPathsPlugin(tsconfigPath) {
674
- function readConfig(cfgPath) {
675
- const dir = path.dirname(path.resolve(cfgPath));
676
- let raw;
677
- try {
678
- raw = parseJsonc(fs.readFileSync(cfgPath, 'utf8'));
679
- }
680
- catch {
681
- return { baseUrl: null, entries: [] };
682
- }
683
- let parentResult = { baseUrl: null, entries: [] };
684
- if (raw.extends) {
685
- const ext = raw.extends;
686
- const parentPath = path.resolve(dir, ext.endsWith('.json') ? ext : `${ext}.json`);
687
- parentResult = readConfig(parentPath);
688
- }
689
- const opts = raw.compilerOptions ?? {};
690
- const baseUrl = opts.baseUrl ? path.resolve(dir, opts.baseUrl) : parentResult.baseUrl;
691
- const ownEntries = Object.entries(opts.paths ?? {}).map(([pattern, targets]) => ({
692
- pattern,
693
- targets: targets,
694
- configDir: dir,
695
- }));
696
- const ownPatterns = new Set(ownEntries.map((e) => e.pattern));
697
- const parentEntries = parentResult.entries.filter((e) => !ownPatterns.has(e.pattern));
698
- return { baseUrl, entries: [...ownEntries, ...parentEntries] };
699
- }
700
- const { baseUrl, entries } = readConfig(tsconfigPath);
616
+ const absPath = path.resolve(tsconfigPath);
617
+ const configDir = path.dirname(absPath);
618
+ const { config, error } = ts.readConfigFile(absPath, ts.sys.readFile);
619
+ if (error)
620
+ return { name: 'tsconfig-paths' };
621
+ const { options } = ts.parseJsonConfigFileContent(config, ts.sys, configDir);
622
+ const baseUrl = options.baseUrl ?? null;
623
+ const pathsBase = options.pathsBasePath ?? configDir;
624
+ const paths = options.paths ?? {};
701
625
  function resolveWithExtensions(base) {
702
626
  for (const ext of ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx']) {
703
627
  if (fs.existsSync(base + ext))
@@ -708,7 +632,7 @@ function tsconfigPathsPlugin(tsconfigPath) {
708
632
  return {
709
633
  name: 'tsconfig-paths',
710
634
  async resolveId(source, importer) {
711
- for (const { pattern, targets, configDir } of entries) {
635
+ for (const [pattern, targets] of Object.entries(paths)) {
712
636
  if (!targets.length)
713
637
  continue;
714
638
  const isWild = pattern.endsWith('/*');
@@ -716,7 +640,7 @@ function tsconfigPathsPlugin(tsconfigPath) {
716
640
  const prefix = pattern.slice(0, -2);
717
641
  if (source === prefix || source.startsWith(prefix + '/')) {
718
642
  const rest = source.startsWith(prefix + '/') ? source.slice(prefix.length + 1) : '';
719
- const targetBase = path.resolve(configDir, targets[0].replace('*', rest));
643
+ const targetBase = path.resolve(pathsBase, targets[0].replace('*', rest));
720
644
  const resolved = resolveWithExtensions(targetBase);
721
645
  if (resolved) {
722
646
  const result = await this.resolve(resolved, importer, { skipSelf: true });
@@ -726,7 +650,7 @@ function tsconfigPathsPlugin(tsconfigPath) {
726
650
  }
727
651
  }
728
652
  else if (source === pattern) {
729
- const targetBase = path.resolve(configDir, targets[0]);
653
+ const targetBase = path.resolve(pathsBase, targets[0]);
730
654
  const resolved = resolveWithExtensions(targetBase);
731
655
  if (resolved) {
732
656
  const result = await this.resolve(resolved, importer, { skipSelf: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steambrew/ttc",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -9,8 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "rollup -c",
12
- "dev": "rollup -c -w",
13
- "prepare": "bun run build"
12
+ "dev": "rollup -c -w"
14
13
  },
15
14
  "publishConfig": {
16
15
  "access": "public"
@@ -32,6 +31,7 @@
32
31
  "@rollup/plugin-replace": "^6.0.2",
33
32
  "@rollup/plugin-terser": "^0.4.4",
34
33
  "@rollup/plugin-typescript": "^12.1.2",
34
+ "typescript": ">=5.0.0",
35
35
  "@rollup/plugin-url": "^8.0.2",
36
36
  "@rollup/pluginutils": "^5.1.4",
37
37
  "chalk": "^5.4.1",
package/rollup.config.js CHANGED
@@ -2,17 +2,22 @@ import commonjs from '@rollup/plugin-commonjs';
2
2
  import typescript from '@rollup/plugin-typescript';
3
3
  import json from '@rollup/plugin-json';
4
4
  import nodeResolve from '@rollup/plugin-node-resolve';
5
+ import replace from '@rollup/plugin-replace';
5
6
  import { readFileSync } from 'fs';
7
+ import { execSync } from 'child_process';
6
8
 
7
9
  const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
8
10
 
11
+ let gitCommit = 'unknown';
12
+ try { gitCommit = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); } catch {}
13
+
9
14
  export default {
10
15
  input: 'src/index.ts',
11
16
  context: 'window',
12
17
  output: {
13
18
  file: 'dist/index.js',
14
19
  },
15
- plugins: [commonjs(), typescript(), json(), nodeResolve()],
20
+ plugins: [replace({ __GIT_COMMIT__: JSON.stringify(gitCommit), preventAssignment: true }), commonjs(), typescript(), json(), nodeResolve()],
16
21
  external: [
17
22
  ...Object.keys(pkg.dependencies || {}),
18
23
  ...Object.keys(pkg.peerDependencies || {}),
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import path from 'path';
2
32
  import { existsSync, readFile } from 'fs';
3
33
  import { Logger } from './logger';
package/src/index.ts CHANGED
@@ -1,4 +1,34 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin / env node
2
+
3
+ /**
4
+ * ==================================================
5
+ * _____ _ _ _ _
6
+ * | |_| | |___ ___ ___|_|_ _ _____
7
+ * | | | | | | | -_| | | | | | |
8
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
9
+ *
10
+ * ==================================================
11
+ *
12
+ * Copyright (c) 2026 Project Millennium
13
+ *
14
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ * of this software and associated documentation files (the "Software"), to deal
16
+ * in the Software without restriction, including without limitation the rights
17
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ * copies of the Software, and to permit persons to whom the Software is
19
+ * furnished to do so, subject to the following conditions:
20
+ *
21
+ * The above copyright notice and this permission notice shall be included in all
22
+ * copies or substantial portions of the Software.
23
+ *
24
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ * SOFTWARE.
31
+ */
2
32
 
3
33
  /**
4
34
  * this component serves as:
package/src/logger.ts CHANGED
@@ -1,11 +1,41 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import chalk from 'chalk';
2
32
  import { readFileSync } from 'fs';
3
33
  import path, { dirname } from 'path';
4
34
  import { fileURLToPath } from 'url';
5
35
 
6
- const version: string = JSON.parse(
7
- readFileSync(path.resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8'),
8
- ).version;
36
+ declare const __GIT_COMMIT__: string;
37
+
38
+ const version: string = JSON.parse(readFileSync(path.resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')).version;
9
39
 
10
40
  interface DoneOptions {
11
41
  elapsedMs: number;
@@ -38,11 +68,11 @@ const Logger = {
38
68
 
39
69
  done({ elapsedMs, buildType, sysfsCount, envCount }: DoneOptions) {
40
70
  const elapsed = `${(elapsedMs / 1000).toFixed(2)}s`;
41
- const meta: string[] = [`ttc v${version}`];
71
+ const meta: string[] = [`ttc v${version} (${__GIT_COMMIT__})`];
42
72
  if (buildType === 'dev') meta.push('no type checking');
43
- if (sysfsCount) meta.push(`${sysfsCount} constSysfsExpr`);
73
+ if (sysfsCount) meta.push(`${sysfsCount} bundled file${sysfsCount > 1 ? 's' : ''}`);
44
74
  if (envCount) meta.push(`${envCount} env var${envCount > 1 ? 's' : ''}`);
45
- console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim('(' + meta.join(', ') + ')'));
75
+ console.log(`${chalk.green('Finished')} ${buildType} in ${elapsed} ` + chalk.dim(meta.join(', ')));
46
76
  },
47
77
 
48
78
  failed({ elapsedMs, buildType }: Pick<DoneOptions, 'elapsedMs' | 'buildType'>) {
package/src/plugin-api.ts CHANGED
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  declare global {
2
32
  interface Window {
3
33
  /**
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  /**
2
32
  * generated from https://raw.githubusercontent.com/SteamClientHomebrew/Millennium/main/src/sys/plugin-schema.json
3
33
  */
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import { Logger } from './logger';
2
32
 
3
33
  export const PrintParamHelp = () => {
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import * as parser from '@babel/parser';
2
32
  import { createFilter } from '@rollup/pluginutils';
3
33
  import fs from 'fs';
@@ -47,6 +77,7 @@ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsP
47
77
  const filter = createFilter(options.include, options.exclude);
48
78
  const pluginName = 'millennium-const-sysfs-expr';
49
79
  let count = 0;
80
+ const globCache = new Map<string, string>();
50
81
 
51
82
  const plugin: Plugin = {
52
83
  name: pluginName,
@@ -97,9 +128,6 @@ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsP
97
128
  }
98
129
  }
99
130
  },
100
- });
101
-
102
- traverse(ast, {
103
131
  CallExpression: (nodePath) => {
104
132
  const node = nodePath.node;
105
133
  if (node.callee.type === 'Identifier' && node.callee.name === 'constSysfsExpr') {
@@ -220,55 +248,61 @@ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsP
220
248
 
221
249
  let embeddedContent: string;
222
250
 
223
- const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
224
-
225
- if (
226
- !isPotentialPattern &&
227
- fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
228
- fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()
229
- ) {
230
- const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
231
-
232
- try {
233
- const rawContent: string | Buffer = fs.readFileSync(singleFilePath, callOptions.encoding);
234
- const contentString = rawContent.toString();
235
- const fileInfo: FileInfo = {
236
- content: contentString,
237
- filePath: singleFilePath,
238
- fileName: path.relative(searchBasePath, singleFilePath),
239
- };
240
- embeddedContent = JSON.stringify(fileInfo);
241
- this.addWatchFile(singleFilePath);
242
- } catch (fileError: unknown) {
243
- let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
244
- this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
245
- return;
246
- }
251
+ const cacheKey = `${searchBasePath}\0${pathOrPattern}\0${callOptions.encoding}`;
252
+ if (globCache.has(cacheKey)) {
253
+ embeddedContent = globCache.get(cacheKey)!;
247
254
  } else {
248
- const matchingFiles = glob.sync(pathOrPattern, {
249
- cwd: searchBasePath,
250
- nodir: true,
251
- absolute: true,
252
- });
253
-
254
- const fileInfoArray: FileInfo[] = [];
255
- for (const fullPath of matchingFiles) {
255
+ const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
256
+
257
+ if (
258
+ !isPotentialPattern &&
259
+ fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
260
+ fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()
261
+ ) {
262
+ const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
263
+
256
264
  try {
257
- const rawContent: string | Buffer = fs.readFileSync(fullPath, callOptions.encoding);
265
+ const rawContent: string | Buffer = fs.readFileSync(singleFilePath, callOptions.encoding);
258
266
  const contentString = rawContent.toString();
259
- fileInfoArray.push({
267
+ const fileInfo: FileInfo = {
260
268
  content: contentString,
261
- filePath: fullPath,
262
- fileName: path.relative(searchBasePath, fullPath),
263
- });
264
- this.addWatchFile(fullPath);
269
+ filePath: singleFilePath,
270
+ fileName: path.relative(searchBasePath, singleFilePath),
271
+ };
272
+ embeddedContent = JSON.stringify(fileInfo);
273
+ this.addWatchFile(singleFilePath);
265
274
  } catch (fileError: unknown) {
266
275
  let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
267
- this.warn(`Error reading file ${fullPath}: ${message}`);
276
+ this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
277
+ return;
268
278
  }
279
+ } else {
280
+ const matchingFiles = glob.sync(pathOrPattern, {
281
+ cwd: searchBasePath,
282
+ nodir: true,
283
+ absolute: true,
284
+ });
285
+
286
+ const fileInfoArray: FileInfo[] = [];
287
+ for (const fullPath of matchingFiles) {
288
+ try {
289
+ const rawContent: string | Buffer = fs.readFileSync(fullPath, callOptions.encoding);
290
+ const contentString = rawContent.toString();
291
+ fileInfoArray.push({
292
+ content: contentString,
293
+ filePath: fullPath,
294
+ fileName: path.relative(searchBasePath, fullPath),
295
+ });
296
+ this.addWatchFile(fullPath);
297
+ } catch (fileError: unknown) {
298
+ let message = String(fileError instanceof Error ? fileError.message : (fileError ?? 'Unknown file read error'));
299
+ this.warn(`Error reading file ${fullPath}: ${message}`);
300
+ }
301
+ }
302
+ embeddedContent = JSON.stringify(fileInfoArray);
269
303
  }
270
- embeddedContent = JSON.stringify(fileInfoArray);
271
- }
304
+ globCache.set(cacheKey, embeddedContent);
305
+ } // end cache miss
272
306
 
273
307
  // Replace the call expression with the generated content string
274
308
  magicString.overwrite(node.start, node.end, embeddedContent);
package/src/transpiler.ts CHANGED
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import babel from '@rollup/plugin-babel';
2
32
  import commonjs from '@rollup/plugin-commonjs';
3
33
  import json from '@rollup/plugin-json';
@@ -6,6 +36,7 @@ import replace from '@rollup/plugin-replace';
6
36
  import terser from '@rollup/plugin-terser';
7
37
  import typescript from '@rollup/plugin-typescript';
8
38
  import esbuild from 'rollup-plugin-esbuild';
39
+ import ts from 'typescript';
9
40
  import url from '@rollup/plugin-url';
10
41
  import nodePolyfills from 'rollup-plugin-polyfill-node';
11
42
  import chalk from 'chalk';
@@ -137,104 +168,21 @@ function stripPluginPrefix(message: string): string {
137
168
 
138
169
  class BuildFailedError extends Error {}
139
170
 
140
- interface PathEntry {
141
- pattern: string;
142
- targets: string[];
143
- configDir: string;
144
- }
145
-
146
- /**
147
- * tsconfig.json files use JSONC, not regular JSON.
148
- * JSONC supports comments and trailing commas, while JSON does not.
149
- * This function sanitizes JSONC into JSON.parse()-able content.
150
- *
151
- * @param text input text
152
- * @returns object
153
- */
154
- function parseJsonc(text: string): any {
155
- let out = '';
156
- let i = 0;
157
- const n = text.length;
158
- while (i < n) {
159
- const ch = text[i];
160
- if (ch === '"') {
161
- out += ch;
162
- i++;
163
- while (i < n) {
164
- const c = text[i];
165
- out += c;
166
- if (c === '\\') {
167
- i++;
168
- if (i < n) {
169
- out += text[i];
170
- i++;
171
- }
172
- } else if (c === '"') {
173
- i++;
174
- break;
175
- } else i++;
176
- }
177
- } else if (ch === '/' && i + 1 < n && text[i + 1] === '/') {
178
- i += 2;
179
- while (i < n && text[i] !== '\n') i++;
180
- } else if (ch === '/' && i + 1 < n && text[i + 1] === '*') {
181
- i += 2;
182
- while (i < n - 1 && !(text[i] === '*' && text[i + 1] === '/')) i++;
183
- i += 2;
184
- } else if (ch === ',') {
185
- let j = i + 1;
186
- while (j < n && (text[j] === ' ' || text[j] === '\t' || text[j] === '\n' || text[j] === '\r')) j++;
187
- if (j < n && (text[j] === '}' || text[j] === ']')) {
188
- i++;
189
- } else {
190
- out += ch;
191
- i++;
192
- }
193
- } else {
194
- out += ch;
195
- i++;
196
- }
197
- }
198
- return JSON.parse(out);
199
- }
200
-
201
171
  function tsconfigPathsPlugin(tsconfigPath: string): InputPluginOption {
202
- function readConfig(cfgPath: string): { baseUrl: string | null; entries: PathEntry[] } {
203
- const dir = path.dirname(path.resolve(cfgPath));
204
- let raw: any;
205
- try {
206
- raw = parseJsonc(fs.readFileSync(cfgPath, 'utf8'));
207
- } catch {
208
- return { baseUrl: null, entries: [] };
209
- }
172
+ const absPath = path.resolve(tsconfigPath);
173
+ const configDir = path.dirname(absPath);
210
174
 
211
- let parentResult: { baseUrl: string | null; entries: PathEntry[] } = { baseUrl: null, entries: [] };
212
- if (raw.extends) {
213
- const ext = raw.extends as string;
214
- const parentPath = path.resolve(dir, ext.endsWith('.json') ? ext : `${ext}.json`);
215
- parentResult = readConfig(parentPath);
216
- }
217
-
218
- const opts = raw.compilerOptions ?? {};
219
- const baseUrl = opts.baseUrl ? path.resolve(dir, opts.baseUrl as string) : parentResult.baseUrl;
220
-
221
- const ownEntries: PathEntry[] = Object.entries(opts.paths ?? {}).map(([pattern, targets]) => ({
222
- pattern,
223
- targets: targets as string[],
224
- configDir: dir,
225
- }));
226
-
227
- const ownPatterns = new Set(ownEntries.map((e) => e.pattern));
228
- const parentEntries = parentResult.entries.filter((e) => !ownPatterns.has(e.pattern));
229
-
230
- return { baseUrl, entries: [...ownEntries, ...parentEntries] };
231
- }
175
+ const { config, error } = ts.readConfigFile(absPath, ts.sys.readFile);
176
+ if (error) return { name: 'tsconfig-paths' };
232
177
 
233
- const { baseUrl, entries } = readConfig(tsconfigPath);
178
+ const { options } = ts.parseJsonConfigFileContent(config, ts.sys, configDir);
179
+ const baseUrl = options.baseUrl ?? null;
180
+ const pathsBase = (options.pathsBasePath as string | undefined) ?? configDir;
181
+ const paths = options.paths ?? {};
234
182
 
235
183
  function resolveWithExtensions(base: string): string | null {
236
184
  for (const ext of ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx']) {
237
- if (fs.existsSync(base + ext)) return base + ext;
185
+ if (fs.existsSync(base + ext) && fs.statSync(base + ext).isFile()) return base + ext;
238
186
  }
239
187
  return null;
240
188
  }
@@ -242,14 +190,14 @@ function tsconfigPathsPlugin(tsconfigPath: string): InputPluginOption {
242
190
  return {
243
191
  name: 'tsconfig-paths',
244
192
  async resolveId(source: string, importer: string | undefined) {
245
- for (const { pattern, targets, configDir } of entries) {
193
+ for (const [pattern, targets] of Object.entries(paths)) {
246
194
  if (!targets.length) continue;
247
195
  const isWild = pattern.endsWith('/*');
248
196
  if (isWild) {
249
197
  const prefix = pattern.slice(0, -2);
250
198
  if (source === prefix || source.startsWith(prefix + '/')) {
251
199
  const rest = source.startsWith(prefix + '/') ? source.slice(prefix.length + 1) : '';
252
- const targetBase = path.resolve(configDir, targets[0].replace('*', rest));
200
+ const targetBase = path.resolve(pathsBase, targets[0].replace('*', rest));
253
201
  const resolved = resolveWithExtensions(targetBase);
254
202
  if (resolved) {
255
203
  const result = await this.resolve(resolved, importer, { skipSelf: true });
@@ -257,7 +205,7 @@ function tsconfigPathsPlugin(tsconfigPath: string): InputPluginOption {
257
205
  }
258
206
  }
259
207
  } else if (source === pattern) {
260
- const targetBase = path.resolve(configDir, targets[0]);
208
+ const targetBase = path.resolve(pathsBase, targets[0]);
261
209
  const resolved = resolveWithExtensions(targetBase);
262
210
  if (resolved) {
263
211
  const result = await this.resolve(resolved, importer, { skipSelf: true });
@@ -1,3 +1,33 @@
1
+ /**
2
+ * ==================================================
3
+ * _____ _ _ _ _
4
+ * | |_| | |___ ___ ___|_|_ _ _____
5
+ * | | | | | | | -_| | | | | | |
6
+ * |_|_|_|_|_|_|___|_|_|_|_|_|___|_|_|_|
7
+ *
8
+ * ==================================================
9
+ *
10
+ * Copyright (c) 2026 Project Millennium
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
1
31
  import path from 'path';
2
32
  import { fileURLToPath } from 'url';
3
33
  import { readFile, access } from 'fs/promises';
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm show:*)",
5
- "Bash(bun remove:*)",
6
- "Bash(bun run:*)",
7
- "Bash(node:*)"
8
- ]
9
- }
10
- }