Repository: huntabyte/shadcn-svelte (ts)

Issue:
Error parsing json response from https://next.shadcn-svelte.com/registry/index.json
### Describe the bug

I tried to install new shadcn version and I got this error
```sh
[CLI Error]: Error parsing json response from https://next.shadcn-svelte.com/registry/index.json: Error SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
```

If you try to open this url, it will redirect you to main website. Possible fix here is to change url to https://shadcn-svelte.com/registry/index.json in installer.

### Reproduction

Run `pnpm dlx shadcn-svelte@latest init` on a project with shadcn-next installed.

### Logs

```bash
(node:22718) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
(Use `node --trace-deprecation ...` to show where the warning was created)
┌   shadcn-svelte  v1.0.2 
│
◇  Which base color would you like to use?
│  Zinc
│
◇  Where is your global CSS file? (this file will be overwritten)
│  src/app.css
│
◇  Configure the import alias for lib:
│  $lib
│
◇  Configure the import alias for components:
│  $lib/components
│
◇  Configure the import alias for ui:
│  $lib/components/ui
│
◇  Configure the import alias for utils:
│  $lib/utils
│
◇  Configure the import alias for hooks:
│  $lib/hooks
│
◇  Config file components.json created
│
◇  Alias paths validated
│
└  [CLI Error]: Error parsing json response from https://next.shadcn-svelte.com/registry/index.json: Error SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
```

### System Info

```bash
System:
    OS: macOS 15.5
    CPU: (12) arm64 Apple M2 Pro
    Memory: 98.44 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.1.0 - /opt/homebrew/bin/node
    npm: 11.3.0 - /opt/homebrew/bin/npm
    pnpm: 9.15.4 - /opt/homebrew/bin/pnpm
    bun: 1.2.15 - /opt/homebrew/bin/bun
    Watchman: 2025.05.26.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 137.0.7151.103
    Safari: 18.5
  npmPackages:
    @lucide/svelte: ^0.511.0 => 0.511.0 
    @sveltejs/kit: ^2.16.0 => 2.21.4 
    svelte: ^5.0.0 => 5.33.19 
    tailwindcss: ^4.0.0 => 4.1.8
```

### Severity

blocking an upgrade

Pull request:
fix: Update `components.json` registry before running `init`
Fixes #2121 

This PR consolidates the preflight logic used for `init`, `add`, and `update`. `init` now updates the `registry` key as expected before running.

> One side effect of this is that init now updates to the latest schema version before running. I don't necessarily think this is an issue since init should only be used with the latest version of everything anyways but it's something to note

Patch:
diff --git a/.changeset/tasty-steaks-ring.md b/.changeset/tasty-steaks-ring.md
new file mode 100644
index 00000000..b14d2011
--- /dev/null
+++ b/.changeset/tasty-steaks-ring.md
@@ -0,0 +1,5 @@
+---
+"shadcn-svelte": patch
+---
+
+fix: Update `components.json` registry before running `init`
diff --git a/docs/src/lib/registry/ui/sidebar/sidebar-separator.svelte b/docs/src/lib/registry/ui/sidebar/sidebar-separator.svelte
index 9e096af8..a6e0a932 100644
--- a/docs/src/lib/registry/ui/sidebar/sidebar-separator.svelte
+++ b/docs/src/lib/registry/ui/sidebar/sidebar-separator.svelte
@@ -14,6 +14,6 @@
 	bind:ref
 	data-slot="sidebar-separator"
 	data-sidebar="separator"
-	class={cn("bg-sidebar-border mx-2 w-auto", className)}
+	class={cn("bg-sidebar-border", className)}
 	{...restProps}
 />
diff --git a/package.json b/package.json
index 65c66840..1afefcf1 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,10 @@
 	},
 	"pnpm": {
 		"onlyBuiltDependencies": [
-			"esbuild"
+			"@tailwindcss/oxide",
+			"esbuild",
+			"sharp",
+			"workerd"
 		]
 	}
 }
