@kinetixui/cli 0.12.0 → 0.14.0

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 +200 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc4 from "picocolors";
5
+ import pc7 from "picocolors";
6
6
 
7
7
  // src/commands/add.ts
8
8
  import { existsSync as existsSync4 } from "fs";
@@ -19,6 +19,7 @@ import { existsSync } from "fs";
19
19
  import { readFile, writeFile } from "fs/promises";
20
20
  import path from "path";
21
21
  var CONFIG_FILE = "kinetixui.json";
22
+ var DEFAULT_REGISTRY = "https://kinetixui.com/r";
22
23
  var DEFAULT_CONFIG = {
23
24
  $schema: "https://kinetixui.com/schema/config.json",
24
25
  tailwind: { css: "app/globals.css" },
@@ -160,6 +161,17 @@ async function fetchRegistryItem(registryUrl, name) {
160
161
  }
161
162
  return await res.json();
162
163
  }
164
+ async function fetchComponentSpec(registryUrl, name) {
165
+ const base = assertRegistryUrl(registryUrl).replace(/\/$/, "");
166
+ const specsBase = base.endsWith("/r") ? `${base.slice(0, -2)}/specs` : `${base}/specs`;
167
+ const url = `${specsBase}/${assertComponentName(name)}.json`;
168
+ const res = await fetch(url);
169
+ if (res.status === 404) return null;
170
+ if (!res.ok) {
171
+ throw new Error(`Could not load the component spec for "${name}" (${res.status} at ${url}).`);
172
+ }
173
+ return await res.json();
174
+ }
163
175
  async function resolveTree(registryUrl, names) {
164
176
  const resolved = /* @__PURE__ */ new Map();
165
177
  const queue = [...names];
@@ -203,15 +215,88 @@ async function add(names, options) {
203
215
  console.log(pc.green("Done."));
204
216
  }
205
217
 
206
- // src/commands/init.ts
218
+ // src/commands/doctor.ts
207
219
  import { existsSync as existsSync5 } from "fs";
220
+ import { readdir } from "fs/promises";
208
221
  import path5 from "path";
209
222
  import pc2 from "picocolors";
223
+ var ICON = { pass: pc2.green("\u2714"), warn: pc2.yellow("!"), fail: pc2.red("\u2716") };
224
+ function report(level, message) {
225
+ console.log(ICON[level], message);
226
+ return level;
227
+ }
228
+ async function doctor() {
229
+ const cwd = process.cwd();
230
+ const levels = [];
231
+ let config = null;
232
+ if (!existsSync5(path5.join(cwd, CONFIG_FILE))) {
233
+ levels.push(report("fail", `No ${CONFIG_FILE} found \u2014 run "kinetixui init" first.`));
234
+ } else {
235
+ try {
236
+ config = await readConfig(cwd);
237
+ levels.push(report("pass", `${CONFIG_FILE} found and parses.`));
238
+ } catch {
239
+ levels.push(report("fail", `${CONFIG_FILE} exists but isn't valid JSON.`));
240
+ }
241
+ }
242
+ if (config) {
243
+ for (const alias of ["components", "ui", "lib"]) {
244
+ const dir = resolveAliasDir(cwd, config, alias);
245
+ if (existsSync5(dir)) {
246
+ levels.push(report("pass", `"${alias}" alias resolves to ${path5.relative(cwd, dir) || "."}`));
247
+ } else {
248
+ levels.push(report("warn", `"${alias}" alias points to ${path5.relative(cwd, dir) || "."}, which doesn't exist yet.`));
249
+ }
250
+ }
251
+ const cssPath = path5.join(cwd, config.srcDir ? "src" : "", config.tailwind.css);
252
+ if (existsSync5(cssPath)) {
253
+ levels.push(report("pass", `Tailwind CSS file found at ${path5.relative(cwd, cssPath)}`));
254
+ } else {
255
+ levels.push(report("warn", `Tailwind CSS file not found at ${path5.relative(cwd, cssPath)} \u2014 the token contract has nowhere to land.`));
256
+ }
257
+ }
258
+ let registryNames = null;
259
+ try {
260
+ const items = await fetchRegistryIndex(DEFAULT_REGISTRY);
261
+ registryNames = new Set(items.filter((item) => item.type === "registry:ui").map((item) => item.name));
262
+ levels.push(report("pass", `Registry reachable (${DEFAULT_REGISTRY}) \u2014 ${registryNames.size} component(s) listed.`));
263
+ } catch (err) {
264
+ levels.push(report("fail", `Registry unreachable at ${DEFAULT_REGISTRY}: ${err.message}`));
265
+ }
266
+ if (config && registryNames) {
267
+ const uiDir = resolveAliasDir(cwd, config, "ui");
268
+ if (existsSync5(uiDir)) {
269
+ const files = (await readdir(uiDir)).filter((f) => f.endsWith(".tsx"));
270
+ const unknown = files.filter((f) => !registryNames.has(f.replace(/\.tsx$/, "")));
271
+ if (unknown.length === 0) {
272
+ levels.push(report("pass", `Every .tsx file in ${path5.relative(cwd, uiDir)} matches a name in the registry.`));
273
+ } else {
274
+ levels.push(
275
+ report(
276
+ "warn",
277
+ `${unknown.length} file(s) in ${path5.relative(cwd, uiDir)} don't match any current registry name (hand-written, or removed from the registry since you added them): ${unknown.join(", ")}`
278
+ )
279
+ );
280
+ }
281
+ }
282
+ }
283
+ const passed = levels.filter((l) => l === "pass").length;
284
+ const warned = levels.filter((l) => l === "warn").length;
285
+ const failed = levels.filter((l) => l === "fail").length;
286
+ console.log();
287
+ console.log(`${passed} passed, ${warned} warned, ${failed} failed`);
288
+ if (failed > 0) process.exitCode = 1;
289
+ }
290
+
291
+ // src/commands/init.ts
292
+ import { existsSync as existsSync6 } from "fs";
293
+ import path6 from "path";
294
+ import pc3 from "picocolors";
210
295
  import prompts from "prompts";
211
296
  async function init(options) {
212
297
  const cwd = process.cwd();
213
- const configPath = path5.join(cwd, CONFIG_FILE);
214
- if (existsSync5(configPath) && !options.yes) {
298
+ const configPath = path6.join(cwd, CONFIG_FILE);
299
+ if (existsSync6(configPath) && !options.yes) {
215
300
  const { proceed } = await prompts({
216
301
  type: "confirm",
217
302
  name: "proceed",
@@ -223,7 +308,7 @@ async function init(options) {
223
308
  return;
224
309
  }
225
310
  }
226
- const srcDirDetected = existsSync5(path5.join(cwd, "src"));
311
+ const srcDirDetected = existsSync6(path6.join(cwd, "src"));
227
312
  const answers = options.yes ? {
228
313
  srcDir: srcDirDetected,
229
314
  css: DEFAULT_CONFIG.tailwind.css,
@@ -273,38 +358,110 @@ async function init(options) {
273
358
  }
274
359
  };
275
360
  await writeConfig(cwd, config);
276
- console.log(pc2.green("\u2714"), `Created ${CONFIG_FILE}`);
361
+ console.log(pc3.green("\u2714"), `Created ${CONFIG_FILE}`);
277
362
  const items = await resolveTree(options.registry, ["tokens"]);
278
363
  const { written, skipped } = await writeItems(cwd, config, items.values(), false);
279
- for (const file of written) console.log(pc2.green("\u2714"), `Wrote ${file}`);
364
+ for (const file of written) console.log(pc3.green("\u2714"), `Wrote ${file}`);
280
365
  for (const file of skipped) {
281
- console.log(pc2.yellow("!"), `${file} already exists \u2014 merge the token contract in by hand, or delete it and re-run init.`);
366
+ console.log(pc3.yellow("!"), `${file} already exists \u2014 merge the token contract in by hand, or delete it and re-run init.`);
282
367
  }
283
368
  console.log();
284
- console.log(`Next: ${pc2.cyan("npx @kinetixui/cli add button")}`);
285
- console.log(`See everything: ${pc2.cyan("npx @kinetixui/cli list")}`);
369
+ console.log(`Next: ${pc3.cyan("npx @kinetixui/cli add button")}`);
370
+ console.log(`See everything: ${pc3.cyan("npx @kinetixui/cli list")}`);
371
+ }
372
+
373
+ // src/commands/inspect.ts
374
+ import { existsSync as existsSync7 } from "fs";
375
+ import path7 from "path";
376
+ import pc4 from "picocolors";
377
+ async function inspect(name, options) {
378
+ const item = await fetchRegistryItem(options.registry, name);
379
+ console.log(`${pc4.bold(item.title ?? item.name)} ${pc4.dim(`(${item.name})`)}`);
380
+ if (item.description) console.log(item.description);
381
+ console.log();
382
+ console.log(`${pc4.dim("Type")} ${item.type}`);
383
+ const cwd = process.cwd();
384
+ const config = await readConfig(cwd);
385
+ if (config) {
386
+ const uiDir = resolveAliasDir(cwd, config, "ui");
387
+ const installed = item.files.some(
388
+ (file) => file.type === "registry:ui" && existsSync7(path7.join(uiDir, path7.basename(file.target)))
389
+ );
390
+ console.log(`${pc4.dim("Installed")} ${installed ? pc4.green("yes") : pc4.yellow("no")}`);
391
+ }
392
+ console.log(
393
+ `${pc4.dim("Depends on")} ${item.dependencies?.length ? item.dependencies.join(", ") : pc4.dim("(none)")}`
394
+ );
395
+ console.log(
396
+ `${pc4.dim("Registry")} ${item.registryDependencies?.length ? item.registryDependencies.join(", ") : pc4.dim("(none)")}`
397
+ );
398
+ console.log();
399
+ console.log(pc4.dim("Files:"));
400
+ for (const file of item.files) {
401
+ console.log(` ${file.path} ${pc4.dim("\u2192")} ${file.target}`);
402
+ }
403
+ const spec = await fetchComponentSpec(options.registry, name);
404
+ if (spec) {
405
+ console.log();
406
+ console.log(pc4.dim("Variants:"));
407
+ for (const [axis, options_] of Object.entries(spec.variants)) {
408
+ console.log(` ${pc4.dim(axis)} ${options_.join(", ")}`);
409
+ }
410
+ }
411
+ console.log();
412
+ console.log(`Add it: ${pc4.cyan(`npx @kinetixui/cli add ${item.name}`)}`);
286
413
  }
287
414
 
288
415
  // src/commands/list.ts
289
- import pc3 from "picocolors";
416
+ import pc5 from "picocolors";
290
417
  async function list(options) {
291
418
  const items = await fetchRegistryIndex(options.registry);
292
419
  const components = items.filter((item) => item.type === "registry:ui").sort((a, b) => a.name.localeCompare(b.name));
293
420
  console.log(`${components.length} component(s):`);
294
421
  console.log();
295
422
  for (const item of components) {
296
- const description = item.description ? pc3.dim(` \u2014 ${item.description}`) : "";
297
- console.log(` ${pc3.cyan(item.name)}${description}`);
423
+ const description = item.description ? pc5.dim(` \u2014 ${item.description}`) : "";
424
+ console.log(` ${pc5.cyan(item.name)}${description}`);
425
+ }
426
+ console.log();
427
+ console.log(`Add one: ${pc5.cyan("npx @kinetixui/cli add <name>")}`);
428
+ console.log(`Add everything: ${pc5.cyan("npx @kinetixui/cli add --all")}`);
429
+ }
430
+
431
+ // src/commands/parity.ts
432
+ import pc6 from "picocolors";
433
+ var PLATFORMS = ["React", "SwiftUI", "Compose", "Flutter"];
434
+ var PLATFORM_ABBR = {
435
+ React: "RE",
436
+ SwiftUI: "SW",
437
+ Compose: "JC",
438
+ Flutter: "FL"
439
+ };
440
+ async function parity(names, options) {
441
+ const items = await fetchRegistryIndex(options.registry);
442
+ const components = items.filter((item) => item.type === "registry:ui").sort((a, b) => a.name.localeCompare(b.name));
443
+ const rows = names.length ? names.map((name) => {
444
+ const item = components.find((c) => c.name === name);
445
+ if (!item) throw new Error(`"${name}" isn't in the registry. Run "kinetixui list" to see what's available.`);
446
+ return item;
447
+ }) : components;
448
+ const nameWidth = Math.max(9, ...rows.map((r) => r.name.length));
449
+ const header = `${"COMPONENT".padEnd(nameWidth)} ${PLATFORMS.map((p) => PLATFORM_ABBR[p]).join(" ")} STATUS`;
450
+ console.log(pc6.dim(header));
451
+ for (const item of rows) {
452
+ const on = new Set(item.platforms ?? PLATFORMS);
453
+ const marks = PLATFORMS.map((p) => on.has(p) ? pc6.green("\u2713 ") : pc6.dim("\u2014 "));
454
+ const status = item.status ? pc6.yellow(item.status) : "";
455
+ console.log(`${item.name.padEnd(nameWidth)} ${marks.join(" ")} ${status}`);
298
456
  }
299
457
  console.log();
300
- console.log(`Add one: ${pc3.cyan("npx @kinetixui/cli add <name>")}`);
301
- console.log(`Add everything: ${pc3.cyan("npx @kinetixui/cli add --all")}`);
458
+ console.log(`${rows.length} of ${components.length} component(s) shown.`);
302
459
  }
303
460
 
304
461
  // package.json
305
462
  var package_default = {
306
463
  name: "@kinetixui/cli",
307
- version: "0.12.0",
464
+ version: "0.14.0",
308
465
  description: "Add KinetixUI components and tokens to your project.",
309
466
  type: "module",
310
467
  license: "MIT",
@@ -342,14 +499,13 @@ var package_default = {
342
499
  };
343
500
 
344
501
  // src/index.ts
345
- var DEFAULT_REGISTRY = "https://kinetixui.com/r";
346
502
  var program = new Command();
347
503
  program.name("kinetixui").description("Add KinetixUI components and tokens to your project.").version(package_default.version);
348
504
  program.command("init").description("Set up kinetixui.json and pull in the token contract.").option("-y, --yes", "skip prompts and use defaults", false).option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (opts) => {
349
505
  try {
350
506
  await init({ registry: opts.registry, yes: opts.yes });
351
507
  } catch (err) {
352
- console.error(pc4.red("\u2716"), err.message);
508
+ console.error(pc7.red("\u2716"), err.message);
353
509
  process.exitCode = 1;
354
510
  }
355
511
  });
@@ -357,7 +513,7 @@ program.command("add").description("Add one or more components to your project."
357
513
  try {
358
514
  await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
359
515
  } catch (err) {
360
- console.error(pc4.red("\u2716"), err.message);
516
+ console.error(pc7.red("\u2716"), err.message);
361
517
  process.exitCode = 1;
362
518
  }
363
519
  });
@@ -365,7 +521,31 @@ program.command("list").description("List every component available in the regis
365
521
  try {
366
522
  await list({ registry: opts.registry });
367
523
  } catch (err) {
368
- console.error(pc4.red("\u2716"), err.message);
524
+ console.error(pc7.red("\u2716"), err.message);
525
+ process.exitCode = 1;
526
+ }
527
+ });
528
+ program.command("inspect").description("Show everything the registry knows about one component.").argument("<name>", "component name, e.g. button").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (name, opts) => {
529
+ try {
530
+ await inspect(name, { registry: opts.registry });
531
+ } catch (err) {
532
+ console.error(pc7.red("\u2716"), err.message);
533
+ process.exitCode = 1;
534
+ }
535
+ });
536
+ program.command("parity").description("Show which platforms carry each component (or one, by name).").argument("[components...]", "component names, e.g. button card \u2014 omit for the full matrix").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (components, opts) => {
537
+ try {
538
+ await parity(components, { registry: opts.registry });
539
+ } catch (err) {
540
+ console.error(pc7.red("\u2716"), err.message);
541
+ process.exitCode = 1;
542
+ }
543
+ });
544
+ program.command("doctor").description("Check kinetixui.json, its aliases, and the registry connection.").action(async () => {
545
+ try {
546
+ await doctor();
547
+ } catch (err) {
548
+ console.error(pc7.red("\u2716"), err.message);
369
549
  process.exitCode = 1;
370
550
  }
371
551
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinetixui/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Add KinetixUI components and tokens to your project.",
5
5
  "type": "module",
6
6
  "license": "MIT",