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: Add flags for init command #1186

Merged
merged 23 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
11 changes: 9 additions & 2 deletions apps/www/src/content/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ Usage: shadcn-svelte init [options]
initialize your project and install dependencies

Options:
-c, --cwd <cwd> the working directory. (default: the current directory)
-h, --help display help for command
-c, --cwd <cwd> the working directory. defaults to the current directory. (default: "E:\\github\\shadcn-svelte\\packages\\cli")
ieedan marked this conversation as resolved.
Show resolved Hide resolved
-ts, --typescript use TypeScript
-s, --style <name> the style (choices: "default", "new-york")
-bc, --base-color <name> the base color for the components (choices: "slate", "gray", "zinc", "neutral", "stone")
-gc, --global-css <path> path to the global css file
-tc, --tailwind-config <path> path to the tailwind config file
-ca, --component-alias <path> import alias for components
-ua, --utils-alias <path> import alias for utils
-h, --help display help for command
```

## add
Expand Down
289 changes: 196 additions & 93 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import path from "node:path";
import process from "node:process";
import color from "chalk";
import * as v from "valibot";
import { Command } from "commander";
import { Command, Option, type OptionValues } from "commander";
import { execa } from "execa";
import * as cliConfig from "../utils/get-config.js";
import type { Config } from "../utils/get-config.js";
import { getPackageManager } from "../utils/get-package-manager.js";
import { error, handleError } from "../utils/errors.js";
import { getRegistryBaseColor, getRegistryBaseColors, getRegistryStyles } from "../utils/registry";
import { getBaseColors, getRegistryBaseColor, getStyles } from "../utils/registry";
import * as templates from "../utils/templates.js";
import * as p from "../utils/prompts.js";
import { intro } from "../utils/prompt-helpers.js";
Expand All @@ -20,6 +20,9 @@ import { detectConfigs } from "../utils/auto-detect.js";
const PROJECT_DEPENDENCIES = ["tailwind-variants", "clsx", "tailwind-merge"] as const;
const highlight = (...args: unknown[]) => color.bold.cyan(...args);

const baseColors = getBaseColors();
const styles = getStyles();

export const init = new Command()
.command("init")
.description("initialize your project and install dependencies")
Expand All @@ -28,6 +31,19 @@ export const init = new Command()
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-ts, --typescript", `use TypeScript`)
.addOption(
new Option("-s, --style <name>", "the style").choices(styles.map((style) => style.name))
)
.addOption(
new Option("-bc, --base-color <name>", "the base color for the components").choices(
baseColors.map((color) => color.name)
)
)
.option("-gc, --global-css <path>", "path to the global css file")
.option("-tc, --tailwind-config <path>", "path to the tailwind config file")
.option("-ca, --component-alias <path>", "import alias for components")
.option("-ua, --utils-alias <path>", "import alias for utils")
.action(async (options) => {
intro();
const cwd = path.resolve(options.cwd);
Expand All @@ -40,7 +56,7 @@ export const init = new Command()

// Read config.
const existingConfig = await cliConfig.getConfig(cwd);
const config = await promptForConfig(cwd, existingConfig);
const config = await promptForConfig(cwd, existingConfig, options);

await runInit(cwd, config);

Expand All @@ -50,21 +66,23 @@ export const init = new Command()
}
});

async function promptForConfig(cwd: string, defaultConfig: Config | null) {
async function promptForConfig(cwd: string, defaultConfig: Config | null, options: OptionValues) {
// if it's a SvelteKit project, run sync so that the aliases are always up to date
await syncSvelteKit(cwd);

const styles = await getRegistryStyles();
const baseColors = await getRegistryBaseColors();
const detectedConfigs = detectConfigs(cwd, { relative: true });

const typescript = await p.confirm({
message: `Would you like to use ${highlight("TypeScript")}? ${color.gray("(recommended)")}`,
initialValue: defaultConfig?.typescript ?? cliConfig.DEFAULT_TYPESCRIPT,
});
if (p.isCancel(typescript)) {
p.cancel("Operation cancelled.");
process.exit(0);
let typescript = options.typescript;

if (options.typescript === undefined) {
typescript = await p.confirm({
message: `Would you like to use ${highlight("TypeScript")}? ${color.gray("(recommended)")}`,
initialValue: defaultConfig?.typescript ?? cliConfig.DEFAULT_TYPESCRIPT,
});
if (p.isCancel(typescript)) {
p.cancel("Operation cancelled.");
process.exit(0);
}
}

const tsconfigName = typescript ? "tsconfig.json" : "jsconfig.json";
Expand All @@ -80,95 +98,180 @@ async function promptForConfig(cwd: string, defaultConfig: Config | null) {
return `"${color.bold(alias)}" does not use an existing path alias defined in your ${color.bold(tsconfigName)}. See: ${color.underline("https://www.shadcn-svelte.com/docs/installation/manual#configure-path-aliases")}`;
};

const options = await p.group(
{
style: () =>
p.select({
message: `Which ${highlight("style")} would you like to use?`,
initialValue: defaultConfig?.style ?? cliConfig.DEFAULT_STYLE,
options: styles.map((style) => ({
label: style.label,
value: style.name,
})),
}),
tailwindBaseColor: () =>
p.select({
message: `Which ${highlight("base color")} would you like to use?`,
initialValue: defaultConfig?.tailwind.baseColor ?? cliConfig.DEFAULT_TAILWIND_BASE_COLOR,
options: baseColors.map((color) => ({
label: color.label,
value: color.name,
})),
}),
tailwindCss: () =>
p.text({
message: `Where is your ${highlight("global CSS")} file? ${color.gray("(this file will be overwritten)")}`,
initialValue:
defaultConfig?.tailwind.css ??
detectedConfigs.cssPath ??
cliConfig.DEFAULT_TAILWIND_CSS,
placeholder: detectedConfigs.cssPath ?? cliConfig.DEFAULT_TAILWIND_CSS,
validate: (value) => {
if (value && existsSync(path.resolve(cwd, value))) {
return;
}
return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
},
}),
tailwindConfig: () =>
p.text({
message: `Where is your ${highlight("Tailwind config")} located? ${color.gray("(this file will be overwritten)")}`,
initialValue:
defaultConfig?.tailwind.config ??
detectedConfigs.tailwindPath ??
cliConfig.DEFAULT_TAILWIND_CONFIG,
placeholder: detectedConfigs.tailwindPath ?? cliConfig.DEFAULT_TAILWIND_CONFIG,
validate: (value) => {
if (value && existsSync(path.resolve(cwd, value))) {
return;
}
return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
},
}),
components: () =>
p.text({
message: `Configure the import alias for ${highlight("components")}:`,
initialValue: defaultConfig?.aliases.components ?? cliConfig.DEFAULT_COMPONENTS,
placeholder: cliConfig.DEFAULT_COMPONENTS,
validate: validateImportAlias,
}),
utils: ({ results: { components } }) =>
p.text({
message: `Configure the import alias for ${highlight("utils")}:`,
initialValue:
defaultConfig?.aliases.utils ??
// infers the alias from `components`. if `components = @/comps` then suggest `utils = @/utils`
`${components?.split("/").slice(0, -1).join("/")}/utils` ??
cliConfig.DEFAULT_UTILS,
placeholder: cliConfig.DEFAULT_UTILS,
validate: validateImportAlias,
}),
},
{
onCancel: () => {
p.cancel("Operation cancelled.");
process.exit(0);
// -- get style --

let style: string | symbol | undefined = styles.find(
(style) => style.name === options.style
)?.name;

if (style === undefined) {
style = await p.select({
message: `Which ${highlight("style")} would you like to use?`,
initialValue: defaultConfig?.style ?? cliConfig.DEFAULT_STYLE,
options: styles.map((style) => ({
label: style.label,
value: style.name,
})),
});
}

if (p.isCancel(style)) {
p.cancel("Operation cancelled.");
process.exit(0);
}

// -- get base color --

let tailwindBaseColor: string | symbol | undefined = baseColors.find(
(color) => color.name === options.baseColor
)?.name;

if (tailwindBaseColor === undefined) {
tailwindBaseColor = await p.select({
message: `Which ${highlight("base color")} would you like to use?`,
initialValue: defaultConfig?.tailwind.baseColor ?? cliConfig.DEFAULT_TAILWIND_BASE_COLOR,
options: baseColors.map((color) => ({
label: color.label,
value: color.name,
})),
});
}

if (p.isCancel(tailwindBaseColor)) {
p.cancel("Operation cancelled.");
process.exit(0);
}

// -- get global css file --

let globalCss: string | undefined = options.globalCss;

if (globalCss === undefined || !existsSync(path.resolve(cwd, globalCss))) {
if (globalCss !== undefined) {
throw error(`"${color.bold(globalCss)}" does not exist. Please enter a valid path.`);
}

const promptResult = await p.text({
message: `Where is your ${highlight("global CSS")} file? ${color.gray("(this file will be overwritten)")}`,
initialValue:
defaultConfig?.tailwind.css ?? detectedConfigs.cssPath ?? cliConfig.DEFAULT_TAILWIND_CSS,
placeholder: detectedConfigs.cssPath ?? cliConfig.DEFAULT_TAILWIND_CSS,
validate: (value) => {
if (value && existsSync(path.resolve(cwd, value))) {
return;
}
return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
},
});

if (p.isCancel(promptResult)) {
p.cancel("Operation cancelled.");
process.exit(0);
}

globalCss = promptResult;
}

// -- get tailwind config --

let tailwindConfig: string | undefined = options.tailwindConfig;

if (tailwindConfig === undefined || !existsSync(path.resolve(cwd, tailwindConfig))) {
if (tailwindConfig !== undefined) {
throw error(`"${color.bold(tailwindConfig)}" does not exist. Please enter a valid path.`);
}

const promptResult = await p.text({
message: `Where is your ${highlight("Tailwind config")} located? ${color.gray("(this file will be overwritten)")}`,
initialValue:
defaultConfig?.tailwind.config ??
detectedConfigs.tailwindPath ??
cliConfig.DEFAULT_TAILWIND_CONFIG,
placeholder: detectedConfigs.tailwindPath ?? cliConfig.DEFAULT_TAILWIND_CONFIG,
validate: (value) => {
if (value && existsSync(path.resolve(cwd, value))) {
return;
}
return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
},
});

if (p.isCancel(promptResult)) {
p.cancel("Operation cancelled.");
process.exit(0);
}

tailwindConfig = promptResult;
}

// -- get component alias --

let componentAlias: string | undefined = options.componentAlias;

const importAliasValidationResult = componentAlias ? validateImportAlias(componentAlias) : "";

if (componentAlias === undefined || typeof importAliasValidationResult == "string") {
if (componentAlias !== undefined && typeof importAliasValidationResult == "string") {
throw error(importAliasValidationResult);
}

const promptResult = await p.text({
message: `Configure the import alias for ${highlight("components")}:`,
initialValue: defaultConfig?.aliases.components ?? cliConfig.DEFAULT_COMPONENTS,
placeholder: cliConfig.DEFAULT_COMPONENTS,
validate: validateImportAlias,
});

if (p.isCancel(promptResult)) {
p.cancel("Operation cancelled.");
process.exit(0);
}

componentAlias = promptResult;
}

// -- get utils alias --

let utilsAlias: string | undefined = options.utilsAlias;

const utilsAliasValidationResult = utilsAlias ? validateImportAlias(utilsAlias) : "";

if (utilsAlias === undefined || typeof utilsAliasValidationResult == "string") {
if (utilsAlias !== undefined && typeof utilsAliasValidationResult == "string") {
throw error(utilsAliasValidationResult);
}

const promptResult = await p.text({
message: `Configure the import alias for ${highlight("utils")}:`,
initialValue:
defaultConfig?.aliases.utils ??
// infers the alias from `components`. if `components = @/comps` then suggest `utils = @/utils`
`${componentAlias?.split("/").slice(0, -1).join("/")}/utils` ??
cliConfig.DEFAULT_UTILS,
placeholder: cliConfig.DEFAULT_UTILS,
validate: validateImportAlias,
});

if (p.isCancel(promptResult)) {
p.cancel("Operation cancelled.");
process.exit(0);
}
);

utilsAlias = promptResult;
}

const config = v.parse(cliConfig.rawConfigSchema, {
$schema: "https://shadcn-svelte.com/schema.json",
style: options.style,
style,
typescript,
tailwind: {
config: options.tailwindConfig,
css: options.tailwindCss,
baseColor: options.tailwindBaseColor,
config: tailwindConfig,
css: globalCss,
baseColor: tailwindBaseColor,
},
aliases: {
utils: options.utils,
components: options.components,
utils: utilsAlias,
components: componentAlias,
},
});

Expand Down
Loading
Loading