Files
gitlab-foss/scripts/frontend/warm_jest_cache.mjs
2025-06-20 06:07:14 +00:00

153 lines
4.9 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { join, relative } from 'node:path';
import { spawnSync } from 'node:child_process';
import { writeFile, rename, rm, mkdir } from 'node:fs/promises';
import chalk from 'chalk';
import pkg from 'glob';
const { glob } = pkg;
const rootPath = join(import.meta.dirname, '..', '..');
const testPath = join(rootPath, 'spec/frontend/_warm_cache');
const maxTestPerFile = 50;
let currentTestFile = 0;
function findFiles() {
return glob
.sync('{jh,ee/,}app/assets/javascripts/**/*.{js,vue,graphql}', {
cwd: rootPath,
ignore: [
// Try to avoid side effects and unparseable files (e.g., node modules
// we don't transpile for Jest, like mermaid) by excluding files we
// normally wouldn't write specs for.
//
// The most correct way to do this would be to parse all our spec files
// and import all files *they* import, but that's a lot more effort.
'**/app/assets/javascripts/main{_ee,_jh,}.js',
'**/app/assets/javascripts/{behaviors,pages,entrypoints}/**/*',
// A dev-only file
'**/app/assets/javascripts/webpack_non_compiled_placeholder.js',
// Generated translation files
'**/app/assets/javascripts/locale/*/app.js',
// Storybook stories
'**/*.stories.js',
// This file imports the `mermaid` node module, which is written in ES
// module format, and Jest isn't configured to transpile it. It's
// surprising that we don't have any specs that even transitively
// import mermaid.
'**/app/assets/javascripts/lib/mermaid.js',
// These *should* be in /pages/ 🤷
'**/app/assets/javascripts/snippet/snippet_show.js',
'**/app/assets/javascripts/admin/application_settings/setup_metrics_and_profiling.js',
// # Thse ones have a problem with jQuery.ajaxPrefilter not being defined
'app/assets/javascripts/lib/utils/rails_ujs.js',
'app/assets/javascripts/profile/profile.js',
'app/assets/javascripts/namespaces/leave_by_url.js',
// # These ones aren't working for some reason or another,
'app/assets/javascripts/blob/stl_viewer.js',
'app/assets/javascripts/blob/3d_viewer/index.js',
'app/assets/javascripts/filtered_search/**/*',
// This component recursively uses itself, so it cannot be imported synchronously
'**/app/assets/javascripts/security_inventory/components/recursive_breadcrumbs.vue',
],
})
.sort();
}
async function writeTestFile(arr) {
currentTestFile += 1;
const data = `${arr.join('\n')}
it('nothing', () => { expect(1).toBe(1); })
`;
const baseName = `${currentTestFile}`.padStart(3, '0');
return writeFile(join(testPath, `${baseName}_spec.js`), data);
}
function setExitCode(statusOrError) {
// No error, do nothing.
if (statusOrError === 0) return;
if (process.env.CI) {
if (process.env.CI_MERGE_REQUEST_IID) {
// In merge requests, fail the pipeline by setting the exit code to
// something other than the allowed failure value.
process.exitCode = 2;
} else {
// In master and other pipelines, set it to the allowed exit code.
process.exitCode = 1;
}
} else {
// Not in CI, pass through status as-is
process.exitCode = typeof statusOrError === 'number' ? statusOrError : 1;
}
}
async function main() {
let curr = [];
await mkdir(testPath, { recursive: true });
const files = findFiles();
for (const item of files) {
const transformedPath = item
.replace(/^app\/assets\/javascripts\//, '~/')
.replace(/^(ee|jh)\/app\/assets\/javascripts\//, '$1/')
.replace(/\.js$/, '');
if (curr.length >= maxTestPerFile) {
// eslint-disable-next-line no-await-in-loop
await writeTestFile(curr);
curr = [];
}
curr.push(`import '${transformedPath}';`);
}
await writeTestFile(curr);
console.log(`[WARMING JEST]: Start execution`);
const result = spawnSync('yarn', ['run', 'jest', testPath], {
cwd: rootPath,
detached: true,
stdio: 'inherit',
});
console.log(`[WARMING JEST]: End execution: jest exited with ${result.status}`);
if (process.env.CI) {
console.log(`Moving spec/frontend/_warm_cache to tmp/`);
await rename(testPath, join(rootPath, 'tmp/cache/jest/_warm_cache'));
} else {
console.log(`Removing spec/frontend/_warm_cache`);
await rm(testPath, { recursive: true, force: true });
}
if (result.status !== 0) {
const scriptPath = relative(rootPath, import.meta.filename);
console.log(chalk.red('Jest cache warming failed!'));
console.log(
chalk.red(
`If the failure is due to an import error, add the problematic file(s) to the ignore list in ${scriptPath}.`,
),
);
console.log(chalk.red('For help, contact the Manage:Foundations team.'));
}
return result.status;
}
try {
setExitCode(await main());
} catch (error) {
setExitCode(error);
console.error(error);
}