@d1g1tal/tsbuild 1.8.9 → 1.9.0

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,99 @@
1
+ ## [1.9.0](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.8.10...v1.9.0) (2026-05-23)
2
+
3
+ ### Features
4
+
5
+ * **cache:** implement configuration fingerprinting for incremental builds (4bb8b9ec6415cd5a3bd15d9a5265725c3ff0242c)
6
+ - Introduces deterministic JSON fingerprinting covering platform, target, formats, and bundler options
7
+ - Replaces strict boolean minification caching with robust multi-variable configuration fingerprints
8
+ - Compares fresh configurations with on-disk persisted fingerprint states to flush outputs dynamically
9
+ - Enhances cache manager implementations allowing seamless integration with esbuild pipelines
10
+ - Overrides unit tests validating caching scenarios evaluating string-based hashes correctly
11
+
12
+
13
+ ### Performance Improvements
14
+
15
+ * **core:** optimize iteration loops during data serialization (08cd6d3f77af3b58072dab008bee7a3217cc1d56)
16
+ - Restructures array destructuring and explicit index loops addressing slow map operations
17
+ - Avoids `Object.entries()` array memory spikes during physical build loop mapping
18
+ - Optimizes the declaration exports loop preventing excessive intermediary filtering blocks
19
+ - Reduces overall execution times slightly during intensive build output parsing
20
+
21
+
22
+ ### Documentation
23
+
24
+ * **readme:** update build documentation with cache and loader details (764514b987812743ffd11153e678e6723f8e09e4)
25
+ - Details the configuration change detection mechanism that avoids stale caches when the config differs
26
+ - Adds a dedicated section for the self-hosting loader to help clarify bootstrapping logic
27
+
28
+
29
+ ### Styles
30
+
31
+ * **docs:** prune redundant jsdoc comments across codebase (3b106dade1b5bc7dc713ef37463a984ca40f02b4)
32
+ - Removes repetitive property comments from standard interfaces like typescript configurations
33
+ - Strips excess constructor `@param` tags throughout standard object models
34
+ - Erases leftover `@returns` definitions across standard helper functions
35
+ - Fixes minor spacing issues affecting multiple comment block fragments
36
+
37
+
38
+ ### Miscellaneous Chores
39
+
40
+ * **eslint:** disable excessive jsdoc property requirements (11f4d21f0515841cdd2c5b1e112db67f42cbaf02)
41
+ - Reduces the strictness of JSDoc returns and param description rules
42
+ - Subdues linting checks on empty constructors and standard types to cut down developer fatigue
43
+
44
+
45
+ ### Tests
46
+
47
+ * **project:** refactor integration suites adopting build method structures (749970133479e51bc8d9a68c591ac926d394170e)
48
+ - Rewrites older validation strategies bypassing internal private methods completely
49
+ - Tests public `project.build()` execution flows simulating realistic cli invocations directly
50
+ - Handles unexpected esbuild crash reporting verifications robustly
51
+ - Adjusts browser platform resolving logic testing ensuring appropriate outputs trigger successfully
52
+ - Drops deprecated testing suites previously locked behind internal mock validations
53
+
54
+ ## [1.8.10](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.8.9...v1.8.10) (2026-05-23)
55
+
56
+ ### Bug Fixes
57
+
58
+ * **cache:** fix minify state caching and rename manager interfaces (a66c98ec190b7398b2a9a81d9b02bbf36617c8e4)
59
+ - Added `latestFiles` field to prevent `saveMinifyState()` from overwriting concurrent build declarations
60
+ - Updated `save()` signature to accept and persist `minify: boolean` correctly
61
+ - Fixed `saveMinifyState()` to empty its Map when invalidated, avoiding stale cache writes
62
+ - Updated rebuild forcing logic to trigger when minify mode differs in either direction
63
+ - Renamed `shouldForceForMinify` to `requiresRebuild`
64
+ - Renamed cache types to `BuildCache` and `BuildCacheManager` for clearer API boundaries
65
+ - Propagated minify configuration arguments into `FileManager` and `TypeScriptProject`
66
+ - Added thorough regression tests for incremental cache behaviors and minify configurations
67
+
68
+ * **iife:** forward minify option to secondary esbuild call (bec434d1ab86ef2de1b3defdc2922fda9cb62890)
69
+ - Captured the minify option from build.initialOptions in iifePlugin
70
+ - Passed the minify parameter down through buildIife() hook
71
+ - Enabled minify inside secondary esbuild configurations when requested
72
+ - Added plugin tests to ensure minify toggles are correctly respected for true and false
73
+
74
+
75
+ ### Miscellaneous Chores
76
+
77
+ * **ci:** Update GitHub Actions to latest version (68c227fadab5d71d40afb6c965eeb91139294261)
78
+
79
+ ### Tests
80
+
81
+ * **core:** improve watch mode and fix diverse testing warnings (b30822589036113be622c655e9a8f995a0f78992)
82
+ - Replaced private method watch() calls with project.build() in integration tests alongside setImmediate flush
83
+ - Added `@d1g1tal/watchr` vi.mock stub to enable integration testing isolation
84
+ - Removed leftover `process.exit()` spies
85
+ - Improved object indexing semantics (dot vs bracket notation) safely ignoring TypeScript index issues in tsbuild.test.ts and benchmark.ts
86
+ - Added assertions via `.toBeDefined()` and updated mock object casting mechanisms
87
+ - Updated `tsconfig.json` to properly traverse and handle local `src/@types` directory
88
+
89
+
90
+ ### Build System
91
+
92
+ * **deps:** update dependencies to latest (c7fc745a1f61ad92c3e9328e6e187fdbb8d29348)
93
+ - Updated @types/node, @typescript-eslint plugins, @vitest tools, and eslint-plugin-jsdoc to latest versions in package.json
94
+ - Updated object-deep-merge, postcss, and semver to their latest versions
95
+ - Refreshed pnpm-lock.yaml to lock all dependency updates
96
+
1
97
  ## [1.8.9](https://github.com/D1g1talEntr0py/tsbuild/compare/v1.8.8...v1.8.9) (2026-05-18)