diff --git a/packages/cli/src/commands/add/index.ts b/packages/cli/src/commands/add/index.ts
index 4a6dc9de..afa5806e 100644
--- a/packages/cli/src/commands/add/index.ts
+++ b/packages/cli/src/commands/add/index.ts
@@ -10,10 +10,10 @@ import { getEnvProxy } from "../../utils/get-env-proxy.js";
 import { cancel, intro, prettifyList } from "../../utils/prompt-helpers.js";
 import * as p from "@clack/prompts";
 import * as registry from "../../utils/registry/index.js";
-import { preflightAdd } from "./preflight.js";
 import { addRegistryItems } from "../../utils/add-registry-items.js";
 import { highlight } from "../../utils/utils.js";
 import { installDependencies } from "../../utils/install-deps.js";
+import { checkPreconditions } from "../../utils/preconditions.js";
 
 const addOptionsSchema = z.object({
 	components: z.string().array().optional(),
@@ -48,8 +48,6 @@ export const add = new Command()
 				throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
 			}
 
-			await preflightAdd(cwd);
-
 			const config = await cliConfig.getConfig(cwd);
 			if (!config) {
 				throw new ConfigError(
@@ -57,7 +55,9 @@ export const add = new Command()
 				);
 			}
 
-			await runAdd(cwd, config, options);
+			const updatedConfig = checkPreconditions({ config, cwd });
+
+			await runAdd(cwd, updatedConfig, options);
 
 			p.outro(`${color.green("Success!")} Components added.`);
 		} catch (error) {
diff --git a/packages/cli/src/commands/add/preflight.ts b/packages/cli/src/commands/add/preflight.ts
deleted file mode 100644
index 650acfdf..00000000
--- a/packages/cli/src/commands/add/preflight.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import color from "chalk";
-import * as semver from "semver";
-import { ConfigError, error } from "../../utils/errors.js";
-import * as cliConfig from "../../utils/get-config.js";
-import { SITE_BASE_URL, TW3_SITE_BASE_URL } from "../../constants.js";
-import { highlight } from "../../utils/utils.js";
-import { getDependencyPackageInfo, getProjectPackageInfo } from "../../utils/get-package-info.js";
-
-/**
- * Runs preflight checks for the `add` command.
- * `add` in this CLI version should work for both Tailwind CSS v3 and v4 and Svelte 5,
- * but fail for Svelte 4.
- *
- * @param cwd - The current working directory.
- */
-export async function preflightAdd(cwd: string) {
-	const config = await cliConfig.getConfig(cwd);
-	if (!config) {
-		throw new ConfigError(
-			`Configuration file is missing. Please run ${color.green("init")} to create a ${highlight("components.json")} file.`
-		);
-	}
-
-	const sveltePkg = getDependencyPackageInfo(cwd, "svelte")?.pkg;
-	const tailwindPkg = getDependencyPackageInfo(cwd, "tailwindcss")?.pkg;
-
-	const pkg = getProjectPackageInfo(cwd);
-	const deps = { ...pkg.dependencies, ...pkg.devDependencies };
-
-	const svelte = sveltePkg?.version ?? deps["svelte"];
-	const tailwindcss = tailwindPkg?.version ?? deps["tailwindcss"];
-
-	checkAddDependencies({ svelte, tailwindcss }, cwd, config);
-}
-
-/**
- * Checks the dependencies of the user's project to ensure that the project is compatible
- * with the expected dependencies.
- */
-function checkAddDependencies(
-	dependencies: Partial<Record<string, string>>,
-	cwd: string,
-	config: cliConfig.RawConfig
-) {
-	if (!dependencies.tailwindcss || !dependencies.svelte) {
-		throw error(`This CLI version requires Tailwind CSS (v3 or v4) and Svelte v5.\n`);
-	}
-
-	const isSvelte5 = semver.satisfies(semver.coerce(dependencies.svelte) || "", "^5.0.0");
-	const isTailwind4 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^4.0.0");
-	const isTailwind3 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^3.0.0");
-
-	// this is the happy path
-	if (isTailwind4 && isSvelte5) {
-		const host = new URL(config.registry).host;
-		if (host === "next.shadcn-svelte.com") {
-			// update `next` registry and schema urls
-			config.$schema = cliConfig.DEFAULT_CONFIG.$schema;
-			config.registry = cliConfig.DEFAULT_CONFIG.registry;
-			cliConfig.writeConfig(cwd, config);
-		}
-		return;
-	}
-
-	if (isTailwind3 && isSvelte5) {
-		// if no `style` field, then we can assume their components.json is already updated
-		if (!config.style) return;
-
-		updateLegacyConfig(cwd, config);
-		return;
-	}
-
-	// if incompatible, throw error
-	throw error(
-		`This CLI version requires Tailwind CSS (v3 or v4) and Svelte v5.\n\n` +
-			`If you are on Svelte v4, use ${highlight("shadcn-svelte@0.14 add")} instead, or consider migrating to Svelte 5: ${SITE_BASE_URL}/docs/migration/svelte-5`
-	);
-}
-
-/**
- * Updates a legacy config (Tailwind v3 / Svelte v5) to be compatible with the new CLI.
- * It does so by updating the `registry` field to point to the Tailwind v3/Svelte v5 registry
- * based on their `style` field and removes the `style` field before writing the config back.
- */
-function updateLegacyConfig(cwd: string, config: cliConfig.RawConfig) {
-	// should we just write the config back here or prompt them to confirm?
-	cliConfig.writeConfig(cwd, {
-		...config,
-		registry: `${TW3_SITE_BASE_URL}/registry/${config.style}`,
-	});
-}
diff --git a/packages/cli/src/commands/init/index.ts b/packages/cli/src/commands/init/index.ts
index 0103bcf8..c1cc61a7 100644
--- a/packages/cli/src/commands/init/index.ts
+++ b/packages/cli/src/commands/init/index.ts
@@ -5,6 +5,7 @@ import path from "node:path";
 import process from "node:process";
 import { z } from "zod/v4";
 import * as p from "@clack/prompts";
+import type { TsConfigResult } from "get-tsconfig";
 import { detectConfigs } from "../../utils/auto-detect.js";
 import { error, handleError } from "../../utils/errors.js";
 import type { ResolvedConfig } from "../../utils/get-config.js";
@@ -19,7 +20,7 @@ import { addRegistryItems } from "../../utils/add-registry-items.js";
 import { getEnvProxy } from "../../utils/get-env-proxy.js";
 import { highlight, stripTrailingSlash } from "../../utils/utils.js";
 import { installDependencies } from "../../utils/install-deps.js";
-import type { TsConfigResult } from "get-tsconfig";
+import { checkPreconditions } from "../../utils/preconditions.js";
 
 const baseColors = registry.getBaseColors();
 
@@ -70,8 +71,11 @@ export const init = new Command()
 
 			preflightInit(cwd);
 
-			// Read config.
-			const existingConfig = cliConfig.loadConfig(cwd);
+			let existingConfig = cliConfig.loadConfig(cwd);
+			if (existingConfig) {
+				existingConfig = checkPreconditions({ cwd, config: existingConfig });
+			}
+
 			const config = await promptForConfig(cwd, existingConfig, options);
 
 			await runInit(cwd, config, options);
diff --git a/packages/cli/src/commands/update/index.ts b/packages/cli/src/commands/update/index.ts
index 449fa2c7..3ea63693 100644
--- a/packages/cli/src/commands/update/index.ts
+++ b/packages/cli/src/commands/update/index.ts
@@ -56,9 +56,8 @@ export const update = new Command()
 				);
 			}
 
