eigent/scripts/verify-tailwind4.mjs
Tong Chen 59f43afcb1
Feat/workspace bundle product (#1842)
Co-authored-by: Douglas Lai <115660088+Douglasymlai@users.noreply.github.com>
Co-authored-by: Douglas <douglas.ym.lai@gmail.com>
2026-08-28 19:16:22 +08:00

138 lines
4.8 KiB
JavaScript

// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const repositoryRoot = path.resolve(scriptDirectory, '..');
const fixturePath = path.join(
repositoryRoot,
'src',
'style',
`.tailwind4-verify-${process.pid}.css`
);
const outputPath = path.join(
os.tmpdir(),
`eigent-tailwind4-verify-${process.pid}.css`
);
const tailwindBinary = path.join(
repositoryRoot,
'node_modules',
'.bin',
'tailwindcss'
);
const fixture = `@import './index.css';
@source inline('short:block max-lg:block smooth-shadow-md smooth-shadow-ring-md bg-ds-accent-strong-default bg-ds-neutral-default-default bg-ds-bg-neutral-default-default text-ds-text-base text-body-sm text-ds-ink-default-default border-ds-hairline-default-default ring-ds-ring-focus shadow-ds-elevation-control h-ds-control-md');
`;
const requiredOutput = [
['public Neutral fill utility', '.bg-ds-neutral-default-default'],
['compatibility Neutral fill utility', '.bg-ds-bg-neutral-default-default'],
['compatibility typography alias', '.text-body-sm'],
['legacy custom shadow utility', '.shadow-button-shadow'],
['Tailwind 4 replacement utility', '.shrink-0'],
['shadow-plugin utility', '.smooth-shadow-md'],
['shadow-plugin ring utility', '.smooth-shadow-ring-md'],
['custom short-screen media query', '@media (max-height: 800px)'],
['custom max-lg responsive utility', '.max-lg\\:block'],
['public accent fill utility', '.bg-ds-accent-strong-default'],
['public Ink utility', '.text-ds-ink-default-default'],
['public Hairline utility', '.border-ds-hairline-default-default'],
['public focus ring utility', '.ring-ds-ring-focus'],
['generated type role utility', '.text-ds-text-base'],
['semantic elevation utility', '.shadow-ds-elevation-control'],
['control height utility', '.h-ds-control-md'],
];
const forbiddenOutput = [
['unprocessed Tailwind theme directive', '@theme'],
['unprocessed Tailwind utility directive', '@utility'],
['unprocessed config directive', '@config'],
['unprocessed source directive', '@source'],
['invalid empty selector', ':where()'],
['invalid layer media query', '@media layer(utilities)'],
];
function fail(message) {
process.stderr.write(`FAIL ${message}\n`);
process.exitCode = 1;
}
try {
const tailwindVersion = JSON.parse(
fs.readFileSync(
path.join(repositoryRoot, 'node_modules', 'tailwindcss', 'package.json'),
'utf8'
)
).version;
const shadowPluginVersion = JSON.parse(
fs.readFileSync(
path.join(
repositoryRoot,
'node_modules',
'shadow-plugin',
'package.json'
),
'utf8'
)
).version;
if (!tailwindVersion.startsWith('4.')) {
fail(`Expected Tailwind 4, found ${tailwindVersion}`);
}
if (!shadowPluginVersion.startsWith('2.')) {
fail(`Expected shadow-plugin 2, found ${shadowPluginVersion}`);
}
fs.writeFileSync(fixturePath, fixture, 'utf8');
const result = spawnSync(
tailwindBinary,
['-i', fixturePath, '-o', outputPath],
{
cwd: repositoryRoot,
encoding: 'utf8',
}
);
if (result.status !== 0) {
process.stderr.write(result.stdout);
process.stderr.write(result.stderr);
fail(`Tailwind compilation exited with status ${result.status}`);
} else {
if (/\b(?:warning|unexpected)\b/i.test(result.stderr)) {
process.stderr.write(result.stderr);
fail('Tailwind compilation emitted a CSS warning');
}
const css = fs.readFileSync(outputPath, 'utf8');
for (const [label, sentinel] of requiredOutput) {
if (!css.includes(sentinel)) fail(`Missing ${label}: ${sentinel}`);
}
for (const [label, sentinel] of forbiddenOutput) {
if (css.includes(sentinel)) fail(`Found ${label}: ${sentinel}`);
}
}
if (!process.exitCode) {
process.stdout.write(
`PASS Tailwind ${tailwindVersion}, shadow-plugin ${shadowPluginVersion}, and Eigent config sentinels compiled correctly.\n`
);
}
} finally {
fs.rmSync(fixturePath, { force: true });
fs.rmSync(outputPath, { force: true });
}