UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

166 lines (164 loc) 7.34 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]="e8f5272f-9787-5175-951f-23267b3cd5e9")}catch(e){}}(); import { action } from "@oclif/core/ux"; import * as fs from "node:fs/promises"; import path from "node:path"; import { spawnAndWait } from "./spawn-and-wait.js"; export async function fileExists(p) { try { await fs.access(p); return true; } catch { return false; } } const FRAMEWORK_CONFIGS = { angular: { mainFileExt: ".ts", appEntryCandidates: ["src/app.component.ts", "src/App.component.ts"], generateAppContent: (templateId) => { const componentClassName = `${templateId}Component`; const selectorName = camelToKebab(templateId); return `import { Component } from "@angular/core";\nimport { ${componentClassName} } from "./components/${templateId}/${templateId}";\nimport { SnPrototypeComponent } from "../supernova/helpers/sn-prototype.component";\n\n@Component({\n selector: "app-root",\n standalone: true,\n imports: [SnPrototypeComponent, ${componentClassName}],\n template: \`<sn-prototype><${selectorName}></${selectorName}></sn-prototype>\`,\n})\nexport class AppComponent {}\n`; }, }, react: { mainFileExt: ".tsx", appEntryCandidates: ["src/App.tsx", "src/app.tsx"], generateAppContent: (templateId) => `import { ${templateId} } from "./components/${templateId}/${templateId}"\n\nexport default function App() {\n return <${templateId} />\n}`, }, }; const FRAMEWORK_DETECTION_ORDER = ["angular", "react"]; async function detectFrameworkAndEntryPath() { for (const framework of FRAMEWORK_DETECTION_ORDER) { const config = FRAMEWORK_CONFIGS[framework]; for (const candidate of config.appEntryCandidates) { const resolved = path.resolve(candidate); if (await fileExists(resolved)) return { config, appEntryPath: resolved }; } } const config = FRAMEWORK_CONFIGS.react; return { config, appEntryPath: path.resolve(config.appEntryCandidates[0]) }; } function camelToKebab(str) { return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); } export async function validateTemplates(templates, logger, debug) { action.start(`🔍 Validating ${Object.keys(templates).length} template(s)`); const { config } = await detectFrameworkAndEntryPath(); for (const [templateKey, template] of Object.entries(templates)) { action.start(`Validating template '${templateKey}'`); if (!template.files || template.files.length === 0) { action.stop("no files specified"); logger.error(`Template '${templateKey}': No files specified`); } const expectedMainFile = `supernova/templates/${templateKey}/${templateKey}${config.mainFileExt}`; if (!template.files.includes(expectedMainFile)) { action.stop("main file missing"); logger.error(`Template '${templateKey}': Expected main file '${expectedMainFile}' not found in files array`); } for (const filePath of template.files) { if (!(await fileExists(path.resolve(filePath)))) { action.stop("files missing"); logger.error(`Template '${templateKey}': File does not exist: ${filePath}`); } } try { await testTemplateBuild(templateKey, template.files, logger, debug); action.stop("build successful"); } catch (error) { action.stop("build failed"); logger.error(`Template '${templateKey}': Build test failed: ${error instanceof Error ? error.message : String(error)}`); } } action.stop("validation complete"); } async function testTemplateBuild(templateKey, files, logger, debug) { const { config, appEntryPath } = await detectFrameworkAndEntryPath(); const componentsDir = path.resolve("src/components"); const toDestPath = (filePath) => path.join(componentsDir, filePath.replace(/^supernova\/(patterns|templates)\//, "")); let originalAppContent = ""; let copiedFiles = []; let failed = false; try { await checkNoDirsExist(files.map(f => path.dirname(toDestPath(f)))); copiedFiles = await copyFiles(files, toDestPath); originalAppContent = await patchAppEntry(appEntryPath, templateKey, config); await runBuild(logger); } catch (error) { failed = true; throw error; } finally { const keepFilesForDebugging = debug && failed; if (!keepFilesForDebugging) { await cleanup(copiedFiles, appEntryPath, originalAppContent); } } } async function checkNoDirsExist(dirs) { for (const dir of new Set(dirs)) { if (await fileExists(dir)) { throw new Error(`Component directory already exists: ${path.relative(process.cwd(), dir)}. Please remove or rename this directory before testing the template.`); } } } async function copyFiles(files, toDestPath) { const copiedFiles = []; for (const filePath of files) { const destPath = toDestPath(filePath); await fs.mkdir(path.dirname(destPath), { recursive: true }); await fs.copyFile(path.resolve(filePath), destPath); copiedFiles.push(destPath); if (destPath.endsWith(".tsx") || destPath.endsWith(".ts")) { await updateImportsInFile(destPath); } } return copiedFiles; } async function patchAppEntry(appEntryPath, templateKey, config) { if (!(await fileExists(appEntryPath))) return ""; const originalContent = await fs.readFile(appEntryPath, "utf8"); await fs.writeFile(appEntryPath, config.generateAppContent(templateKey)); return originalContent; } async function runBuild(logger) { try { await spawnAndWait("npm", ["run", "build"], { cwd: process.cwd() }); } catch (error) { logger.log(`Build failed with error: ${error instanceof Error ? error.message : String(error)}`); throw error; } } async function cleanup(copiedFiles, appTsxPath, originalAppContent) { for (const filePath of copiedFiles) { if (await fileExists(filePath)) { await fs.rm(filePath, { force: true }); } } const sortedDirs = [...new Set(copiedFiles.map(f => path.dirname(f)))].sort((a, b) => b.length - a.length); for (const dir of sortedDirs) { try { await fs.rm(dir, { recursive: true, force: true }); } catch { } } if (originalAppContent && (await fileExists(appTsxPath))) { await fs.writeFile(appTsxPath, originalAppContent); } } async function updateImportsInFile(filePath) { const content = await fs.readFile(filePath, "utf8"); const updatedContent = content.replaceAll(/from\s+["']\.\.\/\.\.\/(patterns|templates)\/([^/"']+\/[^"']+)["']/g, 'from "../$2"'); if (content !== updatedContent) { await fs.writeFile(filePath, updatedContent); } } //# sourceMappingURL=validate-templates.js.map //# debugId=e8f5272f-9787-5175-951f-23267b3cd5e9