sb-mig
Version:
CLI to rule the world. (and handle stuff related to Storyblok CMS)
136 lines (135 loc) • 4.33 kB
JavaScript
import { mkdir, rm } from "fs/promises";
import path from "path";
import rollupSwc from "@rollup/plugin-swc";
import { rollup } from "rollup";
/**
* Extract the component name from a file path.
* Handles both POSIX (`/`) and Windows (`\`) separators because glob v11
* and node's `fs` APIs return native paths on Windows.
*
* e.g., "/path/to/my-component.sb.ts" -> "my-component.sb"
* "C:\\path\\to\\my-component.sb.ts" -> "my-component.sb"
*/
export const extractComponentName = (filePath) => {
const normalized = filePath.replace(/\\/g, "/");
const lastElement = normalized.substring(normalized.lastIndexOf("/") + 1);
return lastElement.replace(/\.ts$/, "");
};
/**
* Build a single file using Rollup
*/
async function buildFile(inputPath, outputCjs, outputEsm) {
const inputOptions = {
input: inputPath,
plugins: [
rollupSwc({
swc: {
jsc: {
parser: {
syntax: "typescript",
},
},
},
}),
],
};
const outputOptionsList = [
{ file: outputCjs, format: "cjs" },
{ file: outputEsm, format: "es" },
];
let bundle;
try {
bundle = await rollup(inputOptions);
for (const outputOptions of outputOptionsList) {
await bundle.write(outputOptions);
}
}
finally {
if (bundle) {
await bundle.close();
}
}
}
/**
* Precompile TypeScript schema files to JavaScript
*
* This uses Rollup with SWC for fast transpilation, producing
* both CommonJS (.cjs) and ESM (.js) outputs.
*
* @param files - Array of TypeScript file paths to compile
* @param options - Precompile options
* @returns Result with compiled files and any errors
*
* @example
* ```ts
* const result = await precompile([
* '/path/to/hero.sb.ts',
* '/path/to/card.sb.ts',
* ], { cacheDir: '.cache/sb-mig' });
*
* // Use compiled CJS files
* for (const compiled of result.compiled) {
* const content = require(compiled.outputCjs);
* }
* ```
*/
export async function precompile(files, options = {}) {
const { cacheDir = ".sb-mig-cache", flushCache = true, projectDir = process.cwd(), } = options;
const fullCacheDir = path.join(projectDir, cacheDir, "sb-mig");
// Optionally clear cache
if (flushCache) {
try {
await rm(fullCacheDir, { recursive: true, force: true });
}
catch {
// Ignore if doesn't exist
}
}
// Ensure cache directory exists
await mkdir(fullCacheDir, { recursive: true });
const result = {
compiled: [],
errors: [],
};
// Filter to only TypeScript files
const tsFiles = files.filter((f) => f.endsWith(".ts"));
if (tsFiles.length === 0) {
return result;
}
// Compile all files in parallel
await Promise.all(tsFiles.map(async (inputPath) => {
const componentName = extractComponentName(inputPath);
const outputCjs = path.join(fullCacheDir, `${componentName}.cjs`);
const outputEsm = path.join(fullCacheDir, `${componentName}.js`);
try {
await buildFile(inputPath, outputCjs, outputEsm);
result.compiled.push({
input: inputPath,
outputCjs,
outputEsm,
});
}
catch (error) {
result.errors.push({
input: inputPath,
error: error instanceof Error ? error.message : String(error),
});
}
}));
return result;
}
/**
* Get the compiled file path for a TypeScript source file
*
* @param tsFilePath - Original .ts file path
* @param options - Options with cacheDir and projectDir
* @param format - Output format ('cjs' or 'esm')
* @returns Path to the compiled file
*/
export function getCompiledPath(tsFilePath, options = {}, format = "cjs") {
const { cacheDir = ".sb-mig-cache", projectDir = process.cwd() } = options;
const fullCacheDir = path.join(projectDir, cacheDir, "sb-mig");
const componentName = extractComponentName(tsFilePath);
const ext = format === "cjs" ? ".cjs" : ".js";
return path.join(fullCacheDir, `${componentName}${ext}`);
}