Comparing version 0.0.0-beta.ec41f85 to 0.0.0-beta.fa20368
1208
dist/index.js
#!/usr/bin/env node | ||
// src/index.ts | ||
import { existsSync as existsSync2, promises as fs3 } from "fs"; | ||
import path3 from "path"; | ||
import { Command } from "commander"; | ||
import { execa } from "execa"; | ||
import ora from "ora"; | ||
import prompts from "prompts"; | ||
// src/commands/add.ts | ||
import { existsSync, promises as fs3 } from "fs"; | ||
import path5 from "path"; | ||
// src/utils/get-components.ts | ||
import fetch from "node-fetch"; | ||
// src/utils/get-config.ts | ||
import path from "path"; | ||
// 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/get-config.ts | ||
import { cosmiconfig } from "cosmiconfig"; | ||
import { loadConfig } from "tsconfig-paths"; | ||
import * as z from "zod"; | ||
var baseUrl = process.env.COMPONENTS_BASE_URL ?? "https://ui.shadcn.com"; | ||
var componentSchema = z.object({ | ||
component: z.string(), | ||
name: z.string(), | ||
dependencies: z.array(z.string()).optional(), | ||
files: z.array( | ||
z.object({ | ||
name: z.string(), | ||
dir: z.string(), | ||
content: z.string() | ||
}) | ||
) | ||
var DEFAULT_COMPONENTS = "@/components"; | ||
var DEFAULT_UTILS = "@/lib/utils"; | ||
var DEFAULT_TAILWIND_CSS = "app/globals.css"; | ||
var DEFAULT_TAILWIND_CONFIG = "tailwind.config.js"; | ||
var explorer = cosmiconfig("components", { | ||
searchPlaces: ["components.json"] | ||
}); | ||
var componentsSchema = z.array(componentSchema); | ||
async function getAvailableComponents() { | ||
try { | ||
const response = await fetch(`${baseUrl}/api/components`); | ||
const components = await response.json(); | ||
return componentsSchema.parse(components); | ||
} catch (error) { | ||
var rawConfigSchema = z.object({ | ||
$schema: z.string().optional(), | ||
style: z.string(), | ||
rsc: z.coerce.boolean().default(false), | ||
tailwind: z.object({ | ||
config: z.string(), | ||
css: z.string(), | ||
baseColor: z.string(), | ||
cssVariables: z.boolean().default(true) | ||
}), | ||
aliases: z.object({ | ||
components: z.string(), | ||
utils: z.string() | ||
}) | ||
}).strict(); | ||
var configSchema = rawConfigSchema.extend({ | ||
resolvedPaths: z.object({ | ||
tailwindConfig: z.string(), | ||
tailwindCss: z.string(), | ||
utils: z.string(), | ||
components: z.string() | ||
}) | ||
}); | ||
async function getConfig(cwd) { | ||
const config = await getRawConfig(cwd); | ||
if (!config) { | ||
return null; | ||
} | ||
return await resolveConfigPaths(cwd, config); | ||
} | ||
async function resolveConfigPaths(cwd, config) { | ||
const tsConfig = await loadConfig(cwd); | ||
if (tsConfig.resultType === "failed") { | ||
throw new Error( | ||
`Failed to fetch components from ${baseUrl}/api/components.` | ||
`Failed to load tsconfig.json. ${tsConfig.message ?? ""}`.trim() | ||
); | ||
} | ||
return configSchema.parse({ | ||
...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) | ||
} | ||
}); | ||
} | ||
// src/utils/get-package-info.ts | ||
import path from "path"; | ||
import fs from "fs-extra"; | ||
function getPackageInfo() { | ||
const packageJsonPath = path.join("package.json"); | ||
return fs.readJSONSync(packageJsonPath); | ||
async function getRawConfig(cwd) { | ||
try { | ||
const configResult = await explorer.search(cwd); | ||
if (!configResult) { | ||
return null; | ||
} | ||
return rawConfigSchema.parse(configResult.config); | ||
} catch (error) { | ||
throw new Error(`Invald configuration found in ${cwd}/components.json.`); | ||
} | ||
} | ||
// src/utils/get-package-manager.ts | ||
function getPackageManager() { | ||
import { promises as fs } from "fs"; | ||
import path2 from "path"; | ||
async function fileExists(path9) { | ||
try { | ||
await fs.access(path9); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
async function getPackageManager(targetDir) { | ||
const [yarnLock, npmLock, pnpmLock] = await Promise.all([ | ||
fileExists(path2.resolve(targetDir, "yarn.lock")), | ||
fileExists(path2.resolve(targetDir, "package-lock.json")), | ||
fileExists(path2.resolve(targetDir, "pnpm-lock.yaml")) | ||
]); | ||
if (yarnLock) { | ||
return "yarn"; | ||
} | ||
if (pnpmLock) { | ||
return "pnpm"; | ||
} | ||
if (npmLock) { | ||
return "npm"; | ||
} | ||
const userAgent = process.env.npm_config_user_agent; | ||
@@ -63,40 +130,2 @@ if (!userAgent) { | ||
// src/utils/get-project-info.ts | ||
import { existsSync } from "fs"; | ||
import path2 from "path"; | ||
import fs2 from "fs-extra"; | ||
async function getProjectInfo() { | ||
const info = { | ||
tsconfig: null, | ||
alias: null, | ||
srcDir: false, | ||
appDir: false | ||
}; | ||
try { | ||
const tsconfig = await getTsConfig(); | ||
const paths = tsconfig?.compilerOptions?.paths; | ||
const alias = paths ? Object.keys(paths)[0].replace("*", "") : null; | ||
return { | ||
tsconfig, | ||
alias, | ||
srcDir: existsSync(path2.resolve("./src")), | ||
appDir: existsSync(path2.resolve("./app")) || existsSync(path2.resolve("./src/app")) | ||
}; | ||
} catch (error) { | ||
return info; | ||
} | ||
} | ||
async function getTsConfig() { | ||
try { | ||
const tsconfigPath = path2.join("tsconfig.json"); | ||
const tsconfig = await fs2.readJSON(tsconfigPath); | ||
if (!tsconfig) { | ||
throw new Error("tsconfig.json is missing"); | ||
} | ||
return tsconfig; | ||
} catch (error) { | ||
return null; | ||
} | ||
} | ||
// src/utils/logger.ts | ||
@@ -116,88 +145,598 @@ import chalk from "chalk"; | ||
console.log(chalk.green(...args)); | ||
}, | ||
break() { | ||
console.log(""); | ||
} | ||
}; | ||
// src/utils/templates.ts | ||
var STYLES = `@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
@layer base { | ||
:root { | ||
--background: 0 0% 100%; | ||
--foreground: 222.2 47.4% 11.2%; | ||
--muted: 210 40% 96.1%; | ||
--muted-foreground: 215.4 16.3% 46.9%; | ||
--popover: 0 0% 100%; | ||
--popover-foreground: 222.2 47.4% 11.2%; | ||
--card: 0 0% 100%; | ||
--card-foreground: 222.2 47.4% 11.2%; | ||
--border: 214.3 31.8% 91.4%; | ||
--input: 214.3 31.8% 91.4%; | ||
--primary: 222.2 47.4% 11.2%; | ||
--primary-foreground: 210 40% 98%; | ||
--secondary: 210 40% 96.1%; | ||
--secondary-foreground: 222.2 47.4% 11.2%; | ||
--accent: 210 40% 96.1%; | ||
--accent-foreground: 222.2 47.4% 11.2%; | ||
--destructive: 0 100% 50%; | ||
--destructive-foreground: 210 40% 98%; | ||
--ring: 215 20.2% 65.1%; | ||
--radius: 0.5rem; | ||
// src/utils/handle-error.ts | ||
function handleError(error) { | ||
if (typeof error === "string") { | ||
logger.error(error); | ||
process.exit(1); | ||
} | ||
.dark { | ||
--background: 224 71% 4%; | ||
--foreground: 213 31% 91%; | ||
--muted: 223 47% 11%; | ||
--muted-foreground: 215.4 16.3% 56.9%; | ||
--popover: 224 71% 4%; | ||
--popover-foreground: 215 20.2% 65.1%; | ||
--card: 0 0% 100%; | ||
--card-foreground: 222.2 47.4% 11.2%; | ||
--border: 216 34% 17%; | ||
--input: 216 34% 17%; | ||
--primary: 210 40% 98%; | ||
--primary-foreground: 222.2 47.4% 1.2%; | ||
--secondary: 222.2 47.4% 11.2%; | ||
--secondary-foreground: 210 40% 98%; | ||
--accent: 216 34% 17%; | ||
--accent-foreground: 210 40% 98%; | ||
--destructive: 0 63% 31%; | ||
--destructive-foreground: 210 40% 98%; | ||
--ring: 216 34% 17%; | ||
--radius: 0.5rem; | ||
if (error instanceof Error) { | ||
logger.error(error.message); | ||
process.exit(1); | ||
} | ||
logger.error("Something went wrong. Please try again."); | ||
process.exit(1); | ||
} | ||
@layer base { | ||
* { | ||
@apply border-border; | ||
// src/utils/registry/index.ts | ||
import path3 from "path"; | ||
// src/utils/registry/schema.ts | ||
import * as z2 from "zod"; | ||
var registryItemSchema = z2.object({ | ||
name: z2.string(), | ||
dependencies: z2.array(z2.string()).optional(), | ||
registryDependencies: z2.array(z2.string()).optional(), | ||
files: z2.array(z2.string()), | ||
type: z2.enum(["components:ui", "components:component", "components:example"]) | ||
}); | ||
var registryIndexSchema = z2.array(registryItemSchema); | ||
var registryItemWithContentSchema = registryItemSchema.extend({ | ||
files: z2.array( | ||
z2.object({ | ||
name: z2.string(), | ||
content: z2.string() | ||
}) | ||
) | ||
}); | ||
var registryWithContentSchema = z2.array(registryItemWithContentSchema); | ||
var stylesSchema = z2.array( | ||
z2.object({ | ||
name: z2.string(), | ||
label: z2.string() | ||
}) | ||
); | ||
var registryBaseColorSchema = z2.object({ | ||
inlineColors: z2.object({ | ||
light: z2.record(z2.string(), z2.string()), | ||
dark: z2.record(z2.string(), z2.string()) | ||
}), | ||
cssVars: z2.object({ | ||
light: z2.record(z2.string(), z2.string()), | ||
dark: z2.record(z2.string(), z2.string()) | ||
}), | ||
inlineColorsTemplate: z2.string(), | ||
cssVarsTemplate: z2.string() | ||
}); | ||
// src/utils/registry/index.ts | ||
import { HttpsProxyAgent } from "https-proxy-agent"; | ||
import fetch from "node-fetch"; | ||
var baseUrl = process.env.COMPONENTS_REGISTRY_URL ?? "https://ui.shadcn.com"; | ||
var agent = process.env.https_proxy ? new HttpsProxyAgent(process.env.https_proxy) : void 0; | ||
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.`); | ||
} | ||
body { | ||
@apply bg-background text-foreground; | ||
font-feature-settings: "rlig" 1, "calt" 1; | ||
} | ||
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.`); | ||
} | ||
}`; | ||
var UTILS = `import { ClassValue, clsx } from "clsx" | ||
} | ||
async function getRegistryBaseColors() { | ||
return [ | ||
{ | ||
name: "slate", | ||
label: "Slate" | ||
}, | ||
{ | ||
name: "gray", | ||
label: "Gray" | ||
}, | ||
{ | ||
name: "zinc", | ||
label: "Zinc" | ||
}, | ||
{ | ||
name: "neutral", | ||
label: "Neutral" | ||
}, | ||
{ | ||
name: "stone", | ||
label: "Stone" | ||
} | ||
]; | ||
} | ||
async function getRegistryBaseColor(baseColor) { | ||
try { | ||
const [result] = await fetchRegistry([`colors/${baseColor}.json`]); | ||
return registryBaseColorSchema.parse(result); | ||
} catch (error) { | ||
throw new Error(`Failed to fetch base color 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, index2, self) => self.findIndex((c) => c.name === component.name) === index2 | ||
); | ||
} | ||
async function fetchTree(style, tree) { | ||
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.`); | ||
} | ||
} | ||
async function getItemTargetPath(config, item, override) { | ||
if (override && item.type !== "components:ui") { | ||
return override; | ||
} | ||
const [parent, type] = item.type.split(":"); | ||
if (!(parent in config.resolvedPaths)) { | ||
return null; | ||
} | ||
return path3.join( | ||
config.resolvedPaths[parent], | ||
type | ||
); | ||
} | ||
async function fetchRegistry(paths) { | ||
try { | ||
const results = await Promise.all( | ||
paths.map(async (path9) => { | ||
const response = await fetch(`${baseUrl}/registry/${path9}`, { | ||
agent | ||
}); | ||
return await response.json(); | ||
}) | ||
); | ||
return results; | ||
} catch (error) { | ||
console.log(error); | ||
throw new Error(`Failed to fetch registry from ${baseUrl}.`); | ||
} | ||
} | ||
// src/utils/transformers/index.ts | ||
import { promises as fs2 } from "fs"; | ||
import { tmpdir } from "os"; | ||
import path4 from "path"; | ||
// src/utils/transformers/transform-css-vars.ts | ||
import { SyntaxKind } from "ts-morph"; | ||
var transformCssVars = async ({ | ||
sourceFile, | ||
config, | ||
baseColor | ||
}) => { | ||
if (config.tailwind?.cssVariables || !baseColor?.inlineColors) { | ||
return sourceFile; | ||
} | ||
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => { | ||
const value = node.getText(); | ||
if (value) { | ||
const valueWithColorMapping = applyColorMapping( | ||
value.replace(/"/g, ""), | ||
baseColor.inlineColors | ||
); | ||
node.replaceWithText(`"${valueWithColorMapping.trim()}"`); | ||
} | ||
}); | ||
return sourceFile; | ||
}; | ||
function splitClassName(className) { | ||
if (!className.includes("/") && !className.includes(":")) { | ||
return [null, className, null]; | ||
} | ||
const parts = []; | ||
let [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; | ||
} | ||
var PREFIXES = ["bg-", "text-", "border-", "ring-offset-", "ring-"]; | ||
function applyColorMapping(input, mapping) { | ||
if (input.includes(" border ")) { | ||
input = input.replace(" border ", " border border-border "); | ||
} | ||
const classNames = input.split(" "); | ||
const lightMode = []; | ||
const darkMode = []; | ||
for (let className of classNames) { | ||
const [variant, value, modifier] = splitClassName(className); | ||
const prefix = PREFIXES.find((prefix2) => value?.startsWith(prefix2)); | ||
if (!prefix) { | ||
if (!lightMode.includes(className)) { | ||
lightMode.push(className); | ||
} | ||
continue; | ||
} | ||
const needle = value?.replace(prefix, ""); | ||
if (needle && needle in mapping.light) { | ||
lightMode.push( | ||
[variant, `${prefix}${mapping.light[needle]}`, modifier].filter(Boolean).join(":") | ||
); | ||
darkMode.push( | ||
["dark", variant, `${prefix}${mapping.dark[needle]}`, modifier].filter(Boolean).join(":") | ||
); | ||
continue; | ||
} | ||
if (!lightMode.includes(className)) { | ||
lightMode.push(className); | ||
} | ||
} | ||
return lightMode.join(" ") + " " + darkMode.join(" ").trim(); | ||
} | ||
// 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\/[^/]+/, | ||
config.aliases.components | ||
) | ||
); | ||
} | ||
if (moduleSpecifier == "@/lib/utils") { | ||
const namedImports = importDeclaration.getNamedImports(); | ||
const cnImport = namedImports.find((i) => i.getName() === "cn"); | ||
if (cnImport) { | ||
importDeclaration.setModuleSpecifier( | ||
moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils) | ||
); | ||
} | ||
} | ||
} | ||
return sourceFile; | ||
}; | ||
// src/utils/transformers/transform-rsc.ts | ||
import { SyntaxKind as SyntaxKind2 } from "ts-morph"; | ||
var transformRsc = async ({ sourceFile, config }) => { | ||
if (config.rsc) { | ||
return sourceFile; | ||
} | ||
const first = sourceFile.getFirstChildByKind(SyntaxKind2.ExpressionStatement); | ||
if (first?.getText() === `"use client"`) { | ||
first.remove(); | ||
} | ||
return sourceFile; | ||
}; | ||
// src/utils/transformers/index.ts | ||
import { Project, ScriptKind as ScriptKind2 } from "ts-morph"; | ||
var transformers = [ | ||
transformImport, | ||
transformRsc, | ||
transformCssVars | ||
]; | ||
var project = new Project({ | ||
compilerOptions: {} | ||
}); | ||
async function createTempSourceFile(filename) { | ||
const dir = await fs2.mkdtemp(path4.join(tmpdir(), "shadcn-")); | ||
return path4.join(dir, filename); | ||
} | ||
async function transform(opts) { | ||
const tempFile = await createTempSourceFile(opts.filename); | ||
const sourceFile = project.createSourceFile(tempFile, opts.raw, { | ||
scriptKind: ScriptKind2.TSX | ||
}); | ||
for (const transformer of transformers) { | ||
transformer({ sourceFile, ...opts }); | ||
} | ||
return sourceFile.getFullText(); | ||
} | ||
// src/commands/add.ts | ||
import chalk2 from "chalk"; | ||
import { Command } from "commander"; | ||
import { execa } from "execa"; | ||
import ora from "ora"; | ||
import prompts from "prompts"; | ||
import * as z3 from "zod"; | ||
var addOptionsSchema = z3.object({ | ||
components: z3.array(z3.string()).optional(), | ||
yes: z3.boolean(), | ||
overwrite: z3.boolean(), | ||
cwd: z3.string(), | ||
path: z3.string().optional() | ||
}); | ||
var add = new Command().name("add").description("add a component to your project").argument("[components...]", "the components to add").option("-y, --yes", "skip confirmation prompt.", false).option("-o, --overwrite", "overwrite existing files.", false).option( | ||
"-c, --cwd <cwd>", | ||
"the working directory. defaults to the current directory.", | ||
process.cwd() | ||
).option("-p, --path <path>", "the path to add the component to.").action(async (components, opts) => { | ||
try { | ||
const options = addOptionsSchema.parse({ | ||
components, | ||
...opts | ||
}); | ||
const cwd = path5.resolve(options.cwd); | ||
if (!existsSync(cwd)) { | ||
logger.error(`The path ${cwd} does not exist. Please try again.`); | ||
process.exit(1); | ||
} | ||
const config = await getConfig(cwd); | ||
if (!config) { | ||
logger.warn( | ||
`Configuration is missing. Please run ${chalk2.green( | ||
`init` | ||
)} to create a components.json file.` | ||
); | ||
process.exit(1); | ||
} | ||
const registryIndex = await getRegistryIndex(); | ||
let selectedComponents = options.components; | ||
if (!options.components?.length) { | ||
const { components: components2 } = await prompts({ | ||
type: "multiselect", | ||
name: "components", | ||
message: "Which components would you like to add?", | ||
hint: "Space to select. A to toggle all. Enter to submit.", | ||
instructions: false, | ||
choices: registryIndex.map((entry) => ({ | ||
title: entry.name, | ||
value: entry.name | ||
})) | ||
}); | ||
selectedComponents = components2; | ||
} | ||
if (!selectedComponents?.length) { | ||
logger.warn("No components selected. Exiting."); | ||
process.exit(0); | ||
} | ||
const tree = await resolveTree(registryIndex, selectedComponents); | ||
const payload = await fetchTree(config.style, tree); | ||
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); | ||
if (!payload.length) { | ||
logger.warn("Selected components not found. Exiting."); | ||
process.exit(0); | ||
} | ||
if (!options.yes) { | ||
const { proceed } = await prompts({ | ||
type: "confirm", | ||
name: "proceed", | ||
message: `Ready to install components and dependencies. Proceed?`, | ||
initial: true | ||
}); | ||
if (!proceed) { | ||
process.exit(0); | ||
} | ||
} | ||
const spinner = ora(`Installing components...`).start(); | ||
for (const item of payload) { | ||
spinner.text = `Installing ${item.name}...`; | ||
const targetDir = await getItemTargetPath( | ||
config, | ||
item, | ||
options.path ? path5.resolve(cwd, options.path) : void 0 | ||
); | ||
if (!targetDir) { | ||
continue; | ||
} | ||
if (!existsSync(targetDir)) { | ||
await fs3.mkdir(targetDir, { recursive: true }); | ||
} | ||
const existingComponent = item.files.filter( | ||
(file) => existsSync(path5.resolve(targetDir, file.name)) | ||
); | ||
if (existingComponent.length && !options.overwrite) { | ||
if (selectedComponents.includes(item.name)) { | ||
logger.warn( | ||
`Component ${item.name} already exists. Use ${chalk2.green( | ||
"--overwrite" | ||
)} to overwrite.` | ||
); | ||
process.exit(1); | ||
} | ||
continue; | ||
} | ||
for (const file of item.files) { | ||
const filePath = path5.resolve(targetDir, file.name); | ||
const content = await transform({ | ||
filename: file.name, | ||
raw: file.content, | ||
config, | ||
baseColor | ||
}); | ||
await fs3.writeFile(filePath, content); | ||
} | ||
if (item.dependencies?.length) { | ||
const packageManager = await getPackageManager(cwd); | ||
await execa( | ||
packageManager, | ||
[ | ||
packageManager === "npm" ? "install" : "add", | ||
...item.dependencies | ||
], | ||
{ | ||
cwd | ||
} | ||
); | ||
} | ||
} | ||
spinner.succeed(`Done.`); | ||
} catch (error) { | ||
handleError(error); | ||
} | ||
}); | ||
// src/commands/diff.ts | ||
import { existsSync as existsSync2, promises as fs4 } from "fs"; | ||
import path6 from "path"; | ||
import chalk3 from "chalk"; | ||
import { Command as Command2 } from "commander"; | ||
import { diffLines } from "diff"; | ||
import * as z4 from "zod"; | ||
var updateOptionsSchema = z4.object({ | ||
component: z4.string().optional(), | ||
yes: z4.boolean(), | ||
cwd: z4.string(), | ||
path: z4.string().optional() | ||
}); | ||
var diff = new Command2().name("diff").description("check for updates against the registry").argument("[component]", "the component name").option("-y, --yes", "skip confirmation prompt.", false).option( | ||
"-c, --cwd <cwd>", | ||
"the working directory. defaults to the current directory.", | ||
process.cwd() | ||
).action(async (name, opts) => { | ||
try { | ||
const options = updateOptionsSchema.parse({ | ||
component: name, | ||
...opts | ||
}); | ||
const cwd = path6.resolve(options.cwd); | ||
if (!existsSync2(cwd)) { | ||
logger.error(`The path ${cwd} does not exist. Please try again.`); | ||
process.exit(1); | ||
} | ||
const config = await getConfig(cwd); | ||
if (!config) { | ||
logger.warn( | ||
`Configuration is missing. Please run ${chalk3.green( | ||
`init` | ||
)} to create a components.json file.` | ||
); | ||
process.exit(1); | ||
} | ||
const registryIndex = await getRegistryIndex(); | ||
if (!options.component) { | ||
const targetDir = config.resolvedPaths.components; | ||
const projectComponents = registryIndex.filter((item) => { | ||
for (const file of item.files) { | ||
const filePath = path6.resolve(targetDir, file); | ||
if (existsSync2(filePath)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
const componentsWithUpdates = []; | ||
for (const component2 of projectComponents) { | ||
const changes2 = await diffComponent(component2, config); | ||
if (changes2.length) { | ||
componentsWithUpdates.push({ | ||
name: component2.name, | ||
changes: changes2 | ||
}); | ||
} | ||
} | ||
if (!componentsWithUpdates.length) { | ||
logger.info("No updates found."); | ||
process.exit(0); | ||
} | ||
logger.info("The following components have updates available:"); | ||
for (const component2 of componentsWithUpdates) { | ||
logger.info(`- ${component2.name}`); | ||
for (const change of component2.changes) { | ||
logger.info(` - ${change.filePath}`); | ||
} | ||
} | ||
logger.break(); | ||
logger.info( | ||
`Run ${chalk3.green(`diff <component>`)} to see the changes.` | ||
); | ||
process.exit(0); | ||
} | ||
const component = registryIndex.find( | ||
(item) => item.name === options.component | ||
); | ||
if (!component) { | ||
logger.error( | ||
`The component ${chalk3.green(options.component)} does not exist.` | ||
); | ||
process.exit(1); | ||
} | ||
const changes = await diffComponent(component, config); | ||
if (!changes.length) { | ||
logger.info(`No updates found for ${options.component}.`); | ||
process.exit(0); | ||
} | ||
for (const change of changes) { | ||
logger.info(`- ${change.filePath}`); | ||
await printDiff(change.patch); | ||
logger.info(""); | ||
} | ||
} catch (error) { | ||
handleError(error); | ||
} | ||
}); | ||
async function diffComponent(component, config) { | ||
const payload = await fetchTree(config.style, [component]); | ||
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); | ||
const changes = []; | ||
for (const item of payload) { | ||
const targetDir = await getItemTargetPath(config, item); | ||
if (!targetDir) { | ||
continue; | ||
} | ||
for (const file of item.files) { | ||
const filePath = path6.resolve(targetDir, file.name); | ||
if (!existsSync2(filePath)) { | ||
continue; | ||
} | ||
const fileContent = await fs4.readFile(filePath, "utf8"); | ||
const registryContent = await transform({ | ||
filename: file.name, | ||
raw: file.content, | ||
config, | ||
baseColor | ||
}); | ||
const patch = diffLines(registryContent, fileContent); | ||
if (patch.length > 1) { | ||
changes.push({ | ||
file: file.name, | ||
filePath, | ||
patch | ||
}); | ||
} | ||
} | ||
} | ||
return changes; | ||
} | ||
async function printDiff(diff2) { | ||
diff2.forEach((part) => { | ||
if (part) { | ||
if (part.added) { | ||
return process.stdout.write(chalk3.green(part.value)); | ||
} | ||
if (part.removed) { | ||
return process.stdout.write(chalk3.red(part.value)); | ||
} | ||
return process.stdout.write(part.value); | ||
} | ||
}); | ||
} | ||
// src/commands/init.ts | ||
import { existsSync as existsSync3, promises as fs5 } from "fs"; | ||
import path7 from "path"; | ||
// src/utils/templates.ts | ||
var UTILS = `import { type ClassValue, clsx } from "clsx" | ||
import { twMerge } from "tailwind-merge" | ||
@@ -216,2 +755,3 @@ | ||
'./app/**/*.{ts,tsx}', | ||
'./src/**/*.{ts,tsx}', | ||
], | ||
@@ -227,2 +767,38 @@ theme: { | ||
extend: { | ||
keyframes: { | ||
"accordion-down": { | ||
from: { height: 0 }, | ||
to: { height: "var(--radix-accordion-content-height)" }, | ||
}, | ||
"accordion-up": { | ||
from: { height: "var(--radix-accordion-content-height)" }, | ||
to: { height: 0 }, | ||
}, | ||
}, | ||
animation: { | ||
"accordion-down": "accordion-down 0.2s ease-out", | ||
"accordion-up": "accordion-up 0.2s ease-out", | ||
}, | ||
}, | ||
}, | ||
plugins: [require("tailwindcss-animate")], | ||
}`; | ||
var TAILWIND_CONFIG_WITH_VARIABLES = `/** @type {import('tailwindcss').Config} */ | ||
module.exports = { | ||
darkMode: ["class"], | ||
content: [ | ||
'./pages/**/*.{ts,tsx}', | ||
'./components/**/*.{ts,tsx}', | ||
'./app/**/*.{ts,tsx}', | ||
'./src/**/*.{ts,tsx}', | ||
], | ||
theme: { | ||
container: { | ||
center: true, | ||
padding: "2rem", | ||
screens: { | ||
"2xl": "1400px", | ||
}, | ||
}, | ||
extend: { | ||
colors: { | ||
@@ -287,5 +863,9 @@ border: "hsl(var(--border))", | ||
// src/index.ts | ||
process.on("SIGINT", () => process.exit(0)); | ||
process.on("SIGTERM", () => process.exit(0)); | ||
// src/commands/init.ts | ||
import chalk4 from "chalk"; | ||
import { Command as Command3 } from "commander"; | ||
import { execa as execa2 } from "execa"; | ||
import ora2 from "ora"; | ||
import prompts2 from "prompts"; | ||
import * as z5 from "zod"; | ||
var PROJECT_DEPENDENCIES = [ | ||
@@ -295,146 +875,204 @@ "tailwindcss-animate", | ||
"clsx", | ||
"tailwind-merge", | ||
"lucide-react" | ||
"tailwind-merge" | ||
]; | ||
async function main() { | ||
const packageInfo = await getPackageInfo(); | ||
const projectInfo = await getProjectInfo(); | ||
const packageManager = getPackageManager(); | ||
const program = new Command().name("shadcn-ui").description("Add shadcn-ui components to your project").version( | ||
packageInfo.version || "1.0.0", | ||
"-v, --version", | ||
"display the version number" | ||
); | ||
program.command("init").description("Configure your Next.js project.").option("-y, --yes", "Skip confirmation prompt.").action(async (options) => { | ||
logger.warn( | ||
"This command assumes a Next.js project with TypeScript and Tailwind CSS." | ||
var initOptionsSchema = z5.object({ | ||
cwd: z5.string(), | ||
yes: z5.boolean() | ||
}); | ||
var init = new Command3().name("init").description("initialize your project and install dependencies").option("-y, --yes", "skip confirmation prompt.", false).option( | ||
"-c, --cwd <cwd>", | ||
"the working directory. defaults to the current directory.", | ||
process.cwd() | ||
).action(async (opts) => { | ||
try { | ||
const options = initOptionsSchema.parse(opts); | ||
const cwd = path7.resolve(options.cwd); | ||
if (!existsSync3(cwd)) { | ||
logger.error(`The path ${cwd} does not exist. Please try again.`); | ||
process.exit(1); | ||
} | ||
const existingConfig = await getConfig(cwd); | ||
const config = await promptForConfig(cwd, existingConfig, options.yes); | ||
await runInit(cwd, config); | ||
logger.info(""); | ||
logger.info( | ||
`${chalk4.green("Success!")} Project initialization completed.` | ||
); | ||
logger.warn( | ||
"If you don't have these, follow the manual steps at https://ui.shadcn.com/docs/installation." | ||
); | ||
logger.warn(""); | ||
if (!options.yes) { | ||
const { proceed } = await prompts({ | ||
type: "confirm", | ||
name: "proceed", | ||
message: "Running this command will install dependencies and overwrite your existing tailwind.config.js. Proceed?", | ||
initial: true | ||
}); | ||
if (!proceed) { | ||
process.exit(0); | ||
} | ||
logger.info(""); | ||
} catch (error) { | ||
handleError(error); | ||
} | ||
}); | ||
async function promptForConfig(cwd, defaultConfig = null, skip = false) { | ||
const highlight = (text) => chalk4.cyan(text); | ||
const styles = await getRegistryStyles(); | ||
const baseColors = await getRegistryBaseColors(); | ||
const options = await prompts2([ | ||
{ | ||
type: "select", | ||
name: "style", | ||
message: `Which ${highlight("style")} would you like to use?`, | ||
choices: styles.map((style) => ({ | ||
title: style.label, | ||
value: style.name | ||
})) | ||
}, | ||
{ | ||
type: "select", | ||
name: "tailwindBaseColor", | ||
message: `Which color would you like to use as ${highlight( | ||
"base color" | ||
)}?`, | ||
choices: baseColors.map((color) => ({ | ||
title: color.label, | ||
value: color.name | ||
})) | ||
}, | ||
{ | ||
type: "text", | ||
name: "tailwindCss", | ||
message: `Where is your ${highlight("global CSS")} file?`, | ||
initial: defaultConfig?.tailwind.css ?? DEFAULT_TAILWIND_CSS | ||
}, | ||
{ | ||
type: "toggle", | ||
name: "tailwindCssVariables", | ||
message: `Do you want to use ${highlight("CSS variables")} for colors?`, | ||
initial: defaultConfig?.tailwind.cssVariables ?? true, | ||
active: "yes", | ||
inactive: "no" | ||
}, | ||
{ | ||
type: "text", | ||
name: "tailwindConfig", | ||
message: `Where is your ${highlight("tailwind.config.js")} located?`, | ||
initial: defaultConfig?.tailwind.config ?? DEFAULT_TAILWIND_CONFIG | ||
}, | ||
{ | ||
type: "text", | ||
name: "components", | ||
message: `Configure the import alias for ${highlight("components")}:`, | ||
initial: defaultConfig?.aliases["components"] ?? DEFAULT_COMPONENTS | ||
}, | ||
{ | ||
type: "text", | ||
name: "utils", | ||
message: `Configure the import alias for ${highlight("utils")}:`, | ||
initial: defaultConfig?.aliases["utils"] ?? DEFAULT_UTILS | ||
}, | ||
{ | ||
type: "toggle", | ||
name: "rsc", | ||
message: `Are you using ${highlight("React Server Components")}?`, | ||
initial: defaultConfig?.rsc ?? true, | ||
active: "yes", | ||
inactive: "no" | ||
} | ||
const dependenciesSpinner = ora(`Installing dependencies...`).start(); | ||
await execa(packageManager, [ | ||
packageManager === "npm" ? "install" : "add", | ||
...PROJECT_DEPENDENCIES | ||
]); | ||
dependenciesSpinner.succeed(); | ||
if (!projectInfo?.appDir) { | ||
const stylesDir = projectInfo?.srcDir ? "./src/styles" : "./styles"; | ||
if (!existsSync2(path3.resolve(stylesDir))) { | ||
await fs3.mkdir(path3.resolve(stylesDir), { recursive: true }); | ||
} | ||
]); | ||
const config = rawConfigSchema.parse({ | ||
$schema: "https://ui.shadcn.com/schema.json", | ||
style: options.style, | ||
tailwind: { | ||
config: options.tailwindConfig, | ||
css: options.tailwindCss, | ||
baseColor: options.tailwindBaseColor, | ||
cssVariables: options.tailwindCssVariables | ||
}, | ||
rsc: options.rsc, | ||
aliases: { | ||
utils: options.utils, | ||
components: options.components | ||
} | ||
let stylesDestination = projectInfo?.srcDir ? "./src/styles/globals.css" : "./styles/globals.css"; | ||
if (projectInfo?.appDir) { | ||
stylesDestination = projectInfo?.srcDir ? "./src/app/globals.css" : "./app/globals.css"; | ||
} | ||
const stylesSpinner = ora(`Adding styles with CSS variables...`).start(); | ||
await fs3.writeFile(stylesDestination, STYLES, "utf8"); | ||
stylesSpinner.succeed(); | ||
const libDir = projectInfo?.srcDir ? "./src/lib" : "./lib"; | ||
if (!existsSync2(path3.resolve(libDir))) { | ||
await fs3.mkdir(path3.resolve(libDir), { recursive: true }); | ||
} | ||
const utilsDestination = projectInfo?.srcDir ? "./src/lib/utils.ts" : "./lib/utils.ts"; | ||
const utilsSpinner = ora(`Adding utils...`).start(); | ||
await fs3.writeFile(utilsDestination, UTILS, "utf8"); | ||
utilsSpinner.succeed(); | ||
const tailwindDestination = "./tailwind.config.js"; | ||
const tailwindSpinner = ora(`Updating tailwind.config.js...`).start(); | ||
await fs3.writeFile(tailwindDestination, TAILWIND_CONFIG, "utf8"); | ||
tailwindSpinner.succeed(); | ||
}); | ||
program.command("add").description("add components to your project").argument("[components...]", "name of components").action(async (components) => { | ||
logger.warn( | ||
"Running the following command will overwrite existing files." | ||
); | ||
logger.warn( | ||
"Make sure you have committed your changes before proceeding." | ||
); | ||
logger.warn(""); | ||
const availableComponents = await getAvailableComponents(); | ||
if (!availableComponents?.length) { | ||
logger.error( | ||
"An error occurred while fetching components. Please try again." | ||
); | ||
if (!skip) { | ||
const { proceed } = await prompts2({ | ||
type: "confirm", | ||
name: "proceed", | ||
message: `Write configuration to ${highlight( | ||
"components.json" | ||
)}. Proceed?`, | ||
initial: true | ||
}); | ||
if (!proceed) { | ||
process.exit(0); | ||
} | ||
let selectedComponents = availableComponents.filter( | ||
(component) => components.includes(component.component) | ||
); | ||
if (!selectedComponents?.length) { | ||
selectedComponents = await promptForComponents(availableComponents); | ||
} | ||
logger.info(""); | ||
const spinner = ora2(`Writing components.json...`).start(); | ||
const targetPath = path7.resolve(cwd, "components.json"); | ||
await fs5.writeFile(targetPath, JSON.stringify(config, null, 2), "utf8"); | ||
spinner.succeed(); | ||
return await resolveConfigPaths(cwd, config); | ||
} | ||
async function runInit(cwd, config) { | ||
const spinner = ora2(`Initializing project...`)?.start(); | ||
for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) { | ||
let dirname = path7.extname(resolvedPath) ? path7.dirname(resolvedPath) : resolvedPath; | ||
if (key === "utils" && resolvedPath.endsWith("/utils")) { | ||
dirname = dirname.replace(/\/utils$/, ""); | ||
} | ||
const dir = await promptForDestinationDir(); | ||
if (!selectedComponents?.length) { | ||
logger.warn("No components selected. Nothing to install."); | ||
process.exit(0); | ||
if (!existsSync3(dirname)) { | ||
await fs5.mkdir(dirname, { recursive: true }); | ||
} | ||
const destinationDir = path3.resolve(dir); | ||
if (!existsSync2(destinationDir)) { | ||
const spinner = ora(`Creating ${dir}...`).start(); | ||
await fs3.mkdir(destinationDir, { recursive: true }); | ||
spinner.succeed(); | ||
} | ||
logger.success( | ||
`Installing ${selectedComponents.length} component(s) and dependencies...` | ||
} | ||
await fs5.writeFile( | ||
config.resolvedPaths.tailwindConfig, | ||
config.tailwind.cssVariables ? TAILWIND_CONFIG_WITH_VARIABLES : TAILWIND_CONFIG, | ||
"utf8" | ||
); | ||
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor); | ||
if (baseColor) { | ||
await fs5.writeFile( | ||
config.resolvedPaths.tailwindCss, | ||
config.tailwind.cssVariables ? baseColor.cssVarsTemplate : baseColor.inlineColorsTemplate, | ||
"utf8" | ||
); | ||
for (const component of selectedComponents) { | ||
const componentSpinner = ora(`${component.name}...`).start(); | ||
for (const file of component.files) { | ||
if (projectInfo?.alias) { | ||
file.content = file.content.replace(/@\//g, projectInfo.alias); | ||
} | ||
const filePath = path3.resolve(dir, file.name); | ||
await fs3.writeFile(filePath, file.content); | ||
} | ||
if (component.dependencies?.length) { | ||
await execa(packageManager, [ | ||
packageManager === "npm" ? "install" : "add", | ||
...component.dependencies | ||
]); | ||
} | ||
componentSpinner.succeed(component.name); | ||
} | ||
await fs5.writeFile( | ||
`${config.resolvedPaths.utils}.ts`, | ||
UTILS, | ||
"utf8" | ||
); | ||
spinner?.succeed(); | ||
const dependenciesSpinner = ora2(`Installing dependencies...`)?.start(); | ||
const packageManager = await getPackageManager(cwd); | ||
const deps = [ | ||
...PROJECT_DEPENDENCIES, | ||
config.style === "new-york" ? "@radix-ui/react-icons" : "lucide-react" | ||
]; | ||
await execa2( | ||
packageManager, | ||
[packageManager === "npm" ? "install" : "add", ...deps], | ||
{ | ||
cwd | ||
} | ||
}); | ||
program.parse(); | ||
); | ||
dependenciesSpinner?.succeed(); | ||
} | ||
async function promptForComponents(components) { | ||
const { components: selectedComponents } = await prompts({ | ||
type: "autocompleteMultiselect", | ||
name: "components", | ||
message: "Which component(s) would you like to add?", | ||
hint: "Space to select. A to select all. I to invert selection.", | ||
instructions: false, | ||
choices: components.map((component) => ({ | ||
title: component.name, | ||
value: component | ||
})) | ||
}); | ||
return selectedComponents; | ||
// src/index.ts | ||
import { Command as Command4 } from "commander"; | ||
// src/utils/get-package-info.ts | ||
import path8 from "path"; | ||
import fs6 from "fs-extra"; | ||
function getPackageInfo() { | ||
const packageJsonPath = path8.join("package.json"); | ||
return fs6.readJSONSync(packageJsonPath); | ||
} | ||
async function promptForDestinationDir() { | ||
const { dir } = await prompts([ | ||
{ | ||
type: "text", | ||
name: "dir", | ||
message: "Where would you like to install the component(s)?", | ||
initial: "./components/ui" | ||
} | ||
]); | ||
return dir; | ||
// src/index.ts | ||
process.on("SIGINT", () => process.exit(0)); | ||
process.on("SIGTERM", () => process.exit(0)); | ||
async function main() { | ||
const packageInfo = await getPackageInfo(); | ||
const program = new Command4().name("shadcn-ui").description("add components and dependencies to your project").version( | ||
packageInfo.version || "1.0.0", | ||
"-v, --version", | ||
"display the version number" | ||
); | ||
program.addCommand(init).addCommand(add).addCommand(diff); | ||
program.parse(); | ||
} | ||
main(); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "shadcn-ui", | ||
"version": "0.0.0-beta.ec41f85", | ||
"version": "0.0.0-beta.fa20368", | ||
"description": "Add components to your apps.", | ||
@@ -34,10 +34,16 @@ "publishConfig": { | ||
"commander": "^10.0.0", | ||
"cosmiconfig": "^8.1.3", | ||
"diff": "^5.1.0", | ||
"execa": "^7.0.0", | ||
"fs-extra": "^11.1.0", | ||
"https-proxy-agent": "^6.2.0", | ||
"node-fetch": "^3.3.0", | ||
"ora": "^6.1.2", | ||
"prompts": "^2.4.2", | ||
"ts-morph": "^18.0.0", | ||
"tsconfig-paths": "^4.2.0", | ||
"zod": "^3.20.2" | ||
}, | ||
"devDependencies": { | ||
"@types/diff": "^5.0.3", | ||
"@types/fs-extra": "^11.0.1", | ||
@@ -55,3 +61,3 @@ "@types/prompts": "^2.4.2", | ||
"clean": "rimraf dist && rimraf components", | ||
"start:dev": "cross-env COMPONENTS_BASE_URL=http://localhost:3001 node dist/index.js", | ||
"start:dev": "cross-env COMPONENTS_REGISTRY_URL=http://localhost:3001 node dist/index.js", | ||
"start": "node dist/index.js", | ||
@@ -63,4 +69,5 @@ "format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache", | ||
"pub:next": "pnpm build && pnpm publish --no-git-checks --access public --tag next", | ||
"pub:release": "pnpm build && pnpm publish --access public" | ||
"pub:release": "pnpm build && pnpm publish --access public", | ||
"test": "vitest run" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
97225
1048
13
7
10
3
+ Addedcosmiconfig@^8.1.3
+ Addeddiff@^5.1.0
+ Addedhttps-proxy-agent@^6.2.0
+ Addedts-morph@^18.0.0
+ Addedtsconfig-paths@^4.2.0
+ Added@babel/code-frame@7.26.2(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@nodelib/fs.scandir@2.1.5(transitive)
+ Added@nodelib/fs.stat@2.0.5(transitive)
+ Added@nodelib/fs.walk@1.2.8(transitive)
+ Added@ts-morph/common@0.19.0(transitive)
+ Addedagent-base@7.1.3(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedcallsites@3.1.0(transitive)
+ Addedcode-block-writer@12.0.0(transitive)
+ Addedcosmiconfig@8.3.6(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addeddiff@5.2.0(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedfast-glob@3.3.3(transitive)
+ Addedfastq@1.19.0(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedhttps-proxy-agent@6.2.1(transitive)
+ Addedimport-fresh@3.3.0(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedjs-yaml@4.1.0(transitive)
+ Addedjson-parse-even-better-errors@2.3.1(transitive)
+ Addedjson5@2.2.3(transitive)
+ Addedlines-and-columns@1.2.4(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedminimatch@7.4.6(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedmkdirp@2.1.6(transitive)
+ Addedms@2.1.3(transitive)
+ Addedparent-module@1.0.1(transitive)
+ Addedparse-json@5.2.0(transitive)
+ Addedpath-browserify@1.0.1(transitive)
+ Addedpath-type@4.0.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedqueue-microtask@1.2.3(transitive)
+ Addedresolve-from@4.0.0(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedrun-parallel@1.2.0(transitive)
+ Addedstrip-bom@3.0.0(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedts-morph@18.0.0(transitive)
+ Addedtsconfig-paths@4.2.0(transitive)