zerozeeker 2.2.2 → 2.2.4

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 +178 -87
  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,27 +148,33 @@ 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) {
130
180
  const tailwindConfigs = [
@@ -137,63 +187,105 @@ function checkTailwindConfig(projectRoot) {
137
187
  return hasTailwind ? { name: "Tailwind CSS", passed: true } : { name: "Tailwind CSS", passed: false, message: "Config file not found" };
138
188
  }
139
189
  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 };
190
+ const packageJson = parseJsonFile(join(projectRoot, "package.json"));
191
+ if (!packageJson) {
192
+ return { name: "React/Next.js", passed: false, message: "Could not read package.json" };
148
193
  }
194
+ const deps = {
195
+ ...packageJson.dependencies ?? {},
196
+ ...packageJson.devDependencies ?? {}
197
+ };
198
+ if (deps.next) return { name: "Next.js", passed: true };
199
+ if (deps.react) return { name: "React", passed: true };
149
200
  return { name: "React/Next.js", passed: false, message: "Not found in dependencies" };
150
201
  }
151
202
  function displaySetupHelp(checks) {
152
- const tsCheck = checks.find((c) => c.name === "TypeScript");
203
+ const tsCheck = checks.find((c) => c.name.startsWith("TypeScript"));
153
204
  const tailwindCheck = checks.find((c) => c.name === "Tailwind CSS");
205
+ const pm = detectPackageManager();
154
206
  if (tsCheck && !tsCheck.passed) {
155
207
  console.log(chalk.dim("Install TypeScript:"));
156
- console.log(chalk.white(" npm install -D typescript @types/node @types/react"));
208
+ console.log(chalk.white(` ${pm} ${pm === "npm" ? "install" : "add"} -D typescript @types/node @types/react`));
157
209
  console.log(chalk.dim("\nCreate tsconfig.json with path aliases:"));
158
210
  console.log(chalk.white(" npx tsc --init"));
159
211
  }
160
212
  if (tailwindCheck && !tailwindCheck.passed) {
161
213
  console.log(chalk.dim("\nInstall Tailwind CSS:"));
162
- console.log(chalk.white(" npm install -D tailwindcss postcss autoprefixer"));
214
+ console.log(chalk.white(` ${pm} ${pm === "npm" ? "install" : "add"} -D tailwindcss postcss autoprefixer`));
163
215
  console.log(chalk.white(" npx tailwindcss init -p"));
164
216
  }
165
217
  console.log(chalk.dim("\nOr create a new Next.js project:"));
166
218
  console.log(chalk.white(" npx create-next-app@latest --typescript --tailwind\n"));
167
219
  }
168
220
  async function autoFixSetup(projectRoot, needsTypeScript, needsTailwind, spinner) {
221
+ const pm = detectPackageManager();
222
+ const installCmd = pm === "npm" ? "install" : "add";
169
223
  if (needsTypeScript) {
170
224
  spinner.text = "Installing TypeScript and types...";
171
- execSync("npm install -D typescript @types/node @types/react", { stdio: "inherit" });
225
+ execSync(`${pm} ${installCmd} -D typescript @types/node @types/react`, { stdio: "inherit" });
172
226
  }
173
227
  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 {
228
+ const tsconfig = parseJsonFile(tsconfigPath) ?? { compilerOptions: {} };
229
+ if (!existsSync(tsconfigPath)) {
182
230
  spinner.text = "Creating tsconfig.json...";
183
- tsconfig = { compilerOptions: {} };
184
231
  writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), "utf-8");
185
232
  }
186
- tsconfig.compilerOptions = tsconfig.compilerOptions || {};
187
- tsconfig.compilerOptions.baseUrl = tsconfig.compilerOptions.baseUrl || ".";
188
- tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
233
+ tsconfig.compilerOptions ??= {};
234
+ tsconfig.compilerOptions.baseUrl ??= ".";
235
+ tsconfig.compilerOptions.paths ??= {};
189
236
  tsconfig.compilerOptions.paths["@/components/*"] = ["components/*"];
190
237
  tsconfig.compilerOptions.paths["@/lib/*"] = ["lib/*"];
191
238
  writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2), "utf-8");
