zerozeeker 2.2.3 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +274 -94
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import { execSync } from "child_process";
6
6
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
7
7
  import { join, dirname } from "path";
8
- import readline from "readline";
8
+ import * as readline from "readline/promises";
9
9
  import ora from "ora";
10
10
  import chalk from "chalk";
11
11
  var REGISTRY_URL = "https://www.zerozeeker.com/r";
@@ -18,12 +18,24 @@ var COMPONENTS = [
18
18
  "circle-reveal-button",
19
19
  "index"
20
20
  ];
21
- async function fetchRegistry(url) {
22
- const response = await fetch(url);
23
- if (!response.ok) {
24
- throw new Error(`Failed to fetch registry: ${response.statusText}`);
21
+ async function fetchRegistry(url, retries = 3) {
22
+ const controller = new AbortController();
23
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
24
+ try {
25
+ const response = await fetch(url, { signal: controller.signal });
26
+ clearTimeout(timeoutId);
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to fetch registry: ${response.statusText}`);
29
+ }
30
+ return response.json();
31
+ } catch (error) {
32
+ clearTimeout(timeoutId);
33
+ if (retries > 0 && error instanceof Error && error.name !== "AbortError") {
34
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
35
+ return fetchRegistry(url, retries - 1);
36
+ }
37
+ throw error;
25
38
  }
26
- return response.json();
27
39
  }
28
40
  function ensureDir(filePath) {
29
41
  const dir = dirname(filePath);
@@ -33,7 +45,8 @@ function ensureDir(filePath) {
33
45
  }
34
46
  function findProjectRoot() {
35
47
  let currentDir = process.cwd();
36
- while (currentDir !== dirname(currentDir)) {
48
+ const root = dirname(currentDir);
49
+ while (currentDir !== root) {
37
50
  if (existsSync(join(currentDir, "package.json"))) {
38
51
  return currentDir;
39
52
  }
@@ -41,40 +54,73 @@ function findProjectRoot() {
41
54
  }
42
55
  throw new Error("Could not find project root (no package.json found)");
43
56
  }
57
+ function detectPackageManager() {
58
+ const projectRoot = process.cwd();
59
+ if (existsSync(join(projectRoot, "bun.lockb"))) return "bun";
60
+ if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
61
+ if (existsSync(join(projectRoot, "yarn.lock"))) return "yarn";
62
+ return "npm";
63
+ }
64
+ function getInstallCommand(deps) {
65
+ const pm = detectPackageManager();
66
+ const depsStr = deps.join(" ");
67
+ switch (pm) {
68
+ case "bun":
69
+ return `bun add ${depsStr}`;
70
+ case "pnpm":
71
+ return `pnpm add ${depsStr}`;
72
+ case "yarn":
73
+ return `yarn add ${depsStr}`;
74
+ default:
75
+ return `npm install ${depsStr}`;
76
+ }
77
+ }
44
78
  function installDependencies(deps) {
45
79
  if (deps.length === 0) return;
80
+ const command = getInstallCommand(deps);
46
81
  console.log(chalk.dim(` Installing dependencies: ${deps.join(", ")}`));
47
82
  try {
48
- execSync(`npm install ${deps.join(" ")}`, {
83
+ execSync(command, {
49
84
  stdio: "pipe",
50
85
  encoding: "utf-8"
51
86
  });
52
87
  } catch {
53
- console.warn(chalk.yellow(` [!] Failed to auto-install dependencies. Install manually: npm install ${deps.join(" ")}`));
88
+ console.warn(chalk.yellow(` [!] Failed to auto-install dependencies. Install manually: ${command}`));
54
89
  }
55
90
  }
56
91
  async function askYesNo(question, defaultYes = false) {
57
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
58
- return new Promise((resolve) => {
59
- rl.question(`${question} (${defaultYes ? "Y/n" : "y/N"}): `, (answer) => {
60
- rl.close();
61
- const a = answer.trim().toLowerCase();
62
- if (a === "") return resolve(defaultYes);
63
- if (["y", "yes"].includes(a)) return resolve(true);
64
- return resolve(false);
65
- });
92
+ const rl = readline.createInterface({
93
+ input: process.stdin,
94
+ output: process.stdout
66
95
  });
96
+ try {
97
+ const answer = await rl.question(`${question} (${defaultYes ? "Y/n" : "y/N"}): `);
98
+ const normalized = answer.trim().toLowerCase();
99
+ if (normalized === "") return defaultYes;
100
+ return ["y", "yes"].includes(normalized);
101
+ } finally {
102
+ rl.close();
103
+ }
67
104
  }
68
105
  async function installRegistryDependencies(deps, projectRoot, installed = /* @__PURE__ */ new Set()) {
69
106
  const allFiles = [];
70
107
  const allNpmDeps = [];
71
- for (const dep of deps) {
72
- if (installed.has(dep)) continue;
73
- installed.add(dep);
74
- const url = `${REGISTRY_URL}/${dep}.json`;
75
- try {
108
+ const results = await Promise.allSettled(
109
+ deps.filter((dep) => !installed.has(dep)).map(async (dep) => {
110
+ installed.add(dep);
111
+ const url = `${REGISTRY_URL}/${dep}.json`;
76
112
  const registry = await fetchRegistry(url);
77
- if (registry.registryDependencies && registry.registryDependencies.length > 0) {
113
+ return { dep, registry };
114
+ })
115
+ );
116
+ for (const result of results) {
117
+ if (result.status === "rejected") {
118
+ console.warn(chalk.yellow(` [!] Could not fetch registry dependency`));
119
+ continue;
120
+ }
121
+ const { dep, registry } = result.value;
122
+ try {
123
+ if (registry.registryDependencies?.length) {
78
124
  const nested = await installRegistryDependencies(
79
125
  registry.registryDependencies,
80
126
  projectRoot,
@@ -83,13 +129,11 @@ async function installRegistryDependencies(deps, projectRoot, installed = /* @__
83
129
  allFiles.push(...nested.files);
84
130
  allNpmDeps.push(...nested.npmDeps);
85
131
  }
86
- if (registry.files && registry.files.length > 0) {
132
+ if (registry.files?.length) {
87
133
  for (const file of registry.files) {
88
134
  const targetPath = file.target || file.path;
89
135
  const fullPath = join(projectRoot, targetPath);
90
- if (existsSync(fullPath)) {
91
- continue;
92
- }
136
+ if (existsSync(fullPath)) continue;
93
137
  ensureDir(fullPath);
94
138
  writeFileSync(fullPath, file.content, "utf-8");
95
139
  allFiles.push(targetPath);
@@ -104,96 +148,233 @@ async function installRegistryDependencies(deps, projectRoot, installed = /* @__
104
148
  }
105
149
  return { files: allFiles, npmDeps: allNpmDeps };
106
150
  }
151
+ function parseJsonFile(filePath) {
152
+ try {
153
+ return JSON.parse(readFileSync(filePath, "utf-8"));
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
107
158
  function checkTypeScriptConfig(projectRoot) {
108
159
  const tsconfigPath = join(projectRoot, "tsconfig.json");
109
160
  if (!existsSync(tsconfigPath)) {
110
161
  return { name: "TypeScript", passed: false, message: "tsconfig.json not found" };
111
162
  }
112
- try {
113
- const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
114
- const paths = tsconfig.compilerOptions?.paths || {};
115
- const hasComponentsAlias = paths["@/components/*"] || paths["@/components"];
116
- const hasLibAlias = paths["@/lib/*"] || paths["@/lib"];
117
- if (hasComponentsAlias && hasLibAlias) {
118
- return { name: "TypeScript paths", passed: true };
119
- }
120
- return {
121
- name: "TypeScript paths",
122
- passed: false,
123
- message: "Missing @/components or @/lib path aliases"
124
- };
125
- } catch {
163
+ const tsconfig = parseJsonFile(tsconfigPath);
164
+ if (!tsconfig) {
126
165
  return { name: "TypeScript config", passed: false, message: "Invalid tsconfig.json" };
127
166
  }
167
+ const paths = tsconfig.compilerOptions?.paths ?? {};
168
+ const hasComponentsAlias = "@/components/*" in paths || "@/components" in paths;
169
+ const hasLibAlias = "@/lib/*" in paths || "@/lib" in paths;
170
+ if (hasComponentsAlias && hasLibAlias) {
171
+ return { name: "TypeScript paths", passed: true };
172
+ }
173
+ return {
174
+ name: "TypeScript paths",
175
+ passed: false,
176
+ message: "Missing @/components or @/lib path aliases"
177
+ };
128
178
  }
129
179
  function checkTailwindConfig(projectRoot) {
180
+ const cssFileLocations = [
181
+ "app/globals.css",
182
+ "src/app/globals.css",
183
+ "styles/globals.css",
184
+ "src/styles/globals.css",
185
+ "app/global.css",
186
+ "src/app/global.css",
187
+ "styles/global.css",
188
+ "src/styles/global.css",
189
+ "app/index.css",
190
+ "src/index.css",
191
+ "index.css"
192
+ ];
193
+ for (const cssPath of cssFileLocations) {
194
+ const fullPath = join(projectRoot, cssPath);
195
+ if (existsSync(fullPath)) {
196
+ try {
197
+ const content = readFileSync(fullPath, "utf-8");
198
+ if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'") || content.includes("@theme")) {
199
+ return { name: "Tailwind CSS v4", passed: true };
200
+ }
201
+ if (content.includes("@tailwind base") || content.includes("@tailwind components") || content.includes("@tailwind utilities")) {
202
+ return { name: "Tailwind CSS v3", passed: true };
203
+ }
204
+ } catch {
205
+ }
206
+ }
207
+ }
130
208
  const tailwindConfigs = [
131
209
  "tailwind.config.ts",
132
210
  "tailwind.config.js",
133
211
  "tailwind.config.mjs",
134
212
  "tailwind.config.cjs"
135
213
  ];
136
- const hasTailwind = tailwindConfigs.some((config) => existsSync(join(projectRoot, config)));
137
- return hasTailwind ? { name: "Tailwind CSS", passed: true } : { name: "Tailwind CSS", passed: false, message: "Config file not found" };
214
+ const hasTailwindConfig = tailwindConfigs.some((config) => existsSync(join(projectRoot, config)));
215
+ if (hasTailwindConfig) {
216
+ return { name: "Tailwind CSS", passed: true };
217
+ }
218
+ const packageJson = parseJsonFile(join(projectRoot, "package.json"));
219
+ if (packageJson) {
220
+ const deps = {
221
+ ...packageJson.dependencies ?? {},
222
+ ...packageJson.devDependencies ?? {}
223
+ };
224
+ if (deps.tailwindcss) {
225
+ return {
226
+ name: "Tailwind CSS",
227
+ passed: true,
228
+ message: "Installed but config not detected - may need setup"
229
+ };
230
+ }
231
+ }
232
+ return { name: "Tailwind CSS", passed: false, message: "Config file not found" };
138
233
  }
139
234
  function checkFramework(projectRoot) {
140
- const packageJsonPath = join(projectRoot, "package.json");
141
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
142
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
143
- if (deps.next) {
144
- return { name: "Next.js", passed: true };
145
- }
146
- if (deps.react) {
147
- return { name: "React", passed: true };
235
+ const packageJson = parseJsonFile(join(projectRoot, "package.json"));
236
+ if (!packageJson) {
237
+ return { name: "React/Next.js", passed: false, message: "Could not read package.json" };
148
238
  }
239
+ const deps = {
240
+ ...packageJson.dependencies ?? {},
241
+ ...packageJson.devDependencies ?? {}
242
+ };
243
+ if (deps.next) return { name: "Next.js", passed: true };
244
+ if (deps.react) return { name: "React", passed: true };
149
245
  return { name: "React/Next.js", passed: false, message: "Not found in dependencies" };
150
246
  }
151
247
  function displaySetupHelp(checks) {
152
- const tsCheck = checks.find((c) => c.name === "TypeScript");
153
- const tailwindCheck = checks.find((c) => c.name === "Tailwind CSS");
248
+ const tsCheck = checks.find((c) => c.name.startsWith("TypeScript"));
249
+ const tailwindCheck = checks.find((c) => c.name.startsWith("Tailwind"));
250
+ const pm = detectPackageManager();
154
251
  if (tsCheck && !tsCheck.passed) {
155
252
  console.log(chalk.dim("Install TypeScript:"));
156
- console.log(chalk.white(" npm install -D typescript @types/node @types/react"));
253
+ console.log(chalk.white(` ${pm} ${pm === "npm" ? "install" : "add"} -D typescript @types/node @types/react`));
157
254
  console.log(chalk.dim("\nCreate tsconfig.json with path aliases:"));
158
255
  console.log(chalk.white(" npx tsc --init"));
159
256
  }
160
257
  if (tailwindCheck && !tailwindCheck.passed) {
161
- console.log(chalk.dim("\nInstall Tailwind CSS:"));
162
- console.log(chalk.white(" npm install -D tailwindcss postcss autoprefixer"));
258
+ console.log(chalk.dim("\nInstall Tailwind CSS v4:"));
259
+ console.log(chalk.white(` ${pm} ${pm === "npm" ? "install" : "add"} -D tailwindcss @tailwindcss/postcss postcss`));
260
+ console.log(chalk.dim("\nOr for Tailwind CSS v3:"));
261
+ console.log(chalk.white(` ${pm} ${pm === "npm" ? "install" : "add"} -D tailwindcss postcss autoprefixer`));
163
262
  console.log(chalk.white(" npx tailwindcss init -p"));
164
263
  }
165
264
  console.log(chalk.dim("\nOr create a new Next.js project:"));
166
265
  console.log(chalk.white(" npx create-next-app@latest --typescript --tailwind\n"));
167
266
  }
168
267
  async function autoFixSetup(projectRoot, needsTypeScript, needsTailwind, spinner) {
268
+ const pm = detectPackageManager();
269
+ const installCmd = pm === "npm" ? "install" : "add";
169
270
  if (needsTypeScript) {
170
271
  spinner.text = "Installing TypeScript and types...";
171
- execSync("npm install -D typescript @types/node @types/react", { stdio: "inherit" });
272
+ execSync(`${pm} ${installCmd} -D typescript @types/node @types/react`, { stdio: "inherit" });
172
273
  }
173
274
  const tsconfigPath = join(projectRoot, "tsconfig.json");
174
- let tsconfig = {};
175
- if (existsSync(tsconfigPath)) {
176
- try {
177
- tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
178
- } catch {
179
- tsconfig = {};
180
- }
181
- } else {
275
+ const tsconfig = parseJsonFile(tsconfigPath) ?? { compilerOptions: {} };
276
+ if (!existsSync(tsconfigPath)) {
182
277
  spinner.text = "Creating tsconfig.json...";
183
- tsconfig = { compilerOptions: {} };
184
278
  writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), "utf-8");
185
279
  }
186
- tsconfig.compilerOptions = tsconfig.compilerOptions || {};
187
- tsconfig.compilerOptions.baseUrl = tsconfig.compilerOptions.baseUrl || ".";
188
- tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
280
+ tsconfig.compilerOptions ??= {};
281
+ tsconfig.compilerOptions.baseUrl ??= ".";
282
+ tsconfig.compilerOptions.paths ??= {};
189
283
  tsconfig.compilerOptions.paths["@/components/*"] = ["components/*"];
190
284
  tsconfig.compilerOptions.paths["@/lib/*"] = ["lib/*"];
191
285
  writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), "utf-8");
192
286
  if (needsTailwind) {
193
- spinner.text = "Installing Tailwind CSS...";
194
- execSync("npm install -D tailwindcss postcss autoprefixer", { stdio: "inherit" });
195
- spinner.text = "Initializing Tailwind config...";
196
- execSync("npx tailwindcss init -p", { stdio: "inherit" });
287
+ spinner.text = "Installing Tailwind CSS v4...";
288
+ try {
289
+ execSync(`${pm} ${installCmd} -D tailwindcss @tailwindcss/postcss postcss`, { stdio: "inherit" });
290
+ } catch {
291
+ console.warn(chalk.yellow("\nWarning: Tailwind CSS v4 packages not available. Installing v3 instead."));
292
+ execSync(`${pm} ${installCmd} -D tailwindcss postcss autoprefixer`, { stdio: "inherit" });
293
+ }
294
+ spinner.text = "Creating Tailwind CSS configuration...";
295
+ try {
296
+ const postcssConfig = `export default {
297
+ plugins: {
298
+ "@tailwindcss/postcss": {},
299
+ },
300
+ };
301
+ `;
302
+ const appDir = join(projectRoot, "app");
303
+ const srcAppDir = join(projectRoot, "src", "app");
304
+ let globalsCssDir;
305
+ let globalsCssPath;
306
+ if (existsSync(srcAppDir)) {
307
+ globalsCssDir = srcAppDir;
308
+ globalsCssPath = join(srcAppDir, "globals.css");
309
+ } else if (existsSync(appDir)) {
310
+ globalsCssDir = appDir;
311
+ globalsCssPath = join(appDir, "globals.css");
312
+ } else {
313
+ globalsCssDir = appDir;
314
+ globalsCssPath = join(appDir, "globals.css");
315
+ mkdirSync(appDir, { recursive: true });
316
+ }
317
+ const globals = `@import "tailwindcss";
318
+
319
+ /*
320
+ * Tailwind CSS v4 uses CSS-first configuration.
321
+ * Customize your theme using the @theme directive:
322
+ *
323
+ * @theme {
324
+ * --color-primary: oklch(0.7 0.15 200);
325
+ * --font-display: "Satoshi", sans-serif;
326
+ * }
327
+ *
328
+ * Learn more: https://tailwindcss.com/docs/v4-beta
329
+ */
330
+ `;
331
+ writeFileSync(join(projectRoot, "postcss.config.mjs"), postcssConfig, "utf-8");
332
+ if (!existsSync(globalsCssPath)) {
333
+ writeFileSync(globalsCssPath, globals, "utf-8");
334
+ console.log(chalk.green(`Created postcss.config.mjs and ${globalsCssPath.replace(projectRoot, "")}`));
335
+ } else {
336
+ const existingContent = readFileSync(globalsCssPath, "utf-8");
337
+ if (!existingContent.includes('@import "tailwindcss"') && !existingContent.includes("@import 'tailwindcss'") && !existingContent.includes("@tailwind")) {
338
+ writeFileSync(globalsCssPath, `@import "tailwindcss";
339
+
340
+ ${existingContent}`, "utf-8");
341
+ console.log(chalk.green(`Created postcss.config.mjs and added Tailwind import to ${globalsCssPath.replace(projectRoot, "")}`));
342
+ } else {
343
+ console.log(chalk.green("Created postcss.config.mjs"));
344
+ }
345
+ }
346
+ } catch (writeErr) {
347
+ const errorMessage = writeErr instanceof Error ? writeErr.message : String(writeErr);
348
+ console.warn(chalk.yellow(`Failed to write Tailwind config files: ${errorMessage}`));
349
+ console.log(chalk.dim("Attempting fallback to Tailwind v3 configuration..."));
350
+ try {
351
+ execSync("npx tailwindcss init -p", { stdio: "inherit" });
352
+ } catch {
353
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
354
+ export default {
355
+ content: [
356
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
357
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
358
+ './pages/**/*.{js,ts,jsx,tsx,mdx}'
359
+ ],
360
+ theme: {
361
+ extend: {},
362
+ },
363
+ plugins: [],
364
+ };
365
+ `;
366
+ const postcssConfig = `export default {
367
+ plugins: {
368
+ tailwindcss: {},
369
+ autoprefixer: {},
370
+ },
371
+ };
372
+ `;
373
+ writeFileSync(join(projectRoot, "tailwind.config.js"), tailwindConfig, "utf-8");
374
+ writeFileSync(join(projectRoot, "postcss.config.js"), postcssConfig, "utf-8");
375
+ console.log(chalk.green("Created tailwind.config.js and postcss.config.js (v3 fallback)"));
376
+ }
377
+ }
197
378
  }
198
379
  }
