UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

277 lines (275 loc) 14 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]="0de7f429-2e03-5634-bb1f-8a633e7a9c38")}catch(e){}}(); import { execFile as execFileCallback } from "node:child_process"; import fs from "node:fs"; import fsPromises from "node:fs/promises"; import path from "node:path"; import { promisify } from "node:util"; import inquirer from "inquirer"; import { getApiClient as getDefaultApiClient } from "../../api-client.js"; import { fileExists } from "../../validate-templates.js"; import { TEMP_NPMRC_MARKER } from "../constants.js"; import { containerRepositoryUrlForFramework, findNextAvailablePatchVersion } from "../helpers.js"; import { promptConfirmation } from "../prompts.js"; import { colorize, muted, success, warning } from "../ui.js"; const execFile = promisify(execFileCallback); export function createTemplateService(input) { const getApiClient = input.getApiClient ?? getDefaultApiClient; return { async detectDockerAvailabilityIssue() { try { await execFile("docker", ["info"]); return undefined; } catch (caughtError) { if (caughtError?.code === "ENOENT") { return { kind: "missing", message: "Docker CLI was not found on this system. Install Docker and make sure `docker` is available in PATH before uploading.", }; } return { kind: "not_running", message: "Docker is installed, but does not appear to be running. Start Docker Desktop or your Docker daemon.", }; } }, async ensureTemplateRepository(framework, destinationPath) { const repositoryUrl = containerRepositoryUrlForFramework(framework); const resolvedDestination = path.resolve(destinationPath); const destinationExists = fs.existsSync(resolvedDestination); if (!destinationExists) { input.log(`${muted("Cloning")} ${repositoryUrl} ${muted("->")} ${resolvedDestination}`); await cloneRepository(repositoryUrl, resolvedDestination, input.error); return; } const entries = fs.readdirSync(resolvedDestination); if (entries.length === 0) { input.log(`${muted("Cloning")} ${repositoryUrl} ${muted("into")} ${resolvedDestination}`); await cloneRepositoryIntoCurrentDirectory(repositoryUrl, resolvedDestination, input.error); return; } const replaceExisting = await promptConfirmation(`Destination ${resolvedDestination} is non-empty. Replace its contents with the ${framework} container starter?`, { defaultValue: false, }); if (!replaceExisting) { input.log(`${warning("Keeping existing files.")} Clone manually if needed: ${repositoryUrl}`); return; } input.log(`${muted("Removing existing contents from")} ${resolvedDestination}`); for (const entry of entries) { await fsPromises.rm(path.join(resolvedDestination, entry), { force: true, recursive: true }); } input.log(`${muted("Cloning")} ${repositoryUrl} ${muted("into")} ${resolvedDestination}`); await cloneRepositoryIntoCurrentDirectory(repositoryUrl, resolvedDestination, input.error); }, async resolveTemplateUploadPath(defaultPath) { const candidateFromContext = path.resolve(defaultPath); const normalizedFromContext = normalizeTemplatePath(candidateFromContext); if (normalizedFromContext && hasTemplatePackageJson(normalizedFromContext)) { return normalizedFromContext; } input.log(warning(`package.json not found in ${candidateFromContext}`)); input.log(muted("Checked:")); input.log(muted(`- ${path.join(candidateFromContext, "package.json")}`)); input.log(muted(`- ${path.join(candidateFromContext, "*/package.json")} (single nested auto-detect)`)); input.log(muted("Select the folder that contains your prepared container.")); while (true) { const response = await inquirer.prompt([ { default: candidateFromContext, message: colorize("Path to container root (folder with package.json):", "blue"), name: "templateUploadPath", type: "input", }, ]); const candidate = path.resolve(response.templateUploadPath.trim()); if (!fs.existsSync(candidate)) { input.log(warning(`Path does not exist: ${candidate}`)); if (!(await promptRetryTemplatePath())) input.error("Upload cancelled."); continue; } const normalizedCandidate = normalizeTemplatePath(candidate); if (!normalizedCandidate) { input.log(warning(`Path is neither a directory nor a package.json file: ${candidate}`)); if (!(await promptRetryTemplatePath())) input.error("Upload cancelled."); continue; } if (hasTemplatePackageJson(normalizedCandidate)) { return normalizedCandidate; } const discoveredNestedPath = findSingleNestedPackageJsonDirectory(normalizedCandidate); if (discoveredNestedPath) { input.log(success(`Found nested container package.json: ${discoveredNestedPath}`)); return discoveredNestedPath; } input.log(warning(`package.json not found in ${normalizedCandidate}`)); if (!(await promptRetryTemplatePath())) input.error("Upload cancelled."); } }, async ensureTemplateVersionIsUnique(versionInput) { const pkg = await readTemplatePackageMeta(versionInput.templateDestinationPath); if (!pkg) return; const templates = await listSandboxTemplates(getApiClient, input.env, versionInput.workspaceId, versionInput.designSystemId); const conflictingTemplate = templates.find(template => template.name === pkg.name); const existingVersions = new Set((conflictingTemplate?.versions ?? []).map(version => version.name)); if (!existingVersions.has(pkg.version)) return; input.log(warning(`Template version conflict detected: ${pkg.name}@${pkg.version} already exists in this design system.`)); const response = await inquirer.prompt([ { choices: [ { name: "Bump patch version in package.json and continue", value: "bump_patch" }, { name: "Continue anyway", value: "continue" }, { name: "Cancel upload", value: "cancel" }, ], default: "bump_patch", message: colorize("Choose how to resolve the version conflict:", "yellow"), name: "action", type: "list", }, ]); if (response.action === "cancel") { input.error("Upload cancelled due to template version conflict."); } if (response.action === "continue") { return; } const nextVersion = findNextAvailablePatchVersion(pkg.version, existingVersions); if (!nextVersion) { input.error(`Cannot auto-bump non-semver version "${pkg.version}". Update package.json manually and retry.`); } await updateTemplatePackageVersion(versionInput.templateDestinationPath, nextVersion); input.log(success(`Updated package.json version: ${pkg.version} -> ${nextVersion}`)); }, async runTemplateUploadWithCleanup(uploadInput) { const markerPath = path.join(uploadInput.templateDestinationPath, TEMP_NPMRC_MARKER); const npmrcPath = path.join(uploadInput.templateDestinationPath, ".npmrc"); const packageMeta = await readTemplatePackageMeta(uploadInput.templateDestinationPath); if (!packageMeta) { input.error("Unable to resolve uploaded container template: package.json name or version is missing."); } try { await runTemplateUpload(input.runTemplateUploadCommand, { designSystemId: uploadInput.designSystemId, npmToken: uploadInput.npmToken, templateDestinationPath: uploadInput.templateDestinationPath, workspaceId: uploadInput.workspaceId, }); const uploadedTemplate = await resolveUploadedSandboxTemplate(getApiClient, input.env, { designSystemId: uploadInput.designSystemId, packageName: packageMeta.name, packageVersion: packageMeta.version, workspaceId: uploadInput.workspaceId, }); if (!uploadedTemplate) { input.error(`Unable to resolve uploaded container template ${packageMeta.name}@${packageMeta.version} in Supernova.`); } return uploadedTemplate; } finally { if (await fileExists(markerPath)) { await fsPromises.rm(markerPath, { force: true }); await fsPromises.rm(npmrcPath, { force: true }); } } }, }; } async function runTemplateUpload(runTemplateUploadCommand, input) { const args = ["--workspaceId", input.workspaceId, "--designSystemId", input.designSystemId]; if (input.npmToken) { args.push("--npmToken", input.npmToken); } await runTemplateUploadCommand(input.templateDestinationPath, args); } async function cloneRepository(repositoryUrl, destinationPath, error) { try { await execFile("git", ["clone", repositoryUrl, destinationPath]); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); error(`Container clone failed: ${message}`); } } async function cloneRepositoryIntoCurrentDirectory(repositoryUrl, cwd, error) { try { await execFile("git", ["clone", repositoryUrl, "."], { cwd }); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : String(caughtError); error(`Container clone failed: ${message}`); } } function hasTemplatePackageJson(templatePath) { return fs.existsSync(path.join(templatePath, "package.json")); } function normalizeTemplatePath(inputPath) { if (!fs.existsSync(inputPath)) return null; const stat = fs.statSync(inputPath); if (stat.isDirectory()) return inputPath; if (stat.isFile() && path.basename(inputPath) === "package.json") return path.dirname(inputPath); return null; } function findSingleNestedPackageJsonDirectory(rootPath) { const childEntries = fs.readdirSync(rootPath, { withFileTypes: true }); const dirsWithPackageJson = childEntries .filter(entry => entry.isDirectory()) .map(entry => path.join(rootPath, entry.name)) .filter(dirPath => hasTemplatePackageJson(dirPath)); return dirsWithPackageJson.length === 1 ? dirsWithPackageJson[0] : null; } async function promptRetryTemplatePath() { return promptConfirmation("Try a different container path?"); } async function readTemplatePackageMeta(templateDestinationPath) { const packageJsonPath = path.join(templateDestinationPath, "package.json"); if (!fs.existsSync(packageJsonPath)) return null; try { const raw = await fsPromises.readFile(packageJsonPath, "utf8"); const parsed = JSON.parse(raw); if (!parsed.name || !parsed.version) return null; return { name: parsed.name, version: parsed.version }; } catch { return null; } } async function updateTemplatePackageVersion(templateDestinationPath, nextVersion) { const packageJsonPath = path.join(templateDestinationPath, "package.json"); const raw = await fsPromises.readFile(packageJsonPath, "utf8"); const parsed = JSON.parse(raw); parsed.version = nextVersion; await fsPromises.writeFile(packageJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8"); } async function listSandboxTemplates(getApiClient, env, workspaceId, designSystemId) { const client = await getApiClient(env); const { templates } = await client.sandboxes.templates.list({ designSystemId, workspaceId, }); return templates; } async function resolveUploadedSandboxTemplate(getApiClient, env, input) { const templates = await listSandboxTemplates(getApiClient, env, input.workspaceId, input.designSystemId); const uploadedTemplate = templates.find(template => template.name === input.packageName); if (!uploadedTemplate?.id) return undefined; const uploadedVersion = uploadedTemplate.versions?.find(version => version.name === input.packageVersion); if (!uploadedVersion?.name) return undefined; return { id: uploadedTemplate.id, version: uploadedVersion.name, }; } //# sourceMappingURL=template-service.js.map //# debugId=0de7f429-2e03-5634-bb1f-8a633e7a9c38