Skip to content

Commit

Permalink
feat(cli): rework of init command finished & fix #137
Browse files Browse the repository at this point in the history
  • Loading branch information
sek-consulting committed Sep 30, 2024
1 parent 7ccc211 commit 0108054
Show file tree
Hide file tree
Showing 9 changed files with 605 additions and 74 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-cheetahs-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"solidui-cli": minor
---

complete rework of the init command to be more inline with shadcn
9 changes: 6 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@
"pub:release": "pnpm build && pnpm publish --access public"
},
"dependencies": {
"@antfu/ni": "^0.23.0",
"@clack/prompts": "^0.7.0",
"chalk": "^5.3.0",
"commander": "^12.1.0"
"commander": "^12.1.0",
"execa": "^9.4.0",
"tsconfig-paths": "^4.2.0"
},
"devDependencies": {
"@types/node": "^20.14.12",
"tsup": "^8.2.2",
"typescript": "^5.1.6"
"tsup": "^8.3.0",
"typescript": "^5.6.2"
}
}
104 changes: 82 additions & 22 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import { existsSync } from "node:fs"
import { writeFile } from "node:fs/promises"
import { mkdir, writeFile } from "node:fs/promises"
import path from "node:path"

import * as p from "@clack/prompts"
import chalk from "chalk"
import { Command } from "commander"
import { execa } from "execa"
import * as v from "valibot"

import type { Config } from "~/utils/config"
import type { RawConfig } from "~/utils/config"
import {
ConfigSchema,
DEFAULT_COMPONENTS,
DEFAULT_CSS_FILE,
DEFAULT_TAILWIND_CONFIG,
DEFAULT_UTILS
DEFAULT_TAILWIND_PREFIX,
DEFAULT_UTILS,
RawConfigSchema,
resolveConfigPaths
} from "~/utils/config"
import { getPackageInfo } from "~/utils/get-package-info"
import { getPackageManager } from "~/utils/get-package-manager"
import { handleError } from "~/utils/handle-error"
import * as templates from "~/utils/templates"

const highlight = (text: string) => chalk.bold.cyan(text)
const PROJECT_DEPENDENCIES = [
"tailwindcss-animate",
"class-variance-authority",
"clsx",
"tailwind-merge"
]

const headline = (text: string) => chalk.bgGreen.bold.black(text)
const highlight = (text: string) => chalk.bold.green(text)

const initOptionsSchema = v.object({
cwd: v.string()
Expand All @@ -38,18 +51,69 @@ export const init = new Command()
}

const info = getPackageInfo()
p.intro(chalk.bgCyan.bold.black(` ${info.name} - ${info.version} `))
p.intro(headline(` ${info.name} - ${info.version} `))

const rawConfig = await promptForConfig()

const spinner = p.spinner()
spinner.start(`Creating ui.config.json...`)

const targetPath = path.resolve(cwd, "ui.config.json")
await writeFile(targetPath, JSON.stringify(rawConfig, null, 2), "utf-8")

spinner.stop(`ui.config.json created.`)

const config = await resolveConfigPaths(cwd, rawConfig)

spinner.start(`Initializing project...`)

// make sure all the directories exist
for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) {
let dirname = path.extname(resolvedPath) ? path.dirname(resolvedPath) : resolvedPath

if (key === "utils" && resolvedPath.endsWith("/utils")) {
dirname = dirname.replace(/\/utils$/, "") // remove /utils at the end
}

if (!existsSync(dirname)) {
await mkdir(dirname, { recursive: true })
}
}

const extension = config.tsx ? "ts" : "js"

const config = await promptForConfig()
await runInit(cwd, config)
await writeFile(
config.resolvedPaths.tailwindConfig,
templates.TAILWIND_CONFIG.replace("<%- prefix %>", config.tailwind.prefix),
"utf-8"
)

p.outro(`${chalk.green("Success!")} Project initialization completed.`)
await writeFile(config.resolvedPaths.tailwindCss, templates.TAILWIND_CSS, "utf-8")

await writeFile(
`${config.resolvedPaths.utils}.${extension}`,
extension === "ts" ? templates.UTILS : templates.UTILS_JS,
"utf-8"
)

spinner.stop(`Project initialized.`)

spinner.start(`Installing dependencies...`)

