UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

353 lines (351 loc) 13.6 kB
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bcbe1867-7328-5445-a005-8e72df65cdb5")}catch(e){}}(); import { globSync } from "glob"; import fs from "node:fs"; import path from "node:path"; import * as ts from "typescript"; import { normalizePath, packageJsonEntryCandidates, readPackageJson, resolveExistingModuleFile, resolvePatternTarget, } from "./module-resolution.js"; import { hasSourceEntry, resolveWorkspacePackageDirs } from "./workspace-packages.js"; const buildOutputDirs = new Set(["build", "dist", "lib", "out"]); function uniqueSorted(values) { return [...new Set(values)].sort((a, b) => a.localeCompare(b)); } function unique(values) { return [...new Set(values)]; } function pathExistsAsDirectory(value) { return fs.existsSync(value) && fs.statSync(value).isDirectory(); } function resolveExistingSourceFile(entryPath) { return resolveExistingModuleFile(entryPath, { allowDeclarations: false }); } function parseTsConfig(tsconfigPath) { if (!fs.existsSync(tsconfigPath)) { return null; } const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile); if (configFile.error) { return null; } const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(tsconfigPath), undefined, tsconfigPath); return parsed; } function tsConfigSourceRewrites(packageDir, packageJsonEntry) { const parsedTsConfig = parseTsConfig(path.join(packageDir, "tsconfig.json")); const rootDir = parsedTsConfig?.options.rootDir; const outDir = parsedTsConfig?.options.outDir; if (!rootDir || !outDir) { return []; } const absoluteEntry = path.resolve(packageDir, packageJsonEntry); const relativeToOutDir = path.relative(outDir, absoluteEntry); if (relativeToOutDir.startsWith("..") || path.isAbsolute(relativeToOutDir)) { return []; } return [path.join(rootDir, relativeToOutDir)]; } function genericSourceRewrites(packageJsonEntry) { const normalizedEntry = normalizePath(packageJsonEntry).replace(/^\.\//, ""); const segments = normalizedEntry.split("/"); const outputDirIndex = segments.findIndex(segment => buildOutputDirs.has(segment)); if (outputDirIndex === -1) { return []; } const sourceSegments = [...segments]; sourceSegments[outputDirIndex] = "src"; return [sourceSegments.join("/")]; } function sourceFileCandidatesForPackageEntry(packageDir, packageJsonEntry) { const directEntry = path.resolve(packageDir, packageJsonEntry); const rewriteEntries = [...tsConfigSourceRewrites(packageDir, packageJsonEntry), ...genericSourceRewrites(packageJsonEntry)]; return [directEntry, ...rewriteEntries.map(entry => path.resolve(packageDir, entry))]; } function importFromSourceFile(packageDir, sourceFilePath) { const relativePath = normalizePath(path.relative(packageDir, sourceFilePath)); const parsed = path.parse(relativePath); const withoutExtension = path.join(parsed.dir, parsed.name); if (parsed.name === "index") { return normalizePath(parsed.dir || "."); } return normalizePath(withoutExtension || "."); } function resolvePackageJsonSourceEntry(packageDir, packageJson, subpath) { for (const entry of packageJsonEntryCandidates(packageJson, subpath)) { for (const sourceFileCandidate of sourceFileCandidatesForPackageEntry(packageDir, entry)) { const sourceFile = resolveExistingSourceFile(sourceFileCandidate); if (sourceFile) { return importFromSourceFile(packageDir, sourceFile); } } } return null; } function sourceEntryImportForSubpath(packageDir, subpath) { const normalizedSubpath = subpath.replace(/^\.\//, ""); if (!normalizedSubpath || normalizedSubpath === ".") { return hasSourceEntry(packageDir) ? "src" : null; } const sourceSubpath = path.join(packageDir, "src", normalizedSubpath); const sourceFile = resolveExistingSourceFile(sourceSubpath); if (sourceFile) { return importFromSourceFile(packageDir, sourceFile); } return null; } function parsePackageImport(importFrom) { const parts = importFrom.split("/").filter(Boolean); if (parts.length === 0) { return null; } if (parts[0].startsWith("@")) { if (parts.length < 2) { return null; } return { packageName: `${parts[0]}/${parts[1]}`, subpath: parts.length > 2 ? `./${parts.slice(2).join("/")}` : ".", }; } return { packageName: parts[0], subpath: parts.length > 1 ? `./${parts.slice(1).join("/")}` : ".", }; } function packageResolutionFor(packageDir, importSubpath) { const packageJson = readPackageJson(packageDir); if (packageJson) { const packageJsonSourceEntry = resolvePackageJsonSourceEntry(packageDir, packageJson, importSubpath); if (packageJsonSourceEntry) { return { dir: packageDir, importFrom: packageJsonSourceEntry, reason: "package-export" }; } } const sourceEntry = sourceEntryImportForSubpath(packageDir, importSubpath); if (sourceEntry) { return { dir: packageDir, importFrom: sourceEntry, reason: "package-source-entry" }; } return { dir: packageDir, importFrom: importSubpath === "." ? "." : importSubpath.replace(/^\.\//, ""), reason: "package-root", }; } function workspaceProtocolWarnings(packages) { const packageNames = new Set(packages.map(item => item.name)); const warnings = []; for (const projectPackage of packages) { const packageJson = readPackageJson(projectPackage.dir); if (!packageJson) { continue; } const dependencySections = [ packageJson.dependencies, packageJson.devDependencies, packageJson.optionalDependencies, packageJson.peerDependencies, ]; for (const dependencies of dependencySections) { for (const [dependencyName, dependencyVersion] of Object.entries(dependencies ?? {})) { if (dependencyVersion.startsWith("workspace:") && !packageNames.has(dependencyName)) { warnings.push(`${projectPackage.name} declares ${dependencyName}@${dependencyVersion}, but no local package named ${dependencyName} was discovered.`); } } } } return warnings; } function packageNameFromDir(packageDir) { const packageJson = readPackageJson(packageDir); return packageJson?.name?.trim() || null; } export function resolveCandidatePackageDirs(rootDir) { const subpackages = (() => { const workspacePackageDirs = resolveWorkspacePackageDirs(rootDir); if (workspacePackageDirs.length > 0) { return workspacePackageDirs; } if (!fs.existsSync(path.join(rootDir, "package.json"))) { const recursivePackageDirs = resolveRecursivePackageDirs(rootDir); if (recursivePackageDirs.length > 0) { return recursivePackageDirs; } } return resolveDirectChildPackageDirs(rootDir); })(); if (fs.existsSync(path.join(rootDir, "package.json"))) { subpackages.unshift(rootDir); } return unique(subpackages); } export function resolveProjectSetup(rootDir) { const packages = resolveCandidatePackageDirs(rootDir).flatMap(packageDir => { const name = packageNameFromDir(packageDir); if (!name) { return []; } return [ { dir: packageDir, name, packageJsonPath: path.join(packageDir, "package.json"), }, ]; }); return { packages, rootDir, warnings: workspaceProtocolWarnings(packages), }; } function resolveDirectChildPackageDirs(rootDir) { if (!pathExistsAsDirectory(rootDir)) { return []; } const result = []; for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) { if (!entry.isDirectory()) { continue; } const packageDir = path.join(rootDir, entry.name); if (fs.existsSync(path.join(packageDir, "package.json"))) { result.push(packageDir); } } return result; } function resolveRecursivePackageDirs(rootDir) { const packageJsonPaths = globSync("**/package.json", { absolute: true, cwd: rootDir, dot: false, ignore: [ "**/.git/**", "**/.next/**", "**/.supernova/**", "**/build/**", "**/coverage/**", "**/dist/**", "**/node_modules/**", "**/out/**", ], nodir: true, }); return uniqueSorted(packageJsonPaths.map(packageJsonPath => path.dirname(packageJsonPath))); } function tsConfigPaths(rootDir) { const configs = ["tsconfig.json", "tsconfig.base.json", "jsconfig.json"]; return configs.flatMap(configName => { const configPath = path.join(rootDir, configName); const parsed = parseTsConfig(configPath); const paths = parsed?.options.paths; if (!parsed || !paths || Object.keys(paths).length === 0) { return []; } return [ { baseUrl: parsed.options.baseUrl ?? rootDir, paths, }, ]; }); } function sourceRootFromAliasTarget(rootDir, targetPath) { const sourceFile = resolveExistingSourceFile(targetPath); if (!sourceFile) { return null; } const relativeSourceFile = normalizePath(path.relative(rootDir, sourceFile)); const segments = relativeSourceFile.split("/"); const srcIndex = segments.indexOf("src"); if (srcIndex > 0) { const importFrom = normalizePath(path.join(...segments.slice(0, srcIndex + 1), ...segments.slice(srcIndex + 1, -1), path.parse(sourceFile).name)); return { importFrom: importFrom.endsWith("/index") ? importFrom.slice(0, -"index".length - 1) : importFrom, rootDir, }; } return { importFrom: importFromSourceFile(rootDir, sourceFile), rootDir, }; } function resolveTsConfigPathTarget(rootDir, importFrom) { for (const config of tsConfigPaths(rootDir)) { for (const [aliasPattern, targetPatterns] of Object.entries(config.paths)) { for (const targetPattern of targetPatterns) { const matchedTarget = resolvePatternTarget(aliasPattern, targetPattern, importFrom); if (!matchedTarget) { continue; } const resolution = sourceRootFromAliasTarget(rootDir, path.resolve(config.baseUrl, matchedTarget)); if (resolution) { return resolution; } } } } return null; } function findPackageByName(packages, packageName) { return packages.find(projectPackage => projectPackage.name === packageName) ?? null; } export function resolveProjectAnalyzeTarget(input) { if (input.scannerType === "usage") { return { importFrom: input.importFrom, resolutionReason: "usage-scan", rootDir: input.rootDir, warnings: [], }; } const importFrom = Array.isArray(input.importFrom) ? input.importFrom[0] : input.importFrom; if (!importFrom) { return { importFrom: ".", resolutionReason: "empty-import", rootDir: input.rootDir, warnings: [], }; } const projectSetup = resolveProjectSetup(input.rootDir); const parsedImport = parsePackageImport(importFrom); const matchingPackage = parsedImport ? findPackageByName(projectSetup.packages, parsedImport.packageName) : null; if (matchingPackage) { const packageResolution = packageResolutionFor(matchingPackage.dir, parsedImport?.subpath ?? "."); return { importFrom: packageResolution.importFrom, packageName: matchingPackage.name, resolutionReason: packageResolution.reason, rootDir: packageResolution.dir, warnings: projectSetup.warnings, }; } const tsConfigPathTarget = resolveTsConfigPathTarget(input.rootDir, importFrom); if (tsConfigPathTarget) { return { importFrom: tsConfigPathTarget.importFrom, packageName: parsedImport?.packageName, resolutionReason: "tsconfig-path", rootDir: tsConfigPathTarget.rootDir, warnings: projectSetup.warnings, }; } const explicitPath = path.resolve(input.rootDir, importFrom); if (pathExistsAsDirectory(explicitPath)) { if (hasSourceEntry(explicitPath)) { return { importFrom: "src", resolutionReason: "explicit-path", rootDir: explicitPath, warnings: projectSetup.warnings, }; } return { importFrom: ".", resolutionReason: "explicit-path", rootDir: explicitPath, warnings: projectSetup.warnings, }; } return null; } //# sourceMappingURL=project-setup-resolver.js.map //# debugId=bcbe1867-7328-5445-a005-8e72df65cdb5