UNPKG

@storm-stack/core

Version:

A build toolkit and runtime used by Storm Software in TypeScript applications

468 lines (452 loc) 21 kB
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 };