const packageManager = await getPackageManager(cwd)
await execa(packageManager, ["add", ...PROJECT_DEPENDENCIES], { cwd })

spinner.stop(`Dependencies installed.`)

p.outro(
`${highlight("Success!")} Project initialization completed. You may now add components.`
)
} catch (e) {
handleError(e)
}
})

async function promptForConfig(): Promise<Config> {
async function promptForConfig(): Promise<RawConfig> {
const options = await p.group(
{
typescript: () =>
Expand All @@ -67,6 +131,11 @@ async function promptForConfig(): Promise<Config> {
message: `Where is your ${highlight("Tailwind config")} located? ${chalk.gray("(this file will be overwritten)")}`,
initialValue: DEFAULT_TAILWIND_CONFIG
}),
tailwindPrefix: () =>
p.text({
message: `Are you using a custom ${highlight("tailwind prefix eg. tw-")}? (Leave blank if not)`,
initialValue: DEFAULT_TAILWIND_PREFIX
}),
components: () =>
p.text({
message: `Configure the import alias for ${highlight("components")}:`,
Expand All @@ -86,12 +155,13 @@ async function promptForConfig(): Promise<Config> {
}
)

const config = v.parse(ConfigSchema, {
const config = v.parse(RawConfigSchema, {
$schema: "https://solid-ui.com/schema.json",
tsx: options.typescript,
tailwind: {
css: options.cssFile,
config: options.tailwindConfig
config: options.tailwindConfig,
prefix: options.tailwindPrefix
},
aliases: {
components: options.components,
Expand All @@ -101,13 +171,3 @@ async function promptForConfig(): Promise<Config> {

return config
}

async function runInit(cwd: string, config: Config) {
const spinner = p.spinner()

// write config to file
spinner.start(`Creating config file...`)
const targetPath = path.resolve(cwd, "ui.config.json")
await writeFile(targetPath, JSON.stringify(config, null, 2), "utf-8")
spinner.stop(`Config file created.`)
}
43 changes: 41 additions & 2 deletions packages/cli/src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,60 @@
import path from "node:path"

import { loadConfig } from "tsconfig-paths"
import * as v from "valibot"

import { resolveImport } from "~/utils/resolve-import"

export const DEFAULT_COMPONENTS = "~/components"
export const DEFAULT_UTILS = "~/lib/utils"
export const DEFAULT_CSS_FILE = "src/app.css"
export const DEFAULT_TAILWIND_CONFIG = "tailwind.config.cjs"
export const DEFAULT_TAILWIND_PREFIX = ""

export const ConfigSchema = v.object({
export const RawConfigSchema = v.object({
$schema: v.optional(v.string()),
tsx: v.boolean(),
tailwind: v.object({
css: v.string(),
config: v.string()
config: v.string(),
prefix: v.optional(v.string(), "")
}),
aliases: v.object({
components: v.string(),
utils: v.string()
})
})

export type RawConfig = v.InferOutput<typeof RawConfigSchema>

export const ConfigSchema = v.object({
...RawConfigSchema.entries,
resolvedPaths: v.object({
tailwindConfig: v.string(),
tailwindCss: v.string(),
utils: v.string(),
components: v.string()
})
})

export type Config = v.InferOutput<typeof ConfigSchema>

export async function resolveConfigPaths(cwd: string, config: RawConfig) {
const tsConfig = await loadConfig(cwd)

if (tsConfig.resultType === "failed") {
throw new Error(
`Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.json. ${tsConfig.message ?? ""}`.trim()
)
}

return v.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)
}
})
}
13 changes: 13 additions & 0 deletions packages/cli/src/utils/get-package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { detect } from "@antfu/ni"

export async function getPackageManager(
targetDir: string
): Promise<"yarn" | "pnpm" | "bun" | "npm"> {
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"
}
11 changes: 11 additions & 0 deletions packages/cli/src/utils/resolve-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createMatchPath, type ConfigLoaderSuccessResult } from "tsconfig-paths"

export async function resolveImport(
importPath: string,
config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">
) {
return createMatchPath(config.absoluteBaseUrl, config.paths)(importPath, undefined, () => true, [
".ts",
".tsx"
])
}
Loading

0 comments on commit 0108054

Please sign in to comment.