fix(build): rename threadSummary.ts — case-collision broke the Windows release
threadSummary.ts (pure helpers) and ThreadSummary.tsx (chip component) lived in the same directory differing only by case. On the case-insensitive Windows release runner, RoomTimeline's extensionless import of ./thread/ThreadSummary resolved .ts BEFORE .tsx and matched the helper module → rolldown MISSING_EXPORT "ThreadSummary" — invisible on every Linux/macOS build (and the cause of the earlier masked pdf.worker failure). Helper module renamed to threadSummaryData.ts (+ test), 3 importers updated. Prevention: new caseCollision.test.ts walks src/ and fails on any same-directory names differing only by case (extensionless compare, so Foo.tsx vs foo.ts is caught) — verified it fails on the pre-rename tree. Runs in the hard CI gate. Gates: tsc clean, eslint/prettier clean, build OK, 658/659 tests (1 IDB skip). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/**
|
||||
* Guard against same-directory filenames that differ only by case (e.g.
|
||||
* `threadSummary.ts` vs `ThreadSummary.tsx`). On case-insensitive filesystems
|
||||
* (the Windows release runner) an extensionless import of one can resolve to
|
||||
* the OTHER file — rolldown tries `.ts` before `.tsx` — producing
|
||||
* MISSING_EXPORT failures that never reproduce on the Linux/macOS machines the
|
||||
* project is developed and web-deployed on. This broke the desktop release
|
||||
* build twice before being diagnosed; this test makes the collision a local,
|
||||
* immediate failure instead.
|
||||
*/
|
||||
const findCaseCollisions = (dir: string, collisions: string[]): void => {
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
const seen = new Map<string, string>();
|
||||
entries.forEach((entry) => {
|
||||
// Compare basenames without extension: `Foo.tsx` collides with `foo.ts`
|
||||
// because module resolution is extensionless.
|
||||
const stem = entry.isDirectory() ? entry.name : entry.name.replace(/\.[^.]+$/, '');
|
||||
const key = stem.toLowerCase();
|
||||
const existing = seen.get(key);
|
||||
if (existing !== undefined && existing !== stem) {
|
||||
collisions.push(`${dir}: "${existing}" vs "${stem}"`);
|
||||
}
|
||||
if (existing === undefined) seen.set(key, stem);
|
||||
if (entry.isDirectory()) {
|
||||
findCaseCollisions(join(dir, entry.name), collisions);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('no same-directory filenames differing only by case under src/', () => {
|
||||
const collisions: string[] = [];
|
||||
findCaseCollisions('src', collisions);
|
||||
assert.deepEqual(
|
||||
collisions,
|
||||
[],
|
||||
`Case-colliding names break Windows builds:\n${collisions.join('\n')}`,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user