From 6d4463b4a0d5e80a05fe1baa35ed939c3be05a9a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 14:32:12 +0000 Subject: [PATCH] fix(build): emit .d.ts files in program order so two spec builds match byte for byte tsup's bundled rollup-plugin-dts emitted each declaration file when rollup transformed it. Rollup transforms in the order its async loads finish, which changes from run to run, and TypeScript prints union members in the order the one shared program's checker created them. Two clean builds of one commit of @objectstack/spec therefore differed in 18 of 134 .d.ts files, chunk names included, with the same public surface. The tsup patch now emits every source file once, in the program's own file order, before rollup reads any; the transform hook reads the stored text. Each file is still emitted exactly once. A pin builds a two-entry fixture through the real tsup CLI twice, forcing opposite load orders, and requires byte-equal output. Its controls prove the fixture is order-sensitive under this TypeScript and that the forced order reached rollup's loader. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01WGufzeza8Qg2GQUtJQtxRW --- .../spec/scripts/dts-emit-order.pin.test.ts | 187 ++++++++++++++++++ packages/spec/tsup.config.ts | 33 ++-- patches/tsup@8.5.1.patch | 66 ++++++- pnpm-lock.yaml | 30 +-- pnpm-workspace.yaml | 6 + 5 files changed, 295 insertions(+), 27 deletions(-) create mode 100644 packages/spec/scripts/dts-emit-order.pin.test.ts diff --git a/packages/spec/scripts/dts-emit-order.pin.test.ts b/packages/spec/scripts/dts-emit-order.pin.test.ts new file mode 100644 index 00000000000..d414d726be9 --- /dev/null +++ b/packages/spec/scripts/dts-emit-order.pin.test.ts @@ -0,0 +1,187 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// Two DTS passes over one source give the same bytes, whatever order rollup +// loads the modules in (#30061). +// +// This package's DTS pass (`BUILD_DTS=true tsup`, the docblock above +// `mainConfig` in `tsup.config.ts`) runs every entry through ONE ts.Program. +// Its checker creates each type once, on first use, and the declaration +// printer writes union members, and the object members derived from them, in +// type-creation order. tsup's bundled rollup-plugin-dts emitted each file when +// rollup TRANSFORMED it, and rollup transforms in the order its async loads +// finish, which differs between runs. So whichever file came first decided the +// order of every union it shared with a later one. On `main` at 1f13eb31, two +// clean builds of the same tree differed in 18 of 134 declaration files, chunk +// names included, with the same public surface. +// +// `patches/tsup@8.5.1.patch` now emits every source file once, in the +// program's own file order, before rollup is handed any of them. This file +// pins that with the smallest tree that shows the defect: two entries that +// infer the same three string literals in opposite orders. It runs the real +// tsup CLI twice and forces the two runs to load the entries in opposite +// orders, by delaying the read of one entry in each run. +// +// The two controls make the equality mean something: +// - the fixture really is order-sensitive under this TypeScript: emitting +// it in the two orders by hand gives two different texts. If a TypeScript +// release starts sorting union members, this control fails, so the pin +// cannot go quietly vacuous; +// - the delay really reached rollup's loader: each run's read log shows the +// entries loaded in the order this file asked for. +// +// Why a fixture and not two builds of this package: one DTS pass of this +// package takes ~45 s and ~3.5 GB of RSS, and of four unforced builds of +// `main` measured on #30061 three agreed byte for byte. Two full builds would +// double that cost in CI and still pass a regression most of the time. The +// forced order turns the same defect into a failure on every run. + +import { execFileSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import ts from 'typescript'; +import { afterAll, describe, expect, it } from 'vitest'; + +/** This package's root. Every read and write below stays inside it. */ +const PKG_DIR = path.resolve(__dirname, '..'); +const CACHE_DIR = path.join(PKG_DIR, 'node_modules', '.cache'); +mkdirSync(CACHE_DIR, { recursive: true }); +const WORK_DIR = mkdtempSync(path.join(CACHE_DIR, 'dts-emit-order-')); +afterAll(() => rmSync(WORK_DIR, { recursive: true, force: true })); + +/** The tsup CLI this package's `build` script runs. */ +const TSUP_CLI = path.join( + path.dirname(createRequire(path.join(PKG_DIR, 'package.json')).resolve('tsup/package.json')), + 'dist', + 'cli-default.js', +); + +const ENTRIES = ['a.ts', 'b.ts'] as const; + +// Each entry infers the union of the same three literals, written in opposite +// orders. Whichever file the checker reads first creates the three literal +// types, and their creation order is the order both declarations print. +const SOURCES: Record = { + 'lib.ts': 'export function oneOf(...values: T[]): T {\n return values[0];\n}\n', + 'a.ts': "import { oneOf } from './lib';\nexport const a = oneOf('zeta', 'alpha', 'mid');\n", + 'b.ts': "import { oneOf } from './lib';\nexport const b = oneOf('mid', 'alpha', 'zeta');\n", +}; + +// tsup sets `baseUrl`, which TypeScript 6 reports as deprecated. +const TSCONFIG = { + compilerOptions: { + target: 'ES2022', + module: 'ESNext', + moduleResolution: 'Bundler', + strict: true, + declaration: true, + skipLibCheck: true, + ignoreDeprecations: '6.0', + }, + include: ['src'], +}; + +// Loaded into tsup's DTS worker through NODE_OPTIONS: delays rollup's read of +// the files named in DTS_ORDER_DELAY, and logs every fixture read in the +// order it finishes. +const DELAY_PRELOAD = ` +const fsp = require('node:fs/promises'); +const path = require('node:path'); +const slow = new Set((process.env.DTS_ORDER_DELAY || '').split(',').filter(Boolean)); +const log = process.env.DTS_ORDER_LOG; +const src = process.env.DTS_ORDER_SRC; +const readFile = fsp.readFile; +fsp.readFile = async function (file, ...rest) { + const name = typeof file === 'string' && path.dirname(file) === src ? path.basename(file) : undefined; + if (name && slow.has(name)) await new Promise((resolve) => setTimeout(resolve, 400)); + const result = await readFile.call(this, file, ...rest); + if (name && log) require('node:fs').appendFileSync(log, name + '\\n'); + return result; +}; +`; + +const FIXTURE_DIR = path.join(WORK_DIR, 'fixture'); +const SRC_DIR = path.join(FIXTURE_DIR, 'src'); +mkdirSync(SRC_DIR, { recursive: true }); +for (const [name, text] of Object.entries(SOURCES)) writeFileSync(path.join(SRC_DIR, name), text); +writeFileSync(path.join(FIXTURE_DIR, 'tsconfig.json'), JSON.stringify(TSCONFIG, null, 2)); +writeFileSync(path.join(FIXTURE_DIR, 'package.json'), '{ "name": "dts-emit-order-fixture", "private": true }\n'); +const PRELOAD = path.join(WORK_DIR, 'delay-preload.cjs'); +writeFileSync(PRELOAD, DELAY_PRELOAD); + +/** One DTS-only tsup pass with `delayed` read late; returns the output tree and the read order. */ +function dtsPass(label: string, delayed: string): { files: Map; readOrder: string[] } { + const outDir = path.join(WORK_DIR, label); + const log = path.join(WORK_DIR, `${label}.reads`); + writeFileSync(log, ''); + execFileSync( + process.execPath, + [ + TSUP_CLI, + ...ENTRIES.map((entry) => `src/${entry}`), + '--dts-only', + '--format', + 'esm,cjs', + '--tsconfig', + 'tsconfig.json', + '--out-dir', + outDir, + '--silent', + ], + { + cwd: FIXTURE_DIR, + env: { + ...process.env, + NODE_OPTIONS: `--require ${PRELOAD}`, + DTS_ORDER_DELAY: delayed, + DTS_ORDER_LOG: log, + DTS_ORDER_SRC: SRC_DIR, + }, + stdio: 'pipe', + }, + ); + const files = new Map(); + for (const name of readdirSync(outDir).sort()) files.set(name, readFileSync(path.join(outDir, name), 'utf8')); + const readOrder = readFileSync(log, 'utf8') + .split('\n') + .filter((name) => (ENTRIES as readonly string[]).includes(name)); + return { files, readOrder }; +} + +/** The fixture's declarations, emitted by hand in `order` through one program. */ +function emitInOrder(order: readonly string[]): string { + const options: ts.CompilerOptions = { ...ts.convertCompilerOptionsFromJson(TSCONFIG.compilerOptions, FIXTURE_DIR).options, emitDeclarationOnly: true }; + const roots = ENTRIES.map((entry) => path.join(SRC_DIR, entry)); + const program = ts.createProgram(roots, options); + let text = ''; + for (const entry of order) { + program.emit(program.getSourceFile(path.join(SRC_DIR, entry)), (_, out) => { + text += out; + }, undefined, true); + } + return text.split('\n').filter((line) => line.startsWith('export declare const')).sort().join('\n'); +} + +describe('the DTS pass emits the same bytes whatever order rollup loads modules in (#30061)', () => { + it('the fixture is order-sensitive: emitting a then b and b then a gives different declarations', () => { + const aFirst = emitInOrder(['a.ts', 'b.ts']); + const bFirst = emitInOrder(['b.ts', 'a.ts']); + expect(aFirst).toContain('"zeta" | "alpha" | "mid"'); + expect(bFirst).toContain('"mid" | "alpha" | "zeta"'); + expect(aFirst, 'this TypeScript no longer prints unions in creation order, so the pin below proves nothing').not.toBe(bFirst); + }); + + it('two tsup DTS passes that load the entries in opposite orders write the same files, byte for byte', () => { + const aLate = dtsPass('a-late', 'a.ts'); + const bLate = dtsPass('b-late', 'b.ts'); + + // The control: the delay reached rollup's loader, so the two passes really + // loaded the entries in opposite orders. + expect(aLate.readOrder, 'the a.ts delay did not reach rollup').toEqual(['b.ts', 'a.ts']); + expect(bLate.readOrder, 'the b.ts delay did not reach rollup').toEqual(['a.ts', 'b.ts']); + + expect([...aLate.files.keys()]).toEqual(['a.d.mts', 'a.d.ts', 'b.d.mts', 'b.d.ts']); + expect(aLate.files.get('a.d.ts')).toMatch(/declare const a: "(zeta|mid)" \| "alpha" \| "(mid|zeta)";/); + expect(Object.fromEntries(bLate.files)).toEqual(Object.fromEntries(aLate.files)); + }); +}); diff --git a/packages/spec/tsup.config.ts b/packages/spec/tsup.config.ts index 9c9b83dcb09..e7a5cf1def0 100644 --- a/packages/spec/tsup.config.ts +++ b/packages/spec/tsup.config.ts @@ -441,17 +441,28 @@ async function modulesReachingTheSeam( * `build` script went 189s → 87s. For scale, `tsc --noEmit` over the whole * package peaked at 1103 MB live on 8cdbe0c6e5. * - * NOT BYTE-STABLE: this pass does not emit the same bytes twice. TypeScript - * prints union members, and the members of object types derived from them, in - * type-creation order, and that order follows emit order. Three runs of one - * commit gave three different trees, and rollup's content-hashed chunk names - * moved with them. The one-program pass also reorders top-level statements - * and `import`/`export` specifiers, and names one shared chunk after a - * different module (`data-engine` → `analytics.zod`). Normalise all of those - * (sort union and property-only type-literal members, statements and - * specifiers; strip chunk hashes; pair chunks by content), and the unpatched - * and patched trees are the same tree: 130 files, 0 differing. ⇒ Compare two - * declaration trees in that order-insensitive form, never by a byte digest. + * BYTE-STABLE FOR ONE TREE, ORDER-SENSITIVE ACROSS TREES. TypeScript prints + * union members, and the members of object types derived from them, in + * type-creation order, and in one shared program that order follows emit + * order. The bundled rollup-plugin-dts emitted each file when rollup + * transformed it, in the order rollup's async loads happened to finish, so + * two builds of one commit wrote different bytes and chunk names (18 of 134 + * files on 1f13eb31, #30061). The same `patches/tsup@8.5.1.patch` now emits + * every source file once, in the program's own file order, before rollup + * reads any of them. Two builds of one tree are byte-identical, chunk names + * included, and `scripts/dts-emit-order.pin.test.ts` pins that. + * + * The order is still the PROGRAM's. A change that moves the program's file + * order (an entry added or reordered, an import moved, a module split in two) + * can reorder members in files it never touched. The one-program pass also + * reordered top-level statements and `import`/`export` specifiers against the + * per-entry programs, and named one shared chunk after a different module + * (`data-engine` → `analytics.zod`). Normalise all of those (sort union and + * property-only type-literal members, statements and specifiers; strip chunk + * hashes; pair chunks by content), and the unpatched and patched trees are the + * same tree: 130 files, 0 differing. ⇒ Compare the declaration trees of two + * DIFFERENT sources in that order-insensitive form, never by a byte digest; a + * byte digest answers only "same source, same bytes?". * * If this pass starts failing with `ERR_WORKER_OUT_OF_MEMORY`, the live type * graph has outgrown 6144 — that is a loud, actionable failure and the point of diff --git a/patches/tsup@8.5.1.patch b/patches/tsup@8.5.1.patch index d33719e72db..3e69599e5ad 100644 --- a/patches/tsup@8.5.1.patch +++ b/patches/tsup@8.5.1.patch @@ -1,5 +1,5 @@ diff --git a/dist/rollup.js b/dist/rollup.js -index e128b61b9558318b9f86dc11d72c31f09a8eb7db..35f85e39c2af6c645fb2d3da115742df445d2343 100644 +index e128b61b9558318b9f86dc11d72c31f09a8eb7db..b69ec9c39d208a378cdc96b3886e4279c34b3b27 100644 --- a/dist/rollup.js +++ b/dist/rollup.js @@ -4915,6 +4915,11 @@ var require_rollup_plugin_dts = _chunkTWFEYLU4js.__commonJS.call(void 0, { @@ -14,3 +14,67 @@ index e128b61b9558318b9f86dc11d72c31f09a8eb7db..35f85e39c2af6c645fb2d3da115742df const { fileNames, options, errors } = configByPath.get(cacheKey); dtsFiles = fileNames.filter((name) => DTS_EXTENSIONS.test(name)); if (errors.length) { +@@ -6351,6 +6356,38 @@ export { ${[...exportedNames].join(", ")} }; + return null; + } + } ++ var emittedInProgramOrder = /* @__PURE__ */ new WeakMap(); ++ function emitInProgramOrder(program) { ++ // objectstack patch: one ts.Program serves every entry, so its checker ++ // creates each type once, on first use, and the declaration printer ++ // writes union members (and the object members derived from them) in ++ // type-creation order. Emitting per file in rollup's transform order -- ++ // set by async module loading, different on every run -- made the ++ // published .d.ts bytes and chunk hashes differ between two builds of ++ // one source. Emitting every source file once, in the program's own ++ // (deterministic) file order, before any is handed to rollup, fixes the ++ // type-creation order. Each file is still emitted exactly once. ++ let emitted = emittedInProgramOrder.get(program); ++ if (emitted) return emitted; ++ emitted = /* @__PURE__ */ new Map(); ++ emittedInProgramOrder.set(program, emitted); ++ for (const sourceFile of program.getSourceFiles()) { ++ if (sourceFile.isDeclarationFile || program.isSourceFileFromExternalLibrary(sourceFile) || !TS_EXTENSIONS.test(sourceFile.fileName)) { ++ continue; ++ } ++ let declarationText; ++ const { emitSkipped, diagnostics } = program.emit( ++ sourceFile, ++ (_, text) => { ++ declarationText = text; ++ }, ++ void 0, ++ true ++ ); ++ emitted.set(sourceFile.fileName, { declarationText, emitSkipped, diagnostics }); ++ } ++ return emitted; ++ } + var plugin = (options = {}) => { + const transformPlugin = transform(); + const ctx = { programs: [], resolvedOptions: resolveDefaultOptions(options) }; +@@ -6413,15 +6450,21 @@ export { ${[...exportedNames].join(", ")} }; + watchFiles(module2); + const declarationId = id.replace(TS_EXTENSIONS, dts); + let generated; +- const { emitSkipped, diagnostics } = module2.program.emit( ++ // objectstack patch: read this file's declaration off the ++ // program's one ordered emit (emitInProgramOrder) instead of ++ // emitting it here, in whatever order rollup loads modules. ++ const { declarationText, emitSkipped, diagnostics } = emitInProgramOrder(module2.program).get(module2.source.fileName) || module2.program.emit( + module2.source, +- (_, declarationText) => { +- generated = transformPlugin.transform.call(this, declarationText, declarationId); ++ (_, text) => { ++ generated = transformPlugin.transform.call(this, text, declarationId); + }, + void 0, + // cancellationToken + true + ); ++ if (declarationText !== void 0) { ++ generated = transformPlugin.transform.call(this, declarationText, declarationId); ++ } + if (emitSkipped) { + const errors = diagnostics.filter((diag) => diag.category === ts2.DiagnosticCategory.Error); + if (errors.length) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5c092e5ce0..9da1e2dd6f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,7 +62,7 @@ patchedDependencies: hash: 41d44316f54bd1c52774d12640dfe9062604489561eee39886b1912c4197e822 path: patches/@radix-ui__react-focus-scope.patch tsup@8.5.1: - hash: d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4 + hash: 3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8 path: patches/tsup@8.5.1.patch tsx@4.23.15: hash: 4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6 @@ -98,7 +98,7 @@ importers: version: 28.0.0 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) tsx: specifier: ^4.23.15 version: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) @@ -421,7 +421,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -518,7 +518,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -540,7 +540,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1084,7 +1084,7 @@ importers: version: 4.0.2 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1486,7 +1486,7 @@ importers: version: 4.10.2 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1508,7 +1508,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1560,7 +1560,7 @@ importers: version: 8.23.1 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -1653,7 +1653,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -2900,7 +2900,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -4174,7 +4174,7 @@ importers: version: 26.6.5 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -4454,7 +4454,7 @@ importers: version: 2.4.9 tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -4482,7 +4482,7 @@ importers: version: link:../test-support tsup: specifier: ^8.5.1 - version: 8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) + version: 8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -21854,7 +21854,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(patch_hash=d1c8d6bd9db5a776a639cd37b802f8b962f7846a6e3758af4478b0e0bde082e4)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1): + tsup@8.5.1(patch_hash=3b8e24cc030bee95729ead29db742cd9f65872aad110b4c59c6c424a15373ed8)(@microsoft/api-extractor@7.58.2(@types/node@26.6.5))(@swc/core@1.15.33(@swc/helpers@0.5.23))(jiti@2.7.0)(postcss@8.5.32)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(typescript@6.0.3)(yaml@2.9.1): dependencies: bundle-require: 5.1.0(esbuild@0.28.2) cac: 6.7.14 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 20d5fc16473..21fc8128794 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -46,6 +46,12 @@ onlyBuiltDependencies: # here have more than one DTS entry; @objectstack/spec had 18 programs # and now has one, and its DTS pass went from 4997 MB to 882 MB of live # heap. Every one of the twelve emits the same types as before. +# The patch also emits every source file once, in the program's own +# file order, before rollup transforms any (#30061). The plugin emitted +# each file in rollup's transform order, which async loading changes +# from run to run; TypeScript prints union members in type-creation +# order, so two builds of one commit wrote different .d.ts bytes and +# chunk names. packages/spec/scripts/dts-emit-order.pin.test.ts pins it. # The key names the exact version. When the installed tsup is any other # version, the patch goes unused and `pnpm install` fails # (ERR_PNPM_UNUSED_PATCH), so a tsup upgrade cannot drop the patch