Files
buzz/scripts/check-file-sizes-core.mjs
T
545bb46b82 fix(desktop): make lint and unit-test gates work on Windows (#2943)
## Summary

On a Windows checkout the desktop quality gate does not work. This fixes
four defects in it. Two checks report success without examining
anything, one fails on every file, and one reports violations that its
own allowlist already covers.

**1. `pnpm test` finds no tests and still exits 0.** The `test` script
quotes the glob with single quotes. On Windows pnpm runs scripts through
`cmd.exe`, which does not strip single quotes, so node receives them as
part of the pattern and matches nothing. The run prints `# tests 0` and
exits 0 — a silent green. Double quotes are stripped by `cmd.exe` and by
POSIX shells alike, so Linux CI behaviour is unchanged.

**2. Every text file is checked out as CRLF.** There is no
`.gitattributes`, and `core.autocrlf=true` is the Git for Windows
default. Biome formats with LF, so `biome check .` fails on 1632 of 1633
files. `desktop/src/features/messages/ui/virtuaWheelModePatch.test.mjs`
fails too, because it matches `patches/*.patch` with `\n`-joined
patterns. The stored blobs are already LF, so `eol=lf` adds no
renormalisation churn — `git status` stays clean after the change.

**3. `check:px-text` never finds its own allowlist.**
`scripts/check-px-text-core.mjs` builds the key from `path.relative`,
which returns `\` separators on Windows, while the allowlist in
`desktop/scripts/check-px-text.mjs` is written with `/`. Nothing
matches, so the check reports 5 false violations on a clean tree.

**4. `check:file-sizes` examines nothing at all.** `findRule` compares
against `` `${rule.root}${path.sep}` ``. The roots are multi-segment
(`src/app`, `src/features`, `src-tauri/src`), so on Windows `src/app\`
never matches `src\app\...`. No rule matches any file: the check walks 0
of 1097 files and exits 0.

`scripts/check-pubkey-truncation-core.mjs` already normalises paths this
way (`relativePath.split(path.sep).join("/")`). This applies the same
idiom to the other two.

### Related issue

None found — no open issue covers this. The closest open PR is #2758,
which fixes a fifth Windows defect in `desktop/test-loader-hooks.mjs`;
it is required before the desktop unit tests can pass here, and it does
not overlap with these files. I checked the changed-file list of every
open PR: none touch `.gitattributes`, `desktop/package.json`,
`scripts/check-px-text-core.mjs` or `scripts/check-file-sizes-core.mjs`.

### Testing

Windows 11 (10.0.26200), node 22.17.1, pnpm 11.4.0, clean checkout with
the default `core.autocrlf=true`.

| Command | Before | After |
| --- | --- | --- |
| `pnpm test` | `# tests 0`, exit 0 | 374 test files discovered, exit 1
|
| `biome check .` | 1632 of 1633 files fail | 1633 checked, 0 errors |
| `pnpm check:px-text` | 5 false violations | passes |
| `pnpm check:file-sizes` | 0 of 1097 files examined, exit 0 | 1097
examined |

`check:file-sizes` now reports `src-tauri/src/managed_agents/runtime.rs:
2220 lines (limit 2216)`. That violation is pre-existing and not
introduced here — `main` currently fails on the same line in CI (Desktop
Core, run 30185213010, commit c2a4ee7). Before this change Windows
reported success while CI was red; now the Windows result agrees with
CI.

Desktop unit tests still fail on Windows until #2758 lands. With #2758
applied on top of this branch the full suite passes: 3515 tests, 0
failures. This change stops hiding those failures rather than fixing
them.

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-27 13:00:25 -04:00

115 lines
3.3 KiB
JavaScript

import { promises as fs } from "node:fs";
import path from "node:path";
/**
* Shared file-size check used by the desktop and web workspaces.
*
* Each app supplies its own `rules` (which roots/extensions to scan) and an
* optional `overrides` map of TEMP per-file ceilings. Everything else — the
* walk, the line count, the violation report, the non-zero exit — lives here so
* the two apps can never drift.
*/
// `rules[].root` and the `overrides` keys are authored with `/`, but
// path.relative yields `\` on Windows — so every comparison against them has
// to happen in posix form or it silently matches nothing.
function toPosixPath(relativePath) {
return relativePath.split(path.sep).join("/");
}
async function walkFiles(directory) {
const entries = await fs.readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
return walkFiles(fullPath);
}
return [fullPath];
}),
);
return files.flat();
}
function findRule(rules, relativePath) {
const posixPath = toPosixPath(relativePath);
return rules.find((rule) => posixPath.startsWith(`${rule.root}/`));
}
function countLines(content) {
if (content.length === 0) {
return 0;
}
return content.split(/\r?\n/).length;
}
/**
* @param {object} options
* @param {string} options.projectRoot Absolute path the rule roots resolve against.
* @param {Array<{root: string, extensions: Set<string>, maxLines: number}>} options.rules
* @param {string} options.label Human label for the failure header (e.g. "Desktop").
* @param {Map<string, number>} [options.overrides] TEMP per-file ceilings, keyed by path relative to projectRoot.
* @param {string} options.scriptPath Path mentioned in the failure hint where overrides live.
*/
export async function runFileSizeCheck({
projectRoot,
rules,
label,
overrides = new Map(),
scriptPath,
}) {
const candidateFiles = (
await Promise.all(
rules.map((rule) => {
const dir = path.join(projectRoot, rule.root);
return fs
.access(dir)
.then(() => walkFiles(dir))
.catch(() => []);
}),
)
).flat();
const violations = [];
for (const filePath of candidateFiles) {
const relativePath = path.relative(projectRoot, filePath);
const rule = findRule(rules, relativePath);
if (!rule) {
continue;
}
const extension = path.extname(relativePath);
if (!rule.extensions.has(extension)) {
continue;
}
const limit = overrides.get(toPosixPath(relativePath)) ?? rule.maxLines;
const content = await fs.readFile(filePath, "utf8");
const lineCount = countLines(content);
if (lineCount > limit) {
violations.push({
limit,
lineCount,
relativePath: toPosixPath(relativePath),
});
}
}
if (violations.length > 0) {
console.error(`${label} file size check failed:`);
for (const violation of violations) {
console.error(
`- ${violation.relativePath}: ${violation.lineCount} lines (limit ${violation.limit})`,
);
}
console.error(
`Split the file or add a narrowly scoped exception in \`${scriptPath}\`.`,
);
process.exit(1);
}
}