199
380
  var COMPONENT_DESCRIPTIONS = {
@@ -205,7 +386,7 @@ var COMPONENT_DESCRIPTIONS = {
205
386
  "circle-reveal-button": "Icon to pill expansion - the element of surprise"
206
387
  };
207
388
  var program = new Command();
208
- program.name("zerozeeker").description("CLI for installing ZeroZeeker UI components - because life is too short for boring interfaces").version("2.2.0");
389
+ program.name("zerozeeker").description("CLI for installing ZeroZeeker UI components - because life is too short for boring interfaces").version("2.3.0");
209
390
  program.command("init").description("Initialize ZeroZeeker in your project").action(async () => {
210
391
  const spinner = ora("Checking project setup...").start();
211
392
  try {
@@ -236,10 +417,8 @@ program.command("init").description("Initialize ZeroZeeker in your project").act
236
417
  }
237
418
  console.log(chalk.yellow("\n[!] Some setup issues detected\n"));
238
419
  displaySetupHelp(checks);
239
- const needsTypeScript = !checks.find(
240
- (c) => c.name === "TypeScript" || c.name === "TypeScript paths" || c.name === "TypeScript config"
241
- )?.passed;
242
- const needsTailwind = !checks.find((c) => c.name === "Tailwind CSS")?.passed;
420
+ const needsTypeScript = !checks.find((c) => c.name.startsWith("TypeScript"))?.passed;
421
+ const needsTailwind = !checks.find((c) => c.name.startsWith("Tailwind"))?.passed;
243
422
  const doAuto = await askYesNo("Would you like ZeroZeeker to try to automatically fix these issues now?", true);
244
423
  if (!doAuto) {
245
424
  process.exit(1);
@@ -267,7 +446,7 @@ program.command("init").description("Initialize ZeroZeeker in your project").act
267
446
  process.exit(1);
268
447
  }
269
448
  });
270
- program.command("add <component>").description("Add a component from ZeroZeeker registry").action(async (component) => {
449
+ program.command("add <component>").description("Add a component from ZeroZeeker registry").option("-f, --force", "Overwrite existing files").action(async (component, options) => {
271
450
  if (!COMPONENTS.includes(component)) {
272
451
  console.error(chalk.red(`[x] Component "${component}" does not exist in this dimension.`));
273
452
  console.log(chalk.dim("\nHere are the components that actually exist:"));
@@ -284,9 +463,9 @@ program.command("add <component>").description("Add a component from ZeroZeeker
284
463
  spinner.text = `Fetching ${chalk.cyan(component)} from registry...`;
285
464
  const registry = await fetchRegistry(url);
286
465
  const filesInstalled = [];
287
- const allDependencies = [...registry.dependencies || []];
288
- if (registry.registryDependencies && registry.registryDependencies.length > 0) {
289
- spinner.text = `Installing registry dependencies...`;
466
+ const allDependencies = [...registry.dependencies ?? []];
467
+ if (registry.registryDependencies?.length) {
468
+ spinner.text = "Installing registry dependencies...";
290
469
  const registryResult = await installRegistryDependencies(
291
470
  registry.registryDependencies,
292
471
  projectRoot
@@ -294,18 +473,18 @@ program.command("add <component>").description("Add a component from ZeroZeeker
294
473
  filesInstalled.push(...registryResult.files);
295
474
  allDependencies.push(...registryResult.npmDeps);
296
475
  }
297
- if (registry.files && registry.files.length > 0) {
476
+ if (registry.files?.length) {
298
477
  spinner.text = `Installing ${chalk.cyan(component)} files...`;
299
478
  for (const file of registry.files) {
300
479
  const targetPath = file.target || file.path;
301
480
  const fullPath = join(projectRoot, targetPath);
302
- if (existsSync(fullPath)) {
481
+ if (existsSync(fullPath) && !options.force) {
303
482
  spinner.stop();
304
483
  console.warn(chalk.yellow(`
305
484
  [!] File already exists: ${targetPath}`));
306
485
  console.log(chalk.dim(" Skipping to avoid overwriting your changes."));
307
- console.log(chalk.dim(` To force reinstall: delete the file and run the command again.
308
- `));
486
+ console.log(chalk.dim(" Use --force flag to overwrite existing files.\n"));
487
+ spinner.start();
309
488
  continue;
310
489
  }
311
490
  ensureDir(fullPath);
@@ -323,15 +502,15 @@ program.command("add <component>").description("Add a component from ZeroZeeker
323
502
  [+] Successfully installed ${component}`));
324
503
  if (filesInstalled.length > 0) {
325
504
  console.log(chalk.dim("\n Files added:"));
326
- filesInstalled.forEach((f) => {
505
+ for (const f of filesInstalled) {
327
506
  console.log(chalk.cyan(` ${f}`));
328
- });
507
+ }
329
508
  }
330
509
  if (uniqueDeps.length > 0) {
331
510
  console.log(chalk.dim("\n Dependencies installed:"));
332
- uniqueDeps.forEach((d) => {
511
+ for (const d of uniqueDeps) {
333
512
  console.log(chalk.cyan(` ${d}`));
334
- });
513
+ }
335
514
  }
336
515
  console.log(chalk.dim("\n Now go make something beautiful.\n"));
337
516
  } catch (error) {
@@ -341,7 +520,7 @@ program.command("add <component>").description("Add a component from ZeroZeeker
341
520
  if (error instanceof Error) {
342
521
  if (error.message.includes("package.json")) {
343
522
  console.log(chalk.dim("\n Make sure you're in a React/Next.js project directory."));
344
- } else if (error.message.includes("fetch")) {
523
+ } else if (error.message.includes("fetch") || error.name === "AbortError") {
345
524
  console.log(chalk.dim("\n Check your internet connection and try again."));
346
525
  } else {
347
526
  console.log(chalk.dim(`
@@ -355,11 +534,12 @@ program.command("add <component>").description("Add a component from ZeroZeeker
355
534
  program.command("list").description("List all available components").action(() => {
356
535
  console.log(chalk.bold("\nZeroZeeker UI Components\n"));
357
536
  console.log(chalk.dim("Modern, polished components that make users actually want to interact.\n"));
358
- COMPONENTS.filter((c) => c !== "index").forEach((component) => {
537
+ for (const component of COMPONENTS) {
538
+ if (component === "index") continue;
359
539
  console.log(chalk.cyan(` ${component}`));
360
540
  console.log(chalk.dim(` ${COMPONENT_DESCRIPTIONS[component]}
361
541
  `));
362
- });
542
+ }
363
543
  console.log(chalk.dim("Install any component with: ") + chalk.white("npx zerozeeker add <component>"));
364
544
  console.log(chalk.dim("More components coming soon. Ship fast. Look good doing it.\n"));
365
545
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerozeeker",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Zero-config CLI for installing ZeroZeeker UI components. No shadcn required.",
5
5
  "type": "module",
6
6
  "bin": {