UNPKG

@storm-stack/core

Version:

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

612 lines (589 loc) 25.3 kB
import { resolveConfig, getParsedTypeScriptConfig } from './chunk-O5JSYXAY.js'; import { restoreVfs, createVfs } from './chunk-DGSLMHIC.js'; import { __VFS_VIRTUAL__ } from './chunk-7AH46LR2.js'; import { getSourceFile, getString } from './chunk-LK23XV73.js'; import { createLog } from './chunk-K7ITYUIA.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 { getEnvPaths } from '@stryke/env/get-env-paths'; import { getWorkspaceRoot, getProjectRoot, relativeToWorkspaceRoot } from '@stryke/fs/get-workspace-root'; import { readJsonFile } from '@stryke/fs/json'; import '@stryke/fs/list-files'; import { removeFile } from '@stryke/fs/remove-file'; import { resolvePackage } from '@stryke/fs/resolve'; import { hash } from '@stryke/hash/hash'; import { hashDirectory } from '@stryke/hash/hash-files'; import '@stryke/helpers/get-unique'; import { findFileExtension, relativePath } from '@stryke/path/file-path-fns'; import { joinPaths } from '@stryke/path/join-paths'; import { kebabCase } from '@stryke/string-format/kebab-case'; import { uuid } from '@stryke/unique-id/uuid'; import defu from 'defu'; import { createJiti } from 'jiti'; import { isParentPath } from '@stryke/path/is-parent-path'; import { replacePath } from '@stryke/path/replace'; import { Extractor, ExtractorConfig } from '@microsoft/api-extractor'; import { existsSync as existsSync$1 } from 'node:fs'; import { createProgram, createCompilerHost, getPreEmitDiagnostics, getLineAndCharacterOfPosition, flattenDiagnosticMessageText } from 'typescript'; import { declare } from '@babel/helper-plugin-utils'; import * as t from '@babel/types'; // src/commands/prepare/index.ts init_esm_shims(); // src/lib/context.ts init_esm_shims(); // src/lib/resolver.ts init_esm_shims(); function createResolver(options) { return createJiti(joinPaths(options.workspaceRoot, options.projectRoot), { ...options, interopDefault: true, fsCache: joinPaths(options.cacheDir, "jiti"), moduleCache: true }); } __name(createResolver, "createResolver"); // src/lib/context.ts var PROJECT_ROOT_HASH_LENGTH = 45; function getPrefixedProjectRootHash(name, projectRootHash) { const combined = `${kebabCase(name)}_${projectRootHash}`; return combined.length > PROJECT_ROOT_HASH_LENGTH ? combined.slice(0, PROJECT_ROOT_HASH_LENGTH) : combined; } __name(getPrefixedProjectRootHash, "getPrefixedProjectRootHash"); async function getChecksum(path) { return hashDirectory(path, { ignore: [ "node_modules", ".git", ".nx", ".cache", ".storm", "tmp", "dist" ] }); } __name(getChecksum, "getChecksum"); async function getPersistedMeta(context) { const metaFilePath = joinPaths(context.dataPath, "meta.json"); if (existsSync(metaFilePath)) { try { return await readJsonFile(metaFilePath); } catch { context.log(LogLevelLabel.WARN, `Failed to read meta file at ${metaFilePath}. It may be corrupted.`); await removeFile(metaFilePath); context.persistedMeta = void 0; } } return void 0; } __name(getPersistedMeta, "getPersistedMeta"); async function createContext(inlineConfig, workspaceConfig, options = {}) { const workspaceRoot = workspaceConfig?.workspaceRoot ?? getWorkspaceRoot(); const projectRoot = (inlineConfig.root ?? getProjectRoot()) || process.cwd(); const resolvedWorkspaceConfig = defu(workspaceConfig, { workspaceRoot }); let projectJson; const projectJsonPath = joinPaths(projectRoot, "project.json"); if (existsSync(projectJsonPath)) { projectJson = await readJsonFile(projectJsonPath); } let packageJson; const packageJsonPath = joinPaths(projectRoot, "package.json"); if (existsSync(packageJsonPath)) { packageJson = await readJsonFile(packageJsonPath); } else if (inlineConfig.command === "new") { const workspacePackageJsonPath = joinPaths(workspaceRoot, "package.json"); packageJson = await readJsonFile(workspacePackageJsonPath); resolvedWorkspaceConfig.repository ??= typeof packageJson?.repository === "string" ? packageJson.repository : packageJson?.repository?.url; } else { throw new Error(`The package.json file is missing in the project root directory: ${projectRoot}. Please run the "new" command to create a new Storm Stack project.`); } const checksum = await getChecksum(projectRoot); const meta = { buildId: uuid(), releaseId: uuid(), checksum, timestamp: Date.now(), projectRootHash: hash(joinPaths(workspaceRoot, projectRoot), { maxLength: PROJECT_ROOT_HASH_LENGTH }), runtimeIdMap: {}, virtualFiles: {} }; const artifactsPath = joinPaths(workspaceRoot, projectRoot, ".storm"); const runtimePath = joinPaths(artifactsPath, "runtime"); const entryPath = joinPaths(artifactsPath, "entry"); const envPaths = getEnvPaths({ orgId: "storm-software", appId: "storm-stack", workspaceRoot }); if (!envPaths.cache) { throw new Error("The cache directory could not be determined."); } envPaths.cache = joinPaths(envPaths.cache, "projects", meta.projectRootHash); const partiallyResolvedContext = { options: { ...resolvedWorkspaceConfig, name: projectJson?.name || options.name, ...inlineConfig, userConfig: { config: {} }, inlineConfig, projectRoot, workspaceConfig: resolvedWorkspaceConfig, plugins: {} }, log: createLog(options.name ?? null, defu(inlineConfig, resolvedWorkspaceConfig)), meta, entry: [], envPaths, artifactsPath, runtimePath, entryPath, dtsPath: joinPaths(envPaths.cache, "dts"), runtimeDtsFilePath: joinPaths(projectRoot, "storm.d.ts"), dataPath: joinPaths(envPaths.data, "projects", meta.projectRootHash), cachePath: envPaths.cache, projectJson, packageJson, runtime: { logs: [], storage: [], init: [] }, packageDeps: {}, reflections: {}, resolver: createResolver({ workspaceRoot, projectRoot, cacheDir: envPaths.cache }), relativeToWorkspaceRoot: relativeToWorkspaceRoot(projectRoot) }; const resolvedOptions = await resolveConfig(partiallyResolvedContext, inlineConfig, void 0, projectRoot); const context = partiallyResolvedContext; context.options = resolvedOptions; context.dataPath = joinPaths(context.envPaths.data, "projects", getPrefixedProjectRootHash(context.options.name, context.meta.projectRootHash)); context.persistedMeta = await getPersistedMeta(context); context.runtimeDtsFilePath = 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"); context.tsconfig = getParsedTypeScriptConfig(context.options.workspaceRoot, context.options.projectRoot, context.options.tsconfig); if (context.persistedMeta?.checksum === context.meta.checksum) { context.log(LogLevelLabel.TRACE, `Restoring the virtual file system (VFS) as the meta checksum has not changed.`); context.vfs = restoreVfs(context, { runtimeIdMap: context.persistedMeta.runtimeIdMap, virtualFiles: context.persistedMeta.virtualFiles }); } else { context.vfs = createVfs(context); } const packagePath = process.env.STORM_STACK_LOCAL ? joinPaths(context.options.workspaceRoot, "dist/packages/core") : await resolvePackage("@storm-stack/core"); if (!packagePath) { throw new Error("Could not resolve the Storm Stack core package. Please ensure it is installed."); } return context; } __name(createContext, "createContext"); async function writeMetaFile(context) { const metaFilePath = joinPaths(context.dataPath, "meta.json"); context.log(LogLevelLabel.DEBUG, `Writing runtime metadata to ${metaFilePath}`); await context.vfs.writeFileToDisk(metaFilePath, JSON.stringify({ ...context.meta, runtimeIdMap: Object.fromEntries(context.vfs.runtimeIdMap.entries()), virtualFiles: context.vfs[__VFS_VIRTUAL__].toJSON(context.artifactsPath) }, null, 2)); } __name(writeMetaFile, "writeMetaFile"); // src/commands/prepare/config/index.ts init_esm_shims(); async function prepareConfig(context, hooks) { context.log(LogLevelLabel.TRACE, `Preparing the configuration for the Storm Stack project.`); await hooks.callHook("prepare:config", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while preparing the configuration for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while preparing the configuration for the Storm Stack project", { cause: error }); }); } __name(prepareConfig, "prepareConfig"); // src/commands/prepare/entry/index.ts init_esm_shims(); async function prepareEntry(context, hooks) { context.log(LogLevelLabel.TRACE, "Preparing the entry modules for the Storm Stack project."); await hooks.callHook("prepare:entry", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while preparing the entry modules for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while preparing the entry modules for the Storm Stack project", { cause: error }); }); } __name(prepareEntry, "prepareEntry"); // src/commands/prepare/output/index.ts init_esm_shims(); async function prepareOutput(context, hooks) { context.log(LogLevelLabel.TRACE, `Initializing the output configuration for the Storm Stack project.`); await hooks.callHook("prepare:output", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while initializing the deployment configuration for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while initializing the deployment configuration for the Storm Stack project", { cause: error }); }); } __name(prepareOutput, "prepareOutput"); // src/commands/prepare/runtime/index.ts init_esm_shims(); // src/lib/utilities/file-header.ts init_esm_shims(); function getBaseFileHeader() { return ` // Generated with Storm Stack // Note: Do not edit this file manually - it will be overwritten automatically `; } __name(getBaseFileHeader, "getBaseFileHeader"); function getFileHeader(directive = "", prettierIgnore = false) { if (directive && process.env.STORM_STACK_LOCAL) { directive = directive.replaceAll("@storm-stack/core/runtime-types", "../../dist/packages/types"); } return `/* eslint-disable */ // biome-ignore lint: disable ${prettierIgnore ? `// prettier-ignore` : ""}${directive ? ` ${directive} ` : "\n"} ${getBaseFileHeader()} `; } __name(getFileHeader, "getFileHeader"); // src/commands/prepare/runtime/index.ts async function prepareRuntime(context, hooks) { context.log(LogLevelLabel.TRACE, `Preparing the runtime artifacts for the Storm Stack project.`); await context.vfs.rm(context.runtimePath); await hooks.callHook("prepare:runtime", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while preparing the runtime artifacts for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while preparing the runtime artifacts for the Storm Stack project", { cause: error }); }); context.log(LogLevelLabel.TRACE, "Generating runtime barrel file"); await context.vfs.writeRuntimeFile("index", joinPaths(context.runtimePath, "index.ts"), ` ${getFileHeader()} ${(await context.vfs.listRuntimeFiles()).filter((file) => !isParentPath(file.path, joinPaths(context.runtimePath, "log")) && !isParentPath(file.path, joinPaths(context.runtimePath, "storage"))).map((file) => `export * from "./${replacePath(file.path, context.runtimePath).replace(findFileExtension(file.path), "")}";`).join("\n")} `); } __name(prepareRuntime, "prepareRuntime"); // src/commands/prepare/types/index.ts init_esm_shims(); // src/lib/babel/plugins/module-resolver.ts init_esm_shims(); function resolveModulePath(nodePath, state) { if (!t.isStringLiteral(nodePath.node)) { return; } const sourcePath = nodePath.node.value; const resolvedPath = state.context?.vfs.resolvePath(sourcePath); if (resolvedPath) { nodePath.replaceWith(t.stringLiteral( // Remove the file extension if it exists resolvedPath.replace(/\.(?:ts|mts|cts)x?$/, "") )); } } __name(resolveModulePath, "resolveModulePath"); var TRANSFORM_FUNCTIONS = [ "require", "require.resolve", "System.import", // Jest methods "jest.genMockFromModule", "jest.mock", "jest.unmock", "jest.doMock", // eslint-disable-next-line @cspell/spellchecker "jest.dontMock", "jest.setMock", "jest.requireActual", "jest.requireMock", // Older Jest methods "require.requireActual", "require.requireMock" ]; function matchesPattern(state, calleePath, pattern) { const { node } = calleePath; if (t.isMemberExpression(node)) { return calleePath.matchesPattern(pattern); } if (!t.isIdentifier(node) || pattern.includes(".")) { return false; } const name = pattern.split(".")[0]; return node.name === name; } __name(matchesPattern, "matchesPattern"); var importVisitors = { CallExpression: /* @__PURE__ */ __name((nodePath, state) => { if (state.moduleResolverVisited.has(nodePath)) { return; } const calleePath = nodePath.get("callee"); if (calleePath && TRANSFORM_FUNCTIONS.some((pattern) => matchesPattern(state, calleePath, pattern)) || t.isImport(nodePath.node.callee)) { state.moduleResolverVisited.add(nodePath); resolveModulePath(nodePath.get("arguments.0"), state); } }, "CallExpression"), // eslint-disable-next-line ts/naming-convention "ImportDeclaration|ExportDeclaration|ExportAllDeclaration": /* @__PURE__ */ __name((nodePath, state) => { if (!nodePath || !nodePath.get("source") || state.moduleResolverVisited.has(nodePath)) { return; } state.moduleResolverVisited.add(nodePath); resolveModulePath(nodePath.get("source"), state); }, "ImportDeclaration|ExportDeclaration|ExportAllDeclaration") }; function ModuleResolverPlugin(context) { return declare(/* @__PURE__ */ __name(function builder(api) { let moduleResolverVisited = /* @__PURE__ */ new Set(); return { name: "storm-stack:module-resolver", manipulateOptions(opts) { opts.filename ??= "unknown"; }, pre() { moduleResolverVisited = /* @__PURE__ */ new Set(); }, visitor: { Program: { enter(programPath, state) { programPath.traverse(importVisitors, { ...state, context, moduleResolverVisited, api }); }, exit(programPath, state) { programPath.traverse(importVisitors, { ...state, context, moduleResolverVisited, api }); } } }, post() { moduleResolverVisited.clear(); } }; }, "builder")); } __name(ModuleResolverPlugin, "ModuleResolverPlugin"); // src/commands/prepare/types/index.ts async function prepareTypes(context, hooks) { context.log(LogLevelLabel.TRACE, `Preparing the TypeScript definitions for the Storm Stack project.`); await context.vfs.rm(context.runtimeDtsFilePath); context.log(LogLevelLabel.TRACE, "Transforming runtime files."); const runtimeFiles = await Promise.all((await context.vfs.listRuntimeFiles()).filter((file) => !context.vfs.isMatchingRuntimeId("index", file.id)).map(async (file) => { file.contents = await context.compiler.transform(context, file.path, file.contents, { skipTransformUnimport: true, babel: { plugins: [ ModuleResolverPlugin, ...context.options.babel.plugins ] } }); context.log(LogLevelLabel.TRACE, `Writing transformed runtime file ${file.id}.`); await context.vfs.writeRuntimeFile(file.id, file.path, file.contents); return file.path; })); const typescriptPath = await resolvePackage("typescript"); if (!typescriptPath) { throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed."); } const files = runtimeFiles.reduce( (ret, fileName) => { const formatted = replacePath(fileName, context.options.workspaceRoot); if (!ret.includes(formatted)) { ret.push(formatted); } return ret; }, [ joinPaths(typescriptPath, "lib", "lib.esnext.full.d.ts") ] // await listFiles(joinPaths(typescriptPath, "lib", "lib.*.d.ts")) ); context.log(LogLevelLabel.TRACE, "Parsing TypeScript configuration for the Storm Stack project."); const sourceFileDts = getSourceFile(context.runtimeDtsFilePath, `${getFileHeader(null, false)} `); await hooks.callHook("prepare:types", context, sourceFileDts).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while preparing the TypeScript definitions for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while preparing the TypeScript definitions for the Storm Stack project", { cause: error }); }); await context.vfs.writeFileToDisk(sourceFileDts.id, getString(sourceFileDts.code)); const resolvedTsconfig = getParsedTypeScriptConfig(context.options.workspaceRoot, context.options.projectRoot, context.tsconfig.tsconfigFilePath, defu({ compilerOptions: { strict: false, noEmit: false, declaration: true, declarationMap: false, emitDeclarationOnly: true, skipLibCheck: true }, exclude: [ "node_modules", "dist" ], include: files }, context.options.tsconfigRaw ?? {})); resolvedTsconfig.options.configFilePath = joinPaths(context.options.workspaceRoot, context.tsconfig.tsconfigFilePath); resolvedTsconfig.options.pathsBasePath = context.options.workspaceRoot; resolvedTsconfig.options.suppressOutputPathCheck = true; context.log(LogLevelLabel.TRACE, "Creating the TypeScript compiler host"); const program = createProgram(files, resolvedTsconfig.options, createCompilerHost(resolvedTsconfig.options)); context.log(LogLevelLabel.TRACE, `Running TypeScript compiler on ${runtimeFiles.length} runtime files.`); let runtimeModules = ""; const emitResult = program.emit(void 0, (fileName, text, _, __, sourceFiles, _data) => { const sourceFile2 = sourceFiles?.[0]; if (sourceFile2?.fileName && !fileName.endsWith(".map")) { if (context.vfs.isRuntimeFile(sourceFile2.fileName)) { runtimeModules += ` declare module "${context.vfs.resolveId(sourceFile2.fileName)}" { ${text.trim().replace(/^\s*export\s*declare\s*/gm, "export ").replace(/^\s*declare\s*/gm, "")} } `; } } }, void 0, true); const diagnostics = getPreEmitDiagnostics(program).concat(emitResult.diagnostics); const diagnosticMessages = []; diagnostics.forEach((diagnostic) => { if (diagnostic.file) { const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); const message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); diagnosticMessages.push(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); } else { const message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); diagnosticMessages.push(message); } }); const diagnosticMessage = diagnosticMessages.join("\n"); if (diagnosticMessage) { throw new Error(`TypeScript compilation failed: ${diagnosticMessage.length > 5e3 ? `${diagnosticMessage.slice(0, 5e3)}...` : diagnosticMessage}`); } const corePackagePath = await resolvePackage("@storm-stack/core"); if (!corePackagePath || !existsSync$1(corePackagePath)) { throw new Error(`Could not resolve @storm-stack/core package location: ${corePackagePath} does not exist.`); } const mainEntryPointFilePath = joinPaths(corePackagePath, "dist", "runtime-types", "esm", "index.d.ts"); if (!existsSync$1(mainEntryPointFilePath)) { throw new Error(`Could not resolve @storm-stack/core/runtime-types package location: ${mainEntryPointFilePath} does not exist.`); } context.log(LogLevelLabel.TRACE, `Running API Extractor on @storm-stack/core/runtime-types package at ${mainEntryPointFilePath}.`); const untrimmedFilePath = joinPaths(context.dtsPath, `${context.meta.projectRootHash}.d.ts`); const extractorResult = Extractor.invoke(ExtractorConfig.prepare({ configObject: { mainEntryPointFilePath, apiReport: { enabled: false, // `reportFileName` is not been used. It's just to fit the requirement of API Extractor. reportFileName: "report.api.md" }, docModel: { enabled: false }, dtsRollup: { enabled: true, untrimmedFilePath }, tsdocMetadata: { enabled: false }, compiler: { tsconfigFilePath: relativePath(joinPaths(context.options.workspaceRoot, context.options.projectRoot), joinPaths(context.options.workspaceRoot, context.tsconfig.tsconfigFilePath)) }, projectFolder: joinPaths(context.options.workspaceRoot, context.options.projectRoot), newlineKind: "lf" }, configObjectFullPath: void 0, packageJsonFullPath: joinPaths(context.options.workspaceRoot, context.options.projectRoot, "package.json") }), { localBuild: true, showVerboseMessages: true }); if (!extractorResult.succeeded) { throw new Error(`API Extractor completed with ${extractorResult.errorCount} errors and ${extractorResult.warningCount} warnings when processing @storm-stack/core/runtime-types package.`); } context.log(LogLevelLabel.TRACE, `Generating TypeScript declaration file in ${context.runtimeDtsFilePath}.`); const sourceFile = getSourceFile(context.runtimeDtsFilePath, `${getFileHeader(null, false)} ${(await context.vfs.readFile(untrimmedFilePath)).replace(/\s*export.*__Ω.*;/g, "").replace(/^export\s*\{\s*\}\s*$/gm, "").replace(/^export\s*(?:declare\s*)?interface\s*/gm, "interface ").replace(/^export\s*(?:declare\s*)?type\s*/gm, "type ").replace(/^export\s*(?:declare\s*)?const\s*/gm, "declare const ").replace(/: Storage(?:_\d+)?;$/gm, ': import("unstorage").Storage<import("unstorage").StorageValue>;')} ${runtimeModules}`.replace( // eslint-disable-next-line regexp/no-super-linear-backtracking /import\s*(?:type\s*)?\{?[\w,\s]*(?:\}\s*)?from\s*(?:'|")@?[a-zA-Z0-9-\\/.]*(?:'|");?/g, "" ).replaceAll("#private;", "").replace(/__Ω/g, "")); await hooks.callHook("prepare:types", context, sourceFile).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occurred while preparing the TypeScript definitions for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occurred while preparing the TypeScript definitions for the Storm Stack project", { cause: error }); }); await context.vfs.writeFileToDisk(sourceFile.id, getString(sourceFile.code)); } __name(prepareTypes, "prepareTypes"); // src/commands/prepare/index.ts async function prepare(context, hooks) { await writeMetaFile(context); context.persistedMeta = context.meta; await hooks.callHook("prepare:begin", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occured while starting the prepare process for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occured while starting the prepare process for the Storm Stack project", { cause: error }); }); if (!existsSync(context.cachePath)) { await createDirectory(context.cachePath); } if (!existsSync(context.dataPath)) { await createDirectory(context.dataPath); } await prepareConfig(context, hooks); if (context.options.projectType === "application") { await prepareRuntime(context, hooks); } if (context.options.output.dts !== false) { await prepareTypes(context, hooks); } if (context.options.projectType === "application") { await prepareEntry(context, hooks); } await prepareOutput(context, hooks); 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."); } await hooks.callHook("prepare:complete", context).catch((error) => { context.log(LogLevelLabel.ERROR, `An error occured while finishing the prepare process for the Storm Stack project: ${error.message} ${error.stack ?? ""}`); throw new Error("An error occured while finishing the prepare process for the Storm Stack project", { cause: error }); }); await writeMetaFile(context); } __name(prepare, "prepare"); export { createContext, getChecksum, getPersistedMeta, prepare };