UNPKG

solidui-cli

Version:

Add Solid UI Components to your application using the Solid-UI CLI tool

1,221 lines (1,161 loc) 35.8 kB
#! /usr/bin/env node // src/index.ts import { Command as Command3 } from "commander"; // src/commands/add.ts import { existsSync as existsSync2 } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; import path3 from "node:path"; import * as p2 from "@clack/prompts"; import { Command } from "commander"; import { execa } from "execa"; // ../../node_modules/.pnpm/valibot@0.36.0/node_modules/valibot/dist/index.js var store; function getGlobalConfig(config2) { return { lang: config2?.lang ?? store?.lang, message: config2?.message, abortEarly: config2?.abortEarly ?? store?.abortEarly, abortPipeEarly: config2?.abortPipeEarly ?? store?.abortPipeEarly }; } var store2; function getGlobalMessage(lang) { return store2?.get(lang); } var store3; function getSchemaMessage(lang) { return store3?.get(lang); } var store4; function getSpecificMessage(reference, lang) { return store4?.get(reference)?.get(lang); } function _stringify(input) { const type = typeof input; if (type === "string") { return `"${input}"`; } if (type === "number" || type === "bigint" || type === "boolean") { return `${input}`; } if (type === "object" || type === "function") { return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null"; } return type; } function _addIssue(context, label, dataset, config2, other) { const input = other && "input" in other ? other.input : dataset.value; const expected = other?.expected ?? context.expects ?? null; const received = other?.received ?? _stringify(input); const issue = { kind: context.kind, type: context.type, input, expected, received, message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`, // @ts-expect-error requirement: context.requirement, path: other?.path, issues: other?.issues, lang: config2.lang, abortEarly: config2.abortEarly, abortPipeEarly: config2.abortPipeEarly }; const isSchema = context.kind === "schema"; const message = other?.message ?? // @ts-expect-error context.message ?? getSpecificMessage(context.reference, issue.lang) ?? (isSchema ? getSchemaMessage(issue.lang) : null) ?? config2.message ?? getGlobalMessage(issue.lang); if (message) { issue.message = typeof message === "function" ? message(issue) : message; } if (isSchema) { dataset.typed = false; } if (dataset.issues) { dataset.issues.push(issue); } else { dataset.issues = [issue]; } } var ValiError = class extends Error { /** * The error issues. */ issues; /** * Creates a Valibot error with useful information. * * @param issues The error issues. */ constructor(issues) { super(issues[0].message); this.name = "ValiError"; this.issues = issues; } }; function getDefault(schema, dataset, config2) { return typeof schema.default === "function" ? ( // @ts-expect-error schema.default(dataset, config2) ) : ( // @ts-expect-error schema.default ); } function array(item, message) { return { kind: "schema", type: "array", reference: array, expects: "Array", async: false, item, message, _run(dataset, config2) { const input = dataset.value; if (Array.isArray(input)) { dataset.typed = true; dataset.value = []; for (let key = 0; key < input.length; key++) { const value2 = input[key]; const itemDataset = this.item._run({ typed: false, value: value2 }, config2); if (itemDataset.issues) { const pathItem = { type: "array", origin: "value", input, key, value: value2 }; for (const issue of itemDataset.issues) { if (issue.path) { issue.path.unshift(pathItem); } else { issue.path = [pathItem]; } dataset.issues?.push(issue); } if (!dataset.issues) { dataset.issues = itemDataset.issues; } if (config2.abortEarly) { dataset.typed = false; break; } } if (!itemDataset.typed) { dataset.typed = false; } dataset.value.push(itemDataset.value); } } else { _addIssue(this, "type", dataset, config2); } return dataset; } }; } function boolean(message) { return { kind: "schema", type: "boolean", reference: boolean, expects: "boolean", async: false, message, _run(dataset, config2) { if (typeof dataset.value === "boolean") { dataset.typed = true; } else { _addIssue(this, "type", dataset, config2); } return dataset; } }; } function object(entries, message) { return { kind: "schema", type: "object", reference: object, expects: "Object", async: false, entries, message, _run(dataset, config2) { const input = dataset.value; if (input && typeof input === "object") { dataset.typed = true; dataset.value = {}; for (const key in this.entries) { const value2 = input[key]; const valueDataset = this.entries[key]._run( { typed: false, value: value2 }, config2 ); if (valueDataset.issues) { const pathItem = { type: "object", origin: "value", input, key, value: value2 }; for (const issue of valueDataset.issues) { if (issue.path) { issue.path.unshift(pathItem); } else { issue.path = [pathItem]; } dataset.issues?.push(issue); } if (!dataset.issues) { dataset.issues = valueDataset.issues; } if (config2.abortEarly) { dataset.typed = false; break; } } if (!valueDataset.typed) { dataset.typed = false; } if (valueDataset.value !== void 0 || key in input) { dataset.value[key] = valueDataset.value; } } } else { _addIssue(this, "type", dataset, config2); } return dataset; } }; } function optional(wrapped, ...args) { const schema = { kind: "schema", type: "optional", reference: optional, expects: `${wrapped.expects} | undefined`, async: false, wrapped, _run(dataset, config2) { if (dataset.value === void 0) { if ("default" in this) { dataset.value = getDefault( this, dataset, config2 ); } if (dataset.value === void 0) { dataset.typed = true; return dataset; } } return this.wrapped._run(dataset, config2); } }; if (0 in args) { schema.default = args[0]; } return schema; } function picklist(options, message) { return { kind: "schema", type: "picklist", reference: picklist, expects: options.map(_stringify).join(" | ") || "never", async: false, options, message, _run(dataset, config2) { if (this.options.includes(dataset.value)) { dataset.typed = true; } else { _addIssue(this, "type", dataset, config2); } return dataset; } }; } function string(message) { return { kind: "schema", type: "string", reference: string, expects: "string", async: false, message, _run(dataset, config2) { if (typeof dataset.value === "string") { dataset.typed = true; } else { _addIssue(this, "type", dataset, config2); } return dataset; } }; } function parse(schema, input, config2) { const dataset = schema._run( { typed: false, value: input }, getGlobalConfig(config2) ); if (dataset.issues) { throw new ValiError(dataset.issues); } return dataset.value; } // src/utils/config.ts import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; import { loadConfig } from "tsconfig-paths"; // src/utils/resolve-import.ts import { createMatchPath } from "tsconfig-paths"; async function resolveImport(importPath, config) { return createMatchPath(config.absoluteBaseUrl, config.paths)(importPath, void 0, () => true, [ ".ts", ".tsx" ]); } // src/utils/config.ts var DEFAULT_COMPONENTS = "~/components/ui"; var DEFAULT_UTILS = "~/lib/utils"; var DEFAULT_CSS_FILE = "src/app.css"; var DEFAULT_TAILWIND_CONFIG = "tailwind.config.cjs"; var DEFAULT_TAILWIND_PREFIX = ""; var RawConfigSchema = object({ $schema: optional(string()), tsx: boolean(), tailwind: object({ css: string(), config: string(), prefix: optional(string(), "") }), aliases: object({ components: string(), utils: string() }) }); var ConfigSchema = object({ ...RawConfigSchema.entries, resolvedPaths: object({ tailwindConfig: string(), tailwindCss: string(), utils: string(), components: string() }) }); async function resolveConfigPaths(cwd, config) { const tsConfig = await loadConfig(cwd); if (tsConfig.resultType === "failed") { throw new Error( `Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.json. ${tsConfig.message ?? ""}`.trim() ); } return parse(ConfigSchema, { ...config, resolvedPaths: { tailwindConfig: path.resolve(cwd, config.tailwind.config), tailwindCss: path.resolve(cwd, config.tailwind.css), utils: await resolveImport(config.aliases.utils, tsConfig), components: await resolveImport(config.aliases.components, tsConfig) } }); } function getRawConfig(cwd) { try { const configPath = path.resolve(cwd, "ui.config.json"); if (!existsSync(configPath)) { return null; } const config = JSON.parse(readFileSync(configPath).toString()); return parse(RawConfigSchema, config); } catch (error) { throw new Error(`Invalid configuration found in ${cwd}/ui.config.json.`); } } async function getConfig(cwd) { const config = getRawConfig(cwd); if (!config) { return null; } return await resolveConfigPaths(cwd, config); } // src/utils/get-package-manager.ts import { detect } from "@antfu/ni"; async function getPackageManager(targetDir) { const packageManager = await detect({ programmatic: true, cwd: targetDir }); if (packageManager === "yarn@berry") return "yarn"; if (packageManager === "pnpm@6") return "pnpm"; if (packageManager === "bun") return "bun"; return packageManager ?? "npm"; } // src/utils/logger.ts import * as p from "@clack/prompts"; import chalk from "chalk"; var headline = (text2) => chalk.bgGreen.bold.black(text2); var highlight = (text2) => chalk.bold.green(text2); var subtle = (text2) => chalk.grey(text2); function handleError(error) { p.log.message(); if (typeof error === "string") { p.cancel(error); process.exit(1); } if (error instanceof Error) { p.cancel(error.stack); process.exit(1); } p.cancel("Something went wrong. Please try again."); process.exit(1); } // src/utils/registry.ts var BASE_URL = "https://www.solid-ui.com"; var RegistryIndexSchema = array( object({ name: string(), dependencies: optional(array(string())), registryDependencies: optional(array(string())), files: array(string()), type: picklist(["ui", "example"]) }) ); var RegistryItemSchema = object({ name: string(), dependencies: optional(array(string())), files: array( object({ name: string(), content: string() }) ), type: picklist(["ui", "example"]) }); async function fetchRegistry(paths) { try { const results = await Promise.all( paths.map(async (path5) => { const response = await fetch(`${BASE_URL}/registry/${path5}`); return await response.json(); }) ); return results; } catch (e) { console.log(e); throw new Error(`Failed to fetch registry from ${BASE_URL}.`); } } async function getRegistryIndex() { try { const [result] = await fetchRegistry(["index.json"]); return parse(RegistryIndexSchema, result).filter((item) => item.type === "ui"); } catch (e) { throw new Error(`Failed to fetch components from registry.`); } } async function resolveTree(index, names) { const tree = []; for (const name of names) { const entry = index.find((entry2) => entry2.name === name); if (!entry) { continue; } tree.push(entry); if (entry.registryDependencies) { const dependencies = await resolveTree(index, entry.registryDependencies); tree.push(...dependencies); } } return tree.filter( (component, idx, self) => self.findIndex((c) => c.name === component.name) === idx ); } async function fetchTree(tree) { try { const paths = tree.map((item) => `ui/${item.name}.json`); const results = await fetchRegistry(paths); return parse(array(RegistryItemSchema), results); } catch (e) { throw new Error(`Failed to fetch components from registry.`); } } // src/utils/transformers/index.ts import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import path2 from "node:path"; import { Project, ScriptKind } from "ts-morph"; // src/utils/transformers/transform-import.ts var transformImport = async ({ sourceFile, config }) => { const importDeclarations = sourceFile.getImportDeclarations(); for (const importDeclaration of importDeclarations) { const moduleSpecifier = importDeclaration.getModuleSpecifierValue(); if (moduleSpecifier.startsWith("~/registry/")) { importDeclaration.setModuleSpecifier( moduleSpecifier.replace(/^~\/registry\/ui/, config.aliases.components) ); } else if (moduleSpecifier === "~/lib/utils") { importDeclaration.setModuleSpecifier( moduleSpecifier.replace(/^~\/lib\/utils/, config.aliases.utils) ); } } return sourceFile; }; // src/utils/transformers/transform-jsx.ts import { transformFromAstSync } from "@babel/core"; import { parse as parse2 } from "@babel/parser"; import transformTypescript from "@babel/plugin-transform-typescript"; import * as recast from "recast"; var PARSE_OPTIONS = { sourceType: "module", allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: true, plugins: [ "asyncGenerators", "bigInt", "classPrivateMethods", "classPrivateProperties", "classProperties", "classStaticBlock", "decimal", "decorators-legacy", "doExpressions", "dynamicImport", "exportDefaultFrom", "exportNamespaceFrom", "functionBind", "functionSent", "importAssertions", "importMeta", "nullishCoalescingOperator", "numericSeparator", "objectRestSpread", "optionalCatchBinding", "optionalChaining", [ "pipelineOperator", { proposal: "minimal" } ], [ "recordAndTuple", { syntaxType: "hash" } ], "throwExpressions", "topLevelAwait", "v8intrinsic", "typescript", "jsx" ] }; var transformJsx = async ({ sourceFile, config }) => { const output = sourceFile.getFullText(); if (config.tsx) { return output; } const ast = recast.parse(output, { parser: { parse: (code) => { return parse2(code, PARSE_OPTIONS); } } }); const result = transformFromAstSync(ast, output, { cloneInputAst: false, code: false, ast: true, plugins: [transformTypescript], configFile: false }); if (!result || !result.ast) { throw new Error("Failed to transform JSX"); } return recast.print(result.ast).code; }; // src/utils/transformers/transform-tw-prefix.ts import { SyntaxKind } from "ts-morph"; var transformTwPrefix = async ({ sourceFile, config }) => { if (!config.tailwind.prefix) { return sourceFile; } sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).filter((node) => node.getExpression().getText() === "cva").forEach((node) => { if (node.getArguments()[0]?.isKind(SyntaxKind.StringLiteral)) { const defaultClassNames = node.getArguments()[0]; if (defaultClassNames) { defaultClassNames.replaceWithText( `"${applyPrefix(defaultClassNames.getText()?.replace(/"/g, ""), config.tailwind.prefix)}"` ); } } if (node.getArguments()[1]?.isKind(SyntaxKind.ObjectLiteralExpression)) { node.getArguments()[1]?.getDescendantsOfKind(SyntaxKind.PropertyAssignment).find((node2) => node2.getName() === "variants")?.getDescendantsOfKind(SyntaxKind.PropertyAssignment).forEach((node2) => { node2.getDescendantsOfKind(SyntaxKind.PropertyAssignment).forEach((node3) => { const classNames = node3.getInitializerIfKind(SyntaxKind.StringLiteral); if (classNames) { classNames?.replaceWithText( `"${applyPrefix(classNames.getText()?.replace(/"/g, ""), config.tailwind.prefix)}"` ); } }); }); } }); sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute).forEach((node) => { if (node.getName() === "class") { if (node.getInitializer()?.isKind(SyntaxKind.StringLiteral)) { const value = node.getInitializer(); if (value) { value.replaceWithText( `"${applyPrefix(value.getText()?.replace(/"/g, ""), config.tailwind.prefix)}"` ); } } if (node.getInitializer()?.isKind(SyntaxKind.JsxExpression)) { const callExpression = node.getInitializer()?.getDescendantsOfKind(SyntaxKind.CallExpression).find((node2) => node2.getExpression().getText() === "cn"); if (callExpression) { callExpression.getArguments().forEach((node2) => { if (node2.isKind(SyntaxKind.ConditionalExpression) || node2.isKind(SyntaxKind.BinaryExpression)) { node2.getChildrenOfKind(SyntaxKind.StringLiteral).forEach((node3) => { node3.replaceWithText( `"${applyPrefix(node3.getText()?.replace(/"/g, ""), config.tailwind.prefix)}"` ); }); } if (node2.isKind(SyntaxKind.StringLiteral)) { node2.replaceWithText( `"${applyPrefix(node2.getText()?.replace(/"/g, ""), config.tailwind.prefix)}"` ); } }); } } } }); return sourceFile; }; function applyPrefix(input, prefix = "") { const classNames = input.split(" "); const prefixed = []; for (const className of classNames) { const [variant, value, modifier] = splitClassName(className); if (variant) { modifier ? prefixed.push(`${variant}:${prefix}${value}/${modifier}`) : prefixed.push(`${variant}:${prefix}${value}`); } else { modifier ? prefixed.push(`${prefix}${value}/${modifier}`) : prefixed.push(`${prefix}${value}`); } } return prefixed.join(" "); } function splitClassName(className) { if (!className.includes("/") && !className.includes(":")) { return [null, className, null]; } const parts = []; const [rest, alpha] = className.split("/"); if (!rest?.includes(":")) { return [null, rest, alpha]; } const split = rest?.split(":"); const name = split.pop(); const variant = split.join(":"); parts.push(variant ?? null, name ?? null, alpha ?? null); return parts; } // src/utils/transformers/index.ts var project = new Project({ compilerOptions: {} }); async function createTempSourceFile(filename) { const dir = await mkdtemp(path2.join(tmpdir(), "solidui-")); return path2.join(dir, filename); } async function transform(props) { const tempFile = await createTempSourceFile(props.filename); const sourceFile = project.createSourceFile(tempFile, props.raw, { scriptKind: ScriptKind.TSX }); transformImport({ sourceFile, ...props }); transformTwPrefix({ sourceFile, ...props }); return await transformJsx({ sourceFile, ...props }); } // src/commands/add.ts var addOptionsSchema = object({ components: optional(array(string()), []), cwd: string(), overwrite: boolean(), all: boolean() }); var add = new Command().name("add").description("add components to your project").argument("[components...]", "the components to add").option("-c, --cwd <cwd>", "the working directory", process.cwd()).option("-o --overwrite", "overwrite existing files", false).option("-a, --all", "add all available components", false).action(async (components, opts) => { try { const options = parse(addOptionsSchema, { components, ...opts }); const cwd = path3.resolve(options.cwd); if (!existsSync2(cwd)) { throw new Error(`The path ${cwd} does not exist. Please try again.`); } const config = await getConfig(cwd); if (!config) { p2.log.warning( `Configuration is missing. Please run ${highlight(`init`)} to create a components.json file.` ); process.exit(1); } const registryIndex = await getRegistryIndex(); let selectedComponents = options.all ? registryIndex.map((v) => v.name) : options.components; if (!selectedComponents.length) { const prompts = await p2.group( { components: () => p2.multiselect({ message: `Which ${highlight("components")} would you like to add?`, options: registryIndex.map((v) => ({ label: v.name, value: v.name })) }) }, { onCancel: () => { p2.cancel("Cancelled."); process.exit(0); } } ); selectedComponents = prompts.components; } if (!selectedComponents.length) { p2.log.warn(`No components selected. Exiting.`); process.exit(0); } const tree = await resolveTree(registryIndex, selectedComponents); const payload = await fetchTree(tree); if (!payload.length) { p2.log.warn(`Selected components not found. Exiting.`); process.exit(0); } const spinner3 = p2.spinner(); spinner3.start("Installing..."); const targetDir = config.resolvedPaths.components; if (!existsSync2(targetDir)) { await mkdir(targetDir, { recursive: true }); } for (const item of payload) { spinner3.message(`Installing ${highlight(item.name)}...`); const existingComponent = item.files.filter( (file) => existsSync2(path3.resolve(targetDir, file.name)) ); if (existingComponent.length && !options.overwrite) { if (selectedComponents.includes(item.name)) { spinner3.stop(); const prompts = await p2.group( { overwrite: () => p2.confirm({ message: `Component ${item.name} already exists. Would you like to overwrite?`, initialValue: false }) }, { onCancel: () => { p2.cancel("Cancelled."); process.exit(0); } } ); const overwrite = prompts.overwrite; if (!overwrite) { p2.log.info( `Skipped ${item.name}. To overwrite, run with the ${highlight("--overwrite")} flag.` ); continue; } spinner3.start(`Installing ${highlight(item.name)}...`); } else { continue; } } for (const file of item.files) { let filePath = path3.resolve(targetDir, file.name); const content = await transform({ filename: file.name, raw: file.content, config }); if (!config.tsx) { filePath = filePath.replace(/\.tsx$/, ".jsx"); filePath = filePath.replace(/\.ts$/, ".js"); } await writeFile(filePath, content, "utf-8"); } if (item.dependencies?.length) { const packageManager = await getPackageManager(cwd); await execa(packageManager, ["add", ...item.dependencies], { cwd }); } } spinner3.stop("Done."); } catch (e) { handleError(e); } }); // src/commands/init.ts import { existsSync as existsSync3 } from "node:fs"; import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises"; import path4 from "node:path"; import * as p3 from "@clack/prompts"; import { Command as Command2 } from "commander"; import { execa as execa2 } from "execa"; // src/utils/get-package-info.ts import fs from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; var __filename = fileURLToPath(import.meta.url); var __dirname = dirname(__filename); function getPackageInfo() { const location = resolve(__dirname, "..", "package.json"); return readJSONSync(location); } function readJSONSync(path5) { const content = fs.readFileSync(path5, { encoding: "utf-8" }); return JSON.parse(content); } // src/utils/templates.ts var UTILS = `import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } `; var UTILS_JS = `import { clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs) { return twMerge(clsx(inputs)) } `; var TAILWIND_CONFIG = `/** @type {import('tailwindcss').Config} */ export default { darkMode: ["variant", [".dark &", '[data-kb-theme="dark"] &']], content: ["./src/**/*.{ts,tsx}"], prefix: "<%- prefix %>", theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px" } }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" }, info: { DEFAULT: "hsl(var(--info))", foreground: "hsl(var(--info-foreground))" }, success: { DEFAULT: "hsl(var(--success))", foreground: "hsl(var(--success-foreground))" }, warning: { DEFAULT: "hsl(var(--warning))", foreground: "hsl(var(--warning-foreground))" }, error: { DEFAULT: "hsl(var(--error))", foreground: "hsl(var(--error-foreground))" }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))" }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" } }, borderRadius: { xl: "calc(var(--radius) + 4px)", lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" }, keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--kb-accordion-content-height)" } }, "accordion-up": { from: { height: "var(--kb-accordion-content-height)" }, to: { height: 0 } }, "content-show": { from: { opacity: 0, transform: "scale(0.96)" }, to: { opacity: 1, transform: "scale(1)" } }, "content-hide": { from: { opacity: 1, transform: "scale(1)" }, to: { opacity: 0, transform: "scale(0.96)" } }, "caret-blink": { "0%,70%,100%": { opacity: "1" }, "20%,50%": { opacity: "0" } } }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "content-show": "content-show 0.2s ease-out", "content-hide": "content-hide 0.2s ease-out", "caret-blink": "caret-blink 1.25s ease-out infinite" } } }, plugins: [require("tailwindcss-animate")] } `; var TAILWIND_CSS = `@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --muted: 240 4.8% 95.9%; --muted-foreground: 240 3.8% 46.1%; --popover: 0 0% 100%; --popover-foreground: 240 10% 3.9%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --card: 0 0% 100%; --card-foreground: 240 10% 3.9%; --primary: 240 5.9% 10%; --primary-foreground: 0 0% 98%; --secondary: 240 4.8% 95.9%; --secondary-foreground: 240 5.9% 10%; --accent: 240 4.8% 95.9%; --accent-foreground: 240 5.9% 10%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; --info: 204 94% 94%; --info-foreground: 199 89% 48%; --success: 149 80% 90%; --success-foreground: 160 84% 39%; --warning: 48 96% 89%; --warning-foreground: 25 95% 53%; --error: 0 93% 94%; --error-foreground: 0 84% 60%; --ring: 240 5.9% 10%; --radius: 0.5rem; } .dark, [data-kb-theme="dark"] { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --muted: 240 3.7% 15.9%; --muted-foreground: 240 5% 64.9%; --accent: 240 3.7% 15.9%; --accent-foreground: 0 0% 98%; --popover: 240 10% 3.9%; --popover-foreground: 0 0% 98%; --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --card: 240 10% 3.9%; --card-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; --info: 204 94% 94%; --info-foreground: 199 89% 48%; --success: 149 80% 90%; --success-foreground: 160 84% 39%; --warning: 48 96% 89%; --warning-foreground: 25 95% 53%; --error: 0 93% 94%; --error-foreground: 0 84% 60%; --ring: 240 4.9% 83.9%; --radius: 0.5rem; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; font-feature-settings: "rlig" 1, "calt" 1; } } @layer utilities { .step { counter-increment: step; } .step:before { @apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background; @apply ml-[-50px] mt-[-4px]; content: counter(step); } } @media (max-width: 640px) { .container { @apply px-4; } } ::-webkit-scrollbar { width: 16px; } ::-webkit-scrollbar-thumb { border-radius: 9999px; border: 4px solid transparent; background-clip: content-box; @apply bg-accent; } ::-webkit-scrollbar-corner { display: none; } `; // src/commands/init.ts var PROJECT_DEPENDENCIES = [ "tailwindcss-animate", "class-variance-authority", "clsx", "tailwind-merge" ]; var initOptionsSchema = object({ cwd: string() }); var init = new Command2().name("init").description("initialize your project and install dependencies").option("-c, --cwd <cwd>", "the working directory", process.cwd()).action(async (opts) => { try { const options = parse(initOptionsSchema, opts); const cwd = path4.resolve(options.cwd); if (!existsSync3(cwd)) { throw new Error(`The path ${cwd} does not exist. Please try again.`); } const info = getPackageInfo(); p3.intro(headline(` ${info.name} - ${info.version} `)); const rawConfig = await promptForConfig(); const spinner3 = p3.spinner(); spinner3.start(`Creating ui.config.json...`); const targetPath = path4.resolve(cwd, "ui.config.json"); await writeFile2(targetPath, JSON.stringify(rawConfig, null, 2), "utf-8"); spinner3.stop(`ui.config.json created.`); const config = await resolveConfigPaths(cwd, rawConfig); spinner3.start(`Initializing project...`); for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) { let dirname2 = path4.extname(resolvedPath) ? path4.dirname(resolvedPath) : resolvedPath; if (key === "utils" && resolvedPath.endsWith("/utils")) { dirname2 = dirname2.replace(/\/utils$/, ""); } if (!existsSync3(dirname2)) { await mkdir2(dirname2, { recursive: true }); } } const extension = config.tsx ? "ts" : "js"; await writeFile2( config.resolvedPaths.tailwindConfig, TAILWIND_CONFIG.replace("<%- prefix %>", config.tailwind.prefix), "utf-8" ); await writeFile2(config.resolvedPaths.tailwindCss, TAILWIND_CSS, "utf-8"); await writeFile2( `${config.resolvedPaths.utils}.${extension}`, extension === "ts" ? UTILS : UTILS_JS, "utf-8" ); spinner3.stop(`Project initialized.`); spinner3.start(`Installing dependencies...`); const packageManager = await getPackageManager(cwd); await execa2(packageManager, ["add", ...PROJECT_DEPENDENCIES], { cwd }); spinner3.stop(`Dependencies installed.`); p3.outro( `${highlight("Success!")} Project initialization completed. You may now add components.` ); } catch (e) { handleError(e); } }); async function promptForConfig() { const options = await p3.group( { typescript: () => p3.confirm({ message: `Would you like to use ${highlight("Typescript")} (recommended)?`, initialValue: true }), cssFile: () => p3.text({ message: `Where is your ${highlight("global CSS")} file? ${subtle("(this file will be overwritten)")}`, initialValue: DEFAULT_CSS_FILE }), tailwindConfig: () => p3.text({ message: `Where is your ${highlight("Tailwind config")} located? ${subtle("(this file will be overwritten)")}`, initialValue: DEFAULT_TAILWIND_CONFIG }), tailwindPrefix: () => p3.text({ message: `Are you using a custom ${highlight("tailwind prefix eg. tw-")}? (Leave blank if not)`, initialValue: DEFAULT_TAILWIND_PREFIX }), components: () => p3.text({ message: `Configure the import alias for ${highlight("components")}:`, initialValue: DEFAULT_COMPONENTS }), utils: () => p3.text({ message: `Configure the import alias for ${highlight("utils")}:`, initialValue: DEFAULT_UTILS }) }, { onCancel: () => { p3.cancel("Cancelled."); process.exit(0); } } ); return parse(RawConfigSchema, { $schema: "https://solid-ui.com/schema.json", tsx: options.typescript, tailwind: { css: options.cssFile, config: options.tailwindConfig, prefix: options.tailwindPrefix }, aliases: { components: options.components, utils: options.utils } }); } // src/index.ts process.on("SIGINT", () => process.exit(0)); process.on("SIGTERM", () => process.exit(0)); async function main() { console.clear(); const packageInfo = getPackageInfo(); new Command3().name("solidui-cli").description("add SolidUI components to your project").version(packageInfo.version || "0.0.0", "-v, --version", "display the version number").addCommand(init).addCommand(add).parse(); } main();