@supernovaio/cli
Version:
Supernova.io Command Line Interface
353 lines (351 loc) • 13.6 kB
JavaScript
!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