192
239
  if (needsTailwind) {
193
240
  spinner.text = "Installing Tailwind CSS...";
194
- execSync("npm install -D tailwindcss postcss autoprefixer", { stdio: "inherit" });
241
+ execSync(`${pm} ${installCmd} -D tailwindcss postcss autoprefixer`, { stdio: "inherit" });
195
242
  spinner.text = "Initializing Tailwind config...";
196
- execSync("npx tailwindcss init -p", { stdio: "inherit" });
243
+ try {
244
+ execSync("npx tailwindcss init -p", { stdio: "inherit" });
245
+ } catch {
246
+ console.warn(chalk.yellow("\nWarning: `npx tailwindcss init -p` failed. Creating default config files instead."));
247
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
248
+ export default {
249
+ content: [
250
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
251
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
252
+ './pages/**/*.{js,ts,jsx,tsx,mdx}'
253
+ ],
254
+ theme: {
255
+ extend: {},
256
+ },
257
+ plugins: [],
258
+ };
259
+ `;
260
+ const postcssConfig = `export default {
261
+ plugins: {
262
+ tailwindcss: {},
263
+ autoprefixer: {},
264
+ },
265
+ };
266
+ `;
267
+ const globalsCssDir = join(projectRoot, "app");
268
+ const globalsCssPath = join(globalsCssDir, "globals.css");
269
+ try {
270
+ writeFileSync(join(projectRoot, "tailwind.config.js"), tailwindConfig, "utf-8");
271
+ writeFileSync(join(projectRoot, "postcss.config.js"), postcssConfig, "utf-8");
272
+ if (!existsSync(globalsCssDir)) {
273
+ mkdirSync(globalsCssDir, { recursive: true });
274
+ }
275
+ if (!existsSync(globalsCssPath)) {
276
+ const globals = `@tailwind base;
277
+ @tailwind components;
278
+ @tailwind utilities;
279
+ `;
280
+ writeFileSync(globalsCssPath, globals, "utf-8");
281
+ }
282
+ console.log(chalk.green("Created tailwind.config.js, postcss.config.js, and app/globals.css"));
283
+ } catch (writeErr) {
284
+ const errorMessage = writeErr instanceof Error ? writeErr.message : String(writeErr);
285
+ console.warn(chalk.yellow(`Failed to write fallback Tailwind files: ${errorMessage}`));
286
+ throw writeErr;
287
+ }
288
+ }
197
289
  }
198
290
  }
199
291
  var COMPONENT_DESCRIPTIONS = {
@@ -205,7 +297,7 @@ var COMPONENT_DESCRIPTIONS = {
205
297
  "circle-reveal-button": "Icon to pill expansion - the element of surprise"
206
298
  };
207
299
  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");
300
+ program.name("zerozeeker").description("CLI for installing ZeroZeeker UI components - because life is too short for boring interfaces").version("2.3.0");
209
301
  program.command("init").description("Initialize ZeroZeeker in your project").action(async () => {
210
302
  const spinner = ora("Checking project setup...").start();
211
303
  try {
@@ -236,9 +328,7 @@ program.command("init").description("Initialize ZeroZeeker in your project").act
236
328
  }
237
329
  console.log(chalk.yellow("\n[!] Some setup issues detected\n"));
238
330
  displaySetupHelp(checks);
239
- const needsTypeScript = !checks.find(
240
- (c) => c.name === "TypeScript" || c.name === "TypeScript paths" || c.name === "TypeScript config"
241
- )?.passed;
331
+ const needsTypeScript = !checks.find((c) => c.name.startsWith("TypeScript"))?.passed;
242
332
  const needsTailwind = !checks.find((c) => c.name === "Tailwind CSS")?.passed;
243
333
  const doAuto = await askYesNo("Would you like ZeroZeeker to try to automatically fix these issues now?", true);
244
334
  if (!doAuto) {
@@ -267,7 +357,7 @@ program.command("init").description("Initialize ZeroZeeker in your project").act
267
357
  process.exit(1);
268
358
  }
269
359
  });
270
- program.command("add <component>").description("Add a component from ZeroZeeker registry").action(async (component) => {
360
+ program.command("add <component>").description("Add a component from ZeroZeeker registry").option("-f, --force", "Overwrite existing files").action(async (component, options) => {
271
361
  if (!COMPONENTS.includes(component)) {
272
362
  console.error(chalk.red(`[x] Component "${component}" does not exist in this dimension.`));
273
363
  console.log(chalk.dim("\nHere are the components that actually exist:"));
@@ -284,9 +374,9 @@ program.command("add <component>").description("Add a component from ZeroZeeker
284
374
  spinner.text = `Fetching ${chalk.cyan(component)} from registry...`;
285
375
  const registry = await fetchRegistry(url);
286
376
  const filesInstalled = [];
287
- const allDependencies = [...registry.dependencies || []];
288
- if (registry.registryDependencies && registry.registryDependencies.length > 0) {
289
- spinner.text = `Installing registry dependencies...`;
377
+ const allDependencies = [...registry.dependencies ?? []];
378
+ if (registry.registryDependencies?.length) {
379
+ spinner.text = "Installing registry dependencies...";
290
380
  const registryResult = await installRegistryDependencies(
291
381
  registry.registryDependencies,
292
382
  projectRoot
@@ -294,18 +384,18 @@ program.command("add <component>").description("Add a component from ZeroZeeker
294
384
  filesInstalled.push(...registryResult.files);
295
385
  allDependencies.push(...registryResult.npmDeps);
296
386
  }
297
- if (registry.files && registry.files.length > 0) {
387
+ if (registry.files?.length) {
298
388
  spinner.text = `Installing ${chalk.cyan(component)} files...`;
299
389
  for (const file of registry.files) {
300
390
  const targetPath = file.target || file.path;
301
391
  const fullPath = join(projectRoot, targetPath);
302
- if (existsSync(fullPath)) {
392
+ if (existsSync(fullPath) && !options.force) {
303
393
  spinner.stop();
304
394
  console.warn(chalk.yellow(`
305
395
  [!] File already exists: ${targetPath}`));
306
396
  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
- `));
397
+ console.log(chalk.dim(" Use --force flag to overwrite existing files.\n"));
398
+ spinner.start();
309
399
  continue;
