UNPKG

starwind

Version:

Add beautifully designed components to your Astro applications

689 lines (674 loc) 22.8 kB
#!/usr/bin/env node import { PATHS, fileExists, getConfig, highlighter, init, installDependencies, requestPackageManager, sleep, updateConfig } from "./chunk-HDAZQTOL.js"; // src/index.ts import { Command } from "commander"; // src/commands/add.ts import * as p2 from "@clack/prompts"; // src/utils/component.ts import * as path from "path"; import { fileURLToPath } from "url"; import * as p from "@clack/prompts"; import fs from "fs-extra"; import semver from "semver"; // src/utils/registry.ts import { registry as localRegistry } from "@starwind-ui/core"; import { z } from "zod"; var REGISTRY_CONFIG = { // Set to 'remote' to fetch from remote server or 'local' to use the imported registry SOURCE: "local" }; var componentSchema = z.object({ name: z.string(), version: z.string(), dependencies: z.array(z.string()).default([]), type: z.enum(["component"]) }); var registryRootSchema = z.object({ $schema: z.string().optional(), components: z.array(componentSchema) }); var registryCache = /* @__PURE__ */ new Map(); async function getRegistry(forceRefresh = false) { const cacheKey = REGISTRY_CONFIG.SOURCE === "remote" ? PATHS.STARWIND_REMOTE_COMPONENT_REGISTRY : "local-registry"; if (!forceRefresh && registryCache.has(cacheKey)) { return registryCache.get(cacheKey); } const registryPromise = REGISTRY_CONFIG.SOURCE === "remote" ? fetchRemoteRegistry() : Promise.resolve(getLocalRegistry()); registryCache.set(cacheKey, registryPromise); return registryPromise; } async function fetchRemoteRegistry() { try { const response = await fetch(PATHS.STARWIND_REMOTE_COMPONENT_REGISTRY); if (!response.ok) { throw new Error(`Failed to fetch registry: ${response.status} ${response.statusText}`); } const data = await response.json(); const parsedRegistry = registryRootSchema.parse(data); return parsedRegistry.components; } catch (error) { console.error("Failed to load remote registry:", error); throw error; } } function getLocalRegistry() { try { const components = localRegistry.map((comp) => componentSchema.parse(comp)); return components; } catch (error) { console.error("Failed to validate local registry:", error); throw error; } } async function getComponent(name, forceRefresh = false) { const registry = await getRegistry(forceRefresh); return registry.find((component) => component.name === name); } async function getAllComponents(forceRefresh = false) { return getRegistry(forceRefresh); } // src/utils/component.ts async function copyComponent(name, overwrite = false) { const config = await getConfig(); const currentComponents = Array.isArray(config.components) ? config.components : []; if (!overwrite && currentComponents.some((component) => component.name === name)) { const existingComponent = currentComponents.find((c) => c.name === name); return { status: "skipped", name, version: existingComponent?.version }; } const componentDir = path.join(config.componentDir, "starwind", name); try { await fs.ensureDir(componentDir); const pkgUrl = import.meta.resolve?.(PATHS.STARWIND_CORE); if (!pkgUrl) { throw new Error(`Could not resolve ${PATHS.STARWIND_CORE} package, is it installed?`); } const coreDir = path.dirname(fileURLToPath(pkgUrl)); const sourceDir = path.join(coreDir, PATHS.STARWIND_CORE_COMPONENTS, name); const files = await fs.readdir(sourceDir); for (const file of files) { const sourcePath = path.join(sourceDir, file); const destPath = path.join(componentDir, file); await fs.copy(sourcePath, destPath, { overwrite: true }); } const registry = await getRegistry(); const componentInfo = registry.find((c) => c.name === name); if (!componentInfo) { throw new Error(`Component ${name} not found in registry`); } return { status: "installed", name, version: componentInfo.version }; } catch (error) { return { status: "failed", name, error: error instanceof Error ? error.message : "Unknown error" }; } } async function removeComponent(name, componentDir) { try { const componentPath = path.join(componentDir, "starwind", name); if (await fs.pathExists(componentPath)) { await fs.remove(componentPath); return { name, status: "removed" }; } else { return { name, status: "failed", error: "Component directory not found" }; } } catch (error) { return { name, status: "failed", error: error instanceof Error ? error.message : "Unknown error" }; } } async function updateComponent(name, currentVersion, skipConfirm) { try { const registryComponent = await getComponent(name); if (!registryComponent) { return { name, status: "failed", error: "Component not found in registry" }; } if (!semver.gt(registryComponent.version, currentVersion)) { return { name, status: "skipped", oldVersion: currentVersion, newVersion: registryComponent.version }; } let confirmUpdate = true; if (!skipConfirm) { const confirmedResult = await p.confirm({ message: `Update component ${highlighter.info( name )} from ${highlighter.warn(`v${currentVersion}`)} to ${highlighter.success( `v${registryComponent.version}` )}? This will override the existing implementation.` }); if (p.isCancel(confirmedResult)) { p.cancel("Update cancelled."); return { name, status: "skipped", oldVersion: currentVersion, newVersion: registryComponent.version // Still useful to return the target version }; } confirmUpdate = confirmedResult; } if (!confirmUpdate) { p.log.info(`Skipping update for ${highlighter.info(name)}`); return { name, status: "skipped", oldVersion: currentVersion, newVersion: registryComponent.version }; } const result = await copyComponent(name, true); if (result.status === "installed") { return { name, status: "updated", oldVersion: currentVersion, newVersion: result.version }; } else { return { name, status: "failed", error: result.error || "Failed to update component" }; } } catch (error) { return { name, status: "failed", error: error instanceof Error ? error.message : "Unknown error" }; } } // src/utils/prompts.ts import { confirm as confirm2, multiselect } from "@clack/prompts"; async function selectComponents() { const components = await getAllComponents(); const selected = await multiselect({ message: "Select components to add", options: components.map((component) => ({ label: component.name, value: component.name })), required: false }); if (typeof selected === "symbol") { return []; } return selected; } async function confirmInstall(component) { if (component.dependencies.length === 0) return true; const confirmed = await confirm2({ message: `This component requires the following dependencies: ${component.dependencies.join(", ")}. Install them?` }); if (typeof confirmed === "symbol") { return false; } return confirmed; } // src/utils/install.ts async function installComponent(name) { const component = await getComponent(name); if (!component) { return { status: "failed", name, error: "Component not found in registry" }; } if (component.dependencies.length > 0) { const confirmed = await confirmInstall(component); if (!confirmed) { return { status: "failed", name, error: "Installation cancelled by user" }; } try { const pm = await requestPackageManager(); await installDependencies(component.dependencies, pm); } catch (error) { return { status: "failed", name, error: `Failed to install dependencies: ${error instanceof Error ? error.message : String(error)}` }; } } return await copyComponent(name); } // src/utils/validate.ts async function isValidComponent(component, availableComponents) { const components = availableComponents || await getAllComponents(); return components.some((c) => c.name === component); } // src/commands/add.ts var { init: init2 } = await import("./init-NBNT5V74.js"); async function add(components, options) { try { p2.intro(highlighter.title(" Welcome to the Starwind CLI ")); const configExists = await fileExists(PATHS.LOCAL_CONFIG_FILE); if (!configExists) { const shouldInit = await p2.confirm({ message: `Starwind configuration not found. Would you like to run ${highlighter.info("starwind init")} now?`, initialValue: true }); if (p2.isCancel(shouldInit)) { p2.cancel("Operation cancelled"); process.exit(0); } if (shouldInit) { await init2(true); } else { p2.log.error( `Please initialize starwind with ${highlighter.info("starwind init")} before adding components` ); process.exit(1); } } let componentsToInstall = []; if (options?.all) { const availableComponents = await getAllComponents(); componentsToInstall = availableComponents.map((c) => c.name); p2.log.info(`Adding all ${componentsToInstall.length} available components...`); } else if (components && components.length > 0) { const availableComponents = await getAllComponents(); const { valid, invalid } = await components.reduce( async (accPromise, component) => { const acc = await accPromise; const isValid = await isValidComponent(component, availableComponents); if (isValid) { acc.valid.push(component); } else { acc.invalid.push(component); } return acc; }, Promise.resolve({ valid: [], invalid: [] }) ); if (invalid.length > 0) { p2.log.warn( `${highlighter.warn("Invalid components found:")} ${invalid.map((name) => ` ${name}`).join("\n")}` ); } if (valid.length > 0) { componentsToInstall = valid; } else { p2.log.warn(`${highlighter.warn("No valid components to install")}`); p2.cancel("Operation cancelled"); return process.exit(0); } } else { const selected = await selectComponents(); if (!selected) { p2.cancel("No components selected"); return process.exit(0); } componentsToInstall = selected; } if (componentsToInstall.length === 0) { p2.log.warn(`${highlighter.warn("No components selected")}`); p2.cancel("Operation cancelled"); return process.exit(0); } const results = { installed: [], skipped: [], failed: [] }; const installedComponents = []; for (const comp of componentsToInstall) { const result = await installComponent(comp); switch (result.status) { case "installed": results.installed.push(result); installedComponents.push({ name: result.name, version: result.version }); break; case "skipped": results.skipped.push(result); break; case "failed": results.failed.push(result); break; } } if (installedComponents.length > 0) { try { await updateConfig({ components: installedComponents }, { appendComponents: true }); } catch (error) { p2.log.error( `Failed to update config: ${error instanceof Error ? error.message : "Unknown error"}` ); process.exit(1); } } p2.log.message(` ${highlighter.underline("Installation Summary")}`); if (results.failed.length > 0) { p2.log.error( `${highlighter.error("Failed to install components:")} ${results.failed.map((r) => ` ${r.name} - ${r.error}`).join("\n")}` ); } if (results.skipped.length > 0) { p2.log.warn( `${highlighter.warn("Skipped components (already installed):")} ${results.skipped.map((r) => ` ${r.name} v${r.version}`).join("\n")}` ); } if (results.installed.length > 0) { p2.log.success( `${highlighter.success("Successfully installed components:")} ${results.installed.map((r) => ` ${r.name} v${r.version}`).join("\n")}` ); } await sleep(1e3); p2.outro("Enjoy using Starwind UI \u{1F680}"); } catch (error) { p2.log.error(error instanceof Error ? error.message : "Failed to add components"); p2.cancel("Operation cancelled"); process.exit(1); } } // src/commands/remove.ts import * as p3 from "@clack/prompts"; async function remove(components, options) { try { p3.intro(highlighter.title(" Welcome to the Starwind CLI ")); const configExists = await fileExists(PATHS.LOCAL_CONFIG_FILE); if (!configExists) { p3.log.error("No Starwind configuration found. Please run starwind init first."); process.exit(1); } const config = await getConfig(); const installedComponents = config.components; if (installedComponents.length === 0) { p3.log.warn("No components are currently installed."); process.exit(0); } let componentsToRemove = []; if (options?.all) { componentsToRemove = installedComponents.map((comp) => comp.name); p3.log.info(`Removing all ${componentsToRemove.length} installed components...`); } else if (components && components.length > 0) { const invalid = components.filter( (comp) => !installedComponents.some((ic) => ic.name === comp) ); if (invalid.length > 0) { p3.log.warn( `${highlighter.warn("Components not found:")} ${invalid.map((name) => ` ${name}`).join("\n")}` ); } componentsToRemove = components.filter( (comp) => installedComponents.some((ic) => ic.name === comp) ); if (componentsToRemove.length === 0) { p3.log.warn("No valid components to remove"); process.exit(0); } } else { const choices = installedComponents.map((comp) => ({ value: comp.name, label: comp.name })); const selected = await p3.multiselect({ message: "Select components to remove", options: choices }); if (p3.isCancel(selected)) { p3.cancel("Operation cancelled"); process.exit(0); } componentsToRemove = selected; } if (componentsToRemove.length === 0) { p3.log.warn("No components selected for removal"); process.exit(0); } const confirmed = await p3.confirm({ message: `Remove ${componentsToRemove.map((comp) => highlighter.info(comp)).join(", ")} ${componentsToRemove.length > 1 ? "components" : "component"}?` }); if (!confirmed || p3.isCancel(confirmed)) { p3.cancel("Operation cancelled"); process.exit(0); } const results = { removed: [], failed: [] }; for (const comp of componentsToRemove) { const result = await removeComponent(comp, config.componentDir); if (result.status === "removed") { results.removed.push(result); } else { results.failed.push(result); } } const updatedComponents = config.components.filter( (comp) => !componentsToRemove.includes(comp.name) ); await updateConfig( { ...config, components: updatedComponents }, { appendComponents: false } ); p3.log.message(` ${highlighter.underline("Removal Summary")}`); if (results.failed.length > 0) { p3.log.error( `${highlighter.error("Failed to remove components:")} ${results.failed.map((r) => ` ${r.name} - ${r.error}`).join("\n")}` ); } if (results.removed.length > 0) { p3.log.success( `${highlighter.success("Successfully removed components:")} ${results.removed.map((r) => ` ${r.name}`).join("\n")}` ); } await sleep(1e3); if (results.removed.length > 0) { p3.outro("Components removed successfully \u{1F5D1}\uFE0F"); } else { p3.cancel("Errors occurred while removing components"); process.exit(1); } } catch (error) { p3.log.error(error instanceof Error ? error.message : "Failed to remove components"); p3.cancel("Operation cancelled"); process.exit(1); } } // src/commands/update.ts import * as p4 from "@clack/prompts"; async function update(components, options) { try { p4.intro(highlighter.title(" Welcome to the Starwind CLI ")); const configExists = await fileExists(PATHS.LOCAL_CONFIG_FILE); if (!configExists) { p4.log.error("No Starwind configuration found. Please run starwind init first."); process.exit(1); } const config = await getConfig(); const installedComponents = config.components; if (installedComponents.length === 0) { p4.log.warn("No components are currently installed."); process.exit(0); } let componentsToUpdate = []; if (options?.all) { componentsToUpdate = installedComponents.map((comp) => comp.name); p4.log.info(`Checking updates for all ${componentsToUpdate.length} installed components...`); } else if (components && components.length > 0) { const invalid = components.filter( (comp) => !installedComponents.some((ic) => ic.name === comp) ); if (invalid.length > 0) { p4.log.warn( `${highlighter.warn("Components not found in project:")} ${invalid.map((name) => ` ${name}`).join("\n")}` ); } componentsToUpdate = components.filter( (comp) => installedComponents.some((ic) => ic.name === comp) ); if (componentsToUpdate.length === 0) { p4.log.warn("No valid components to update"); process.exit(0); } } else { const choices = installedComponents.map((comp) => ({ value: comp.name, label: comp.name })); const selected = await p4.multiselect({ message: "Select components to update", options: choices }); if (p4.isCancel(selected)) { p4.cancel("Operation cancelled"); process.exit(0); } componentsToUpdate = selected; } if (componentsToUpdate.length === 0) { p4.log.warn("No components selected for update"); process.exit(0); } const results = { updated: [], skipped: [], failed: [] }; for (const comp of componentsToUpdate) { const currentVersion = installedComponents.find((ic) => ic.name === comp)?.version; if (!currentVersion) { results.failed.push({ name: comp, status: "failed", error: "Could not determine current version" }); continue; } const result = await updateComponent(comp, currentVersion, options?.yes); switch (result.status) { case "updated": results.updated.push(result); break; case "skipped": results.skipped.push(result); break; case "failed": results.failed.push(result); break; } } if (results.updated.length > 0) { try { const updatedComponentNames = new Set(results.updated.map((r) => r.name)); const currentComponents = config.components.filter( (comp) => !updatedComponentNames.has(comp.name) ); const updatedComponents = [ ...currentComponents, ...results.updated.map((r) => ({ name: r.name, version: r.newVersion })) ]; await updateConfig( { components: updatedComponents }, { appendComponents: false } ); } catch (error) { p4.log.error( `Failed to update config: ${error instanceof Error ? error.message : "Unknown error"}` ); process.exit(1); } } p4.log.message(` ${highlighter.underline("Update Summary")}`); if (results.failed.length > 0) { p4.log.error( `${highlighter.error("Failed to update components:")} ${results.failed.map((r) => ` ${r.name} - ${r.error}`).join("\n")}` ); } if (results.skipped.length > 0) { p4.log.info( `${highlighter.info("Components already up to date or skipped:")} ${results.skipped.map((r) => ` ${r.name} (${r.oldVersion})`).join("\n")}` ); } if (results.updated.length > 0) { p4.log.success( `${highlighter.success("Successfully updated components:")} ${results.updated.map((r) => ` ${r.name} (${r.oldVersion} \u2192 ${r.newVersion})`).join("\n")}` ); } await sleep(1e3); if (results.updated.length > 0) { p4.outro("Components updated successfully \u{1F680}"); } else if (results.skipped.length > 0 && results.failed.length === 0) { p4.outro("Components already up to date or skipped \u2728"); } else { p4.cancel("No components were updated"); process.exit(1); } } catch (error) { p4.log.error(error instanceof Error ? error.message : "Failed to update components"); p4.cancel("Operation cancelled"); process.exit(1); } } // src/index.ts var program = new Command().name("starwind").description("Add beautifully designed components to your Astro applications").version("1.7.3"); program.command("init").description("Initialize your project with Starwind").option("-d, --defaults", "Use default values for all prompts").action((options) => init(false, { defaults: options.defaults })); program.command("add").description("Add Starwind components to your project").argument("[components...]", "The components to add (space separated)").allowExcessArguments().option("-a, --all", "Add all available components").action(add); program.command("update").description("Update Starwind components to their latest versions").argument("[components...]", "The components to update (space separated)").allowExcessArguments().option("-a, --all", "Update all installed components").option("-y, --yes", "Skip confirmation prompts").action(update); program.command("remove").description("Remove Starwind components from your project").argument("[components...]", "The components to remove (space separated)").allowExcessArguments().option("-a, --all", "Remove all installed components").action(remove); program.parse(); //# sourceMappingURL=index.js.map