Skip to content

Commit 2168447

Browse files
fix(build): emit spec .d.ts in program order so two builds of one sha match byte for byte (#30131)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB9ASXAhqQeMSZdJgT2FgPH5)_ Part of #30061 (the seat closes the card on landing; a `claude/project-thread-*` branch cannot satisfy `check:closing-target-claim`). ## 维护者速读 - 改了什么:tsup 补丁里的 rollup-plugin-dts 改为在 rollup 读取任何模块之前,按 ts.Program 自身的文件顺序把每个源文件的 .d.ts 一次性生成好;另加一个 pin 测试。spec 源码与 schema 一行未动。 - 为什么改:同一份 main 源码构建两次,spec 的 134 个 .d.ts 中有 18 个字节不同(含 chunk 文件名),拆分大文件的 PR 因此只能两两配对读声明证据。根因是生成顺序跟着 rollup 的异步加载顺序走,每次都不一样。 - 风险与回滚:补丁作用于仓内所有走 tsup DTS 的包;每个文件仍只生成一次,spec 的 DTS 耗时 44s 不变。修复后的产物与 main 已经会产出的两种状态之一逐字节相同。回滚 = revert 本 PR(补丁、锁文件哈希一起回)。 - 先看:`patches/tsup@8.5.1.patch`(新增的 emitInProgramOrder 与 transform 钩子里的读取);`packages/spec/scripts/dts-emit-order.pin.test.ts`;`packages/spec/tsup.config.ts` 中改写的文档块。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --project local scripts/dts-emit-order.pin.test.ts`,预期 2 个测试通过,约 5 秒。 - 需要你做的:无,席位落地。 Clause-②: no Metadata-protocol: none ## What changes **The cause.** `@objectstack/spec`'s DTS pass runs every entry through one `ts.Program` (the existing tsup patch). Its checker creates each type once, on first use, and TypeScript prints union members, and the object members derived from them, in type-creation order. tsup's bundled rollup-plugin-dts emitted each file's declarations inside rollup's `transform` hook, so whichever file rollup transformed first created the shared literal types and fixed the member order of every later file. Rollup transforms in the order its async `fs.promises.readFile` loads finish, which changes from run to run. Measured: in two DTS passes of one tree the transform order differed at 389 of 445 positions, starting with the first module. The parallel-build suspect does not hold. tsup runs the DTS pass as ONE rollup build in one worker, transforms each module once, and writes the `esm` and `cjs` outputs from that one module graph. The `.d.ts` and `.d.mts` twins carry the same member order within a run. The order moves between runs, not between the formats. **The fix.** `patches/tsup@8.5.1.patch` (regenerated with `pnpm patch` / `pnpm patch-commit`; the lockfile's `patch_hash` moves with it) adds `emitInProgramOrder(program)`: on the first transform, it emits every non-declaration, non-external source file of the program once, in `program.getSourceFiles()` order, and stores each file's text and diagnostics. The transform hook then reads the stored text instead of emitting. Each file is still emitted exactly once, and the `noEmitOnError` error path is unchanged. The first hunk, which gives every entry one program, is unchanged. **Docs.** `packages/spec/tsup.config.ts`'s "NOT BYTE-STABLE" paragraph is rewritten: one tree now gives one set of bytes, but the order is the program's, so comparing two DIFFERENT sources still needs the order-insensitive reading. `pnpm-workspace.yaml`'s note on the tsup patch gains the second change. **The pin.** `packages/spec/scripts/dts-emit-order.pin.test.ts` (local project, ~4 s) builds a two-entry fixture through the real tsup CLI twice. A preload delays rollup's read of `a.ts` in one run and `b.ts` in the other, so the two runs transform the entries in opposite orders, and the test requires byte-equal output (`.d.ts` and `.d.mts`). Two controls: the fixture is order-sensitive under this TypeScript (a hand emit in the two orders gives `"zeta" | "alpha" | "mid"` and `"mid" | "alpha" | "zeta"`), and each run's read log shows the forced order reached rollup. Why a fixture and not two full builds in CI: one spec DTS pass is ~45 s and ~3.5 GB RSS, and of four unforced builds of `main`, three agreed byte for byte. Two full builds would double the cost and still miss a regression most of the time. The forced order fails on every run. Its radius, measured: a commit changing only `patches/tsup@8.5.1.patch` and `pnpm-lock.yaml` makes `turbo ls --affected` report 131 packages, `@objectstack/spec` among them, and the root external-dependency hash is in turbo's global cache inputs. So a later tsup patch change re-runs this pin. **Changeset: `skip-changeset`.** Nothing published changes. The post-fix spec declaration tree is byte-identical, 134/134, to one of the two trees `main` already builds (`base1` below). `tsup.config.ts`, the test, `patches/` and the lockfile are not in any `files[]`. ## Tests - Pin: 2/2 pass. **Ablation:** with the installed `rollup.js` swapped for the pre-fix one (one-program hunk only), the byte-equality test fails (`expected { …(4) } to deeply equal { …(4) }`) and the order-sensitivity control still passes. Restored, sha256 checked equal to the fixed file. - `pnpm --filter @objectstack/spec test`: exit 0, 812/812 files, 24397 passed, 1 todo. - `NODE_OPTIONS=--max-old-space-size=6144 pnpm --filter @objectstack/spec typecheck`: exit 0. - `eslint --no-inline-config` on the two touched TypeScript files: exit 0. ## Acceptance notes - The patch serves every package that builds declarations with tsup here (twelve have more than one DTS entry). Their declaration member order now follows their program order too. Equivalent types, possibly a different one of their existing run-to-run orders on their next release. NOT MEASURED per package: reason, outside this card's spec measurement; no published file of theirs changes in this PR. - File surface: the claim named the spec d.ts build configuration under `packages/spec`. The rollup-plugin-dts behaviour lives in the root tsup patch, so the diff also touches `patches/tsup@8.5.1.patch`, `pnpm-lock.yaml` (the patch hash) and `pnpm-workspace.yaml` (comment only). - A move-only split now reads from ONE base/head pair. Measured on #29993's split: the tree with `d79eecfe`'s `packages/spec/src` change reverted and the current tree gave 134/134 byte-identical declarations under the fix. - The `--dts` check in `scripts/codemod-split-*.mjs` is untouched (#30105 is in flight there). ## Verification <details><summary>Spec builds, before and after (raw sha256 per declaration file)</summary> All on the tree of `1f13eb31` (current `main`), `rm -rf dist` then `pnpm build` per run; per-file sha256 lists in the project files under `issue-30061/`. | Pair | Differing of 134 | | --- | --- | | base1 vs base2 (before) | 18 | | two DTS-only passes before the fix (o1, o2) | 0, both equal to base1 | | head1 vs head2 (after) | 0 | | head1 vs base1 | 0 | | head1 vs base2 | 18 | | three DTS-only passes after the fix (f1, f2, f3) | 0, while the transform order differed in each | The 18 differing files before the fix: `ai`, `api`, `automation`, `contracts`, `kernel`, `marketplace`, `system` (`index.d.ts` and `.d.mts` each), and the `automation-api.zod-HASH` and `node-executor.zod-HASH` chunks (both formats, renamed between runs). Surface reading (`dtsnorm.mjs` from `issue-29987/`, base2 vs head1): same file set, 116 raw-equal, 18 differ only in union order, surface digest EQUAL in all 18. The fix moves no published surface; it picks one of `main`'s existing orders every time. DTS pass wall time: 44-47 s before, 43-47 s after (shared box). </details> <details><summary>Derived gates</summary> All on head `6d4463b4`. `node scripts/pm/dispatch-gates.mjs --commands` derived 66 families for this change set (the lockfile pulls in the wide set); each was run and its exit code recorded before any pipe (`issue-30061/gates-ran.list` in the project files). - 63 exit 0, among them `check:cross-package-test-inputs`, `check:test-source-alias`, `check:nul-bytes`, `check:published-files`, `check:dts-closure` (38/38 declared declaration files present), `check:type-check-coverage`, `check:type-check-debt`, `check:ci-filter-parity`, `check:pm-governed-merges` and spec's `check:liveness`, `check:llms-txt`, `check:strictness-ledger`, `check:variant-docs`, `check:empty-state`. - 3 NOT MEASURED, exit 3 (the gate refused its own prerequisite): `check-plugin-teardown-shape.mjs --self-test` (shallow clone, wants `git fetch --unshallow`), `check:dual-build-cjs-loads` and `check:lean-entry-closure` (both want the whole workspace built; only `packages/spec` was). CI runs all three. - `dispatch-gates --ran`: exit 0, 66 derived, 63 run, 3 NOT-MEASURED, 0 UNRUN. - `check-dev-prereqs` (local-only family): NOT MEASURED, exit 1 because 35 other workspace packages are unbuilt in this container; spec's own build stamped its prerequisites (`check-dev-prereqs --stamp` inside `pnpm build`, exit 0). </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzeza8Qg2GQUtJQtxRW)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a9fb14b commit 2168447

5 files changed

Lines changed: 295 additions & 27 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Two DTS passes over one source give the same bytes, whatever order rollup
4+
// loads the modules in (#30061).
5+
//
6+
// This package's DTS pass (`BUILD_DTS=true tsup`, the docblock above
7+
// `mainConfig` in `tsup.config.ts`) runs every entry through ONE ts.Program.
8+
// Its checker creates each type once, on first use, and the declaration
9+
// printer writes union members, and the object members derived from them, in
10+
// type-creation order. tsup's bundled rollup-plugin-dts emitted each file when
11+
// rollup TRANSFORMED it, and rollup transforms in the order its async loads
12+
// finish, which differs between runs. So whichever file came first decided the
13+
// order of every union it shared with a later one. On `main` at 1f13eb31, two
14+
// clean builds of the same tree differed in 18 of 134 declaration files, chunk
15+
// names included, with the same public surface.
16+
//
17+
// `patches/tsup@8.5.1.patch` now emits every source file once, in the
18+
// program's own file order, before rollup is handed any of them. This file
19+
// pins that with the smallest tree that shows the defect: two entries that
20+
// infer the same three string literals in opposite orders. It runs the real
21+
// tsup CLI twice and forces the two runs to load the entries in opposite
22+
// orders, by delaying the read of one entry in each run.
23+
//
24+
// The two controls make the equality mean something:
25+
// - the fixture really is order-sensitive under this TypeScript: emitting
26+
// it in the two orders by hand gives two different texts. If a TypeScript
27+
// release starts sorting union members, this control fails, so the pin
28+
// cannot go quietly vacuous;
29+
// - the delay really reached rollup's loader: each run's read log shows the
30+
// entries loaded in the order this file asked for.
31+
//
32+
// Why a fixture and not two builds of this package: one DTS pass of this
33+
// package takes ~45 s and ~3.5 GB of RSS, and of four unforced builds of
34+
// `main` measured on #30061 three agreed byte for byte. Two full builds would
35+
// double that cost in CI and still pass a regression most of the time. The
36+
// forced order turns the same defect into a failure on every run.
37+
38+
import { execFileSync } from 'node:child_process';
39+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
40+
import { createRequire } from 'node:module';
41+
import path from 'node:path';
42+
import ts from 'typescript';
43+
import { afterAll, describe, expect, it } from 'vitest';
44+
45+
/** This package's root. Every read and write below stays inside it. */
46+
const PKG_DIR = path.resolve(__dirname, '..');
47+
const CACHE_DIR = path.join(PKG_DIR, 'node_modules', '.cache');
48+
mkdirSync(CACHE_DIR, { recursive: true });
49+
const WORK_DIR = mkdtempSync(path.join(CACHE_DIR, 'dts-emit-order-'));
50+
afterAll(() => rmSync(WORK_DIR, { recursive: true, force: true }));
51+
52+
/** The tsup CLI this package's `build` script runs. */
53+
const TSUP_CLI = path.join(
54+
path.dirname(createRequire(path.join(PKG_DIR, 'package.json')).resolve('tsup/package.json')),
55+
'dist',
56+
'cli-default.js',
57+
);
58+
59+
const ENTRIES = ['a.ts', 'b.ts'] as const;
60+
61+
// Each entry infers the union of the same three literals, written in opposite
62+
// orders. Whichever file the checker reads first creates the three literal
63+
// types, and their creation order is the order both declarations print.
64+
const SOURCES: Record<string, string> = {
65+
'lib.ts': 'export function oneOf<T extends string>(...values: T[]): T {\n return values[0];\n}\n',
66+
'a.ts': "import { oneOf } from './lib';\nexport const a = oneOf('zeta', 'alpha', 'mid');\n",
67+
'b.ts': "import { oneOf } from './lib';\nexport const b = oneOf('mid', 'alpha', 'zeta');\n",
68+
};
69+
70+
// tsup sets `baseUrl`, which TypeScript 6 reports as deprecated.
71+
const TSCONFIG = {
72+
compilerOptions: {
73+
target: 'ES2022',
74+
module: 'ESNext',
75+
moduleResolution: 'Bundler',
76+
strict: true,
77+
declaration: true,
78+
skipLibCheck: true,
79+
ignoreDeprecations: '6.0',
80+
},
81+
include: ['src'],
82+
};
83+
84+
// Loaded into tsup's DTS worker through NODE_OPTIONS: delays rollup's read of
85+
// the files named in DTS_ORDER_DELAY, and logs every fixture read in the
86+
// order it finishes.
87+
const DELAY_PRELOAD = `
88+
const fsp = require('node:fs/promises');
89+
const path = require('node:path');
90+
const slow = new Set((process.env.DTS_ORDER_DELAY || '').split(',').filter(Boolean));
91+
const log = process.env.DTS_ORDER_LOG;
92+
const src = process.env.DTS_ORDER_SRC;
93+
const readFile = fsp.readFile;
94+
fsp.readFile = async function (file, ...rest) {
95+
const name = typeof file === 'string' && path.dirname(file) === src ? path.basename(file) : undefined;
96+
if (name && slow.has(name)) await new Promise((resolve) => setTimeout(resolve, 400));
97+
const result = await readFile.call(this, file, ...rest);
98+
if (name && log) require('node:fs').appendFileSync(log, name + '\\n');
99+
return result;
100+
};
101+
`;
102+
103+
const FIXTURE_DIR = path.join(WORK_DIR, 'fixture');
104+
const SRC_DIR = path.join(FIXTURE_DIR, 'src');
105+
mkdirSync(SRC_DIR, { recursive: true });
106+
for (const [name, text] of Object.entries(SOURCES)) writeFileSync(path.join(SRC_DIR, name), text);
107+
writeFileSync(path.join(FIXTURE_DIR, 'tsconfig.json'), JSON.stringify(TSCONFIG, null, 2));
108+
writeFileSync(path.join(FIXTURE_DIR, 'package.json'), '{ "name": "dts-emit-order-fixture", "private": true }\n');
109+
const PRELOAD = path.join(WORK_DIR, 'delay-preload.cjs');
110+
writeFileSync(PRELOAD, DELAY_PRELOAD);
111+
112+
/** One DTS-only tsup pass with `delayed` read late; returns the output tree and the read order. */
113+
function dtsPass(label: string, delayed: string): { files: Map<string, string>; readOrder: string[] } {
114+
const outDir = path.join(WORK_DIR, label);
115+
const log = path.join(WORK_DIR, `${label}.reads`);
116+
writeFileSync(log, '');
117+
execFileSync(
118+
process.execPath,
119+
[
120+
TSUP_CLI,
121+
...ENTRIES.map((entry) => `src/${entry}`),
122+
'--dts-only',
123+
'--format',
124+
'esm,cjs',
125+
'--tsconfig',
126+
'tsconfig.json',
127+
'--out-dir',
128+
outDir,
129+
'--silent',
130+
],
131+
{
132+
cwd: FIXTURE_DIR,
133+
env: {
134+
...process.env,
135+
NODE_OPTIONS: `--require ${PRELOAD}`,
136+
DTS_ORDER_DELAY: delayed,
137+
DTS_ORDER_LOG: log,
138+
DTS_ORDER_SRC: SRC_DIR,
139+
},
140+
stdio: 'pipe',
141+
},
142+
);
143+
const files = new Map<string, string>();
144+
for (const name of readdirSync(outDir).sort()) files.set(name, readFileSync(path.join(outDir, name), 'utf8'));
145+
const readOrder = readFileSync(log, 'utf8')
146+
.split('\n')
147+
.filter((name) => (ENTRIES as readonly string[]).includes(name));
148+
return { files, readOrder };
149+
}
150+
151+
/** The fixture's declarations, emitted by hand in `order` through one program. */
152+
function emitInOrder(order: readonly string[]): string {
153+
const options: ts.CompilerOptions = { ...ts.convertCompilerOptionsFromJson(TSCONFIG.compilerOptions, FIXTURE_DIR).options, emitDeclarationOnly: true };
154+
const roots = ENTRIES.map((entry) => path.join(SRC_DIR, entry));
155+
const program = ts.createProgram(roots, options);
156+
let text = '';
157+
for (const entry of order) {
158+
program.emit(program.getSourceFile(path.join(SRC_DIR, entry)), (_, out) => {
159+
text += out;
160+
}, undefined, true);
161+
}
162+
return text.split('\n').filter((line) => line.startsWith('export declare const')).sort().join('\n');
163+
}
164+
165+
describe('the DTS pass emits the same bytes whatever order rollup loads modules in (#30061)', () => {
166+
it('the fixture is order-sensitive: emitting a then b and b then a gives different declarations', () => {
167+
const aFirst = emitInOrder(['a.ts', 'b.ts']);
168+
const bFirst = emitInOrder(['b.ts', 'a.ts']);
169+
expect(aFirst).toContain('"zeta" | "alpha" | "mid"');
170+
expect(bFirst).toContain('"mid" | "alpha" | "zeta"');
171+
expect(aFirst, 'this TypeScript no longer prints unions in creation order, so the pin below proves nothing').not.toBe(bFirst);
172+
});
173+
174+
it('two tsup DTS passes that load the entries in opposite orders write the same files, byte for byte', () => {
175+
const aLate = dtsPass('a-late', 'a.ts');
176+
const bLate = dtsPass('b-late', 'b.ts');
177+
178+
// The control: the delay reached rollup's loader, so the two passes really
179+
// loaded the entries in opposite orders.
180+
expect(aLate.readOrder, 'the a.ts delay did not reach rollup').toEqual(['b.ts', 'a.ts']);
181+
expect(bLate.readOrder, 'the b.ts delay did not reach rollup').toEqual(['a.ts', 'b.ts']);
182+
183+
expect([...aLate.files.keys()]).toEqual(['a.d.mts', 'a.d.ts', 'b.d.mts', 'b.d.ts']);
184+
expect(aLate.files.get('a.d.ts')).toMatch(/declare const a: "(zeta|mid)" \| "alpha" \| "(mid|zeta)";/);
185+
expect(Object.fromEntries(bLate.files)).toEqual(Object.fromEntries(aLate.files));
186+
});
187+
});

‎packages/spec/tsup.config.ts‎

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -441,17 +441,28 @@ async function modulesReachingTheSeam(
441441
* `build` script went 189s → 87s. For scale, `tsc --noEmit` over the whole
442442
* package peaked at 1103 MB live on 8cdbe0c6e5.
443443
*
444-
* NOT BYTE-STABLE: this pass does not emit the same bytes twice. TypeScript
445-
* prints union members, and the members of object types derived from them, in
446-
* type-creation order, and that order follows emit order. Three runs of one
447-
* commit gave three different trees, and rollup's content-hashed chunk names
448-
* moved with them. The one-program pass also reorders top-level statements
449-
* and `import`/`export` specifiers, and names one shared chunk after a
450-
* different module (`data-engine` → `analytics.zod`). Normalise all of those
451-
* (sort union and property-only type-literal members, statements and
452-
* specifiers; strip chunk hashes; pair chunks by content), and the unpatched
453-
* and patched trees are the same tree: 130 files, 0 differing. ⇒ Compare two
454-
* declaration trees in that order-insensitive form, never by a byte digest.
444+
* BYTE-STABLE FOR ONE TREE, ORDER-SENSITIVE ACROSS TREES. TypeScript prints
445+
* union members, and the members of object types derived from them, in
446+
* type-creation order, and in one shared program that order follows emit
447+
* order. The bundled rollup-plugin-dts emitted each file when rollup
448+
* transformed it, in the order rollup's async loads happened to finish, so
449+
* two builds of one commit wrote different bytes and chunk names (18 of 134
450+
* files on 1f13eb31, #30061). The same `patches/tsup@8.5.1.patch` now emits
451+
* every source file once, in the program's own file order, before rollup
452+
* reads any of them. Two builds of one tree are byte-identical, chunk names
453+
* included, and `scripts/dts-emit-order.pin.test.ts` pins that.
454+
*
455+
* The order is still the PROGRAM's. A change that moves the program's file
456+
* order (an entry added or reordered, an import moved, a module split in two)
457+
* can reorder members in files it never touched. The one-program pass also
458+
* reordered top-level statements and `import`/`export` specifiers against the
459+
* per-entry programs, and named one shared chunk after a different module
460+
* (`data-engine` → `analytics.zod`). Normalise all of those (sort union and
461+
* property-only type-literal members, statements and specifiers; strip chunk
462+
* hashes; pair chunks by content), and the unpatched and patched trees are the
463+
* same tree: 130 files, 0 differing. ⇒ Compare the declaration trees of two
464+
* DIFFERENT sources in that order-insensitive form, never by a byte digest; a
465+
* byte digest answers only "same source, same bytes?".
455466
*
456467
* If this pass starts failing with `ERR_WORKER_OUT_OF_MEMORY`, the live type
457468
* graph has outgrown 6144 — that is a loud, actionable failure and the point of

‎patches/tsup@8.5.1.patch‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
diff --git a/dist/rollup.js b/dist/rollup.js
2-
index e128b61b9558318b9f86dc11d72c31f09a8eb7db..35f85e39c2af6c645fb2d3da115742df445d2343 100644
2+
index e128b61b9558318b9f86dc11d72c31f09a8eb7db..b69ec9c39d208a378cdc96b3886e4279c34b3b27 100644
33
--- a/dist/rollup.js
44
+++ b/dist/rollup.js
55
@@ -4915,6 +4915,11 @@ var require_rollup_plugin_dts = _chunkTWFEYLU4js.__commonJS.call(void 0, {
@@ -14,3 +14,67 @@ index e128b61b9558318b9f86dc11d72c31f09a8eb7db..35f85e39c2af6c645fb2d3da115742df
1414
const { fileNames, options, errors } = configByPath.get(cacheKey);
1515
dtsFiles = fileNames.filter((name) => DTS_EXTENSIONS.test(name));
1616
if (errors.length) {
17+
@@ -6351,6 +6356,38 @@ export { ${[...exportedNames].join(", ")} };
18+
return null;
19+
}
20+
}
21+
+ var emittedInProgramOrder = /* @__PURE__ */ new WeakMap();
22+
+ function emitInProgramOrder(program) {
23+
+ // objectstack patch: one ts.Program serves every entry, so its checker
24+
+ // creates each type once, on first use, and the declaration printer
25+
+ // writes union members (and the object members derived from them) in
26+
+ // type-creation order. Emitting per file in rollup's transform order --
27+
+ // set by async module loading, different on every run -- made the
28+
+ // published .d.ts bytes and chunk hashes differ between two builds of
29+
+ // one source. Emitting every source file once, in the program's own
30+
+ // (deterministic) file order, before any is handed to rollup, fixes the
31+
+ // type-creation order. Each file is still emitted exactly once.
32+
+ let emitted = emittedInProgramOrder.get(program);
33+
+ if (emitted) return emitted;
34+
+ emitted = /* @__PURE__ */ new Map();
35+
+ emittedInProgramOrder.set(program, emitted);
36+
+ for (const sourceFile of program.getSourceFiles()) {
37+
+ if (sourceFile.isDeclarationFile || program.isSourceFileFromExternalLibrary(sourceFile) || !TS_EXTENSIONS.test(sourceFile.fileName)) {
38+
+ continue;
39+
+ }
40+
+ let declarationText;
41+
+ const { emitSkipped, diagnostics } = program.emit(
42+
+ sourceFile,
43+
+ (_, text) => {
44+
+ declarationText = text;
45+
+ },
46+
+ void 0,
47+
+ true
48+
+ );
49+
+ emitted.set(sourceFile.fileName, { declarationText, emitSkipped, diagnostics });
50+
+ }
51+
+ return emitted;
52+
+ }
53+
var plugin = (options = {}) => {
54+
const transformPlugin = transform();
55+
const ctx = { programs: [], resolvedOptions: resolveDefaultOptions(options) };
56+
@@ -6413,15 +6450,21 @@ export { ${[...exportedNames].join(", ")} };
57+
watchFiles(module2);
58+
const declarationId = id.replace(TS_EXTENSIONS, dts);
59+
let generated;
60+
- const { emitSkipped, diagnostics } = module2.program.emit(
61+
+ // objectstack patch: read this file's declaration off the
62+
+ // program's one ordered emit (emitInProgramOrder) instead of
63+
+ // emitting it here, in whatever order rollup loads modules.
64+
+ const { declarationText, emitSkipped, diagnostics } = emitInProgramOrder(module2.program).get(module2.source.fileName) || module2.program.emit(
65+
module2.source,
66+
- (_, declarationText) => {
67+
- generated = transformPlugin.transform.call(this, declarationText, declarationId);
68+
+ (_, text) => {
69+
+ generated = transformPlugin.transform.call(this, text, declarationId);
70+
},
71+
void 0,
72+
// cancellationToken
73+
true
74+
);
75+
+ if (declarationText !== void 0) {
76+
+ generated = transformPlugin.transform.call(this, declarationText, declarationId);
77+
+ }
78+
if (emitSkipped) {
79+
const errors = diagnostics.filter((diag) => diag.category === ts2.DiagnosticCategory.Error);
80+
if (errors.length) {

0 commit comments

Comments
 (0)