2
98
 
3
99
  ### 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;
@@ -1676,12 +1661,13 @@ function iifePlugin(options) {
1676
1661
  }
1677
1662
  build.initialOptions.write = false;
1678
1663
  const sourcemap = build.initialOptions.sourcemap;
1664
+ const minify = build.initialOptions.minify;
1679
1665
  const entryPointNames = extractEntryNames(build.initialOptions.entryPoints);
1680
1666
  build.onEnd(async ({ outputFiles }) => {
1681
1667
  if (!outputFiles || outputFiles.length === 0 || entryPointNames.length === 0) {
1682
1668
  return;
1683
1669
  }
1684
- files.push(...await buildIife(outputFiles, entryPointNames, outdir, options?.globalName, sourcemap));
1670
+ files.push(...await buildIife(outputFiles, entryPointNames, outdir, options?.globalName, sourcemap, minify));
1685
1671
  });
1686
1672
  }
1687
1673
  }
@@ -1746,7 +1732,7 @@ ${text.slice(0, exportStart)}
1746
1732
  ${assignment}
1747
1733
  })();${text.slice(exportEnd)}`;
1748
1734
  }
1749
- async function buildIife(primaryOutputs, entryPointNames, outdir, globalName, sourcemap) {
1735
+ async function buildIife(primaryOutputs, entryPointNames, outdir, globalName, sourcemap, minify) {
1750
1736
  const { build: esbuild } = await import("esbuild");
1751
1737
  const fileContents = /* @__PURE__ */ new Map();
1752
1738
  const primaryWrites = [];
@@ -1787,6 +1773,7 @@ async function buildIife(primaryOutputs, entryPointNames, outdir, globalName, so
1787
1773
  splitting: false,
1788
1774
  outdir: iifeOutdir,
1789
1775
  sourcemap: sourcemapValue,
1776
+ minify,
1790
1777
  write: false,
1791
1778
  logLevel: "warning",
1792
1779
  plugins
@@ -1880,7 +1867,7 @@ var ProcessManager = class {
1880
1867
  }
1881
1868
  this.close();
1882
1869
  };
1883
- /** Handles console exit (ctrl+c) */
1870
+ /** Handles SIGINT (ctrl+c) */
1884
1871
  consoleExit = () => {
1885
1872
  Logger.warn("\nProcess terminated by user");
1886
1873
  this.hasHandledExit = true;
@@ -1904,10 +1891,6 @@ var processManager = new ProcessManager();
1904
1891
  // src/decorators/close-on-exit.ts
1905
1892
  function closeOnExit(value, _context) {
1906
1893
  return class extends value {
1907
- /**
1908
- * Creates an instance and registers it with the process manager.
1909
- * @param args Arguments to pass to the original constructor.
1910
- */
1911
1894
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1912
1895
  constructor(...args) {
1913
1896
  super(...args);
@@ -1952,9 +1935,9 @@ var _PerformanceLogger = class _PerformanceLogger {
1952
1935
  }
1953
1936
  /**
1954
1937
  * Measures the performance of a method and logs the result.
1955
- * @param message - The message to log with the performance measurement.
1956
- * @param logResult - Whether to log the result of the method.
1957
- * @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
1958
1941
  */
1959
1942
  measure(message, logResult = false) {
1960
1943
  const _measure = (propertyKey, result, options) => {
@@ -2143,17 +2126,20 @@ var FileManager = class {
2143
2126
  /**
2144
2127
  * Persists the .tsbuildinfo file and the dts cache to disk in the background. Call this
2145
2128
  * AFTER the build's parallel phases (transpile + dts bundling) have completed so the writes
2146
- * (and Brotli compression for the dts cache) don't compete with esbuild for libuv threadpool
2147
- * slots.
2129
+ * (and Brotli compression for the dts cache) don't compete with esbuild for libuv threadpool slots.
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.
2148
2134
  */
2149
- persistCache() {
2135
+ persistCache(fingerprint, configChanged = false) {
2150
2136
  const tasks = [];
2151
2137
  if (this.pendingBuildInfo) {
2152
2138
  tasks.push(Files.write(this.pendingBuildInfo.path, this.pendingBuildInfo.text));
2153
2139
  this.pendingBuildInfo = void 0;
2154
2140
  }
2155
- if (this.cache !== void 0 && this.hasEmittedFiles) {
2156
- tasks.push(this.cache.save(this.declarationFiles));
2141
+ if (this.cache !== void 0 && (this.hasEmittedFiles || configChanged)) {
2142
+ tasks.push(this.cache.save(this.declarationFiles, fingerprint));
2157
2143
  }
2158
2144
  if (tasks.length === 0) {
2159
2145
  return;
@@ -2311,6 +2297,8 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2311
2297
  outputsSnapshot;
2312
2298
  /** Set to true when invalidate() is called to prevent stale cache from being restored */
2313
2299
  invalidated = false;
2300
+ /** Updated synchronously in save() so fingerprintMatches() sees fresh data without re-reading from disk. */
2301
+ savedFingerprint;
2314
2302
  /**
2315
2303
  * Creates a new build cache instance and begins pre-loading the cache asynchronously.
2316
2304
  * @param projectRoot - Root directory of the project
@@ -2331,7 +2319,10 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2331
2319
  async loadCache() {
2332
2320
  try {
2333
2321
  const cache = await Files.readCompressed(this.cacheFilePath);
2334
- return cache.version === dtsCacheVersion ? cache : void 0;
2322
+ if (cache.version !== dtsCacheVersion) {
2323
+ return void 0;
2324
+ }
2325
+ return cache;
2335
2326
  } catch {
2336
2327
  return void 0;
2337
2328
  }
@@ -2356,12 +2347,33 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2356
2347
  }
2357
2348
  }
2358
2349
  /**
2359
- * 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.
2360
2351
  * Uses V8 serialization for faster read performance on subsequent builds.
2361
2352
  * @param source - The declaration files to cache
2353
+ * @param fingerprint - Deterministic hash of build configuration for cache invalidation on config change
2362
2354
  */
2363
- async save(source) {
2364
- await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files: source });
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;
2365
2377
  }
2366
2378
  /**
2367
2379
  * Loads the previous build's output manifest synchronously.
@@ -2395,6 +2407,13 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2395
2407
  await mkdir4(this.cacheDirectoryPath, defaultDirOptions);
2396
2408
  await writeFile4(this.outputsManifestPath, JSON.stringify(this.outputsSnapshot), "utf8");
2397
2409
  }
2410
+ /**
2411
+ * Checks if the cache is valid (not invalidated).
2412
+ * @returns True if the cache is valid, false if it has been invalidated
2413
+ */
2414
+ isValid() {
2415
+ return !this.invalidated;
2416
+ }
2398
2417
  /** Invalidates the build cache by removing the cache directory. */
2399
2418
  invalidate() {
2400
2419
  this.invalidated = true;
@@ -2411,13 +2430,6 @@ var IncrementalBuildCache = class _IncrementalBuildCache {
2411
2430
  isBuildInfoFile(filePath) {
2412
2431
  return filePath === this.buildInfoPath;
2413
2432
  }
2414
- /**
2415
- * Checks if the cache is valid (not invalidated).
2416
- * @returns True if the cache is valid, false if it has been invalidated
2417
- */
2418
- isValid() {
2419
- return !this.invalidated;
2420
- }
2421
2433
  /**
2422
2434
  * Synchronously checks whether persisted incremental state exists on disk.
2423
2435
  * When the .tsbuildinfo file is missing, the next typecheck will perform a full emit,
@@ -2561,6 +2573,28 @@ var globCharacters = /[*?\\[\]!].*$/;
2561
2573
  var domPredicate = (lib) => lib.toUpperCase() === "DOM";
2562
2574
  var tsLogo = TextFormat.bgBlue(TextFormat.bold(TextFormat.whiteBright(" TS ")));
2563
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
+ }
2564
2598
  var _triggerRebuild_dec, _processDeclarations_dec, _transpile_dec, _typeCheck_dec, _build_dec, _TypeScriptProject_decorators, _init3;
2565
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)];
2566
2600
  var _TypeScriptProject = class _TypeScriptProject {
@@ -2594,7 +2628,14 @@ var _TypeScriptProject = class _TypeScriptProject {
2594
2628
  this.buildConfiguration = { entryPoints: this.getEntryPoints(entryPoints), target: toEsTarget(target), outDir, ...tsbuildOptions };
2595
2629
  this.dependencyPaths = Files.read(Paths.absolute(this.directory, "package.json")).then((content) => {
2596
2630
  const { dependencies = {}, peerDependencies = {} } = Json.parse(content);
2597
- 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);
2598
2639
  }).catch(() => []);
2599
2640
  }
2600
2641
  /**
@@ -2650,11 +2691,13 @@ var _TypeScriptProject = class _TypeScriptProject {
2650
2691
  await this.pendingStaleOutputsCleanup;
2651
2692
  }
2652
2693
  async build() {
2653
- Logger.header(`${tsLogo} tsbuild v${"1.8.9"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2694
+ Logger.header(`${tsLogo} tsbuild v${"1.9.0"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2654
2695
  try {
2655
2696
  const processes = [];
2656
2697
  const buildCache = this.configuration.buildCache;
2657
- const force = this.configuration.tsbuild.force;
2698
+ const currentFingerprint = buildFingerprint(this.buildConfiguration, this.configuration.compilerOptions);
2699
+ const fingerprintMatched = buildCache !== void 0 && await buildCache.fingerprintMatches(currentFingerprint);
2700
+ const force = this.configuration.tsbuild.force || !fingerprintMatched;
2658
2701
  const cleanEnabled = this.configuration.clean && !this.configuration.compilerOptions.noEmit;
2659
2702
  const useManifest = cleanEnabled && buildCache !== void 0 && buildCache.hasPersistedManifest();
2660
2703
  const previousOutputs = useManifest ? buildCache.getPreviousOutputs() : void 0;
@@ -2687,7 +2730,7 @@ var _TypeScriptProject = class _TypeScriptProject {
2687
2730
  newOutputs.push(path);
2688
2731
  }
2689
2732
  }
2690
- this.fileManager.persistCache();
2733
+ this.fileManager.persistCache(currentFingerprint, !fingerprintMatched);
2691
2734
  if (buildCache !== void 0 && newOutputs.length > 0) {
2692
2735
  if (previousOutputs !== void 0) {
2693
2736
  this.cleanupStaleOutputs(previousOutputs, newOutputs);
@@ -2760,7 +2803,7 @@ var _TypeScriptProject = class _TypeScriptProject {
2760
2803
  }
2761
2804
  if (this.configuration.compilerOptions.emitDecoratorMetadata) {
2762
2805
  try {
2763
- const { swcDecoratorMetadataPlugin } = await import("./5DXLYKZU.js");
2806
+ const { swcDecoratorMetadataPlugin } = await import("./3ZUONNU3.js");
2764
2807
  plugins.push(swcDecoratorMetadataPlugin);
2765
2808
  } catch {
2766
2809
  throw new ConfigurationError("emitDecoratorMetadata is enabled but @swc/core is not installed. Install it with: pnpm add -D @swc/core");
@@ -2826,8 +2869,8 @@ var _TypeScriptProject = class _TypeScriptProject {
2826
2869
  }
2827
2870
  }
2828
2871
  const writtenFiles = [];
2829
- for (const [outputPath, { bytes }] of Object.entries(outputs)) {
2830
- writtenFiles.push({ path: outputPath, size: bytes });
2872
+ for (const outputPath in outputs) {
2873
+ writtenFiles.push({ path: outputPath, size: outputs[outputPath].bytes });
2831
2874
  }
2832
2875
  if (iife) {
2833
2876
  writtenFiles.push(...iife.files);
@@ -2982,7 +3025,19 @@ var _TypeScriptProject = class _TypeScriptProject {
2982
3025
  // Auto-inject 'node' only on Node platform — browser/neutral builds shouldn't pay the
2983
3026
  // cost of loading @types/node (~3 MB of declarations). Users can still opt in by
2984
3027
  // listing 'node' explicitly in their tsconfig types array.
2985
- 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 ?? []])]
3028
+ types: (() => {
3029
+ const typesSet = /* @__PURE__ */ new Set();
3030
+ if (platform2 === Platform.NODE) {
3031
+ typesSet.add("node");
3032
+ }
3033
+ for (const t of configResult.config.compilerOptions?.types ?? []) {
3034
+ typesSet.add(t);
3035
+ }
3036
+ for (const t of typeScriptOptions.compilerOptions?.types ?? []) {
3037
+ typesSet.add(t);
3038
+ }
3039
+ return Array.from(typesSet);
3040
+ })()
2986
3041
  }
2987
3042
  };
2988
3043
  const { options, fileNames, errors } = parseJsonConfigFileContent(baseConfig, sys2, directory);
@@ -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 "./QXXMXCRU.js";
6
- import "./QL4XMDMJ.js";
5
+ } from "./S3D7UW6Z.js";
6
+ import "./XTZ3MYA7.js";
7
7
 
8
8
  // src/tsbuild.ts
9
9
  import { sys } from "typescript";
@@ -30,7 +30,7 @@ if (help) {
30
30
  process.exit(0);
31
31
  }
32
32
  if (version) {
33
- console.log("1.8.9");
33
+ console.log("1.9.0");
34
34
  process.exit(0);
35
35
  }
36
36
  var typeScriptOptions = {
@@ -53,6 +53,7 @@ type PerformanceEntryDetail<T = unknown[]> = {
53
53
  message: string;
54
54
  result?: T;
55
55
  steps?: PerformanceSubStep[];
56
+ notes?: string[];
56
57
  };
57
58
  type DetailedPerformanceMeasureOptions<R> = Modify<PerformanceMeasureOptions, {
58
59
  detail: PerformanceEntryDetail<R>;
@@ -128,11 +129,9 @@ type BuildOptions = {
128
129
  project?: Path;
129
130
  /** Force a full rebuild, even if no files have changed. Applicable for incremental builds */
130
131
  force?: boolean;
131
- /** Entry points for the build */
132
132
  entryPoints?: EntryPoints<RelativePath>;
133
133
  /** Platform target. Auto-detected from tsconfig lib (DOM = browser, no DOM = node) */
134
134
  platform?: 'browser' | 'node' | 'neutral';
135
- /** Whether to bundle source files together */
136
135
  bundle?: boolean;
137
136
  /** Remove all files from the output directory before building. Defaults to true */
138
137
  clean?: boolean;
@@ -142,21 +141,14 @@ type BuildOptions = {
142
141
  external?: Pattern[];
143
142
  /** Specific dependencies to bundle (override packages setting) */
144
143
  noExternal?: Pattern[];
145
- /** Enable code splitting */
146
144
  splitting?: boolean;
147
- /** Minify the output */
148
145
  minify?: boolean;
149
146
  /** Source map options. Overrides the value in tsconfig.json CompilerOptions */
150
147
  sourceMap?: boolean | 'inline' | 'external' | 'both';
151
- /** Banner to inject at the start of output files */
152
148
  banner?: BannerOrFooter;
153
- /** Footer to inject at the end of output files */
154
149
  footer?: BannerOrFooter;
155
- /** Environment variables to inject */
156
150
  env?: Record<string, string>;
157
- /** Declaration bundling configuration */
158
151
  dts?: DtsOptions;
159
- /** Watch mode configuration */
160
152
  watch?: WatchOptions;
161
153
  /** Emit decorator metadata (requires `@swc/core` as optional dependency) */
162
154
  decoratorMetadata?: boolean;
@@ -195,24 +187,26 @@ type TypeScriptOptions = {
195
187
  };
196
188
  /** Cached declaration file with pre-processed code and extracted references */
197
189
  type CachedDeclaration = {
198
- /** Pre-processed declaration code */
199
190
  code: string;
200
191
  /** Triple-slash type reference directives extracted during pre-processing */
201
192
  typeReferences: ReadonlySet<string>;
202
193
  /** Triple-slash file reference directives extracted during pre-processing */
203
194
  fileReferences: ReadonlySet<string>;
204
195
  };
205
- /** Interface for build cache operations */
206
- interface BuildCache {
207
- /** Invalidates the build cache */
196
+ /** Persistent cache payload stored in .tsbuild/dts_cache.v8.br */
197
+ type BuildCache = {
198
+ /** Cache format version for compatibility checking */
199
+ version: number;
200
+ /** Cached declaration files: path -> pre-processed code with extracted references */
201
+ files: Map<string, CachedDeclaration>;
202
+ /** Build configuration fingerprint: hash of output-affecting options (minify, iife, declaration, platform, etc.) */
203
+ fingerprint?: string;
204
+ };
205
+ interface BuildCacheManager {
208
206
  invalidate(): void;
209
- /** Restores cached declaration files into the provided map */
210
207
  restore(target: Map<string, CachedDeclaration>): Promise<void>;
211
- /** Saves declaration files to the cache */
212
- save(source: ReadonlyMap<string, CachedDeclaration>): Promise<void>;
213
- /** Checks if the cache is valid */
208
+ save(source: ReadonlyMap<string, CachedDeclaration>, fingerprint: string): Promise<void>;
214
209
  isValid(): boolean;
215
- /** Checks if a file path is the TypeScript build info file */
216
210
  isBuildInfoFile(filePath: AbsolutePath): boolean;
217
211
  /** Synchronously checks whether persisted incremental state exists on disk (i.e. .tsbuildinfo). */
218
212
  hasPersistedState(): boolean;
@@ -222,20 +216,18 @@ interface BuildCache {
222
216
  getPreviousOutputs(): readonly string[] | undefined;
223
217
  /** Persists the project-relative output paths produced by the current build. Fire-and-forget. */
224
218
  saveOutputs(outputs: readonly string[]): Promise<void>;
219
+ /** Checks whether the build configuration has changed since the cache was last saved. */
220
+ fingerprintMatches(currentFingerprint: string): Promise<boolean>;
225
221
  }
226
222
  type TypeScriptConfiguration = Readonly<Modify<TypeScriptOptions, {
227
223
  clean: boolean;
228
224
  compilerOptions: TypeScriptCompilerConfiguration;
229
225
  tsbuild: BuildConfiguration;
230
- /** Project root directory */
231
226
  directory: AbsolutePath;
232
227
  /** Absolute paths of root names used to create the TypeScript program */
233
228
  rootNames: string[];
234
- /** Diagnostics encountered while parsing the config file */
235
229
  configFileParsingDiagnostics: Diagnostic[];
236
- /** Build cache instance for incremental builds */
237
- buildCache: BuildCache | undefined;
238
- /** Module Specifiers in 'include', 'exclude', & 'files' */
230
+ buildCache: BuildCacheManager | undefined;
239
231
  include?: string[];
240
232
  exclude?: string[];
241
233
  files?: string[];
@@ -392,4 +384,4 @@ declare class TypeScriptProject implements Closable {
392
384
  }
393
385
 
394
386
  export { Plugin, TypeScriptProject };
395
- export type { AbsolutePath, AsyncEntryPoints, Brand, BuildCache, BuildConfiguration, CachedDeclaration, Closable, ClosableConstructor, CompilerOptionOverrides, ConditionalPath, DetailedPerformanceEntry, DetailedPerformanceMeasureOptions, EntryPoints, EsTarget, Fn, FormatSupplier, IifeOptions, InferredFunction, JsonString, JsxRenderingMode, LogEntryType, MethodFunction, OptionalReturn, Path, Pattern, PendingFileChange, PerformanceSubStep, PluginReference, ProjectBuildConfiguration, ProjectDependencies, ReadConfigResult, RelativePath, SourceMap, TypeScriptConfiguration, TypeScriptOptions, TypedFunction, WrittenFile };
387
+ export type { AbsolutePath, AsyncEntryPoints, Brand, BuildCache, BuildCacheManager, BuildConfiguration, CachedDeclaration, Closable, ClosableConstructor, CompilerOptionOverrides, ConditionalPath, DetailedPerformanceEntry, DetailedPerformanceMeasureOptions, EntryPoints, EsTarget, Fn, FormatSupplier, IifeOptions, InferredFunction, JsonString, JsxRenderingMode, LogEntryType, MethodFunction, OptionalReturn, Path, Pattern, PendingFileChange, PerformanceSubStep, PluginReference, ProjectBuildConfiguration, ProjectDependencies, ReadConfigResult, RelativePath, SourceMap, TypeScriptConfiguration, TypeScriptOptions, TypedFunction, WrittenFile };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TypeScriptProject
3
- } from "./QXXMXCRU.js";
4
- import "./QL4XMDMJ.js";
3
+ } from "./S3D7UW6Z.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.9",
4
+ "version": "1.9.0",
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",
@@ -50,18 +50,18 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@eslint/js": "^10.0.1",
53
- "@types/node": "^25.8.0",
54
- "@typescript-eslint/eslint-plugin": "^8.59.3",
55
- "@typescript-eslint/parser": "^8.59.3",
56
- "@vitest/coverage-v8": "^4.1.6",
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
57
  "eslint": "^10.4.0",
58
- "eslint-plugin-jsdoc": "^62.9.0",
58
+ "eslint-plugin-jsdoc": "^63.0.0",
59
59
  "fs-monkey": "^1.1.0",
60
60
  "memfs": "^4.57.2",
61
61
  "mitata": "^1.0.34",
62
62
  "typescript": "^6.0.3",
63
- "typescript-eslint": "^8.59.3",
64
- "vitest": "^4.1.6"
63
+ "typescript-eslint": "^8.59.4",
64
+ "vitest": "^4.1.7"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "typescript": ">=5.6.3"