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
JavaScript
// 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();