-			checkPreconditions(cwd);
-
-			await runUpdate(cwd, config, options);
+			const updatedConfig = checkPreconditions({ config, cwd });
+			await runUpdate(cwd, updatedConfig, options);
 
 			p.note(
 				`This action ${color.underline("does not")} update your ${highlight("dependencies")} to their ${color.bold("latest")} versions.\n\nConsider updating them as well.`
@@ -224,15 +223,6 @@ async function runUpdate(cwd: string, config: cliConfig.ResolvedConfig, options:
 		});
 	}
 
-	// Update the config
-	tasks.push({
-		title: "Updating config file",
-		async task() {
-			cliConfig.writeConfig(cwd, config);
-			return `Config file ${highlight("components.json")} updated`;
-		},
-	});
-
 	if (Object.keys(cssVars).length > 0) {
 		// Update the stylesheet
 		tasks.push({
diff --git a/packages/cli/src/utils/preconditions.ts b/packages/cli/src/utils/preconditions.ts
index 904e0f77..6ada7e70 100644
--- a/packages/cli/src/utils/preconditions.ts
+++ b/packages/cli/src/utils/preconditions.ts
@@ -1,51 +1,82 @@
-import color from "chalk";
 import * as semver from "semver";
-import { getProjectPackageInfo } from "./get-package-info.js";
 import { log } from "@clack/prompts";
-import { getPadding } from "./prompt-helpers.js";
+import { getDependencyPackageInfo, getProjectPackageInfo } from "./get-package-info.js";
+import * as cliConfig from "./get-config.js";
+import { error } from "./errors.js";
+import { highlight } from "./utils.js";
+import { SITE_BASE_URL, TW3_SITE_BASE_URL } from "../constants.js";
 
-const peerDependencies: Record<string, string> = {
-	svelte: "5.x.x",
-	tailwindcss: "4.x.x",
+// accepts either a `RawConfig` or `ResolvedConfig`
+type PreconditionOptions<Config extends cliConfig.RawConfig> = {
+	cwd: string;
+	config: Config;
 };
 
-export function checkPreconditions(cwd: string) {
+/** Checks preconditions and updates the config if necessary. */
+export function checkPreconditions<Config extends cliConfig.RawConfig>({
+	cwd,
+	config,
+}: PreconditionOptions<Config>): Config {
+	const sveltePkg = getDependencyPackageInfo(cwd, "svelte")?.pkg;
+	const tailwindPkg = getDependencyPackageInfo(cwd, "tailwindcss")?.pkg;
+
+	// covers the case where `npm install` hasn't been executed yet
 	const pkg = getProjectPackageInfo(cwd);
+	const fallback = { ...pkg.dependencies, ...pkg.devDependencies };
 
-	const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
+	const svelte = sveltePkg?.version ?? fallback["svelte"];
+	const tailwindcss = tailwindPkg?.version ?? fallback["tailwindcss"];
 
-	checkDependencies(dependencies);
+	return checkDependencies({ svelte, tailwindcss }, cwd, config);
 }
 
-/**
- * Checks that all dependencies meet peer dependency requirements and logs a warning if they don't.
- */
-function checkDependencies(dependencies: Partial<Record<string, string>>) {
-	const incompatible: [string, string][] = [];
+/** Checks dependencies and updates config if necessary. */
+function checkDependencies<Config extends cliConfig.RawConfig>(
+	dependencies: Record<string, string | undefined>,
+	cwd: string,
+	config: Config
+): Config {
+	if (!dependencies.tailwindcss || !dependencies.svelte) {
+		throw error(`This CLI requires Tailwind CSS and Svelte to be installed.\n`);
+	}
 
-	for (const [name, version] of Object.entries(dependencies)) {
-		const targetVersion = peerDependencies[name];
-		const currentVersion = semver.coerce(version);
+	const isTailwind3 = semver.satisfies(semver.coerce(dependencies.tailwindcss) ?? "", "^3.0.0");
+	const isTailwind4 = semver.satisfies(semver.coerce(dependencies.tailwindcss) ?? "", "^4.0.0");
+	const isSvelte5 = semver.satisfies(semver.coerce(dependencies.svelte) ?? "", "^5.0.0");
 
-		if (!targetVersion || !currentVersion) continue;
+	// supported versions (tailwind v4 + svelte v5)
+	if (isTailwind4 && isSvelte5) {
+		// no config to update
+		if (!config) return config;
 
-		if (!semver.satisfies(currentVersion, targetVersion)) {
-			incompatible.push([
-				`${color.bold(`${name}@`)}${color.greenBright.bold(targetVersion)}`,
-				color.yellowBright.bold(currentVersion),
-			]);
+		const host = new URL(config.registry).host;
+		if (host === "next.shadcn-svelte.com") {
+			// update from `next` registry and schema urls
+			config.$schema = cliConfig.DEFAULT_CONFIG.$schema;
+			config.registry = cliConfig.DEFAULT_CONFIG.registry;
+			cliConfig.writeConfig(cwd, config);
+			log.step(`Config file ${highlight("components.json")} updated`);
 		}
+		return config;
 	}
 
-	if (incompatible.length > 0) {
-		const padding = getPadding(incompatible.map(([target]) => target));
+	// legacy (tailwind v3 + svelte v5)
+	if (isTailwind3 && isSvelte5) {
+		// no config to update
+		if (!config) return config;
 
-		const lines = incompatible
-			.map(([target, current]) => `  need ${target.padEnd(padding)}  >>  found ${current}`)
-			.join("\n");
+		// if no `style` field, then we can assume their components.json is already updated
+		if (!config.style) return config;
 
-		log.warn(
-			`Incompatible dependency versions detected:\n\n${lines}\n\n${color.white.bold("Use at your own risk!")}`
-		);
+		config.registry = `${TW3_SITE_BASE_URL}/registry/${config.style}`;
+		cliConfig.writeConfig(cwd, config);
+		log.step(`Config file ${highlight("components.json")} updated`);
+		return config;
 	}
+
+	// incompatible
+	throw error(
+		`This CLI version requires Tailwind CSS (v3 or v4) and Svelte v5.\n\n` +
+			`If you are on Svelte v4, use ${highlight("shadcn-svelte@0.14 add")} instead, or consider migrating to Svelte 5: ${SITE_BASE_URL}/docs/migration/svelte-5`
+	);
 }


Test patch:
diff --git a/packages/cli/test/utils/preconditions.test.ts b/packages/cli/test/utils/preconditions.test.ts
index d61ebe23..bf5642b4 100644
--- a/packages/cli/test/utils/preconditions.test.ts
+++ b/packages/cli/test/utils/preconditions.test.ts
@@ -1,96 +1,167 @@
-import { log } from "@clack/prompts";
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { describe, expect, it, vi, beforeEach } from "vitest";
+import { TW3_SITE_BASE_URL } from "../../src/constants.js";
 import { checkPreconditions } from "../../src/utils/preconditions.js";
-import { getProjectPackageInfo } from "../../src/utils/get-package-info.js";
+import { getConfig, writeConfig } from "../../src/utils/get-config.js";
+import {
+	getProjectPackageInfo,
+	getDependencyPackageInfo,
+} from "../../src/utils/get-package-info.js";
 
-vi.mock("../../src/utils/get-package-info.js", () => ({
-	getProjectPackageInfo: vi.fn(),
-}));
+const resolvedPaths = {
+	cwd: "n/a",
+	components: "n/a",
+	hooks: "n/a",
+	lib: "n/a",
+	tailwindCss: "n/a",
+	ui: "n/a",
+	utils: "n/a",
+};
+
+const configFull = {
+	tailwind: {
+		css: "src/app.css",
+		baseColor: "zinc",
+	},
+	aliases: {
+		components: "$lib/components",
+		utils: "$lib/utils",
+		ui: "$lib/components/ui",
+		hooks: "$lib/hooks",
+		lib: "$lib",
+	},
+	resolvedPaths,
+	typescript: true,
+	registry: "https://shadcn-svelte.com/registry",
+};
 
-vi.mock("@clack/prompts", () => ({
-	log: {
-		warn: vi.fn(),
+const configLegacy = {
+	style: "default",
+	tailwind: {
+		css: "src/app.css",
+		baseColor: "zinc",
+	},
+	aliases: {
+		components: "$lib/components",
+		utils: "$lib/utils",
+		ui: "$lib/components/ui",
+		hooks: "$lib/hooks",
+		lib: "$lib",
 	},
+	resolvedPaths,
+	typescript: true,
+	registry: "https://shadcn-svelte.com/registry",
+};
+
+const configLegacyUpdated = {
+	...configLegacy,
+	style: undefined,
+};
+
+vi.mock("../../src/utils/get-package-info.js");
+vi.mock("../../src/utils/utils.js");
+
+vi.mock("../../src/utils/get-config.js", async () => ({
+	...(await vi.importActual<typeof import("../../src/utils/get-config.js")>(
+		"../../src/utils/get-config.js"
+	)),
+	writeConfig: vi.fn(),
+	getConfig: vi.fn(),
 }));
 
-describe("preconditions", () => {
+describe("checkPreconditions", () => {
+	const mockCwd = "/test/cwd";
+
 	beforeEach(() => {
-		vi.clearAllMocks();
+		vi.resetAllMocks();
+		vi.mocked(getDependencyPackageInfo).mockReturnValue(undefined);
 	});
 
-	it("should not warn when dependencies are compatible", () => {
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {
-				svelte: "5.0.0",
-				tailwindcss: "4.0.0",
-			},
-			devDependencies: {},
-		});
+	it("should pass for Tailwind v4 + Svelte v5", async () => {
+		// @ts-expect-error - we're mocking the config
+		vi.mocked(getConfig).mockResolvedValue(configFull);
 
-		checkPreconditions("/test/path");
-		expect(log.warn).not.toHaveBeenCalled();
-	});
-
-	it("should warn when svelte version is incompatible", () => {
 		vi.mocked(getProjectPackageInfo).mockReturnValue({
 			dependencies: {
-				svelte: "4.0.0",
 				tailwindcss: "4.0.0",
+				svelte: "5.0.0",
 			},
 			devDependencies: {},
 		});
 
-		checkPreconditions("/test/path");
-		expect(log.warn).toHaveBeenCalled();
+		const config = (await getConfig(mockCwd))!;
+
+		expect(() => checkPreconditions({ cwd: mockCwd, config })).not.toThrow();
 	});
 
-	it("should warn when tailwindcss version is incompatible", () => {
+	it("should update legacy config for Tailwind v3 + Svelte v5", async () => {
+		// @ts-expect-error - we're mocking the config
+		vi.mocked(getConfig).mockResolvedValue(configLegacy);
 		vi.mocked(getProjectPackageInfo).mockReturnValue({
 			dependencies: {
-				svelte: "5.0.0",
 				tailwindcss: "3.0.0",
+				svelte: "5.0.0",
 			},
 			devDependencies: {},
 		});
 
-		checkPreconditions("/test/path");
-		expect(log.warn).toHaveBeenCalled();
+		const config = (await getConfig(mockCwd))!;
+		const updatedConfig = checkPreconditions({ cwd: mockCwd, config });
+
+		const expectedConfig = {
+			...configLegacy,
+			registry: `${TW3_SITE_BASE_URL}/registry/default`,
+		};
+
+		expect(updatedConfig).toStrictEqual(expectedConfig);
 	});
 
-	it("should check both dependencies and devDependencies", () => {
+	it("should not update config for Tailwind v3 + Svelte v5 if no style field", async () => {
+		// @ts-expect-error - we're mocking the config
+		vi.mocked(getConfig).mockResolvedValue(configLegacyUpdated);
+		vi.mocked(getDependencyPackageInfo).mockReturnValue({
+			pkg: {
+				dependencies: {
+					tailwindcss: "3.0.0",
+					svelte: "5.0.0",
+				},
+				devDependencies: {},
+			},
+			path: "test/path",
+		});
 		vi.mocked(getProjectPackageInfo).mockReturnValue({
 			dependencies: {
-				svelte: "5.0.0",
-			},
-			devDependencies: {
 				tailwindcss: "3.0.0",
+				svelte: "5.0.0",
 			},
-		});
-
-		checkPreconditions("/test/path");
-		expect(log.warn).toHaveBeenCalled();
-	});
-
-	it("should handle missing dependencies gracefully", () => {
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {},
 			devDependencies: {},
 		});
 
-		checkPreconditions("/test/path");
-		expect(log.warn).not.toHaveBeenCalled();
+		const config = (await getConfig(mockCwd))!;
+		checkPreconditions({ cwd: mockCwd, config });
+
+		expect(writeConfig).not.toHaveBeenCalled();
 	});
 
-	it("should handle invalid version strings", () => {
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {
-				svelte: "invalid-version",
-				tailwindcss: "4.0.0",
-			},
-			devDependencies: {},
-		});
+	it("should throw for incompatible versions", async () => {
+		const testCases = [
+			{ tailwindcss: "2.0.0", svelte: "5.0.0" },
+			{ tailwindcss: "4.0.0", svelte: "4.0.0" },
+			{ tailwindcss: "3.0.0", svelte: "4.0.0" },
+			{ tailwindcss: "5.0.0", svelte: "5.0.0" },
+		];
+
+		for (const deps of testCases) {
+			// @ts-expect-error - we're mocking the config
+			vi.mocked(getConfig).mockResolvedValue({ ...configFull, resolvedPaths });
+			vi.mocked(getProjectPackageInfo).mockReturnValue({
+				dependencies: deps,
+				devDependencies: {},
+			});
 
-		checkPreconditions("/test/path");
-		expect(log.warn).not.toHaveBeenCalled();
+			const config = (await getConfig(mockCwd))!;
+			expect(() => checkPreconditions({ cwd: mockCwd, config })).toThrow(
+				"requires Tailwind CSS (v3 or v4) and Svelte v5"
+			);
+		}
 	});
 });
diff --git a/packages/cli/test/utils/preflight-add.test.ts b/packages/cli/test/utils/preflight-add.test.ts
deleted file mode 100644
index adbbf67f..00000000
--- a/packages/cli/test/utils/preflight-add.test.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import { describe, expect, it, vi, beforeEach } from "vitest";
-import { getConf } from "./test-helpers";
-import { ConfigError } from "../../src/utils/errors.js";
-import { TW3_SITE_BASE_URL } from "../../src/constants.js";
-import { preflightAdd } from "../../src/commands/add/preflight.js";
-import { getConfig, writeConfig } from "../../src/utils/get-config.js";
-import {
-	getProjectPackageInfo,
-	getDependencyPackageInfo,
-} from "../../src/utils/get-package-info.js";
-
-const resolvedPaths = {
-	cwd: "n/a",
-	components: "n/a",
-	hooks: "n/a",
-	lib: "n/a",
-	tailwindCss: "n/a",
-	ui: "n/a",
-	utils: "n/a",
-};
-
-const configFull = {
-	tailwind: {
-		css: "src/app.css",
-		baseColor: "zinc",
-	},
-	aliases: {
-		components: "$lib/components",
-		utils: "$lib/utils",
-		ui: "$lib/components/ui",
-		hooks: "$lib/hooks",
-		lib: "$lib",
-	},
-	resolvedPaths,
-	typescript: true,
-	registry: "https://shadcn-svelte.com/registry",
-};
-
-const configLegacy = {
-	style: "default",
-	tailwind: {
-		css: "src/app.css",
-		baseColor: "zinc",
-	},
-	aliases: {
-		components: "$lib/components",
-		utils: "$lib/utils",
-		ui: "$lib/components/ui",
-		hooks: "$lib/hooks",
-		lib: "$lib",
-	},
-	resolvedPaths,
-	typescript: true,
-	registry: "https://shadcn-svelte.com/registry",
-};
-
-const configLegacyUpdated = {
-	...configLegacy,
-	style: undefined,
-};
-
-vi.mock("../../src/utils/get-package-info.js");
-vi.mock("../../src/utils/utils.js");
-
-vi.mock("../../src/utils/get-config.js", async () => ({
-	...(await vi.importActual<typeof import("../../src/utils/get-config.js")>(
-		"../../src/utils/get-config.js"
-	)),
-	writeConfig: vi.fn(),
-	getConfig: vi.fn(),
-}));
-
-describe("preflightAdd", () => {
-	const mockCwd = "/test/cwd";
-
-	beforeEach(() => {
-		vi.resetAllMocks();
-		vi.mocked(getDependencyPackageInfo).mockReturnValue(undefined);
-	});
-
-	it("should throw if config is missing", async () => {
-		vi.mocked(getConfig).mockResolvedValueOnce(await getConf("config-none"));
-
-		await expect(preflightAdd(mockCwd)).rejects.toThrow(ConfigError);
-		expect(getConfig).toHaveBeenCalledWith(mockCwd);
-	});
-
-	it("should pass for Tailwind v4 + Svelte v5", async () => {
-		// @ts-expect-error - we're mocking the config
-		vi.mocked(getConfig).mockResolvedValue(configFull);
-
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {
-				tailwindcss: "4.0.0",
-				svelte: "5.0.0",
-			},
-			devDependencies: {},
-		});
-
-		await expect(preflightAdd(mockCwd)).resolves.not.toThrow();
-	});
-
-	it("should update legacy config for Tailwind v3 + Svelte v5", async () => {
-		// @ts-expect-error - we're mocking the config
-		vi.mocked(getConfig).mockResolvedValue(configLegacy);
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {
-				tailwindcss: "3.0.0",
-				svelte: "5.0.0",
-			},
-			devDependencies: {},
-		});
-
-		await preflightAdd(mockCwd);
-
-		const expectedConfig = {
-			...configLegacy,
-			registry: `${TW3_SITE_BASE_URL}/registry/default`,
-		};
-
-		expect(writeConfig).toHaveBeenCalledWith(mockCwd, expectedConfig);
-	});
-
-	it("should not update config for Tailwind v3 + Svelte v5 if no style field", async () => {
-		// @ts-expect-error - we're mocking the config
-		vi.mocked(getConfig).mockResolvedValue(configLegacyUpdated);
-		vi.mocked(getProjectPackageInfo).mockReturnValue({
-			dependencies: {
-				tailwindcss: "3.0.0",
-				svelte: "5.0.0",
-			},
-			devDependencies: {},
-		});
-
-		await preflightAdd(mockCwd);
-
-		expect(writeConfig).not.toHaveBeenCalled();
-	});
-
-	it("should throw for incompatible versions", async () => {
-		const testCases = [
-			{ tailwindcss: "2.0.0", svelte: "5.0.0" },
-			{ tailwindcss: "4.0.0", svelte: "4.0.0" },
-			{ tailwindcss: "3.0.0", svelte: "4.0.0" },
-			{ tailwindcss: "5.0.0", svelte: "5.0.0" },
-		];
-
-		for (const deps of testCases) {
-			// @ts-expect-error - we're mocking the config
-			vi.mocked(getConfig).mockResolvedValue({ ...configFull, resolvedPaths });
-			vi.mocked(getProjectPackageInfo).mockReturnValue({
-				dependencies: deps,
-				devDependencies: {},
-			});
-
-			await expect(preflightAdd(mockCwd)).rejects.toThrow(
-				"requires Tailwind CSS (v3 or v4) and Svelte v5"
-			);
-		}
-	});
-});