@storm-stack/core
Version:
A build toolkit and runtime used by Storm Software in TypeScript applications
570 lines (552 loc) • 20 kB
JavaScript
import { getString, getSourceFile, getMagicString } from './chunk-LK23XV73.js';
import { extendLog } from './chunk-K7ITYUIA.js';
import { init_esm_shims, __name, __reExport } from './chunk-QH7NXH7H.js';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { existsSync } from '@stryke/fs/exists';
import { createDirectorySync, createDirectory } from '@stryke/fs/helpers';
import { resolvePackage } from '@stryke/fs/resolve';
import { isParentPath } from '@stryke/path/is-parent-path';
import { joinPaths } from '@stryke/path/join-paths';
import { transformAsync } from '@babel/core';
import { isFunction } from '@stryke/type-checks/is-function';
import { isSetString } from '@stryke/type-checks/is-set-string';
import defu, { defu as defu$1 } from 'defu';
import chalk from 'chalk';
import '@stryke/type-checks/is-object';
import { isString } from '@stryke/type-checks/is-string';
import { parse } from '@babel/parser';
import ts from 'typescript';
import * as type_compiler_star from '@deepkit/type-compiler';
import { isWritable } from '@stryke/fs/chmod-x';
import { readFile } from '@stryke/fs/read-file';
import { removeFile } from '@stryke/fs/remove-file';
import { writeFile } from '@stryke/fs/write-file';
import { hash } from '@stryke/hash';
import { findFileName } from '@stryke/path/file-path-fns';
import Diff from 'diff-match-patch';
// src/base/compiler.ts
init_esm_shims();
// src/lib/babel/transform.ts
init_esm_shims();
// src/lib/babel/options.ts
init_esm_shims();
// src/lib/babel/helpers.ts
init_esm_shims();
// src/lib/babel/ast.ts
init_esm_shims();
function parseAst(code, opts = {}) {
return parse(code, {
plugins: [
"typescript"
],
sourceType: "module",
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
...opts
});
}
__name(parseAst, "parseAst");
// src/lib/babel/helpers.ts
function listExports(codeOrAst) {
const ast = isString(codeOrAst) ? parseAst(codeOrAst) : codeOrAst;
return ast.program.body.flatMap((i) => {
if (i.type === "ExportDefaultDeclaration") {
return [
"default"
];
}
if (i.type === "ExportNamedDeclaration" && i.declaration && "declarations" in i.declaration) {
return i.declaration.declarations.map((d) => "name" in d.id ? d.id.name : "");
}
return [];
}).filter(Boolean);
}
__name(listExports, "listExports");
function getPluginName(plugin) {
return isSetString(plugin) ? plugin : Array.isArray(plugin) && plugin.length > 0 ? getPluginName(plugin[0]) : plugin._name || plugin.name ? plugin._name || plugin.name : void 0;
}
__name(getPluginName, "getPluginName");
function isDuplicatePlugin(plugins, plugin) {
return !!(getPluginName(plugin) && plugins.some((existing) => Array.isArray(existing) && getPluginName(existing[0]) === getPluginName(plugin)));
}
__name(isDuplicatePlugin, "isDuplicatePlugin");
// src/lib/babel/options.ts
function resolveBabelPlugins(log, context, sourceFile, options = {}) {
return !options.plugins ? [] : options.plugins.reduce((ret, plugin) => {
if (!isDuplicatePlugin(ret, plugin)) {
if (Array.isArray(plugin) && plugin.length > 0 && plugin[0]) {
if (sourceFile && plugin.length > 2 && plugin[2] && isFunction(plugin[2].filter) && // eslint-disable-next-line ts/no-unsafe-call
!plugin[2].filter(sourceFile)) {
log(LogLevelLabel.TRACE, `Skipping filtered Babel plugin ${chalk.bold.cyanBright(getPluginName(plugin) || "unnamed")} for ${sourceFile.id}`);
return ret;
}
ret.push([
isFunction(plugin[0]) ? plugin[0](context) : plugin[0],
{
...plugin.length > 1 && plugin[1] ? plugin[1] : {},
options
},
plugin.length > 2 ? plugin[2] : void 0
]);
} else {
ret.push([
isFunction(plugin) ? plugin(context) : plugin,
{
options
},
void 0
]);
}
} else {
log(LogLevelLabel.INFO, `Skipping duplicate Babel plugin ${getPluginName(plugin)}${sourceFile?.id ? ` for ${sourceFile.id}` : ""}`);
}
return ret;
}, []);
}
__name(resolveBabelPlugins, "resolveBabelPlugins");
function resolveBabelPresets(log, context, sourceFile, options = {}) {
return !options.presets ? [] : options.presets.reduce((ret, preset) => {
if (!isDuplicatePlugin(ret, preset)) {
if (Array.isArray(preset) && preset.length > 0 && preset[0]) {
if (sourceFile && preset.length > 2 && preset[2] && isFunction(preset[2].filter) && // eslint-disable-next-line ts/no-unsafe-call
!preset[2].filter(sourceFile)) {
log(LogLevelLabel.INFO, `Skipping filtered Babel preset ${getPluginName(preset)} for ${sourceFile.id}`);
return ret;
}
ret.push([
isFunction(preset[0]) ? preset[0](context) : preset[0],
{
...preset.length > 1 && preset[1] ? preset[1] : {},
options
},
preset.length > 2 ? preset[2] : void 0
]);
} else {
ret.push([
isFunction(preset) ? preset(context) : preset,
{
options
},
void 0
]);
}
} else {
log(LogLevelLabel.INFO, `Skipping duplicate Babel preset ${getPluginName(preset)}${sourceFile?.id ? ` for ${sourceFile.id}` : ""}`);
}
return ret;
}, []);
}
__name(resolveBabelPresets, "resolveBabelPresets");
function resolveBabelInputOptions(context, options = {}, plugins = [], presets = []) {
return defu({
plugins: [
"@babel/plugin-syntax-typescript",
...plugins.map((plugin) => {
return [
plugin[0],
defu(plugin.length > 1 && plugin[1] ? plugin[1] : {}, {
options
}),
plugin[0]?.name
];
})
],
presets: presets.map((preset) => {
return [
preset[0],
defu(preset.length > 1 && preset[1] ? preset[1] : {}, {
options
}),
preset[0]?.name
];
})
}, options ? {
...options,
plugins: [],
presets: []
} : {}, {
highlightCode: true,
code: true,
ast: false,
cloneInputAst: false,
comments: true,
sourceType: "module",
configFile: false,
babelrc: false,
envName: context.options.mode,
caller: {
name: "storm-stack"
}
});
}
__name(resolveBabelInputOptions, "resolveBabelInputOptions");
// src/lib/babel/transform.ts
async function transform(log, context, source, options = {}) {
try {
const corePath = process.env.STORM_STACK_LOCAL ? joinPaths(context.options.workspaceRoot, "packages/core") : await resolvePackage("@storm-stack/core");
if (!corePath) {
throw new Error("Could not resolve @storm-stack/core package location.");
}
let sourceFile = source;
if (process.env.STORM_STACK_LOCAL && isParentPath(sourceFile.id, corePath) || options.skipAllTransforms || getString(sourceFile.code).includes("/* @storm-ignore */") || getString(sourceFile.code).includes("/* @storm-disable */")) {
return sourceFile;
}
const opts = defu$1(context.options.babel ?? {}, options.babel ?? {});
const plugins = resolveBabelPlugins(log, context, sourceFile, opts);
const presets = resolveBabelPresets(log, context, sourceFile, opts);
if (!plugins && !presets || Array.isArray(plugins) && plugins.length === 0 && Array.isArray(presets) && presets.length === 0) {
log(LogLevelLabel.WARN, `No Babel plugins or presets configured for ${sourceFile.id}. Skipping Babel transformation.`);
return sourceFile;
}
for (const plugin of plugins.filter((plugin2) => isFunction(plugin2[2]?.onPreTransform))) {
sourceFile = await Promise.resolve(plugin[2].onPreTransform(context, sourceFile));
}
for (const preset of presets.filter((preset2) => isFunction(preset2[2]?.onPreTransform))) {
sourceFile = await Promise.resolve(preset[2].onPreTransform(context, sourceFile));
}
log(LogLevelLabel.TRACE, `Transforming ${sourceFile.id} with Babel`);
const result = await transformAsync(getString(sourceFile.code), defu$1({
filename: sourceFile.id
}, resolveBabelInputOptions(context, opts, plugins, presets)));
if (!result?.code) {
throw new Error(`BabelPluginStormStack failed to compile ${sourceFile.id}`);
}
log(LogLevelLabel.TRACE, `Completed Babel transformations of ${sourceFile.id}`);
sourceFile.code = getMagicString(result.code);
for (const plugin of plugins.filter((plugin2) => isFunction(plugin2[2]?.onPostTransform))) {
sourceFile = await Promise.resolve(plugin[2].onPostTransform(context, sourceFile));
}
for (const preset of presets.filter((preset2) => isFunction(preset2[2]?.onPostTransform))) {
sourceFile = await Promise.resolve(preset[2].onPostTransform(context, sourceFile));
}
return sourceFile;
} catch (error) {
context.log(LogLevelLabel.ERROR, `Error during Babel transformation: ${error?.message ? isSetString(error.message) ? error.message.length > 5e3 ? `${error.message.slice(0, 5e3)}... ${error.message.slice(-100)}` : error.message : error.message : "Unknown error"}
${error?.stack ? `
Stack trace:
${error.stack}
` : ""}`);
throw new Error(`Babel transformation failed for ${source.id}`);
}
}
__name(transform, "transform");
// src/lib/typescript/transpile.ts
init_esm_shims();
// src/lib/deepkit/transformer.ts
init_esm_shims();
// src/deepkit/type-compiler.ts
var type_compiler_exports = {};
init_esm_shims();
__reExport(type_compiler_exports, type_compiler_star);
var cache = new type_compiler_exports.Cache();
function createTransformer(context, options = {}) {
return /* @__PURE__ */ __name(function transformer(ctx) {
cache.tick();
return new type_compiler_exports.ReflectionTransformer(ctx, cache).withReflection({
reflection: options.reflectionMode || "default",
reflectionLevel: options.reflectionLevel || context.tsconfig.tsconfigJson.compilerOptions?.reflectionLevel || context.tsconfig.tsconfigJson.reflectionLevel || "minimal"
});
}, "transformer");
}
__name(createTransformer, "createTransformer");
function createDeclarationTransformer(context, options = {}) {
return /* @__PURE__ */ __name(function declarationTransformer(ctx) {
return new type_compiler_exports.DeclarationTransformer(ctx, cache).withReflection({
reflection: options.reflectionMode || "default",
reflectionLevel: options.reflectionLevel || context.tsconfig.tsconfigJson.compilerOptions?.reflectionLevel || context.tsconfig.tsconfigJson.reflectionLevel || "minimal"
});
}, "declarationTransformer");
}
__name(createDeclarationTransformer, "createDeclarationTransformer");
// src/lib/typescript/transpile.ts
function transpile(context, id, code, options = {}) {
const transformer = createTransformer(context, options);
const declarationTransformer = createDeclarationTransformer(context, options);
return ts.transpileModule(code, {
compilerOptions: {
...context.tsconfig.options
},
fileName: id,
transformers: {
before: [
transformer
],
after: [
declarationTransformer
]
}
});
}
__name(transpile, "transpile");
// src/lib/utilities/cache.ts
init_esm_shims();
function getCacheHashKey(id, code) {
return hash({
id,
code: getString(code)
}, {
maxLength: 32
});
}
__name(getCacheHashKey, "getCacheHashKey");
function getCacheFileName(id, hashKey) {
return `${findFileName(id, {
withExtension: false
})}_${hashKey}.cache`;
}
__name(getCacheFileName, "getCacheFileName");
async function getCache(sourceFile, cacheDir) {
const hashKey = getCacheHashKey(sourceFile.id, sourceFile.code);
const cacheFilePath = joinPaths(cacheDir, getCacheFileName(sourceFile.id, hashKey));
if (!existsSync(cacheFilePath)) {
return void 0;
}
const cache2 = await readFile(cacheFilePath);
if (!cache2.includes(hashComment(hashKey))) {
return void 0;
}
return cache2;
}
__name(getCache, "getCache");
async function setCache(sourceFile, cacheDir, transpiled) {
const hashKey = getCacheHashKey(sourceFile.id, sourceFile.code);
const cacheFilePath = joinPaths(cacheDir, getCacheFileName(sourceFile.id, hashKey));
if (existsSync(cacheFilePath)) {
await removeFile(cacheFilePath);
}
if (transpiled) {
if (!existsSync(cacheDir)) {
await createDirectory(cacheDir);
}
if (!await isWritable(cacheDir)) {
throw new Error(`Cache directory is not writable: ${cacheDir}`);
}
await writeFile(cacheFilePath, `${transpiled}
${hashComment(hashKey)}`.trim());
}
}
__name(setCache, "setCache");
function hashComment(hashKey) {
return `/* storm-stack_${hashKey} */`;
}
__name(hashComment, "hashComment");
// src/lib/utilities/source-map.ts
init_esm_shims();
var dmp = new Diff();
function generateSourceMap(id, source, transpiled) {
if (!transpiled) {
return;
}
const diff = dmp.diff_main(source.toString(), transpiled);
dmp.diff_cleanupSemantic(diff);
let offset = 0;
for (let index = 0; index < diff.length; index++) {
if (diff[index]) {
const [type, text] = diff[index];
const textLength = text.length;
switch (type) {
case 0: {
offset += textLength;
break;
}
case 1: {
source.prependLeft(offset, text);
break;
}
case -1: {
const next = diff.at(index + 1);
if (next && next[0] === 1) {
const replaceText = next[1];
const firstNonWhitespaceIndexOfText = text.search(/\S/);
const offsetStart = offset + Math.max(firstNonWhitespaceIndexOfText, 0);
source.update(offsetStart, offset + textLength, replaceText);
index += 1;
} else {
source.remove(offset, offset + textLength);
}
offset += textLength;
break;
}
}
}
}
if (!source.hasChanged()) {
return;
}
return {
code: source.toString(),
map: source.generateMap({
source: id,
file: `${id}.map`,
includeContent: true
})
};
}
__name(generateSourceMap, "generateSourceMap");
// src/base/compiler.ts
var Compiler = class {
static {
__name(this, "Compiler");
}
#cache = /* @__PURE__ */ new WeakMap();
#options;
#corePath;
/**
* The logger function to use
*/
log;
/**
* The cache directory
*/
cacheDir;
/**
* Create a new compiler instance.
*
* @param context - The compiler context.
* @param options - The compiler options.
*/
constructor(context, options = {}) {
this.log = extendLog(context.log, "compiler");
this.#options = options;
this.cacheDir = joinPaths(context.cachePath, "compiler");
if (!existsSync(this.cacheDir)) {
createDirectorySync(this.cacheDir);
}
}
/**
* Transform the module.
*
* @param context - The compiler context.
* @param fileName - The name of the file to compile.
* @param code - The source code to compile.
* @param options - The transpile options.
* @returns The transpiled module.
*/
async transform(context, fileName, code, options = {}) {
if (await this.shouldSkip(context, fileName, code)) {
this.log(LogLevelLabel.TRACE, `Skipping transform for ${fileName}`);
return getString(code);
}
this.log(LogLevelLabel.TRACE, `Transforming ${fileName}`);
let source = getSourceFile(fileName, code);
if (options.onPreTransform) {
this.log(LogLevelLabel.TRACE, `Running onPreTransform hook for ${source.id}`);
source = await Promise.resolve(options.onPreTransform(context, source));
}
if (this.#options.onPreTransform) {
this.log(LogLevelLabel.TRACE, `Running onPreTransform hook for ${source.id}`);
source = await Promise.resolve(this.#options.onPreTransform(context, source));
}
if (!options.skipAllTransforms) {
if (context.unimport && !options.skipTransformUnimport && !context.vfs.isRuntimeFile(fileName)) {
source = await context.unimport.injectImports(source);
}
this.log(LogLevelLabel.TRACE, `Running transforms for ${source.id} with options: ${JSON.stringify(options)}`);
source = await transform(this.log, context, source, options);
this.log(LogLevelLabel.TRACE, `Transformed: ${source.id}`);
}
if (this.#options.onPostTransform) {
this.log(LogLevelLabel.TRACE, `Running onPostTransform hook for ${source.id}`);
source = await Promise.resolve(this.#options.onPostTransform(context, source));
}
if (options.onPostTransform) {
this.log(LogLevelLabel.TRACE, `Running onPostTransform hook for ${source.id}`);
source = await Promise.resolve(options.onPostTransform(context, source));
}
return getString(source.code);
}
/**
* Transpile the module.
*
* @param context - The compiler context.
* @param id - The name of the file to compile.
* @param code - The source code to compile.
* @returns The transpiled module.
*/
async transpile(context, id, code, options = {}) {
this.log(LogLevelLabel.TRACE, `Transpiling ${id} module with TypeScript compiler`);
const transpiled = transpile(context, id, getString(code), options);
if (transpiled === null) {
this.log(LogLevelLabel.ERROR, `Transform is null: ${id}`);
throw new Error(`Transform is null: ${id}`);
} else {
this.log(LogLevelLabel.TRACE, `Transformed: ${id}`);
}
return transpiled.outputText;
}
/**
* Compile the source code.
*
* @param context - The compiler context.
* @param id - The name of the file to compile.
* @param code - The source code to compile.
* @returns The compiled source code and source map.
*/
async compile(context, id, code, options = {}) {
this.log(LogLevelLabel.TRACE, `Compiling ${id}`);
const source = getSourceFile(id, code);
let compiled;
if (!options.skipCache) {
compiled = await this.getCache(context, source);
if (compiled) {
this.log(LogLevelLabel.TRACE, `Cache hit: ${source.id}`);
} else {
this.log(LogLevelLabel.TRACE, `Cache miss: ${source.id}`);
}
}
if (!compiled) {
const transformed = await this.transform(context, source.id, source.code, options);
compiled = await this.transpile(context, id, transformed, options);
await this.setCache(context, source, compiled);
}
return compiled;
}
/**
* Get the result of the compiler.
*
* @param sourceFile - The source file.
* @param transpiled - The transpiled source code.
* @returns The result of the compiler.
*/
getResult(sourceFile, transpiled) {
return generateSourceMap(sourceFile.id, sourceFile.code, transpiled);
}
async getCache(context, sourceFile) {
let cache2 = this.#cache.get(sourceFile);
if (cache2) {
return cache2;
}
if (context.options.skipCache) {
return;
}
cache2 = await getCache(sourceFile, this.cacheDir);
if (cache2) {
this.#cache.set(sourceFile, cache2);
}
return cache2;
}
async setCache(context, sourceFile, transpiled) {
if (transpiled) {
this.#cache.set(sourceFile, transpiled);
} else {
this.#cache.delete(sourceFile);
}
if (context.options.skipCache) {
return;
}
return setCache(sourceFile, this.cacheDir, transpiled);
}
async shouldSkip(context, id, code) {
if (!this.#corePath) {
this.#corePath = process.env.STORM_STACK_LOCAL ? joinPaths(context.options.workspaceRoot, "packages/core") : await resolvePackage("@storm-stack/core");
if (!this.#corePath) {
throw new Error("Could not resolve @storm-stack/core package location.");
}
}
if (process.env.STORM_STACK_LOCAL && isParentPath(id, this.#corePath) || getString(code).includes("/* @storm-ignore */") || getString(code).includes("/* @storm-disable */")) {
return true;
}
return false;
}
};
export { Compiler, listExports, parseAst };