Skip to content
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
160 changes: 103 additions & 57 deletions packages/cli/src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import * as v from "valibot";
import { detectPM } from "../utils/auto-detect.js";
import { ConfigError, error, handleError } from "../utils/errors.js";
import * as cliConfig from "../utils/get-config.js";
import { getEnvProxy, getEnvRegistry } from "../utils/get-env-proxy.js";
import { getEnvProxy } from "../utils/get-env-proxy.js";
import { cancel, intro, prettifyList } from "../utils/prompt-helpers.js";
import * as p from "../utils/prompts.js";
import * as registry from "../utils/registry/index.js";
import { transformContent } from "../utils/transformers.js";
import { resolveCommand } from "package-manager-detector/commands";
import { checkPreconditions } from "../utils/preconditions.js";
import { isUrl, urlSplitLastPathSegment } from "../utils/utils.js";
import { RegistryWithContent } from "../utils/registry/schema.js";

const highlight = (...args: unknown[]) => color.bold.cyan(...args);

Expand All @@ -34,7 +36,7 @@ type AddOptions = v.InferOutput<typeof addOptionsSchema>;
export const add = new Command()
.command("add")
.description("add components to your project")
.argument("[components...]", "name of components")
.argument("[components...]", "the components to add or a url to the component")
.option("-c, --cwd <cwd>", "the working directory", process.cwd())
.option("--no-deps", "skips adding & installing package dependencies")
.option("-a, --all", "install all components to your project", false)
Expand Down Expand Up @@ -63,10 +65,6 @@ export const add = new Command()
);
}

const registryEnv = getEnvRegistry();

registry.setRegistry(registryEnv ? registryEnv : config.registry);

checkPreconditions(cwd);

await runAdd(cwd, config, options);
Expand All @@ -83,22 +81,39 @@ async function runAdd(cwd: string, config: cliConfig.Config, options: AddOptions
p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
}

const uiRegistryIndex = await registry.getRegistryIndex();
const registryUrl = registry.getRegistryUrl(config);

let selectedComponents = new Set(
options.all ? uiRegistryIndex.map(({ name }) => name) : options.components
);
// get the base urls for any of the remote registries
const remoteRegistries =
options.components
?.filter((c) => isUrl(c))
.map((c) => urlSplitLastPathSegment(new URL(c))[0]) ?? [];

const onlyRemoteComponents =
options.components?.length && remoteRegistries.length === options.components.length;

