* Updates website/src/pages/demo/_styles/rqb-mantine.css with the latest
* content from node_modules/@mantine/core/styles.css.
*
* Usage: bun utils/update-mantine-css.ts
*/
import { resolve } from 'node:path';
const ROOT = resolve(import.meta.dir, '..');
const STYLES_SRC = resolve(ROOT, 'node_modules/@mantine/core/styles.css');
const MANTINE_PKG = resolve(ROOT, 'node_modules/@mantine/core/package.json');
const TARGET = resolve(ROOT, 'website/src/pages/demo/_styles/rqb-mantine.css');
const RESET_MAX_LINES = 50;
const SANITY_THRESHOLD = 0.2;
function warn(msg: string) {
if (process.env.CI) {
console.error(`::warning file=website/src/pages/demo/_styles/rqb-mantine.css::${msg}`);
} else {
console.error(`\x1b[33mWARNING:\x1b[0m ${msg}`);
}
}
const version: string = (await Bun.file(MANTINE_PKG).json()).version;
const sourceLines = (await Bun.file(STYLES_SRC).text()).split('\n');
const RESET_LINE_PATTERN =
/^(:root|:host|html|body|\*|input|button|textarea|select|@media|}\s*$|$)/;
let braceDepth = 0;
let resetEndIndex = -1;
let lastTopLevelClose = -1;
for (let i = 0; i < sourceLines.length; i++) {
const line = sourceLines[i];
for (const ch of line) {
if (ch === '{') braceDepth++;
else if (ch === '}') braceDepth--;
}
if (braceDepth === 0 && line.trim() === '}') {
lastTopLevelClose = i;
}
if (lastTopLevelClose !== -1 && braceDepth === 0) {
let nextContentIndex = i + 1;
while (nextContentIndex < sourceLines.length && sourceLines[nextContentIndex].trim() === '') {
nextContentIndex++;
}
const nextLine = sourceLines[nextContentIndex]?.trim() ?? '';
if (nextLine !== '' && !RESET_LINE_PATTERN.test(nextLine)) {
resetEndIndex = lastTopLevelClose;
break;
}
}
if (i >= RESET_MAX_LINES) {
console.error(
`ERROR: Could not detect end of CSS reset block within ${RESET_MAX_LINES} lines.`
);
console.error('The heuristic may need updating. Check utils/update-mantine-css.ts.');
process.exit(1);
}
}
if (resetEndIndex === -1) {
console.error('ERROR: Could not detect end of CSS reset block.');
process.exit(1);
}
const resetBlock = sourceLines.slice(0, resetEndIndex + 1).join('\n');
const restBlock = sourceLines.slice(resetEndIndex + 1).join('\n');
const targetContent = await Bun.file(TARGET).text();
const splitMarker = '/* BEGIN_MANTINE_STYLES';
const splitIndex = targetContent.indexOf(splitMarker);
if (splitIndex === -1) {
console.error(`ERROR: Could not find "${splitMarker}" in ${TARGET}`);
process.exit(1);
}
const topSection = targetContent.slice(0, splitIndex);
const oldMantineSection = targetContent.slice(splitIndex);
const oldLineCount = oldMantineSection.split('\n').length;
const newMantineSection = `/* BEGIN_MANTINE_STYLES v${version}
The rest of this file was copied from node_modules/@mantine/core/styles.css.
The "CSS reset" section (except for the \`html, body\` styles) and the \`body\` styles from
the "Global styles" section have been commented here and copied into the \`.rqb-mantine\`
block above. The \`:root { color-scheme: ... }\` declaration in the "Default CSS variables"
section has also been commented.
*/
/* BEGIN_MANTINE_CSS_RESET */
/*
${resetBlock}
*/
/* END_MANTINE_CSS_RESET */
${restBlock}`;
const newLineCount = newMantineSection.split('\n').length;
const delta = Math.abs(newLineCount - oldLineCount) / oldLineCount;
if (delta > SANITY_THRESHOLD) {
warn(
`Line count of Mantine CSS section changed by ${Math.round(delta * 100)}% ` +
`(${oldLineCount} → ${newLineCount}). Review the diff carefully.`
);
}
await Bun.write(TARGET, topSection + newMantineSection);
console.log(`Written ${TARGET}`);
const fmt = Bun.spawnSync(['bun', 'fmt', TARGET], {
cwd: ROOT,
stdout: 'inherit',
stderr: 'inherit',
});
if (fmt.exitCode !== 0) {
warn('bun fmt failed — file was written but not formatted.');
}
console.log(`Done. Mantine CSS updated to v${version}.`);