@storm-stack/core
Version:
A build toolkit and runtime used by Storm Software in TypeScript applications
468 lines (452 loc) • 21 kB
JavaScript
import { installPackage } from './chunk-RGKSWMYB.js';
import { writeFile } from './chunk-WEZ7ASIP.js';
import { defaultEnvironmentName, getParsedTypeScriptConfig, getTsconfigFilePath, isIncludeMatchFound } from './chunk-O5JSYXAY.js';
import { __VFS_INIT__ } from './chunk-7AH46LR2.js';
import { Compiler, listExports, parseAst } from './chunk-F356COBY.js';
import { init_esm_shims, __name } from './chunk-QH7NXH7H.js';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { existsSync } from '@stryke/fs/exists';
import { createDirectory } from '@stryke/fs/helpers';
import { throttle } from '@stryke/helpers/throttle';
import { StormJSON } from '@stryke/json/storm-json';
import { relativePath, findFilePath, findFileName } from '@stryke/path/file-path-fns';
import { joinPaths } from '@stryke/path/join-paths';
import { isSetString } from '@stryke/type-checks/is-set-string';
import { createUnimport as createUnimport$1 } from 'unimport';
import { isValidRange } from '@stryke/fs/semver-fns';
import { getPackageName, getPackageVersion, hasPackageVersion } from '@stryke/string-format/package';
import { isSetObject } from '@stryke/type-checks/is-set-object';
import { isString } from '@stryke/type-checks/is-string';
import { formatLogMessage } from '@storm-software/config-tools/logger/console';
import { getObjectDiff } from '@donedeal0/superdiff';
import { readJsonFile } from '@stryke/fs/json';
import { isPackageExists } from '@stryke/fs/package-fns';
import { titleCase } from '@stryke/string-format/title-case';
import chalk from 'chalk';
import { loadTsConfig } from '@stryke/fs/tsconfig';
import ts from 'typescript';
// src/commands/init/index.ts
init_esm_shims();
// src/lib/unimport.ts
init_esm_shims();
var lastImportsDump;
var DEFAULT_UNIMPORT_CONFIG = {
commentsDisable: [
"@unimport-disable",
"@imports-disable",
"@storm-disable",
"@storm-ignore"
],
commentsDebug: [
"@unimport-debug",
"@imports-debug",
"@storm-debug"
],
injectAtEnd: true
};
function createUnimport(context) {
context.log(LogLevelLabel.TRACE, "Creating Unimport context with Storm Stack presets");
let unimport = createUnimport$1({
...DEFAULT_UNIMPORT_CONFIG,
presets: []
});
async function refreshRuntimeImports() {
const presets = [];
for (const id of context.vfs.runtimeIdMap.keys()) {
const contents = await context.vfs.readFile(id);
if (contents) {
context.log(LogLevelLabel.TRACE, `Processing exports from runtime file: ${id}`);
const importNames = listExports(parseAst(contents)).filter((importName) => !presets.some((preset) => preset?.imports && !preset?.imports.some((presetImport) => isSetString(presetImport) && presetImport === importName || Array.isArray(presetImport) && presetImport[0] === importName)));
if (importNames.length > 0) {
presets.push({
imports: importNames,
from: id
});
}
}
}
unimport = createUnimport$1({
...DEFAULT_UNIMPORT_CONFIG,
presets,
virtualImports: Array.from(context.vfs.runtimeIdMap.keys())
});
await unimport.init();
}
__name(refreshRuntimeImports, "refreshRuntimeImports");
async function dumpImports() {
context.log(LogLevelLabel.TRACE, "Dumping import file...");
const items = await unimport.getImports();
const importDumpFile = joinPaths(context.dataPath, "imports-dump.json");
if (!existsSync(findFilePath(importDumpFile))) {
await createDirectory(findFilePath(importDumpFile));
}
context.log(LogLevelLabel.TRACE, `Writing imports-dump JSON file: ${importDumpFile}`);
const content = StormJSON.stringify(items);
if (content.trim() !== lastImportsDump?.trim()) {
lastImportsDump = content;
await writeFile(context.log, importDumpFile, content);
}
}
__name(dumpImports, "dumpImports");
const dumpImportsThrottled = throttle(dumpImports, 500);
async function injectImports(source) {
const result = await unimport.injectImports(source.code, source.id);
if (!source.code.hasChanged()) {
return source;
}
await dumpImportsThrottled();
return {
...source,
code: result.s
};
}
__name(injectImports, "injectImports");
return {
...unimport,
dumpImports: dumpImportsThrottled,
injectImports,
refreshRuntimeImports
};
}
__name(createUnimport, "createUnimport");
// src/commands/init/entry/index.ts
init_esm_shims();
async function initEntry(context, hooks) {
context.log(LogLevelLabel.TRACE, `Initializing the entry points for the Storm Stack project.`);
await hooks.callHook("init:entry", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occurred while initializing the entry points for the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occurred while initializing the entry points for the Storm Stack project", {
cause: error
});
});
context.log(LogLevelLabel.DEBUG, `Storm Stack has initialized ${context.entry.length} entry point(s) for the ${context.options.name} project:
${context.entry.map((entry) => `- ${entry.input.file || entry.file}${entry.output ? ` -> ${entry.output}` : ""}`).join(" \n")}`);
}
__name(initEntry, "initEntry");
// src/commands/init/install/index.ts
init_esm_shims();
// src/commands/init/install/shared.ts
init_esm_shims();
function getSharedDeps(context) {
context.packageDeps ??= {};
context.packageDeps["@storm-stack/core"] = {
type: "dependency"
};
if (context.options.projectType === "application") {
context.packageDeps.unstorage = {
type: "dependency"
};
}
return context.packageDeps;
}
__name(getSharedDeps, "getSharedDeps");
// src/commands/init/install/index.ts
async function initInstall(context, hooks) {
context.log(LogLevelLabel.TRACE, `Checking and installing missing project dependencies.`);
context.packageDeps = getSharedDeps(context);
await hooks.callHook("init:install", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occured while installing project dependencies: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while installing project dependencies", {
cause: error
});
});
context.log(LogLevelLabel.TRACE, `The following packages must be installed as dependencies:
${Object.keys(context.packageDeps).map((key) => ` - ${getPackageName(key)}${hasPackageVersion(key) || isSetObject(context.packageDeps[key]) && isSetString(context.packageDeps[key].version) ? ` v${isSetObject(context.packageDeps[key]) && isSetString(context.packageDeps[key].version) ? context.packageDeps[key].version : getPackageVersion(key)}` : ""} (${(isString(context.packageDeps[key]) ? context.packageDeps[key] : context.packageDeps[key]?.type) || "dependency"})`).join("\n")}`);
for (const [key, value] of Object.entries(context.packageDeps)) {
const version = isSetObject(value) && isValidRange(value.version) && value.version || getPackageVersion(key);
await installPackage(context, version ? `${getPackageName(key)}@${String(version)}` : getPackageName(key), (isSetString(value) ? value : value.type) === "devDependency");
}
}
__name(initInstall, "initInstall");
// src/commands/init/options/index.ts
init_esm_shims();
async function initOptions(context, hooks) {
context.log(LogLevelLabel.TRACE, `Initializing the processing options for the Storm Stack project.`);
if (context.packageJson) {
if (context.options.command === "new") {
context.options.workspaceConfig.repository ??= typeof context.packageJson.repository === "string" ? context.packageJson.repository : context.packageJson.repository?.url;
} else {
if (context.packageJson?.name) {
context.options.name ??= context.packageJson?.name;
}
context.options.description ??= context.packageJson?.description;
context.options.workspaceConfig.repository ??= typeof context.packageJson?.repository === "string" ? context.packageJson.repository : context.packageJson?.repository?.url;
}
} else if (context.options.command !== "new") {
throw new Error(`The package.json file is missing in the project root directory: ${context.options.projectRoot}. Please run the "new" command to create a new Storm Stack project.`);
}
if (context.projectJson) {
context.options.projectType ??= context.projectJson.projectType;
context.options.name ??= context.projectJson.name;
if (context.options.name?.startsWith("@") && context.options.name.split("/").filter(Boolean).length > 1) {
context.options.name = context.options.name.split("/").filter(Boolean)[1];
}
}
context.options.tsconfig ??= joinPaths(context.options.projectRoot, "tsconfig.json");
context.options.override ??= {};
context.options.external ??= [];
context.options.noExternal ??= [];
context.options.variant ??= "standalone";
context.options.environment ??= defaultEnvironmentName(context.options);
await hooks.callHook("init:options", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occurred while initializing the options for the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occurred while initializing the options for the Storm Stack project", {
cause: error
});
});
if (context.options.variant === "esbuild" || context.options.variant === "tsup" || context.options.variant === "standalone") {
context.options.build.target ??= "esnext";
if (context.options.variant !== "standalone" || context.options.projectType === "application") {
context.options.build.format ??= "esm";
}
}
context.log(LogLevelLabel.TRACE, `Initialized the processing options for the Storm Stack project:
${formatLogMessage(context.options)}`);
}
__name(initOptions, "initOptions");
// src/commands/init/reflections/index.ts
init_esm_shims();
async function initReflections(context, hooks) {
context.log(LogLevelLabel.TRACE, "Initializing the reflections for the Storm Stack project.");
await hooks.callHook("init:reflections", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occurred while initializing the reflections for the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occurred while initializing the reflections for the Storm Stack project", {
cause: error
});
});
context.log(LogLevelLabel.TRACE, "Initialized the reflections for the Storm Stack project.");
}
__name(initReflections, "initReflections");
// src/commands/init/tsconfig/index.ts
init_esm_shims();
// src/commands/init/tsconfig/utilities.ts
init_esm_shims();
async function getTsconfigChanges(context) {
const tsconfig = getParsedTypeScriptConfig(context.options.workspaceRoot, context.options.projectRoot, context.options.tsconfig, context.options.tsconfigRaw);
const tsconfigFilePath = getTsconfigFilePath(context.options.projectRoot, context.options.tsconfig);
const tsconfigJson = await readJsonFile(tsconfigFilePath);
tsconfigJson.compilerOptions ??= {};
const extendedTsconfig = await loadTsConfig(tsconfigFilePath);
extendedTsconfig.compilerOptions ??= {};
if (tsconfigJson.reflection !== true) {
tsconfigJson.reflection = true;
}
if (tsconfig.options.experimentalDecorators !== true) {
tsconfigJson.compilerOptions.experimentalDecorators = true;
}
if (tsconfig.options.emitDecoratorMetadata !== true) {
tsconfigJson.compilerOptions.emitDecoratorMetadata = true;
}
if (context.options.output.dts) {
const dtsFilePath = context.options.output.dts ? context.options.output.dts.startsWith(context.options.workspaceRoot) ? context.options.output.dts : joinPaths(context.options.workspaceRoot, context.options.output.dts) : joinPaths(context.options.workspaceRoot, context.options.projectRoot, "storm.d.ts");
const dtsRelativePath = joinPaths(relativePath(joinPaths(context.options.workspaceRoot, context.options.projectRoot), findFilePath(dtsFilePath)), findFileName(dtsFilePath));
if (!tsconfigJson.include?.some((filePattern) => isIncludeMatchFound(filePattern, [
dtsFilePath,
dtsRelativePath,
"storm.d.ts"
]))) {
tsconfigJson.include ??= [];
tsconfigJson.include.push(dtsRelativePath.startsWith("./") ? dtsRelativePath.slice(2) : dtsRelativePath);
}
}
if (!tsconfig.options.lib?.some((lib) => [
"lib.esnext.d.ts",
"lib.es2021.d.ts",
"lib.es2022.d.ts",
"lib.es2023.d.ts"
].includes(lib.toLowerCase()))) {
tsconfigJson.compilerOptions.lib ??= [];
tsconfigJson.compilerOptions.lib.push("esnext");
}
if (tsconfig.options.module !== ts.ModuleKind.ESNext) {
tsconfigJson.compilerOptions.module = "ESNext";
}
if (!tsconfig.options.target || ![
ts.ScriptTarget.ESNext,
ts.ScriptTarget.ES2024,
ts.ScriptTarget.ES2023,
ts.ScriptTarget.ES2022,
ts.ScriptTarget.ES2021
].includes(tsconfig.options.target)) {
tsconfigJson.compilerOptions.target = "ESNext";
}
if (tsconfig.options.moduleResolution !== ts.ModuleResolutionKind.Bundler) {
tsconfigJson.compilerOptions.moduleResolution = "Bundler";
}
if (tsconfig.options.moduleDetection !== ts.ModuleDetectionKind.Force) {
tsconfigJson.compilerOptions.moduleDetection = "force";
}
if (tsconfig.options.allowSyntheticDefaultImports !== true) {
tsconfigJson.compilerOptions.allowSyntheticDefaultImports = true;
}
if (tsconfig.options.noImplicitOverride !== true) {
tsconfigJson.compilerOptions.noImplicitOverride = true;
}
if (tsconfig.options.noUncheckedIndexedAccess !== true) {
tsconfigJson.compilerOptions.noUncheckedIndexedAccess = true;
}
if (tsconfig.options.skipLibCheck !== true) {
tsconfigJson.compilerOptions.skipLibCheck = true;
}
if (tsconfig.options.resolveJsonModule !== true) {
tsconfigJson.compilerOptions.resolveJsonModule = true;
}
if (tsconfig.options.isolatedModules !== true) {
tsconfigJson.compilerOptions.isolatedModules = true;
}
if (tsconfig.options.verbatimModuleSyntax !== false) {
tsconfigJson.compilerOptions.verbatimModuleSyntax = false;
}
if (tsconfig.options.allowJs !== true) {
tsconfigJson.compilerOptions.allowJs = true;
}
if (tsconfig.options.esModuleInterop !== true) {
tsconfigJson.compilerOptions.esModuleInterop = true;
}
if (tsconfig.options.declaration !== true) {
tsconfigJson.compilerOptions.declaration = true;
}
if (context.options.platform === "browser") {
if (tsconfig.options.jsx !== ts.JsxEmit.ReactJSX) {
tsconfigJson.compilerOptions.jsx = "react-jsx";
}
if (!tsconfig.options.lib?.some((lib) => lib.toLowerCase() !== "dom")) {
tsconfigJson.compilerOptions.lib ??= [];
tsconfigJson.compilerOptions.lib.push("dom");
}
if (!tsconfig.options.lib?.some((lib) => lib.toLowerCase() !== "dom.iterable")) {
tsconfigJson.compilerOptions.lib ??= [];
tsconfigJson.compilerOptions.lib.push("dom.iterable");
}
} else if (context.options.platform === "node") {
if (!tsconfig.options.types?.some((type) => type.toLowerCase() === "node" || type.toLowerCase() === "@types/node")) {
tsconfigJson.compilerOptions.types ??= [];
tsconfigJson.compilerOptions.types.push("node");
}
}
return tsconfigJson;
}
__name(getTsconfigChanges, "getTsconfigChanges");
// src/commands/init/tsconfig/index.ts
async function initTsconfig(context, hooks) {
context.log(LogLevelLabel.TRACE, "Initializing TypeScript configuration for the Storm Stack project.");
if (!isPackageExists("typescript")) {
throw new Error('The TypeScript package is not installed. Please install the package using the command: "npm install typescript --save-dev"');
}
const originalTsconfigJson = await readJsonFile(context.options.tsconfig);
const json = await getTsconfigChanges(context);
await writeFile(context.log, context.options.tsconfig, StormJSON.stringify(json));
context.tsconfig = getParsedTypeScriptConfig(context.options.workspaceRoot, context.options.projectRoot, context.options.tsconfig, context.options.tsconfigRaw);
await hooks.callHook("init:tsconfig", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occured while resolving the TypeScript options: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while resolving the TypeScript options", {
cause: error
});
});
const tsconfigFilePath = getTsconfigFilePath(context.options.projectRoot, context.options.tsconfig);
const updateTsconfigJson = await readJsonFile(tsconfigFilePath);
if (updateTsconfigJson?.compilerOptions?.types && Array.isArray(updateTsconfigJson.compilerOptions.types) && !updateTsconfigJson.compilerOptions.types.length) {
delete updateTsconfigJson.compilerOptions.types;
}
const result = getObjectDiff(originalTsconfigJson, updateTsconfigJson, {
ignoreArrayOrder: true,
showOnly: {
statuses: [
"added",
"deleted",
"updated"
],
granularity: "deep"
}
});
const changes = [];
const getChanges = /* @__PURE__ */ __name((difference, property) => {
if (difference.status === "added" || difference.status === "deleted" || difference.status === "updated") {
if (difference.diff) {
for (const diff of difference.diff) {
getChanges(diff, property ? `${property}.${difference.property}` : difference.property);
}
} else {
changes.push({
field: property ? `${property}.${difference.property}` : difference.property,
status: difference.status,
previous: difference.status === "added" ? "---" : StormJSON.stringify(difference.previousValue),
current: difference.status === "deleted" ? "---" : StormJSON.stringify(difference.currentValue)
});
}
}
}, "getChanges");
for (const diff of result.diff) {
getChanges(diff);
}
if (changes.length > 0) {
context.log(LogLevelLabel.WARN, `Updating the following configuration values in "${tsconfigFilePath}" file:
${changes.map((change, i) => `${chalk.bold.whiteBright(`${i + 1}. ${titleCase(change.status)} the ${change.field} field: `)}
${chalk.red(` - Previous: ${change.previous} `)}
${chalk.green(` - Updated: ${change.current} `)}
`).join("\n")}
`);
}
await writeFile(context.log, tsconfigFilePath, StormJSON.stringify(updateTsconfigJson));
context.tsconfig = getParsedTypeScriptConfig(context.options.workspaceRoot, context.options.projectRoot, context.options.tsconfig);
if (!context.tsconfig) {
throw new Error("Failed to parse the TypeScript configuration file.");
}
}
__name(initTsconfig, "initTsconfig");
// src/commands/init/index.ts
async function init(context, hooks) {
await hooks.callHook("init:begin", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occured while starting initialization for the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while starting initialization for the Storm Stack project", {
cause: error
});
});
context.unimport = createUnimport(context);
await context.unimport.init();
await initOptions(context, hooks);
await initInstall(context, hooks);
await initTsconfig(context, hooks);
const handlePreTransform = /* @__PURE__ */ __name(async (context2, sourceFile) => {
await hooks.callHook("build:pre-transform", context2, sourceFile).catch((error) => {
context2.log(LogLevelLabel.ERROR, `An error occured while pre-transforming the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while pre-transforming the Storm Stack project", {
cause: error
});
});
return sourceFile;
}, "handlePreTransform");
const handlePostTransform = /* @__PURE__ */ __name(async (context2, sourceFile) => {
await hooks.callHook("build:post-transform", context2, sourceFile).catch((error) => {
context2.log(LogLevelLabel.ERROR, `An error occured while post-transforming the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while post-transforming the Storm Stack project", {
cause: error
});
});
return sourceFile;
}, "handlePostTransform");
context.compiler = new Compiler(context, {
onPreTransform: handlePreTransform,
onPostTransform: handlePostTransform
});
await initEntry(context, hooks);
await initReflections(context, hooks);
await hooks.callHook("init:complete", context).catch((error) => {
context.log(LogLevelLabel.ERROR, `An error occured while finishing initialization for the Storm Stack project: ${error.message}
${error.stack ?? ""}`);
throw new Error("An error occured while finishing initialization for the Storm Stack project", {
cause: error
});
});
context.vfs[__VFS_INIT__]();
}
__name(init, "init");
export { init };