Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cli init #23

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/code-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Run Biome
run: biome ci --diagnostic-level=warn

check-types:
types:
runs-on: ubuntu-latest
name: pnpm check-types
steps:
Expand Down Expand Up @@ -50,5 +50,4 @@ jobs:
- name: Install dependencies
run: pnpm install


- run: pnpm check-types
37 changes: 37 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Test

on:
pull_request:
branches: ["*"]

jobs:
test:
runs-on: ubuntu-latest
name: pnpm test
steps:
- uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 20

- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install

- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install

- run: pnpm test
7 changes: 3 additions & 4 deletions biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"files": {
"ignore": ["**/dist/**", "**/.turbo/**", "**/.astro/**", "**/public/**"]
},
"organizeImports": {
"enabled": true
},
Expand Down Expand Up @@ -27,9 +30,5 @@
"semicolons": "asNeeded",
"trailingCommas": "es5"
}
},

"files": {
"ignore": ["dist", ".turbo", ".vercel", ".astro", "public"]
}
}
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"scripts": {
"bump": "taze -r -l",
"dev": "turbo dev",
"test": "turbo run test",
"build": "turbo run build",
"check": "biome check",
"check:write": "biome check --write",
"repocheck": "sherif",
"check-types": "turbo run check-types",
"clean": "rimraf -g **/node_modules **/dist **/.turbo **/.astro",
Expand All @@ -25,13 +25,16 @@
"simple-git-hooks": "^2.11.1",
"taze": "^0.17.2",
"turbo": "^2.1.3",
"typescript": "^5.6.2"
"typescript": "^5.6.2",
"vite": "^5.4.3"
},
"packageManager": "[email protected]",
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "pnpm check:write"
"*": [
"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
]
}
}
43 changes: 43 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "shipbase-ui",
"version": "0.0.0",
"type": "module",
"description": "Add components to your apps.",
"exports": "./dist/index.js",
"bin": "./dist/index.js",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/shipbase/ui.git",
"directory": "packages/cli"
},
"author": "@shipbase",
"license": "MIT",
"files": ["dist"],
"keywords": ["shipbase", "components", "ui", "tailwind", "ark-ui"],
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
"check-types": "tsc --noEmit",
"test": "vitest"
},
"devDependencies": {
"@types/prompts": "^2.4.9",
"@ui/lib": "workspace:*",
"@ui/tsconfig": "workspace:*",
"tsup": "^8.2.4",
"vitest": "^2.0.5"
},
"dependencies": {
"cac": "^6.7.14",
"kleur": "^4.1.5",
"mlly": "^1.7.1",
"nypm": "^0.3.11",
"ora": "^8.1.0",
"prompts": "^2.4.2",
"ufo": "^1.5.4",
"zod": "^3.23.8"
}
}
178 changes: 178 additions & 0 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import fs from "node:fs/promises"
import path from "node:path"
import { safeResolveAndRead } from "@ui/lib/utils/mlly"
import { cyan } from "kleur/colors"
import ora from "ora"
import prompts from "prompts"
import {
type Config,
type UserConfig,
userConfigSchema,
} from "../config/schema"
import {
DEFAULT_COMPONENTS,
DEFAULT_STYLE,
DEFAULT_TAILWIND_BASE_COLOR,
DEFAULT_TAILWIND_CONFIG,
DEFAULT_UTILS,
LIBRARIES,
TAILWIND_CSS_FILE_PATH,
} from "../constants"
import { logger } from "../utils/logger"
import { prepare } from "../utils/prepare"

export async function init({ library }: { library?: Config["library"] }) {
let userConfig: UserConfig

try {
await prepare()
userConfig = await promptConfig({ library })

const { write } = await prompts({
type: "confirm",
name: "write",
message: `Write configuration to ${cyan("components.json")}. Proceed?`,
initial: true,
})

// Write components.json.
if (write) {
const writeSpinner = ora("Writing components.json...").start()
const targetPath = path.resolve(process.cwd(), "components.json")
await fs.writeFile(
targetPath,
JSON.stringify(userConfig, null, 2),
"utf8"
)
writeSpinner.succeed()
}
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(error.message)
} else {
logger.error("unknown errors")
}
process.exit(1)
}
}

async function validateFilePath(relative: string) {
const file = await safeResolveAndRead(path.resolve(process.cwd(), relative))
if (!file.success)
return "File not found. Please check the path and try again."
return true
}

async function validateTailwindGlobalCss(relative: string) {
const file = await safeResolveAndRead(path.resolve(process.cwd(), relative))
if (!file.success)
return "File not found. Please check the path and try again."
if (!file.result.includes("@tailwind base")) {
return "The file does not include '@tailwind base'. Please ensure it contains the necessary Tailwind directives."
}
return true
}

