@d1g1tal/tsbuild 1.8.10 → 1.9.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/CHANGELOG.md CHANGED
@@ -1,3 +1,98 @@
1
+ ## [1.9.1](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.9.0...v1.9.1) (2026-06-14)
2
+
3
+ ### Performance Improvements
4
+
5
+ * **loader:** improve loader caching key uniqueness and safety (319eb4f938458aa69ccdc71783a8116934769e6a)
6
+ - Add file inode and configuration status metrics to the transformation cache keys
7
+ - Migrate from async block writes to safe, synchronous filesystem writes
8
+ - Recover on-the-fly from zero-byte or corrupt cached module assets
9
+
10
+
11
+ ### Code Refactoring
12
+
13
+ * **tsbuild:** remove test-only flush methods and standardize process exit (bebbc91a1516dff7560fd373358496d170f15e60)
14
+ - Avoid direct process.exit calls and handle exit state through process.exitCode
15
+ - Remove the flushBackgroundCleanup project hook, refactoring test suites to poll for output removal instead
16
+ - Simplify dependency path resolution by referencing internal promises directly
17
+
18
+
19
+ ### Documentation
20
+
21
+ * refine project guidelines and update copilot instructions (3b7d6bed70d84968be88793897bfa5580931d098)
22
+ - Clean up githooks file permissions and mode headers
23
+ - Condense Copilot instructions into a unified, high-density reference snapshot
24
+ - Document target values and criteria for performance optimization checks
25
+
26
+
27
+ ### Miscellaneous Chores
28
+
29
+ * **bench:** remove platform-specific details and introduce non-dts benchmarks (5887e241de3d4c22967030815f3ff548ac445276)
30
+ - Remove platform-specific MAXRSS memory measurements to ensure portable benchmark execution
31
+ - Introduce comparative benchmarks testing tsbuild with decoration emitting disabled
32
+ - Improve child process crash diagnostics and print cleaner terminal result layouts
33
+
34
+
35
+ ### Build System
36
+
37
+ * **deps:** upgrade Node engine, package manager, and dependencies (a695f4e2e1a7af8b2036e8ed1f2c766cdcaeb21f)
38
+ - Raise Node.js requirement to >=22.6.0 and pnpm package manager to 11.6.0
39
+ - Upgrade esbuild production dependency to 0.28.1
40
+ - Update multiple devDependencies including ESLint, typescript-eslint, memory-fs, and Vitest
41
+ - Regenerate the lockfile to align with package version bumps
42
+
43
+ ## [1.9.0](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.8.10...v1.9.0) (2026-05-23)
44
+
45
+ ### Features
46
+
47
+ * **cache:** implement configuration fingerprinting for incremental builds (4bb8b9ec6415cd5a3bd15d9a5265725c3ff0242c)
48
+ - Introduces deterministic JSON fingerprinting covering platform, target, formats, and bundler options
49
+ - Replaces strict boolean minification caching with robust multi-variable configuration fingerprints
50
+ - Compares fresh configurations with on-disk persisted fingerprint states to flush outputs dynamically
51
+ - Enhances cache manager implementations allowing seamless integration with esbuild pipelines
52
+ - Overrides unit tests validating caching scenarios evaluating string-based hashes correctly
53
+
54
+
55
+ ### Performance Improvements
56
+
57
+ * **core:** optimize iteration loops during data serialization (08cd6d3f77af3b58072dab008bee7a3217cc1d56)
58
+ - Restructures array destructuring and explicit index loops addressing slow map operations
59
+ - Avoids `Object.entries()` array memory spikes during physical build loop mapping
60
+ - Optimizes the declaration exports loop preventing excessive intermediary filtering blocks
61
+ - Reduces overall execution times slightly during intensive build output parsing
62
+
63
+
64
+ ### Documentation
65
+
66
+ * **readme:** update build documentation with cache and loader details (764514b987812743ffd11153e678e6723f8e09e4)
67
+ - Details the configuration change detection mechanism that avoids stale caches when the config differs
68
+ - Adds a dedicated section for the self-hosting loader to help clarify bootstrapping logic
69
+
70
+
71
+ ### Styles
72
+
73
+ * **docs:** prune redundant jsdoc comments across codebase (3b106dade1b5bc7dc713ef37463a984ca40f02b4)
74
+ - Removes repetitive property comments from standard interfaces like typescript configurations
75
+ - Strips excess constructor `@param` tags throughout standard object models
76
+ - Erases leftover `@returns` definitions across standard helper functions
77
+ - Fixes minor spacing issues affecting multiple comment block fragments
78
+
79
+
80
+ ### Miscellaneous Chores
81
+
82
+ * **eslint:** disable excessive jsdoc property requirements (11f4d21f0515841cdd2c5b1e112db67f42cbaf02)
83
+ - Reduces the strictness of JSDoc returns and param description rules
84
+ - Subdues linting checks on empty constructors and standard types to cut down developer fatigue
85
+
86
+
87
+ ### Tests
88
+
89
+ * **project:** refactor integration suites adopting build method structures (749970133479e51bc8d9a68c591ac926d394170e)
90
+ - Rewrites older validation strategies bypassing internal private methods completely
91
+ - Tests public `project.build()` execution flows simulating realistic cli invocations directly
92
+ - Handles unexpected esbuild crash reporting verifications robustly
93
+ - Adjusts browser platform resolving logic testing ensuring appropriate outputs trigger successfully
94
+ - Drops deprecated testing suites previously locked behind internal mock validations
95
+
1
96
  ## [1.8.10](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.8.9...v1.8.10) (2026-05-23)
2
97
 
3
98
  ### Bug Fixes