const registryDepMap = new Map<string, string[]>();
for (const item of uiRegistryIndex) {
registryDepMap.set(item.name, item.registryDependencies);
// if the components aren't just remote components or we want to include all components
// then we add then shadcn-svelte registry
if (!onlyRemoteComponents || options.all) {
remoteRegistries.push(registryUrl);
}

if (selectedComponents === undefined || selectedComponents.size === 0) {
// maps the registry baseUrl to the index of the registry
const registryIndexes = await registry.getRegistryIndexes(remoteRegistries);

// The index of the shadcn-svelte registry
const ogIndex = registryIndexes.get(registryUrl);

let selectedComponents = new Set(
options.all ? ogIndex?.map(({ name }) => name) : options.components
);

// if the user hasn't passed any components prompt them to select components
if (ogIndex && selectedComponents.size === 0) {
const components = await p.multiselect({
message: `Which ${highlight("components")} would you like to install?`,
maxItems: 10,
options: uiRegistryIndex.map(({ name, dependencies, registryDependencies }) => {
options: ogIndex.map(({ name, dependencies, registryDependencies }) => {
const deps = [...(options.deps ? dependencies : []), ...registryDependencies];
return {
label: name,
Expand All @@ -115,55 +130,86 @@ async function runAdd(cwd: string, config: cliConfig.Config, options: AddOptions
p.log.step(`Components to install:\n${color.gray(prettyList)}`);
}

/**
* Adds all the selected items and their registry dependencies to the `selectedComponents`
* set so that they can be individually overwritten.
*/
// load registry dependencies
const registryDepMap = new Map<string, Map<string, string[]>>();
for (const [url, index] of registryIndexes) {
const registryDeps = new Map<string, string[]>();

for (const item of index) {
registryDeps.set(item.name, item.registryDependencies);
}

registryDepMap.set(url, registryDeps);
}

const registryComponents = new Map<string, Set<string>>();

// theoretically this should all run without fetch calls if the index includes all the files
// in other words no need to parallelize
for (const name of selectedComponents) {
if (registryDepMap.has(name)) {
/**
* We will have all the `ui` registry dependencies in the `registryDepMap`,
* so if the `name` is a `ui` component, we go ahead and add its dependencies
* to the `selectedComponents` set.
*/
const regDeps: string[] = registryDepMap.get(name) ?? [];
for (const dep of regDeps) {
selectedComponents.add(dep);
}
} else {
/**
* For blocks, hooks, etc. we need to resolve the tree to get their dependencies
* and add them to the `selectedComponents` set.
*/
const tree = await registry.resolveTree({
index: uiRegistryIndex,
names: [name],
includeRegDeps: true,
config,
});
for (const item of tree) {
for (const dep of item.registryDependencies) {
// we first add the reg dep to the selected components
selectedComponents.add(dep);
const depRegDeps: string[] = registryDepMap.get(dep) ?? [];
// we then add each of that dep's deps to the `selectedComponents` set
for (const depRegDep of depRegDeps) {
selectedComponents.add(depRegDep);
}
let componentName = name;
let componentRegistry = registryUrl;

// handle remote components
if (isUrl(name)) {
// name should come in like `https://example.com/r/avatar.json`
// we split it to get the base url and avatar.json
// eslint-disable-next-line prefer-const -- wrong
let [baseUrl, item] = urlSplitLastPathSegment(new URL(name));

componentRegistry = baseUrl;
componentName = item.replace(".json", "");
}

// we already defined this so we know it exists
const index = registryIndexes.get(componentRegistry)!;

const tree = await registry.resolveTree({
baseUrl: componentRegistry,
names: [componentName],
config,
index,
includeRegDeps: true,
});

const installedComponents = registryComponents.get(componentRegistry) ?? new Set();

// add the current component
installedComponents.add(componentName);

for (const item of tree) {
for (const dep of item.registryDependencies) {
// we first add the reg dep to the selected components
installedComponents.add(dep);
const depRegDeps: string[] = registryDepMap.get(componentRegistry)!.get(dep) ?? [];
// we then add each of that dep's deps to the `selectedComponents` set
for (const depRegDep of depRegDeps) {
installedComponents.add(depRegDep);
}
}
}

registryComponents.set(componentRegistry, installedComponents);
}

const tree = await registry.resolveTree({
index: uiRegistryIndex,
names: Array.from(selectedComponents),
includeRegDeps: false,
config,
});
const fetchContent = async (url: string): Promise<RegistryWithContent> => {
const index = registryIndexes.get(url)!;
const components = registryComponents.get(url)!;

const payload = await registry.fetchTree(tree);
// const baseColor = await getRegistryBaseColor(config.tailwind.baseColor);
const tree = await registry.resolveTree({
baseUrl: url,
index: index,
names: Array.from(components),
includeRegDeps: false,
config,
});

return await registry.fetchTree(url, tree);
};

const payload = (await Promise.all(remoteRegistries.map((url) => fetchContent(url)))).flatMap(
(p) => p
);

if (payload.length === 0) cancel("Selected components not found.");

Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import * as templates from "../utils/templates.js";
import { resolveCommand } from "package-manager-detector/commands";
import { SITE_BASE_URL } from "../constants.js";
import { checkPreconditions } from "../utils/preconditions.js";
import { getEnvRegistry } from "../utils/get-env-proxy.js";

const PROJECT_DEPENDENCIES = [
"tailwind-variants",
Expand Down Expand Up @@ -81,10 +80,6 @@ export const init = new Command()
const existingConfig = await cliConfig.getConfig(cwd);
const config = await promptForConfig(cwd, existingConfig, options);

const registryEnv = getEnvRegistry();

registry.setRegistry(registryEnv ? registryEnv : config.registry);

await runInit(cwd, config, options);

p.outro(`${color.green("Success!")} Project initialization completed.`);
Expand Down Expand Up @@ -267,6 +262,8 @@ function validateImportAlias(alias: string, langConfig: DetectLanguageResult) {
}

export async function runInit(cwd: string, config: Config, options: InitOptions) {
const registryUrl = registry.getRegistryUrl(config);

const tasks: p.Task[] = [];

// Write to file.
Expand Down Expand Up @@ -303,7 +300,10 @@ export async function runInit(cwd: string, config: Config, options: InitOptions)
}

// Write css file.
const baseColor = await registry.getRegistryBaseColor(config.tailwind.baseColor);
const baseColor = await registry.getRegistryBaseColor(
registryUrl,
config.tailwind.baseColor
);
if (baseColor) {
await fs.writeFile(
config.resolvedPaths.tailwindCss,
Expand Down
16 changes: 9 additions & 7 deletions packages/cli/src/commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as v from "valibot";
import { detectPM } from "../utils/auto-detect.js";
import { error, handleError } from "../utils/errors.js";
import * as cliConfig from "../utils/get-config.js";
import { getEnvProxy, getEnvRegistry } from "../utils/get-env-proxy.js";
import { getEnvProxy } from "../utils/get-env-proxy.js";
import { cancel, intro, prettifyList } from "../utils/prompt-helpers.js";
import * as p from "../utils/prompts.js";
import * as registry from "../utils/registry/index.js";
Expand Down Expand Up @@ -59,10 +59,6 @@ export const update = new Command()
);
}

const registryEnv = getEnvRegistry();

registry.setRegistry(registryEnv ? registryEnv : config.registry);

checkPreconditions(cwd);

await runUpdate(cwd, config, options);
Expand All @@ -83,8 +79,11 @@ async function runUpdate(cwd: string, config: cliConfig.Config, options: UpdateO
p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
}

const registryUrl = registry.getRegistryUrl(config);

const components = options.components;
const registryIndex = await registry.getRegistryIndex();

const registryIndex = await registry.getRegistryIndex(registryUrl);

const componentDir = path.resolve(config.resolvedPaths.components, "ui");
if (!existsSync(componentDir)) {
Expand Down Expand Up @@ -179,11 +178,14 @@ async function runUpdate(cwd: string, config: cliConfig.Config, options: UpdateO
}

const tree = await registry.resolveTree({
baseUrl: registryUrl,
index: registryIndex,
names: selectedComponents.map((com) => com.name),
config,
});
const payload = (await registry.fetchTree(tree)).sort((a, b) => a.name.localeCompare(b.name));
const payload = (await registry.fetchTree(registryUrl, tree)).sort((a, b) =>
a.name.localeCompare(b.name)
);

const componentsToRemove: Record<string, string[]> = {};
const dependencies = new Set<string>();
Expand Down
6 changes: 0 additions & 6 deletions packages/cli/src/utils/get-env-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,3 @@ export function getEnvProxy(): string | undefined {
env.npm_config_https_proxy
);
}

export function getEnvRegistry(): string | undefined {
const { env } = process;

return env.COMPONENTS_REGISTRY_URL;
}
Loading