@supernovaio/cli
Version:
Supernova.io Command Line Interface
311 lines (309 loc) • 13.4 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]="2ba5291d-6e75-5d3c-9139-40ca7af32e6e")}catch(e){}}();
import fs from "node:fs";
import path from "node:path";
export function folderNameToDisplayName(folderName) {
return folderName
.replaceAll(/[-_]/g, " ")
.replaceAll(/([A-Z])/g, " $1")
.trim()
.replaceAll(/\s+/g, " ")
.replaceAll(/\b\w/g, c => c.toUpperCase());
}
const filesToIgnore = ["README.md", ".DS_Store"];
function getAllFilesInDirectory(dirPath, relativeTo) {
return fs
.readdirSync(dirPath, { recursive: true, withFileTypes: true })
.filter(entry => entry.isFile() && !filesToIgnore.includes(entry.name))
.map(entry => path.relative(relativeTo, path.join(entry.parentPath || dirPath, entry.name)))
.sort();
}
function findThumbnail(templateDir) {
return [".png", ".svg", ".jpg", ".jpeg"]
.map(ext => `thumbnail${ext}`)
.find(filename => fs.existsSync(path.join(templateDir, filename)));
}
function parseDescriptionFromReadme(readmeFilePath) {
if (!fs.existsSync(readmeFilePath))
return "";
try {
const content = fs.readFileSync(readmeFilePath, "utf8");
const match = content.match(/^#[^\n]*\n+([^\n#]+)/m);
if (!match)
return "";
let description = match[1].trim();
description = description.replaceAll("**", "").replaceAll("*", "");
description = description.charAt(0).toUpperCase() + description.slice(1);
if (!description.endsWith(".") && !description.endsWith("!") && !description.endsWith("?")) {
description += ".";
}
return description;
}
catch {
return "";
}
}
function parseImportsFromTSFile(filePath, baseDir) {
if (!fs.existsSync(filePath))
return [];
try {
const content = fs.readFileSync(filePath, "utf8");
const dependencies = [];
const importRegex = /import\s+(?:[\w*{}\s,]+\s+from\s+)?['"`]([^'"`]+)['"`]/g;
let match;
while ((match = importRegex.exec(content)) !== null) {
const importPath = match[1];
if (importPath.startsWith("./") || importPath.startsWith("../")) {
try {
const fileDir = path.dirname(filePath);
const resolvedPath = path.resolve(fileDir, importPath);
const withoutJs = resolvedPath.endsWith(".js") ? resolvedPath.slice(0, -3) : null;
const possiblePaths = [
resolvedPath,
resolvedPath + ".tsx",
resolvedPath + ".ts",
resolvedPath + ".json",
resolvedPath + ".js",
...(withoutJs ? [withoutJs + ".ts", withoutJs + ".tsx", withoutJs] : []),
];
const actualPath = possiblePaths.find(p => fs.existsSync(p));
if (actualPath) {
const containerRoot = path.resolve(baseDir, "../../..");
const relativePath = path.relative(containerRoot, actualPath).replaceAll("\\", "/");
if (relativePath.startsWith("supernova/")) {
dependencies.push(relativePath);
}
}
}
catch {
}
}
}
return dependencies;
}
catch {
return [];
}
}
function parseImportsFromDirectory(itemDir) {
const dependencies = new Set();
try {
const entries = fs.readdirSync(itemDir, { recursive: true, withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (ext === ".ts" || ext === ".tsx") {
const fullPath = path.join(entry.parentPath || itemDir, entry.name);
const fileDeps = parseImportsFromTSFile(fullPath, itemDir);
for (const dep of fileDeps)
dependencies.add(dep);
}
}
}
}
catch {
}
return [...dependencies].sort();
}
function parseMainReadmeTable(mainReadmePath, itemKey) {
if (!fs.existsSync(mainReadmePath))
return null;
try {
const content = fs.readFileSync(mainReadmePath, "utf8");
const tableMatch = content.match(/(\|\s*(?:Pattern|Template)\s*\|[^\n]*)\n\|[-\s|]*\n((?:\|[^\n]*\n)*)/i);
if (!tableMatch)
return null;
const headerCells = tableMatch[1]
.split("|")
.map(cell => cell.trim())
.filter(Boolean);
const nameIndex = headerCells.findIndex(cell => /^(Pattern|Template)$/i.test(cell));
const descIndex = headerCells.findIndex(cell => /^Description$/i.test(cell));
if (nameIndex === -1 || descIndex === -1)
return null;
const rows = tableMatch[2].split("\n").filter(row => row.trim());
for (const row of rows) {
const cells = row
.split("|")
.map(cell => cell.trim())
.filter(Boolean);
if (cells.length > Math.max(nameIndex, descIndex)) {
const name = cells[nameIndex]?.replace(/\[([^\]]+)\].*/, "$1");
const description = cells[descIndex];
if (name === itemKey) {
return description.endsWith(".") ? description : description + ".";
}
}
}
}
catch {
}
return null;
}
function extractDescriptionFromReadme(itemDir, fallbackName, itemKey, subdirectory) {
const itemReadmePath = path.join(itemDir, "README.md");
const individualDescription = parseDescriptionFromReadme(itemReadmePath);
if (individualDescription) {
return individualDescription;
}
const containerRoot = path.resolve(itemDir, "../../..");
const mainReadmePath = path.join(containerRoot, "supernova", subdirectory, "README.md");
const tableDescription = parseMainReadmeTable(mainReadmePath, itemKey);
if (tableDescription) {
return tableDescription;
}
return `Use this to prototype similar to ${fallbackName}`;
}
function readPackageJson(basePath) {
const packageJsonPath = path.join(basePath, "package.json");
if (!fs.existsSync(packageJsonPath)) {
throw new Error(`package.json not found at ${packageJsonPath}`);
}
const content = fs.readFileSync(packageJsonPath, "utf8");
return JSON.parse(content);
}
function writePackageJson(basePath, packageData) {
const packageJsonPath = path.join(basePath, "package.json");
const content = JSON.stringify(packageData, null, 2) + "\n";
fs.writeFileSync(packageJsonPath, content, "utf8");
}
function discoverItems(basePath, subdirectory) {
const resolvedBasePath = basePath || process.cwd();
const itemsDir = path.join(resolvedBasePath, "supernova", subdirectory);
if (!fs.existsSync(itemsDir)) {
return {};
}
const items = {};
const entries = fs.readdirSync(itemsDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.name === "README.md") {
continue;
}
const itemKey = entry.name;
const itemDir = path.join(itemsDir, itemKey);
const displayName = folderNameToDisplayName(itemKey);
const description = extractDescriptionFromReadme(itemDir, displayName, itemKey, subdirectory);
const thumbnail = findThumbnail(itemDir);
const files = getAllFilesInDirectory(itemDir, itemDir);
const containerRoot = path.resolve(itemDir, "../../..");
const itemRelativePath = path.relative(containerRoot, itemDir).replaceAll("\\", "/");
const fullPathFiles = files.map(file => `${itemRelativePath}/${file}`);
const importDependencies = parseImportsFromDirectory(itemDir);
const allDependencies = [...new Set(importDependencies)];
const itemBasePath = itemRelativePath;
const externalDependencies = allDependencies.filter(dep => !dep.startsWith(itemBasePath + "/"));
const validExternalDependencies = externalDependencies.filter(dep => {
const fullPath = path.join(containerRoot, dep);
const exists = fs.existsSync(fullPath);
if (!exists) {
console.warn(`Warning: Dependency not found: ${dep} (expected at ${fullPath})`);
}
return exists;
});
const allFiles = new Set([...fullPathFiles, ...validExternalDependencies]);
const filteredFiles = [...allFiles].filter(file => !file.endsWith("README.md") &&
!["/thumbnail.png", "/thumbnail.svg", "/thumbnail.jpg", "/thumbnail.jpeg"].some(ext => file.endsWith(ext)));
const existingFiles = filteredFiles
.filter(file => {
const fullPath = path.join(containerRoot, file);
const exists = fs.existsSync(fullPath);
if (!exists) {
console.warn(`Warning: File not found: ${file} (expected at ${fullPath})`);
}
return exists;
})
.sort();
const itemInfo = {
name: displayName,
description,
files: existingFiles,
};
if (thumbnail) {
itemInfo.thumbnail = `${itemRelativePath}/${thumbnail}`;
}
items[itemKey] = itemInfo;
}
return items;
}
function discoverTemplates(basePath) {
const resolvedBasePath = basePath || process.cwd();
return discoverItems(resolvedBasePath, "templates");
}
function discoverPatterns(basePath) {
const resolvedBasePath = basePath || process.cwd();
return discoverItems(resolvedBasePath, "patterns");
}
function resolveTransitiveDependencies(items, patterns, basePath) {
const resolvedItems = {};
for (const [itemKey, itemInfo] of Object.entries(items)) {
const resolvedFiles = new Set(itemInfo.files);
const dependentPatterns = new Set();
for (const file of itemInfo.files) {
const pathMatch = file.match(/^supernova\/patterns\/([^\/]+)\//);
if (pathMatch) {
dependentPatterns.add(pathMatch[1]);
}
}
function addTransitiveDependencies(patternKey, visited = new Set()) {
if (visited.has(patternKey)) {
return;
}
visited.add(patternKey);
const pattern = patterns[patternKey];
if (!pattern) {
return;
}
for (const file of pattern.files)
resolvedFiles.add(file);
for (const file of pattern.files) {
const pathMatch = file.match(/^supernova\/patterns\/([^\/]+)\//);
if (pathMatch && pathMatch[1] !== patternKey) {
addTransitiveDependencies(pathMatch[1], new Set(visited));
}
}
}
for (const patternKey of dependentPatterns) {
addTransitiveDependencies(patternKey);
}
const containerRoot = path.resolve(basePath);
const validFiles = [...resolvedFiles]
.filter(file => {
const fullPath = path.join(containerRoot, file);
const exists = fs.existsSync(fullPath);
if (!exists) {
console.warn(`Warning: Transitive dependency not found: ${file} (expected at ${fullPath})`);
}
return exists;
})
.sort();
resolvedItems[itemKey] = {
...itemInfo,
files: validFiles,
};
}
return resolvedItems;
}
export async function discoverTemplatesAndPatterns(basePath) {
const resolvedBasePath = basePath || process.cwd();
const rawTemplates = discoverTemplates(resolvedBasePath);
const rawPatterns = discoverPatterns(resolvedBasePath);
const templates = resolveTransitiveDependencies(rawTemplates, rawPatterns, resolvedBasePath);
const patterns = resolveTransitiveDependencies(rawPatterns, rawPatterns, resolvedBasePath);
return { templates, patterns };
}
export async function discoverFilesForTemplates(pkgTemplates, basePath) {
const { templates } = await discoverTemplatesAndPatterns(basePath);
return Object.fromEntries(Object.entries(pkgTemplates).map(([key, pkgTemplate]) => [key, { ...templates[key], ...pkgTemplate }]));
}
export async function discoverAndUpdatePackageJson(basePath) {
const result = await discoverTemplatesAndPatterns(basePath);
const resolvedBasePath = basePath || process.cwd();
const packageData = readPackageJson(resolvedBasePath);
if (!packageData.supernova) {
packageData.supernova = {};
}
packageData.supernova.templates = Object.fromEntries(Object.entries(result.templates).map(([key, { name, description }]) => [key, { name, description }]));
writePackageJson(resolvedBasePath, packageData);
return result;
}
//# sourceMappingURL=discover.js.map
//# debugId=2ba5291d-6e75-5d3c-9139-40ca7af32e6e