310
400
  }
311
401
  ensureDir(fullPath);
@@ -323,15 +413,15 @@ program.command("add <component>").description("Add a component from ZeroZeeker
323
413
  [+] Successfully installed ${component}`));
324
414
  if (filesInstalled.length > 0) {
325
415
  console.log(chalk.dim("\n Files added:"));
326
- filesInstalled.forEach((f) => {
416
+ for (const f of filesInstalled) {
327
417
  console.log(chalk.cyan(` ${f}`));
328
- });
418
+ }
329
419
  }
330
420
  if (uniqueDeps.length > 0) {
331
421
  console.log(chalk.dim("\n Dependencies installed:"));
332
- uniqueDeps.forEach((d) => {
422
+ for (const d of uniqueDeps) {
333
423
  console.log(chalk.cyan(` ${d}`));
334
- });
424
+ }
335
425
  }
336
426
  console.log(chalk.dim("\n Now go make something beautiful.\n"));
337
427
  } catch (error) {
@@ -341,7 +431,7 @@ program.command("add <component>").description("Add a component from ZeroZeeker
341
431
  if (error instanceof Error) {
342
432
  if (error.message.includes("package.json")) {
343
433
  console.log(chalk.dim("\n Make sure you're in a React/Next.js project directory."));
344
- } else if (error.message.includes("fetch")) {
434
+ } else if (error.message.includes("fetch") || error.name === "AbortError") {
345
435
  console.log(chalk.dim("\n Check your internet connection and try again."));
346
436
  } else {
347
437
  console.log(chalk.dim(`
@@ -355,11 +445,12 @@ program.command("add <component>").description("Add a component from ZeroZeeker
355
445
  program.command("list").description("List all available components").action(() => {
356
446
  console.log(chalk.bold("\nZeroZeeker UI Components\n"));
357
447
  console.log(chalk.dim("Modern, polished components that make users actually want to interact.\n"));
358
- COMPONENTS.filter((c) => c !== "index").forEach((component) => {
448
+ for (const component of COMPONENTS) {
449
+ if (component === "index") continue;
359
450
  console.log(chalk.cyan(` ${component}`));
360
451
  console.log(chalk.dim(` ${COMPONENT_DESCRIPTIONS[component]}
361
452
  `));
362
- });
453
+ }
363
454
  console.log(chalk.dim("Install any component with: ") + chalk.white("npx zerozeeker add <component>"));
364
455
  console.log(chalk.dim("More components coming soon. Ship fast. Look good doing it.\n"));
365
456
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerozeeker",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "Zero-config CLI for installing ZeroZeeker UI components. No shadcn required.",
5
5
  "type": "module",
6
6
  "bin": {