package/README.md CHANGED
@@ -301,6 +301,8 @@ With incremental enabled, each build maintains two caches inside a `.tsbuild/` d
301
301
 
302
302
  On each build, TypeScript reads `.tsbuildinfo` to determine what changed and only re-emits those files. Changed `.d.ts` files overwrite their entries in the DTS cache; unchanged entries remain valid. If nothing changed, TypeScript skips emission entirely and the output phase is skipped too — this is why incremental rebuilds with no changes take ~5ms.
303
303
 
304
+ **Config change detection** — tsbuild stores a fingerprint of the current build configuration (minify, platform, source maps, iife, splitting, banner/footer, env, noExternal, dts options, etc.) alongside the DTS cache. If any of these settings change between builds, the output phase is forced even when TypeScript reports no source changes. The fingerprint is updated at the end of each build so subsequent runs do not repeatedly trigger a forced rebuild.
305
+
304
306
  ### Ignoring the cache directory
305
307
 
306
308
  The `.tsbuild/` directory contains build artifacts that should not be committed to source control. Add it to your `.gitignore`:
@@ -616,6 +618,10 @@ pnpm test:coverage
616
618
  pnpm lint
617
619
  ```
618
620
 
621
+ ### Self-hosting loader
622
+
623
+ tsbuild bootstraps itself using a minimal custom Node.js module hook (`scripts/loader.ts`). It registers resolve and load hooks via `node:module`'s `registerHooks` API and uses esbuild's `transformSync` to compile TypeScript on-the-fly. Transformed files are cached in `node_modules/.cache/tsbuild-loader`, keyed by file path hash, mtime, byte size, and the running Node.js + esbuild versions, so warm starts pay only a `readFileSync` per file. The cache directory is intentionally kept out of `.tsbuild/` so that `--clearCache` does not blow it away.
624
+
619
625
  ## Contributing
620
626
 
621
627
  Contributions and feedback are welcome. This is a personal project, so response times may vary, but issues and pull requests will be reviewed.
@@ -3,7 +3,7 @@ import {
3
3
  Json,
4
4
  Paths,
5
5
  typeScriptExtensionExpression
6
- } from "./QL4XMDMJ.js";
6
+ } from "./XTZ3MYA7.js";
7
7
 
8
8
  // src/plugins/decorator-metadata.ts
9
9
  import { dirname } from "node:path";
@@ -29,7 +29,7 @@ import {
29
29
  toEsTarget,
30
30
  toJsxRenderingMode,
31
31
  typeMatcher
32
- } from "./QL4XMDMJ.js";
32
+ } from "./XTZ3MYA7.js";
33
33
 
34
34
  // src/files.ts
35
35
  import { dirname, join } from "node:path";
@@ -43,7 +43,6 @@ var Files = class {
43
43
  /**
44
44
  * Check if a file exists.
45
45
  * @param filePath The path to the file.
46
- * @returns True if the file exists, false otherwise.
47
46
  */
48
47
  static async exists(filePath) {
49
48
  try {
@@ -85,7 +84,7 @@ var Files = class {
85
84
  }
86
85
  /**
87
86
  * Write data to a file.
88
- * Ensures the directory exists before writing
87
+ * Ensures the directory exists before writing.
89
88
  * @param filePath The path to the file.
90
89
  * @param data The data to write to the file.
91
90
  * @param options Optional write file options.
@@ -428,11 +427,6 @@ import { basename, posix } from "node:path";
428
427
  // src/errors.ts
429
428
  import { SyntaxKind } from "typescript";
430
429
  var BuildError = class extends Error {
431
- /**
432
- * Creates a new BuildError
433
- * @param message - Error message
434
- * @param code - Exit code (default: 1)
435
- */
436
430
  constructor(message, code = 1) {
437
431
  super(message);
438
432
  this.code = code;
@@ -442,11 +436,6 @@ var BuildError = class extends Error {
442
436
  code;
443
437
  };
444
438
  var TypeCheckError = class extends BuildError {
445
- /**
446
- * Creates a new TypeCheckError
447
- * @param message - Error message
448
- * @param diagnostics - Optional TypeScript diagnostics output
449
- */
450
439
  constructor(message, diagnostics) {
451
440
  super(message, 1);
452
441
  this.diagnostics = diagnostics;
@@ -455,31 +444,18 @@ var TypeCheckError = class extends BuildError {
455
444
  diagnostics;
456
445
  };
457
446
  var BundleError = class extends BuildError {
458
- /**
459
- * Creates a new BundleError
460
- * @param message - Error message
461
- */
462
447
  constructor(message) {
463
448
  super(message, 2);
464
449
  this.name = "BundleError";
465
450
  }
466
451
  };
467
452
  var ConfigurationError = class extends BuildError {
468
- /**
469
- * Creates a new ConfigurationError
470
- * @param message - Error message
471
- */
472
453
  constructor(message) {
473
454
  super(message, 3);
474
455
  this.name = "ConfigurationError";
475
456
  }
476
457
  };
477
458
  var UnsupportedSyntaxError = class extends BundleError {
478
- /**
479
- * Creates an instance of UnsupportedSyntaxError.
480
- * @param node The node with unsupported syntax
481
- * @param message The message to display (default: 'Syntax not yet supported')
482
- */
483
459
  constructor(node, message = "Syntax not yet supported") {
484
460
  const syntaxKindName = SyntaxKind[node.kind] ?? `Unknown(${node.kind})`;
485
461
  const nodeText = node.getText ? node.getText().slice(0, 100) : "<no text>";
@@ -1300,9 +1276,18 @@ var DeclarationBundler = class _DeclarationBundler {
1300
1276
  }
1301
1277
  }
1302
1278
  }
1303
- const finalValueExportsSet = new Set(valueExports.map(exportsMapper));
1304
- const finalTypeExports = [...new Set(typeExports.map(exportsMapper).filter((type2) => !finalValueExportsSet.has(type2)))];
1305
- return { code: magic.toString(), externalImports, typeExports: finalTypeExports, valueExports: [...finalValueExportsSet] };
1279
+ const finalValueExportsSet = /* @__PURE__ */ new Set();
1280
+ for (const name of valueExports) {
1281
+ finalValueExportsSet.add(exportsMapper(name));
1282
+ }
1283
+ const finalTypeExports = [];
1284
+ for (const type2 of typeExports) {
1285
+ const mapped = exportsMapper(type2);
1286
+ if (!finalValueExportsSet.has(mapped)) {
1287
+ finalTypeExports.push(mapped);
1288
+ }
1289
+ }
1290
+ return { code: magic.toString(), externalImports, typeExports: finalTypeExports, valueExports: Array.from(finalValueExportsSet) };
1306
1291
  }
1307
1292
  /**
1308
1293
  * Combine modules into a single output string
@@ -1516,8 +1501,8 @@ var outputPlugin = () => {
1516
1501
  return {
1517
1502
  name: "esbuild:output-plugin",
1518
1503
  /**
1519
- * Checks JS entry points for shebangs and sets executable permissions
1520
- * @param build The esbuild build instance
1504
+ * Checks JS entry points for shebangs and sets executable permissions.
1505
+ * @param build The esbuild plugin build object.
1521
1506
  */
1522
1507
  setup(build) {
1523
1508
  build.onEnd(async ({ metafile }) => {
@@ -1666,8 +1651,8 @@ function iifePlugin(options) {
1666
1651
  plugin: {
1667
1652
  name: "esbuild:iife",
1668
1653
  /**
1669
- * Configures the esbuild build instance to produce IIFE output
1670
- * @param build The esbuild build instance
1654
+ * Configures the esbuild build instance to produce IIFE output.
1655
+ * @param build The esbuild plugin build object.
1671
1656
  */
1672
1657
  setup(build) {
1673
1658
  const outdir = build.initialOptions.outdir;
@@ -1882,7 +1867,7 @@ var ProcessManager = class {
1882
1867
  }
1883
1868
  this.close();
1884
1869
  };
1885
- /** Handles console exit (ctrl+c) */
1870
+ /** Handles SIGINT (ctrl+c) */
1886
1871
  consoleExit = () => {
1887
1872
  Logger.warn("\nProcess terminated by user");
1888
1873
  this.hasHandledExit = true;
@@ -1906,10 +1891,6 @@ var processManager = new ProcessManager();
1906
1891
  // src/decorators/close-on-exit.ts
1907
1892
  function closeOnExit(value, _context) {
1908
1893
  return class extends value {
1909
- /**
1910
- * Creates an instance and registers it with the process manager.
1911
- * @param args Arguments to pass to the original constructor.
1912
- */
1913
1894
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1914
1895
  constructor(...args) {
1915
1896
  super(...args);
@@ -1954,9 +1935,9 @@ var _PerformanceLogger = class _PerformanceLogger {
1954
1935
  }
1955
1936
  /**
1956
1937
  * Measures the performance of a method and logs the result.
1957
- * @param message - The message to log with the performance measurement.
1958
- * @param logResult - Whether to log the result of the method.
1959
- * @returns A Stage 3 method decorator that measures the performance of the method it decorates.
1938
+ * @param message - The message to log with the performance measurement
1939
+ * @param logResult - Whether to include the method return value in the log output
1940
+ * @returns A Stage 3 method decorator that measures execution time of the decorated method
1960
1941
  */
1961
1942
  measure(message, logResult = false) {
1962
1943
  const _measure = (propertyKey, result, options) => {
@@ -2146,16 +2127,19 @@ var FileManager = class {
2146
2127
  * Persists the .tsbuildinfo file and the dts cache to disk in the background. Call this
2147
2128
  * AFTER the build's parallel phases (transpile + dts bundling) have completed so the writes
2148
2129
  * (and Brotli compression for the dts cache) don't compete with esbuild for libuv threadpool slots.
2149
- * @param minify Whether the current build is minified, for future cache compatibility checks
2130
+ * @param fingerprint Build configuration fingerprint for cache invalidation on config change
2131
+ * @param configChanged True when the build configuration fingerprint changed — forces the new
2132
+ * fingerprint to be saved even when TypeScript did not re-emit any files, preventing the
2133
+ * mismatch from triggering unnecessary forced rebuilds on every subsequent build.
2150
2134
  */
2151
- persistCache(minify) {
2135
+ persistCache(fingerprint, configChanged = false) {
2152
2136
  const tasks = [];
2153
2137
  if (this.pendingBuildInfo) {
2154
2138
  tasks.push(Files.write(this.pendingBuildInfo.path, this.pendingBuildInfo.text));
2155
2139
  this.pendingBuildInfo = void 0;
2156
2140
  }
2157
- if (this.cache !== void 0 && this.hasEmittedFiles) {
2158
- tasks.push(this.cache.save(this.declarationFiles, minify));
2141
+ if (this.cache !== void 0 && (this.hasEmittedFiles || configChanged)) {
2142
+ tasks.push(this.cache.save(this.declarationFiles, fingerprint));
2159
2143
  }
2160
2144
  if (tasks.length === 0) {
2161
2145
  return;
@@ -2311,12 +2295,10 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2311
2295
  * subsequent in-process reads are race-free.
2312
2296
  */
2313
2297
  outputsSnapshot;
2314
- /** Snapshot of whether previous successful transpile output used minification. */
2315
- minifySnapshot;
2316
2298
  /** Set to true when invalidate() is called to prevent stale cache from being restored */
2317
2299
  invalidated = false;
2318
- /** Tracks the most recently saved declaration files so saveMinifyState() doesn't revert them */
2319
- latestFiles;
2300
+ /** Updated synchronously in save() so fingerprintMatches() sees fresh data without re-reading from disk. */
2301
+ savedFingerprint;
2320
2302
  /**
2321
2303
  * Creates a new build cache instance and begins pre-loading the cache asynchronously.
2322
2304
  * @param projectRoot - Root directory of the project
@@ -2329,7 +2311,6 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2329
2311
  this.outputsManifestPath = Paths.join(this.cacheDirectoryPath, outputManifestFile);
2330
2312
  this.cacheLoaded = this.loadCache();
2331
2313
  this.outputsSnapshot = _IncrementalBuildCache.loadOutputsSync(this.outputsManifestPath);
2332
- this.minifySnapshot = void 0;
2333
2314
  }
2334
2315
  /**
2335
2316
  * Loads the cache file asynchronously using V8 deserialization.
@@ -2341,7 +2322,6 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2341
2322
  if (cache.version !== dtsCacheVersion) {
2342
2323
  return void 0;
2343
2324
  }
2344
- this.minifySnapshot = cache.minify;
2345
2325
  return cache;
2346
2326
  } catch {
2347
2327
  return void 0;
@@ -2367,16 +2347,33 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2367
2347
  }
2368
2348
  }
2369
2349
  /**
2370
- * Saves declaration files to the compressed cache file with version information.
2350
+ * Saves declaration files to the compressed cache file with version and fingerprint information.
2371
2351
  * Uses V8 serialization for faster read performance on subsequent builds.
2372
2352
  * @param source - The declaration files to cache
2373
- * @param minify - Whether the current build is minified, for future compatibility checks
2374
- * @remarks This should be called after the build completes successfully, so the cache always reflects a valid state on disk. If the build fails, the in-memory cache is still updated to reflect the latest state, but the on-disk cache remains unchanged to preserve compatibility with future builds. The next successful build will overwrite the cache with the correct state.
2353
+ * @param fingerprint - Deterministic hash of build configuration for cache invalidation on config change
2375
2354
  */
2376
- async save(source, minify) {
2377
- this.latestFiles = source;
2378
- this.minifySnapshot = minify;
2379
- await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files: source, minify });
2355
+ async save(source, fingerprint) {
2356
+ this.savedFingerprint = fingerprint;
2357
+ await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files: source, fingerprint });
2358
+ }
2359
+ /**
2360
+ * Checks whether the build configuration has changed since the cache was last saved.
2361
+ * Returns true if the cache is valid and fingerprints match, false if config changed or cache is invalid.
2362
+ * @param currentFingerprint - The fingerprint of the current build configuration
2363
+ * @returns True if the cached configuration matches the current configuration
2364
+ */
2365
+ async fingerprintMatches(currentFingerprint) {
2366
+ if (this.invalidated) {
2367
+ return false;
2368
+ }
2369
+ if (!this.hasPersistedState()) {
2370
+ return false;
2371
+ }
2372
+ if (this.savedFingerprint !== void 0) {
2373
+ return this.savedFingerprint === currentFingerprint;
2374
+ }
2375
+ const cache = await this.cacheLoaded;
2376
+ return cache?.fingerprint === currentFingerprint;
2380
2377
  }
2381
2378
  /**
2382
2379
  * Loads the previous build's output manifest synchronously.
@@ -2411,29 +2408,11 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2411
2408
  await writeFile4(this.outputsManifestPath, JSON.stringify(this.outputsSnapshot), "utf8");
2412
2409
  }
2413
2410
  /**
2414
- * Checks whether the current minify mode requires forcing a rebuild.
2415
- * Forces when the minify setting differs from the previously persisted state in either direction.
2416
- * Unknown previous state is treated as not-minified (safe default for pre-minify-awareness builds).
2417
- * @param minify - Current build minify mode
2418
- * @returns True when a full rebuild should be forced.
2419
- */
2420
- async requiresRebuild(minify) {
2421
- if (!this.hasPersistedState()) {
2422
- return false;
2423
- }
2424
- return minify !== (this.minifySnapshot ?? (await this.cacheLoaded)?.minify ?? false);
2425
- }
2426
- /**
2427
- * Persists minify mode metadata for future incremental-build compatibility checks.
2428
- * @param minify - Current build minify mode
2411
+ * Checks if the cache is valid (not invalidated).
2412
+ * @returns True if the cache is valid, false if it has been invalidated
2429
2413
  */
2430
- async saveMinifyState(minify) {
2431
- this.minifySnapshot = minify;
2432
- if (this.latestFiles !== void 0) {
2433
- return;
2434
- }
2435
- const files = this.invalidated ? /* @__PURE__ */ new Map() : (await this.cacheLoaded)?.files ?? /* @__PURE__ */ new Map();
2436
- await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files, minify });
2414
+ isValid() {
2415
+ return !this.invalidated;
2437
2416
  }
2438
2417
  /** Invalidates the build cache by removing the cache directory. */
2439
2418
  invalidate() {
@@ -2451,13 +2430,6 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2451
2430
  isBuildInfoFile(filePath) {
2452
2431
  return filePath === this.buildInfoPath;
2453
2432
  }
2454
- /**
2455
- * Checks if the cache is valid (not invalidated).
2456
- * @returns True if the cache is valid, false if it has been invalidated
2457
- */
2458
- isValid() {
2459
- return !this.invalidated;
2460
- }
2461
2433
  /**
2462
2434
  * Synchronously checks whether persisted incremental state exists on disk.
2463
2435
  * When the .tsbuildinfo file is missing, the next typecheck will perform a full emit,
@@ -2601,6 +2573,28 @@ var globCharacters = /[*?\\[\]!].*$/;
2601
2573
  var domPredicate = (lib) => lib.toUpperCase() === "DOM";
2602
2574
  var tsLogo = TextFormat.bgBlue(TextFormat.bold(TextFormat.whiteBright(" TS ")));
2603
2575
  var diagnosticsHost = { getNewLine: () => sys2.newLine, getCurrentDirectory: sys2.getCurrentDirectory, getCanonicalFileName: (fileName) => fileName };
2576
+ var serializePattern = (p) => p instanceof RegExp ? `/${p.source}/${p.flags}` : p;
2577
+ function buildFingerprint(buildConfig, compilerOptions) {
2578
+ return JSON.stringify({
2579
+ minify: buildConfig.minify,
2580
+ iife: buildConfig.iife,
2581
+ declaration: compilerOptions.declaration,
2582
+ emitDeclarationOnly: compilerOptions.emitDeclarationOnly,
2583
+ emitDecoratorMetadata: compilerOptions.emitDecoratorMetadata,
2584
+ bundle: buildConfig.bundle,
2585
+ splitting: buildConfig.splitting,
2586
+ format,
2587
+ target: buildConfig.target,
2588
+ platform: buildConfig.platform,
2589
+ sourceMap: buildConfig.sourceMap,
2590
+ banner: buildConfig.banner,
2591
+ footer: buildConfig.footer,
2592
+ noExternal: buildConfig.noExternal.map(serializePattern),
2593
+ dtsResolve: buildConfig.dts.resolve,
2594
+ dtsEntryPoints: buildConfig.dts.entryPoints,
2595
+ env: buildConfig.env
2596
+ });
2597
+ }
2604
2598
  var _triggerRebuild_dec, _processDeclarations_dec, _transpile_dec, _typeCheck_dec, _build_dec, _TypeScriptProject_decorators, _init3;
2605
2599
  _TypeScriptProject_decorators = [closeOnExit], _build_dec = [measure("Build")], _typeCheck_dec = [measure("Type-checking/Emit", true)], _transpile_dec = [measure("Transpile", true)], _processDeclarations_dec = [measure("Bundle Declarations", true)], _triggerRebuild_dec = [debounce(100)];
2606
2600
  var _TypeScriptProject = class _TypeScriptProject {
@@ -2634,7 +2628,14 @@ var _TypeScriptProject = class _TypeScriptProject {
2634
2628
  this.buildConfiguration = { entryPoints: this.getEntryPoints(entryPoints), target: toEsTarget(target), outDir, ...tsbuildOptions };
2635
2629
  this.dependencyPaths = Files.read(Paths.absolute(this.directory, "package.json")).then((content) => {
2636
2630
  const { dependencies = {}, peerDependencies = {} } = Json.parse(content);
2637
- return [.../* @__PURE__ */ new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)])];
2631
+ const dependencySet = /* @__PURE__ */ new Set();
2632
+ for (const key of Object.keys(dependencies)) {
2633
+ dependencySet.add(key);
2634
+ }
2635
+ for (const key of Object.keys(peerDependencies)) {
2636
+ dependencySet.add(key);
2637
+ }
2638
+ return Array.from(dependencySet);
2638
2639
  }).catch(() => []);
2639
2640
  }
2640
2641
  /**
@@ -2682,20 +2683,14 @@ var _TypeScriptProject = class _TypeScriptProject {
2682
2683
  });
2683
2684
  this.pendingStaleOutputsCleanup = cleanup;
2684
2685
  }
2685
- /**
2686
- * Waits for fire-and-forget stale-output cleanup to settle.
2687
- * Useful for deterministic tests that need to assert post-cleanup filesystem state.
2688
- */
2689
- async flushBackgroundCleanup() {
2690
- await this.pendingStaleOutputsCleanup;
2691
- }
2692
2686
  async build() {
2693
- Logger.header(`${tsLogo} tsbuild v${"1.8.10"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2687
+ Logger.header(`${tsLogo} tsbuild v${"1.9.1"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2694
2688
  try {
2695
2689
  const processes = [];
2696
2690
  const buildCache = this.configuration.buildCache;
2697
- const forcedForMinify = await (buildCache?.requiresRebuild(this.buildConfiguration.minify) ?? Promise.resolve(false));
2698
- const force = this.configuration.tsbuild.force || forcedForMinify;
2691
+ const currentFingerprint = buildFingerprint(this.buildConfiguration, this.configuration.compilerOptions);
2692
+ const fingerprintMatched = buildCache !== void 0 && await buildCache.fingerprintMatches(currentFingerprint);
2693
+ const force = this.configuration.tsbuild.force || !fingerprintMatched;
2699
2694
  const cleanEnabled = this.configuration.clean && !this.configuration.compilerOptions.noEmit;
2700
2695
  const useManifest = cleanEnabled && buildCache !== void 0 && buildCache.hasPersistedManifest();
2701
2696
  const previousOutputs = useManifest ? buildCache.getPreviousOutputs() : void 0;
@@ -2728,13 +2723,11 @@ var _TypeScriptProject = class _TypeScriptProject {
2728
2723
  newOutputs.push(path);
2729
2724
  }
2730
2725
  }
2731
- this.fileManager.persistCache(this.buildConfiguration.minify);
2726
+ this.fileManager.persistCache(currentFingerprint, !fingerprintMatched);
2732
2727
  if (buildCache !== void 0 && newOutputs.length > 0) {
2733
2728
  if (previousOutputs !== void 0) {
2734
2729
  this.cleanupStaleOutputs(previousOutputs, newOutputs);
2735
2730
  }
2736
- void buildCache.saveMinifyState(this.buildConfiguration.minify).catch(() => {
2737
- });
2738
2731
  void buildCache.saveOutputs(newOutputs).catch(() => {
2739
2732
  });
2740
2733
  }
@@ -2799,11 +2792,11 @@ var _TypeScriptProject = class _TypeScriptProject {
2799
2792
  }
2800
2793
  plugins.push(outputPlugin());
2801
2794
  if (this.buildConfiguration.noExternal.length > 0) {
2802
- plugins.push(externalModulesPlugin({ dependencies: await this.getProjectDependencyPaths(), noExternal: this.buildConfiguration.noExternal }));
2795
+ plugins.push(externalModulesPlugin({ dependencies: await this.dependencyPaths, noExternal: this.buildConfiguration.noExternal }));
2803
2796
  }
2804
2797
  if (this.configuration.compilerOptions.emitDecoratorMetadata) {
2805
2798
  try {
2806
- const { swcDecoratorMetadataPlugin } = await import("./5DXLYKZU.js");
2799
+ const { swcDecoratorMetadataPlugin } = await import("./3ZUONNU3.js");
2807
2800
  plugins.push(swcDecoratorMetadataPlugin);
2808
2801
  } catch {
2809
2802
  throw new ConfigurationError("emitDecoratorMetadata is enabled but @swc/core is not installed. Install it with: pnpm add -D @swc/core");
@@ -2869,8 +2862,8 @@ var _TypeScriptProject = class _TypeScriptProject {
2869
2862
  }
2870
2863
  }
2871
2864
  const writtenFiles = [];
2872
- for (const [outputPath, { bytes }] of Object.entries(outputs)) {
2873
- writtenFiles.push({ path: outputPath, size: bytes });
2865
+ for (const outputPath in outputs) {
2866
+ writtenFiles.push({ path: outputPath, size: outputs[outputPath].bytes });
2874
2867
  }
2875
2868
  if (iife) {
2876
2869
  writtenFiles.push(...iife.files);
@@ -3025,7 +3018,19 @@ var _TypeScriptProject = class _TypeScriptProject {
3025
3018
  // Auto-inject 'node' only on Node platform — browser/neutral builds shouldn't pay the
3026
3019
  // cost of loading @types/node (~3 MB of declarations). Users can still opt in by
3027
3020
  // listing 'node' explicitly in their tsconfig types array.
3028
- types: platform2 === Platform.NODE ? [.../* @__PURE__ */ new Set(["node", ...configResult.config.compilerOptions?.types ?? [], ...typeScriptOptions.compilerOptions?.types ?? []])] : [.../* @__PURE__ */ new Set([...configResult.config.compilerOptions?.types ?? [], ...typeScriptOptions.compilerOptions?.types ?? []])]
3021
+ types: (() => {
3022
+ const typesSet = /* @__PURE__ */ new Set();
3023
+ if (platform2 === Platform.NODE) {
3024
+ typesSet.add("node");
3025
+ }
3026
+ for (const t of configResult.config.compilerOptions?.types ?? []) {
3027
+ typesSet.add(t);
3028
+ }
3029
+ for (const t of typeScriptOptions.compilerOptions?.types ?? []) {
3030
+ typesSet.add(t);
3031
+ }
3032
+ return Array.from(typesSet);
3033
+ })()
3029
3034
  }
3030
3035
  };
3031
3036
  const { options, fileNames, errors } = parseJsonConfigFileContent(baseConfig, sys2, directory);
@@ -3066,14 +3071,6 @@ var _TypeScriptProject = class _TypeScriptProject {
3066
3071
  }
3067
3072
  return expandedEntryPoints;
3068
3073
  }
3069
- /**
3070
- * Gets the project dependency paths.
3071
- * The promise is started in the constructor so it overlaps with TS Program creation.
3072
- * @returns A promise that resolves to an array of project dependency paths.
3073
- */
3074
- getProjectDependencyPaths() {
3075
- return this.dependencyPaths;
3076
- }
3077
3074
  /**
3078
3075
  * Handles build errors by logging unexpected errors and setting appropriate exit codes.
3079
3076
  * Expected build failures (TypeCheckError, BundleError) are already logged when they occur,
@@ -130,25 +130,22 @@ var Paths = class {
130
130
  }
131
131
  /**
132
132
  * Computes the absolute path by joining the provided segments.
133
- * @param paths Array of path segments to join
134
- * @returns The absolute path
133
+ * @param paths Array of path segments to join.
135
134
  */
136
135
  static absolute(...paths) {
137
136
  return resolve(...paths);
138
137
  }
139
138
  /**
140
139
  * Computes the relative path from one location to another.
141
- * @param from The starting location
142
- * @param to The target location
143
- * @returns The relative path
140
+ * @param from The starting location.
141
+ * @param to The target location.
144
142
  */
145
143
  static relative(from, to) {
146
144
  return relative(from, to);
147
145
  }
148
146
  /**
149
- * Returns the directory name of a path.
150
- * @param path - The path to evaluate
151
- * @returns The directory name of the path
147
+ * Parses a path string into its component parts.
148
+ * @param path The path to parse.
152
149
  */
153
150
  static parse(path) {
154
151
  return parse(path);
@@ -157,7 +154,6 @@ var Paths = class {
157
154
  * Checks if the given path is a directory.
158
155
  * Returns false if the path does not exist.
159
156
  * @param path - The path to check
160
- * @returns True if the path is a directory, false otherwise
161
157
  */
162
158
  static async isDirectory(path) {
163
159
  try {
@@ -173,7 +169,6 @@ var Paths = class {
173
169
  * Checks if the given path is a file.
174
170
  * Returns false if the path does not exist.
175
171
  * @param path - The path to check
176
- * @returns True if the path is a file, false otherwise
177
172
  */
178
173
  static async isFile(path) {
179
174
  try {
@@ -236,7 +231,6 @@ var Json = class {
236
231
  /**
237
232
  * Parse a JSON string into an object of type T.
238
233
  * @param jsonString The JSON string to parse.
239
- * @returns The parsed object of type T.
240
234
  */
241
235
  static parse(jsonString) {
242
236
  return JSON.parse(jsonString);
@@ -244,7 +238,6 @@ var Json = class {
244
238
  /**
245
239
  * Serialize an object of type T into a JSON string.
246
240
  * @param data The object to serialize.
247
- * @returns The serialized JSON string.
248
241
  */
249
242
  static serialize(data) {
250
243
  return JSON.stringify(data);
package/dist/tsbuild.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  BuildError,
4
4
  TypeScriptProject
5
- } from "./4JXWHUXF.js";
6
- import "./QL4XMDMJ.js";
5
+ } from "./7G6BAIKH.js";
6
+ import "./XTZ3MYA7.js";
7
7
 
8
8
  // src/tsbuild.ts
9
9
  import { sys } from "typescript";
@@ -27,19 +27,19 @@ if (help) {
27
27
  console.log(` ${`-${short}, --${long}`.padEnd(20)} ${description}`);
28
28
  }
29
29
  console.log();
30
- process.exit(0);
31
- }
32
- if (version) {
33
- console.log("1.8.10");
34
- process.exit(0);
35
- }
36
- var typeScriptOptions = {
37
- clearCache: args.clearCache,
38
- compilerOptions: { noEmit: args.noEmit },
39
- tsbuild: { force: args.force, minify: args.minify, watch: { enabled: args.watch } }
40
- };
41
- try {
42
- await new TypeScriptProject(args.project, typeScriptOptions).build();
43
- } catch (error) {
44
- process.exitCode = error instanceof BuildError ? error.code : 1;
30
+ process.exitCode = 0;
31
+ } else if (version) {
32
+ console.log("1.9.1");
33
+ process.exitCode = 0;
34
+ } else {
35
+ const typeScriptOptions = {
36
+ clearCache: args.clearCache,
37
+ compilerOptions: { noEmit: args.noEmit },
38
+ tsbuild: { force: args.force, minify: args.minify, watch: { enabled: args.watch } }
39
+ };
40
+ try {
41
+ await new TypeScriptProject(args.project, typeScriptOptions).build();
42
+ } catch (error) {
43
+ process.exitCode = error instanceof BuildError ? error.code : 1;
44
+ }
45
45
  }
@@ -129,11 +129,9 @@ type BuildOptions = {
129
129
  project?: Path;
130
130
  /** Force a full rebuild, even if no files have changed. Applicable for incremental builds */
131
131
  force?: boolean;
132
- /** Entry points for the build */
133
132
  entryPoints?: EntryPoints<RelativePath>;
134
133
  /** Platform target. Auto-detected from tsconfig lib (DOM = browser, no DOM = node) */
135
134
  platform?: 'browser' | 'node' | 'neutral';
136
- /** Whether to bundle source files together */
137
135
  bundle?: boolean;
138
136
  /** Remove all files from the output directory before building. Defaults to true */
139
137
  clean?: boolean;
@@ -143,21 +141,14 @@ type BuildOptions = {
143
141
  external?: Pattern[];
144
142
  /** Specific dependencies to bundle (override packages setting) */
145
143
  noExternal?: Pattern[];
146
- /** Enable code splitting */
147
144
  splitting?: boolean;
148
- /** Minify the output */
149
145
  minify?: boolean;
150
146
  /** Source map options. Overrides the value in tsconfig.json CompilerOptions */
151
147
  sourceMap?: boolean | 'inline' | 'external' | 'both';
152
- /** Banner to inject at the start of output files */
153
148
  banner?: BannerOrFooter;
154
- /** Footer to inject at the end of output files */
155
149
  footer?: BannerOrFooter;
156
- /** Environment variables to inject */
157
150
  env?: Record<string, string>;
158
- /** Declaration bundling configuration */
159
151
  dts?: DtsOptions;
160
- /** Watch mode configuration */
161
152
  watch?: WatchOptions;
162
153
  /** Emit decorator metadata (requires `@swc/core` as optional dependency) */
163
154
  decoratorMetadata?: boolean;
@@ -196,7 +187,6 @@ type TypeScriptOptions = {
196
187
  };
197
188
  /** Cached declaration file with pre-processed code and extracted references */
198
189
  type CachedDeclaration = {
199
- /** Pre-processed declaration code */
200
190
  code: string;
201
191
  /** Triple-slash type reference directives extracted during pre-processing */
202
192
  typeReferences: ReadonlySet<string>;
@@ -209,20 +199,14 @@ type BuildCache = {
209
199
  version: number;
210
200
  /** Cached declaration files: path -> pre-processed code with extracted references */
211
201
  files: Map<string, CachedDeclaration>;
212
- /** Minify mode of the last successful JS output generation */
213
- minify?: boolean;
202
+ /** Build configuration fingerprint: hash of output-affecting options (minify, iife, declaration, platform, etc.) */
203
+ fingerprint?: string;
214
204
  };
215
- /** Interface for build cache operations */
216
205
  interface BuildCacheManager {
217
- /** Invalidates the build cache */
218
206
  invalidate(): void;
219
- /** Restores cached declaration files into the provided map */
220
207
  restore(target: Map<string, CachedDeclaration>): Promise<void>;
221
- /** Saves declaration files to the cache */
222
- save(source: ReadonlyMap<string, CachedDeclaration>, minify: boolean): Promise<void>;
223
- /** Checks if the cache is valid */
208
+ save(source: ReadonlyMap<string, CachedDeclaration>, fingerprint: string): Promise<void>;
224
209
  isValid(): boolean;
225
- /** Checks if a file path is the TypeScript build info file */
226
210
  isBuildInfoFile(filePath: AbsolutePath): boolean;
227
211
  /** Synchronously checks whether persisted incremental state exists on disk (i.e. .tsbuildinfo). */
228
212
  hasPersistedState(): boolean;
@@ -232,24 +216,18 @@ interface BuildCacheManager {
232
216
  getPreviousOutputs(): readonly string[] | undefined;
233
217
  /** Persists the project-relative output paths produced by the current build. Fire-and-forget. */
234
218
  saveOutputs(outputs: readonly string[]): Promise<void>;
235
- /** Checks whether current minify mode requires forcing a rebuild. */
236
- requiresRebuild(minify: boolean): Promise<boolean>;
237
- /** Persists minify mode metadata for future incremental-build compatibility checks. */
238
- saveMinifyState(minify: boolean): Promise<void>;
219
+ /** Checks whether the build configuration has changed since the cache was last saved. */
220
+ fingerprintMatches(currentFingerprint: string): Promise<boolean>;
239
221
  }
240
222
  type TypeScriptConfiguration = Readonly<Modify<TypeScriptOptions, {
241
223
  clean: boolean;
242
224
  compilerOptions: TypeScriptCompilerConfiguration;
243
225
  tsbuild: BuildConfiguration;
244
- /** Project root directory */
245
226
  directory: AbsolutePath;
246
227
  /** Absolute paths of root names used to create the TypeScript program */
247
228
  rootNames: string[];
248
- /** Diagnostics encountered while parsing the config file */
249
229
  configFileParsingDiagnostics: Diagnostic[];
250
- /** Build cache instance for incremental builds */
251
230
  buildCache: BuildCacheManager | undefined;
252
- /** Module Specifiers in 'include', 'exclude', & 'files' */
253
231
  include?: string[];
254
232
  exclude?: string[];
255
233
  files?: string[];
@@ -329,11 +307,6 @@ declare class TypeScriptProject implements Closable {
329
307
  * @param current - Project-relative paths produced by the current build
330
308
  */
331
309
  private cleanupStaleOutputs;
332
- /**
333
- * Waits for fire-and-forget stale-output cleanup to settle.
334
- * Useful for deterministic tests that need to assert post-cleanup filesystem state.
335
- */
336
- flushBackgroundCleanup(): Promise<void>;
337
310
  /**
338
311
  * Builds the project
339
312
  * @returns A promise that resolves when the build is complete.
@@ -383,12 +356,6 @@ declare class TypeScriptProject implements Closable {
383
356
  * @returns A promise that resolves to the entry points.
384
357
  */
385
358
  private getEntryPoints;
386
- /**
387
- * Gets the project dependency paths.
388
- * The promise is started in the constructor so it overlaps with TS Program creation.
389
- * @returns A promise that resolves to an array of project dependency paths.
390
- */
391
- private getProjectDependencyPaths;
392
359
  /**
393
360
  * Handles build errors by logging unexpected errors and setting appropriate exit codes.
394
361
  * Expected build failures (TypeCheckError, BundleError) are already logged when they occur,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TypeScriptProject
3
- } from "./4JXWHUXF.js";
4
- import "./QL4XMDMJ.js";
3
+ } from "./7G6BAIKH.js";
4
+ import "./XTZ3MYA7.js";
5
5
  export {
6
6
  TypeScriptProject
7
7
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@d1g1tal/tsbuild",
3
3
  "author": "D1g1talEntr0py",
4
- "version": "1.8.10",
4
+ "version": "1.9.1",
5
5
  "license": "MIT",
6
6
  "description": "A fast, ESM-only TypeScript build tool combining the TypeScript API for type checking and declaration generation, esbuild for bundling, and SWC for decorator metadata.",
7
7
  "homepage": "https://github.com/D1g1talEntr0py/tsbuild#readme",
@@ -19,7 +19,7 @@
19
19
  }
20
20
  ],
21
21
  "engines": {
22
- "node": ">=22.0.0"
22
+ "node": ">=22.6.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "registry": "https://registry.npmjs.org",
@@ -45,23 +45,23 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@d1g1tal/watchr": "^1.0.6",
48
- "esbuild": "^0.28.0",
48
+ "esbuild": "^0.28.1",
49
49
  "magic-string": "^0.30.21"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@eslint/js": "^10.0.1",
53
- "@types/node": "^25.9.1",
54
- "@typescript-eslint/eslint-plugin": "^8.59.4",
55
- "@typescript-eslint/parser": "^8.59.4",
56
- "@vitest/coverage-v8": "^4.1.7",
57
- "eslint": "^10.4.0",
58
- "eslint-plugin-jsdoc": "^63.0.0",
53
+ "@types/node": "^25.9.3",
54
+ "@typescript-eslint/eslint-plugin": "^8.61.0",
55
+ "@typescript-eslint/parser": "^8.61.0",
56
+ "@vitest/coverage-v8": "^4.1.8",
57
+ "eslint": "^10.5.0",
58
+ "eslint-plugin-jsdoc": "^63.0.2",
59
59
  "fs-monkey": "^1.1.0",
60
- "memfs": "^4.57.2",
60
+ "memfs": "^4.57.7",
61
61
  "mitata": "^1.0.34",
62
62
  "typescript": "^6.0.3",
63
- "typescript-eslint": "^8.59.4",
64
- "vitest": "^4.1.7"
63
+ "typescript-eslint": "^8.61.0",
64
+ "vitest": "^4.1.8"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "typescript": ">=5.6.3"