From 72746421ccbdfb33b71b20d5c54cc8260ffc09ac Mon Sep 17 00:00:00 2001 From: Daniel Castillo Date: Fri, 7 Jun 2024 20:32:46 -0400 Subject: [PATCH] feat: handle select hooks #3 --- src/commands/add.ts | 52 +++++++++--- src/utils/downloadHook.ts | 36 ++++++++ src/utils/registry/index.ts | 155 ----------------------------------- src/utils/registry/schema.ts | 44 ---------- 4 files changed, 76 insertions(+), 211 deletions(-) delete mode 100644 src/utils/registry/index.ts delete mode 100644 src/utils/registry/schema.ts diff --git a/src/commands/add.ts b/src/commands/add.ts index 68b4eac..c319402 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -4,13 +4,6 @@ import { getConfig } from "@/src/utils/get-config"; import { getPackageManager } from "@/src/utils/get-package-manager"; import { handleError } from "@/src/utils/handle-error"; import { logger } from "@/src/utils/logger"; -import { - fetchTree, - getItemTargetPath, - getRegistryBaseColor, - getRegistryIndex, - resolveTree, -} from "@/src/utils/registry"; import { transform } from "@/src/utils/transformers"; import chalk from "chalk"; import { Command } from "commander"; @@ -18,11 +11,10 @@ import { execa } from "execa"; import ora from "ora"; import prompts from "prompts"; import { z } from "zod"; -import { downloadHook } from "../utils/downloadHook"; +import { downloadHook, getAllHooksName } from "../utils/downloadHook"; const addOptionsSchema = z.object({ hooks: z.array(z.string()).optional(), - yes: z.boolean(), overwrite: z.boolean(), cwd: z.string(), all: z.boolean(), @@ -33,7 +25,6 @@ export const add = new Command() .name("add") .description("add a hook to your project") .argument("[hooks...]", "the hooks to add") - .option("-y, --yes", "skip confirmation prompt.", true) .option("-o, --overwrite", "overwrite existing files.", false) .option( "-c, --cwd ", @@ -43,8 +34,45 @@ export const add = new Command() .option("-a, --all", "add all available hooks", false) .option("-p, --path ", "the path to add the hook to.") .action(async (hooks, opts) => { - for (let i = 0; i < hooks.length; i++) { - const hook = hooks[i]; + const options = addOptionsSchema.parse({ + hooks, + ...opts, + }); + + const cwd = path.resolve(options.cwd); + + if (!existsSync(cwd)) { + logger.error(`The path ${cwd} does not exist. Please try again.`); + process.exit(1); + } + + const allHooks = await getAllHooksName(); + + let selectedHooks = options.all ? allHooks : options.hooks; + + if (!options.hooks?.length && !options.all && allHooks) { + const { hooks } = await prompts({ + type: "multiselect", + name: "hooks", + message: "Which hooks would you like to add?", + hint: "Space to select. A to toggle all. Enter to submit.", + instructions: false, + choices: allHooks.map((entry) => ({ + title: entry, + value: entry, + selected: options.all ? true : options.hooks?.includes(entry), + })), + }); + selectedHooks = hooks; + } + + if (!selectedHooks?.length) { + logger.warn("No hooks selected. Exiting."); + process.exit(0); + } + + for (let i = 0; i < selectedHooks.length; i++) { + const hook = selectedHooks[i]; await downloadHook(hook); } }); diff --git a/src/utils/downloadHook.ts b/src/utils/downloadHook.ts index b1b1b65..b1c6f59 100644 --- a/src/utils/downloadHook.ts +++ b/src/utils/downloadHook.ts @@ -1,6 +1,42 @@ import fs from "fs/promises"; import fetch from "node-fetch"; +interface HookList { + name: string; + path: string; + sha: string; + size: number; + url: string; + html_url: string; + git_url: string; + download_url: string; + type: string; + _links: { + self: string; + git: string; + html: string; + }; +} + +export const getAllHooksName = async () => { + const allHooksUrl = + "https://api.github.com/repos/novajslabs/nova.js/contents/src/hooks"; + + try { + const response = await fetch(allHooksUrl); + + if (!response.ok) { + throw Error; + } + + const hooksData = (await response.json()) as HookList[]; + + return hooksData.map((hook) => hook.name.replace(".ts", "")); + } catch (e) { + //console.log(`❌ ${hook} error`); + } +}; + export const downloadHook = async (hookName: string) => { const hookUrl = `https://raw.githubusercontent.com/novajslabs/nova.js/main/src/hooks/${hookName}.ts`; diff --git a/src/utils/registry/index.ts b/src/utils/registry/index.ts deleted file mode 100644 index 29395e2..0000000 --- a/src/utils/registry/index.ts +++ /dev/null @@ -1,155 +0,0 @@ -import path from "path" -import { Config } from "@/src/utils/get-config" -import { - registryBaseColorSchema, - registryIndexSchema, - registryItemWithContentSchema, - registryWithContentSchema, - stylesSchema, -} from "@/src/utils/registry/schema" -import { HttpsProxyAgent } from "https-proxy-agent" -import fetch from "node-fetch" -import { z } from "zod" - -const baseUrl = process.env.COMPONENTS_REGISTRY_URL ?? "https://ui.shadcn.com" -const agent = process.env.https_proxy - ? new HttpsProxyAgent(process.env.https_proxy) - : undefined - -export async function getRegistryIndex() { - try { - const [result] = await fetchRegistry(["index.json"]) - - return registryIndexSchema.parse(result) - } catch (error) { - throw new Error(`Failed to fetch components from registry.`) - } -} - -export async function getRegistryStyles() { - try { - const [result] = await fetchRegistry(["styles/index.json"]) - - return stylesSchema.parse(result) - } catch (error) { - throw new Error(`Failed to fetch styles from registry.`) - } -} - -export async function getRegistryBaseColors() { - return [ - { - name: "slate", - label: "Slate", - }, - { - name: "gray", - label: "Gray", - }, - { - name: "zinc", - label: "Zinc", - }, - { - name: "neutral", - label: "Neutral", - }, - { - name: "stone", - label: "Stone", - }, - ] -} - -export async function getRegistryBaseColor(baseColor: string) { - try { - const [result] = await fetchRegistry([`colors/${baseColor}.json`]) - - return registryBaseColorSchema.parse(result) - } catch (error) { - throw new Error(`Failed to fetch base color from registry.`) - } -} - -export async function resolveTree( - index: z.infer, - names: string[] -) { - const tree: z.infer = [] - - for (const name of names) { - const entry = index.find((entry) => entry.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, index, self) => - self.findIndex((c) => c.name === component.name) === index - ) -} - -export async function fetchTree( - style: string, - tree: z.infer -) { - try { - const paths = tree.map((item) => `styles/${style}/${item.name}.json`) - const result = await fetchRegistry(paths) - - return registryWithContentSchema.parse(result) - } catch (error) { - throw new Error(`Failed to fetch tree from registry.`) - } -} - -export async function getItemTargetPath( - config: Config, - item: Pick, "type">, - override?: string -) { - if (override) { - return override - } - - if (item.type === "components:ui" && config.aliases.ui) { - return config.resolvedPaths.ui - } - - const [parent, type] = item.type.split(":") - if (!(parent in config.resolvedPaths)) { - return null - } - - return path.join( - config.resolvedPaths[parent as keyof typeof config.resolvedPaths], - type - ) -} - -async function fetchRegistry(paths: string[]) { - try { - const results = await Promise.all( - paths.map(async (path) => { - const response = await fetch(`${baseUrl}/registry/${path}`, { - agent, - }) - return await response.json() - }) - ) - - return results - } catch (error) { - console.log(error) - throw new Error(`Failed to fetch registry from ${baseUrl}.`) - } -} diff --git a/src/utils/registry/schema.ts b/src/utils/registry/schema.ts deleted file mode 100644 index b227590..0000000 --- a/src/utils/registry/schema.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { z } from "zod" - -// TODO: Extract this to a shared package. -export const registryItemSchema = z.object({ - name: z.string(), - dependencies: z.array(z.string()).optional(), - devDependencies: z.array(z.string()).optional(), - registryDependencies: z.array(z.string()).optional(), - files: z.array(z.string()), - type: z.enum(["components:ui", "components:component", "components:example"]), -}) - -export const registryIndexSchema = z.array(registryItemSchema) - -export const registryItemWithContentSchema = registryItemSchema.extend({ - files: z.array( - z.object({ - name: z.string(), - content: z.string(), - }) - ), -}) - -export const registryWithContentSchema = z.array(registryItemWithContentSchema) - -export const stylesSchema = z.array( - z.object({ - name: z.string(), - label: z.string(), - }) -) - -export const registryBaseColorSchema = z.object({ - inlineColors: z.object({ - light: z.record(z.string(), z.string()), - dark: z.record(z.string(), z.string()), - }), - cssVars: z.object({ - light: z.record(z.string(), z.string()), - dark: z.record(z.string(), z.string()), - }), - inlineColorsTemplate: z.string(), - cssVarsTemplate: z.string(), -})