async function promptConfig(defaultConfig: Partial<UserConfig>) {
const result = await prompts(
[
{
type:
defaultConfig.library &&
LIBRARIES.findIndex((lib) => lib.value === defaultConfig.library) > -1
? null
: "select",
name: "library",
initial: defaultConfig.library,
message: "Pick your favorite library",
choices: LIBRARIES,
},
{
type: null, // TODO: Implement toggle
name: "typescript",
message: `Would you like to use ${cyan("TypeScript")} (recommended)?`,
initial: true,
active: "yes",
inactive: "no",
},
{
type: null, // TODO: Implement select
name: "style",
message: `Which ${cyan("style")} would you like to use?`,
choices: [{ title: "Default", value: "default" }],
initial: DEFAULT_STYLE,
},
{
type: null, // TODO: Implement select
name: "tailwindBaseColor",
message: `Which color would you like to use as the ${cyan("base color")}?`,
choices: [{ title: "Slate", value: "slate" }],
initial: DEFAULT_TAILWIND_BASE_COLOR,
},
{
type: "text",
name: "tailwindCss",
message: `Where is your ${cyan("global CSS")} file?`,
initial: TAILWIND_CSS_FILE_PATH.vite,
validate: validateTailwindGlobalCss,
},
{
type: "toggle",
name: "tailwindCssVariables",
message: `Would you like to use ${cyan("CSS variables")} for theming?`,
initial: true,
active: "yes",
inactive: "no",
},
{
type: null, // TODO: Implement text
name: "tailwindPrefix",
message: `Are you using a custom ${cyan(
"tailwind prefix eg. tw-"
)}? (Leave blank if not)`,
initial: "",
},
{
type: "text",
name: "tailwindConfig",
message: `Where is your ${cyan("tailwind.config.js")} located?`,
initial: DEFAULT_TAILWIND_CONFIG,
validate: validateFilePath,
},
{
type: "text",
name: "aliasComponents",
message: `Configure the import alias for ${cyan("components")}:`,
initial: DEFAULT_COMPONENTS,
},
{
type: "text",
name: "utils",
message: `Configure the import alias for ${cyan("utils")}:`,
initial: DEFAULT_UTILS,
},
],
{
onCancel: () => {
throw new Error("✖ Operation cancelled")
},
}
)

return userConfigSchema.parse({
library: defaultConfig.library ?? result.library,
style: "default",
tsx: true,
tailwind: {
config: result.tailwindConfig,
css: result.tailwindCss,
baseColor: "slate",
cssVariables: result.tailwindCssVariables,
prefix: "",
},
aliases: {
components: result.aliasComponents,
utils: result.utils,
},
})
}
19 changes: 19 additions & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Config } from "./schema"

export async function resolveConfig() {
// return {
// framework: "react",
// style: "css",
// tailwind: {
// config: "tailwind.config.js",
// css: "src/index.css",
// baseColor: "blue",
// cssVariables: true,
// prefix: "",
// },
// aliases: {
// components: "src/components",
// utils: "src /utils",
// },
// } satisfies Config
}
36 changes: 36 additions & 0 deletions packages/cli/src/config/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from "zod"

export const userConfigSchema = z
.object({
// $schema: z.string().optional(),
library: z.enum(["react", "vue"]),
style: z.string(),
// rsc: z.coerce.boolean().default(false),
tsx: z.coerce.boolean().default(true),
tailwind: z.object({
config: z.string(),
css: z.string(),
baseColor: z.string(),
cssVariables: z.boolean().default(true),
prefix: z.string().default("").optional(),
}),
aliases: z.object({
components: z.string(),
utils: z.string(),
}),
})
.strict()

export type UserConfig = z.infer<typeof userConfigSchema>

export const configSchema = userConfigSchema.extend({
resolvedPaths: z.object({
cwd: z.string(),
tailwindConfig: z.string(),
tailwindCss: z.string(),
components: z.string(),
utils: z.string(),
}),
})

export type Config = z.infer<typeof configSchema>
11 changes: 11 additions & 0 deletions packages/cli/src/constants/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const DEFAULT_LIBRARY = "react"

export const DEFAULT_STYLE = "default"
export const DEFAULT_COMPONENTS = "@/components"
export const DEFAULT_UTILS = "@/lib/utils"
export const DEFAULT_TAILWIND_CONFIG = "tailwind.config.js"
export const DEFAULT_TAILWIND_BASE_COLOR = "slate"

export const TAILWIND_CSS_FILE_PATH = {
vite: "src/assets/base.css",
}
Loading
Loading