Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions .grit/workflows/bun2jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import * as grit from '@getgrit/api';
import * as sdk from '@getgrit/workflows-sdk';

import { QueryBuilder } from '@getgrit/bridge';

/**
* Rewrite a Jest setup file to be usable as a --preload file
*/
async function transformTests(targetPath: string) {
grit.logging.debug(`Transforming Jest test files at ${targetPath}`);
const pattern = `or {
js"describe" where { add_import(js"describe", js"'bun:test'")},
js"it" where { add_import(js"it", js"'bun:test'")},
js"test($_)" where { add_import(js"test", js"'bun:test'")},
js"expect($_)" where { add_import(js"expect", js"'bun:test'")},
}`;
const query = new QueryBuilder(pattern);

const instanceCount = await query.run([targetPath]);
grit.logging.info(`Transformed Jest setup file at ${targetPath}, with ${instanceCount} changes`);

return targetPath;
}

/**
* Rewrite a Jest setup file to be usable as a --preload file
*/
async function transformSetupFile(targetPath: string) {
grit.logging.debug(`Transforming Jest setup file at ${targetPath}`);
const pattern = `js"$func($contents)" where {
$func <: or {
js"beforeAll" => js"beforeEachFile" where { add_import(js"beforeEachFile", js"'bun:test'")},
js"afterAll" => js"afterEachFile" where { add_import(js"afterEachFile", js"'bun:test'")},
}
}`;
const query = new QueryBuilder(pattern);

const instanceCount = await query.run([targetPath]);
grit.logging.info(`Transformed Jest setup file at ${targetPath}, with ${instanceCount} changes`);

return targetPath;
}

/**
* Fix the provided bun config
*/
async function fixBunConfig(targetPath: string, props: { [key: string]: any } = {}) {
grit.logging.debug(`Fixing Bun config at ${targetPath}`);
const pattern = `
language toml

file($body) where {
// $props = {preload: \`"foo.ts"\`},
$props = {${Object.entries(props).map(([key, value]) => `${key}: \`${value}\``).join(', ')}},
if ($body <: contains \`[test]
$existing\`) {
$props <: some bubble($existing) [$key, $value] where {
or {
$existing <: contains \`$key = $_\` => \`$key: $value\`,
$existing += \`\n$key = $value\`
}
}
} else {
$body += \`\n[test]\n\`,
$props <: some bubble($body) [$key, $value] where {
$body += \`$key = $value\n\`
}
}
}`;

grit.logging.debug(`Pattern: ${pattern}`);

const query = new QueryBuilder(pattern);

const instanceCount = await query.run([targetPath]);
grit.logging.info(`Fixed Bun config at ${targetPath}, with ${instanceCount} changes`);

return targetPath;
}

export default await sdk.defineWorkflow({
name: 'jest_to_bun',

run: async (options) => {
const targetDir = process.cwd();

const configRoot = targetDir;

// Look for a jest.config.json file
const targetFile = 'jest.config.js';
const config = await import(`${targetDir}/${targetFile}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the import statement to manage cases where the jest.config.js file does not exist.


grit.logging.debug(`Successfully loaded config`, config);

// Props we want to set up
const props: { [key: string]: string } = {};

await transformTests(`${configRoot}/src`);

// Convert from setupFilesAfterEnv -> preload with beforeEachFile
if (config.setupFilesAfterEnv) {
const preloads = [];
for (const setupFile of config.setupFilesAfterEnv) {
const actualFile = `${configRoot}/${setupFile}`;
const fixed = await transformSetupFile(actualFile);
grit.logging.debug(`Adding ${fixed} to preload`);
preloads.push(setupFile);
}
props.preload = JSON.stringify(preloads);
}

if (config.bail) {
props.bail = config.bail;
}

if (config.collectCoverage) {
props.coverage = config.collectCoverage;
}

if (config.coverageThreshold?.global) {
let parts = [];
if (config.coverageThreshold.global.lines) {
parts.push(`line = ${config.coverageThreshold.global.lines / 100}`);
}
if (config.coverageThreshold.global.functions) {
parts.push(`function = ${config.coverageThreshold.global.functions / 100}`);
}
if (config.coverageThreshold.global.statements) {
parts.push(`statement = ${config.coverageThreshold.global.statements / 100}`);
}
props.coverageThreshold = `{${parts.join(', ')}}`;
}

// Apply the bunfig
const bunConfig = `${configRoot}/Bunfig.toml`;
await fixBunConfig(bunConfig, props);

return {
success: true,
message: 'Jest to Bun',
}
},
});