nxds-cli 0.6.0 → 0.7.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 +296 -55
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // ../claude-design/src/theme-fields.ts
4
+ var isExportThemeFlag = (flag) => flag !== "mode" && flag !== "icons";
5
+ function shellValue(value) {
6
+ if (/^[\w.-]+$/.test(value)) return value;
7
+ return `'${value.replaceAll("'", `'\\''`)}'`;
8
+ }
9
+ function formatThemeFlags(flags) {
10
+ return flags.map(({ flag, value }) => `--${flag} ${shellValue(value)}`).join(" ");
11
+ }
12
+
3
13
  // ../../node_modules/culori/src/rgb/parseNumber.js
4
14
  var parseNumber = (color, len) => {
5
15
  if (typeof color !== "number") return;
@@ -3829,6 +3839,9 @@ for (const pair of APCA_PAIRS) {
3829
3839
  group.pairs.push(pair);
3830
3840
  }
3831
3841
 
3842
+ // ../core/src/lib/typography-scale.ts
3843
+ var TYPOGRAPHY_BASE_PX = 14;
3844
+
3832
3845
  // ../core/src/lib/appearance-model.ts
3833
3846
  var DEFAULT_BRAND_COLOR = "#0a0a0a";
3834
3847
  var BASE_TONE_OPTIONS = [
@@ -3879,7 +3892,7 @@ var DEFAULT_NXDS_APPEARANCE = {
3879
3892
  uiFont: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
3880
3893
  headingFont: null,
3881
3894
  codeFont: 'ui-monospace, "SF Mono", Menlo, monospace',
3882
- uiFontSize: 14,
3895
+ uiFontSize: TYPOGRAPHY_BASE_PX,
3883
3896
  codeFontSize: 12,
3884
3897
  reduceMotion: false,
3885
3898
  pointerCursors: false,
@@ -4543,7 +4556,7 @@ async function fetchRelease(registry, version) {
4543
4556
  if (isNewerSchema(value)) {
4544
4557
  const release = version === "latest" ? "The latest nxds release" : `nxds ${version}`;
4545
4558
  throw new CliError(
4546
- `${release} needs a newer nxds-cli than ${"0.6.0"}. Run \`${CLI_COMMAND}\` to get the newest one.`
4559
+ `${release} needs a newer nxds-cli than ${"0.7.0"}. Run \`${CLI_COMMAND}\` to get the newest one.`
4547
4560
  );
4548
4561
  }
4549
4562
  const invalid2 = (problems) => new CliError(
@@ -6123,14 +6136,18 @@ function report(checks) {
6123
6136
  // ../claude-design/src/system-index.ts
6124
6137
  var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
6125
6138
  var PAGE_PATHS = ["manifest.json", "api/"];
6126
- function mergeSystemIndex(current, exported) {
6127
- return {
6128
- ...current,
6139
+ function mergeSystemIndex(current, exported, { retitle }) {
6140
+ const { theme: _theme, ...kept } = current;
6141
+ const merged = {
6142
+ ...kept,
6129
6143
  namespace: exported.namespace,
6130
6144
  editing: exported.editing,
6131
6145
  libraries: exported.libraries,
6132
6146
  lastChange: exported.lastChange
6133
6147
  };
6148
+ if (retitle) merged["title"] = exported.title;
6149
+ if (exported.theme) merged["theme"] = exported.theme;
6150
+ return merged;
6134
6151
  }
6135
6152
  function isPageGenerated(file) {
6136
6153
  return PAGE_PATHS.some((page) => file === page || file.startsWith(page));
@@ -6179,6 +6196,7 @@ var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
6179
6196
  var DEFAULT_EXPORT_DIR = "nxds-design-system";
6180
6197
  var FETCH_TIMEOUT_MS2 = 6e4;
6181
6198
  var dispatcher2 = new EnvHttpProxyAgent2();
6199
+ var THEME_FILE = "theme.js";
6182
6200
  function redactUrl(url) {
6183
6201
  const redacted = httpUrl(url);
6184
6202
  if (!redacted) return "<invalid URL>";
@@ -6310,6 +6328,11 @@ function readPaths(file) {
6310
6328
  function stalePaths(existing, exported) {
6311
6329
  return existing.filter((file) => file.startsWith("project/")).filter((file) => !isPageGenerated(file.slice("project/".length))).filter((file) => !exported.has(file)).sort();
6312
6330
  }
6331
+ var EXPORT_NAMES = /* @__PURE__ */ new Set(["project", "files.json", "removed.json"]);
6332
+ var isRootName = (name) => name !== "" && name !== "." && name !== ".." && path10.basename(name) === name && !EXPORT_NAMES.has(name);
6333
+ var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
6334
+ var isEntry = (value) => typeof value?.path === "string" && typeof value.sha256 === "string";
6335
+ var isEntryList = (value) => Array.isArray(value) && value.every(isEntry);
6313
6336
  function readManifest(root) {
6314
6337
  const file = path10.join(root, "files.json");
6315
6338
  if (!existsSync9(file)) {
@@ -6318,66 +6341,226 @@ function readManifest(root) {
6318
6341
  );
6319
6342
  }
6320
6343
  const value = readJson(file);
6321
- if (!Array.isArray(value) || !value.every(
6322
- (entry) => typeof entry?.path === "string" && typeof entry.sha256 === "string"
6323
- )) {
6344
+ if (!isEntryList(value?.project) || !isEntryList(value.root) || !(value.theme === void 0 || isEntry(value.theme) && value.theme.path === THEME_FILE)) {
6324
6345
  throw new CliError(
6325
- `${file} must be a JSON array of { path, sha256 } entries.`
6346
+ `${file} must hold project and root, each a JSON array of { path, sha256 } entries, and may hold theme, the { path, sha256 } entry for ${THEME_FILE}.`
6326
6347
  );
6327
6348
  }
6328
- return value;
6349
+ return { project: value.project, root: value.root, theme: value.theme };
6329
6350
  }
6330
- function readProject2(root) {
6331
- const projectDir = path10.join(root, "project");
6351
+ function readEntries(dir, entries, prefix) {
6332
6352
  const files = /* @__PURE__ */ new Map();
6333
- for (const entry of readManifest(root)) {
6334
- const file = path10.join(projectDir, entry.path);
6335
- const relative = path10.relative(projectDir, file);
6336
- if (relative.split(path10.sep)[0] === ".." || path10.isAbsolute(relative)) {
6337
- throw new CliError(`files.json lists ${entry.path}, outside project/.`);
6338
- }
6353
+ for (const entry of entries) {
6354
+ const file = path10.join(dir, entry.path);
6339
6355
  if (!existsSync9(file) || !lstatSync(file).isFile()) {
6340
6356
  throw new CliError(
6341
- `project/${entry.path} is missing from the release, or isn't a regular file.`
6357
+ `${prefix}${entry.path} is missing from the release, or isn't a regular file.`
6342
6358
  );
6343
6359
  }
6344
6360
  const bytes = readFileSync5(file);
6345
- if (createHash("sha256").update(bytes).digest("hex") !== entry.sha256) {
6361
+ if (sha256(bytes) !== entry.sha256) {
6346
6362
  throw new CliError(
6347
- `project/${entry.path} doesn't match the release's files.json, so nothing was written.`
6363
+ `${prefix}${entry.path} doesn't match the release's files.json, so nothing was written.`
6348
6364
  );
6349
6365
  }
6350
6366
  files.set(entry.path, bytes);
6351
6367
  }
6352
6368
  return files;
6353
6369
  }
6354
- var ROOT_FILES = ["coverage.md", "SETUP.md"];
6355
- function readRootFiles(root) {
6356
- const files = /* @__PURE__ */ new Map();
6357
- for (const name of ROOT_FILES) {
6358
- const file = path10.join(root, name);
6370
+ function readRelease(root) {
6371
+ const manifest = readManifest(root);
6372
+ const projectDir = path10.join(root, "project");
6373
+ for (const { path: file } of manifest.project) {
6374
+ const relative = path10.relative(projectDir, path10.join(projectDir, file));
6375
+ if (relative.split(path10.sep)[0] === ".." || path10.isAbsolute(relative)) {
6376
+ throw new CliError(`files.json lists ${file}, outside project/.`);
6377
+ }
6378
+ }
6379
+ for (const { path: file } of manifest.root) {
6380
+ if (!isRootName(file)) {
6381
+ throw new CliError(
6382
+ `files.json lists ${file || "an empty path"} as a root file. A root file is one name beside project/, other than project, files.json and removed.json.`
6383
+ );
6384
+ }
6385
+ }
6386
+ if (!manifest.root.some((entry) => entry.path === "SETUP.md")) {
6387
+ throw new CliError(
6388
+ "The release's files.json lists no SETUP.md, so there's no guide to follow."
6389
+ );
6390
+ }
6391
+ return {
6392
+ files: readEntries(projectDir, manifest.project, "project/"),
6393
+ rootFiles: readEntries(root, manifest.root, ""),
6394
+ themeModule: manifest.theme ? readEntries(root, [manifest.theme], "").get(THEME_FILE) : void 0
6395
+ };
6396
+ }
6397
+ function droppedRootFiles(out, rootFiles) {
6398
+ const file = path10.join(out, "files.json");
6399
+ if (!existsSync9(file)) return [];
6400
+ const value = readJson(file);
6401
+ if (!isEntryList(value?.root)) {
6402
+ throw new UsageError(
6403
+ `${file} must hold root, a JSON array of { path, sha256 } entries. Delete it, then run the command again.`
6404
+ );
6405
+ }
6406
+ return value.root.filter(
6407
+ (entry) => isRootName(entry.path) && !rootFiles.has(entry.path)
6408
+ );
6409
+ }
6410
+ function removeDroppedRootFiles(out, dropped) {
6411
+ const edited = [];
6412
+ for (const entry of dropped) {
6413
+ const file = path10.join(out, entry.path);
6359
6414
  if (!existsSync9(file)) continue;
6360
- if (!lstatSync(file).isFile()) {
6361
- throw new CliError(`The release's ${name} isn't a regular file.`);
6415
+ const unchanged = lstatSync(file).isFile() && sha256(readFileSync5(file)) === entry.sha256;
6416
+ if (unchanged) rmSync(file);
6417
+ else edited.push(entry.path);
6418
+ }
6419
+ return edited;
6420
+ }
6421
+ function writeRootFiles(out, rootFiles) {
6422
+ for (const [name, bytes] of rootFiles) {
6423
+ writeFileSync3(path10.join(out, name), bytes);
6424
+ }
6425
+ }
6426
+ var entriesOf = (files) => [...files].map(([file, bytes]) => ({ path: file, sha256: sha256(bytes) }));
6427
+ function writeManifest(out, files, rootFiles) {
6428
+ const manifest = {
6429
+ project: entriesOf(files),
6430
+ root: entriesOf(rootFiles)
6431
+ };
6432
+ writeFileSync3(
6433
+ path10.join(out, "files.json"),
6434
+ `${JSON.stringify(manifest, null, 2)}
6435
+ `
6436
+ );
6437
+ }
6438
+ function readIndex(file) {
6439
+ const index = readJson(file);
6440
+ if (typeof index !== "object" || index === null || Array.isArray(index)) {
6441
+ throw new UsageError(`${file} must be a system's design-system.json.`);
6442
+ }
6443
+ return index;
6444
+ }
6445
+ function storedTheme(index, file) {
6446
+ const theme = index["theme"];
6447
+ if (theme === void 0) return {};
6448
+ if (typeof theme !== "object" || theme === null || Array.isArray(theme) || !Object.values(theme).every((value) => typeof value === "string")) {
6449
+ throw new UsageError(
6450
+ `${file}: "theme" must be an object of theme flag values, such as {"corners": "round"}. Fix it, or remove it to export the default theme.`
6451
+ );
6452
+ }
6453
+ return theme;
6454
+ }
6455
+ function layeredTheme(stored, passed) {
6456
+ const flags = { ...stored, ...passed };
6457
+ for (const [flag, value] of Object.entries(passed)) {
6458
+ if (value === "") delete flags[flag];
6459
+ }
6460
+ return flags;
6461
+ }
6462
+ var noFlagError = (flag, version) => new UsageError(
6463
+ `nxds ${version} has no --${flag}. Pass --release latest to export a release that has it.`
6464
+ );
6465
+ function noThemeModuleError(theme, { passed, indexFile, version }) {
6466
+ const flag = Object.keys(theme).find((name) => name in passed);
6467
+ if (flag !== void 0) return noFlagError(flag, version);
6468
+ return new UsageError(
6469
+ `${indexFile} has a theme, and nxds ${version} can't theme an export. Pass --release latest to keep the theme.`
6470
+ );
6471
+ }
6472
+ function assertThemeFlags(flags, taken, { passed, indexFile, version }) {
6473
+ for (const flag of Object.keys(flags)) {
6474
+ if (taken.includes(flag)) continue;
6475
+ if (flag in passed) throw noFlagError(flag, version);
6476
+ if (flag === "mode" || flag === "icons") {
6477
+ throw new UsageError(
6478
+ `${indexFile} theme: a system carries Light and Dark and one icon library, so it has no "${flag}". Remove it from "theme".`
6479
+ );
6362
6480
  }
6363
- files.set(name, readFileSync5(file));
6481
+ throw new UsageError(
6482
+ `${indexFile} theme: "${flag}" isn't a theme flag nxds ${version} takes. Remove it from "theme".`
6483
+ );
6364
6484
  }
6365
- return files;
6366
6485
  }
6367
- function writeRootFiles(out, files) {
6368
- for (const name of ROOT_FILES) {
6369
- const bytes = files.get(name);
6370
- if (bytes) writeFileSync3(path10.join(out, name), bytes);
6371
- else rmSync(path10.join(out, name), { force: true });
6486
+ var isThemeModule = (value) => typeof value === "object" && value !== null && value.api === 1 && Array.isArray(value.flags) && typeof value.render === "function" && typeof value.title === "function";
6487
+ async function importTheme(bytes, version) {
6488
+ let imported;
6489
+ try {
6490
+ imported = await import(`data:text/javascript;base64,${bytes.toString("base64")}`);
6491
+ } catch (error) {
6492
+ throw new CliError(
6493
+ `nxds ${version}'s theme.js failed to load (${error.message}). Report it at https://github.com/nexuslabs-ai/nxds/issues.`
6494
+ );
6495
+ }
6496
+ if (!isThemeModule(imported.default)) {
6497
+ throw new CliError(
6498
+ `nxds ${version}'s theme.js is newer than this nxds-cli. Run \`npx nxds-cli@latest\` to get the newest one.`
6499
+ );
6500
+ }
6501
+ return imported.default;
6502
+ }
6503
+ var isRendered = (value) => {
6504
+ const rendered = value;
6505
+ return typeof rendered?.guide === "function" && typeof rendered.index?.title === "string" && typeof rendered.index.theme === "object" && ["tokens.json", "tokens.css", "README.md"].every(
6506
+ (file) => typeof rendered.project?.[file] === "string"
6507
+ );
6508
+ };
6509
+ function themeError(error, { passed, indexFile, version }) {
6510
+ const { message, flag } = error;
6511
+ if (typeof flag === "string" && flag in passed) {
6512
+ return new UsageError(message);
6513
+ }
6514
+ if (typeof flag === "string" && indexFile !== void 0) {
6515
+ return new UsageError(
6516
+ `${indexFile} theme: ${message} Pass --${flag} <value> to change it, or --${flag}= to reset it.`
6517
+ );
6518
+ }
6519
+ return new CliError(`nxds ${version}'s theme.js failed: ${message}`);
6520
+ }
6521
+ function renderTheme(theme, flags, source) {
6522
+ const { version } = source;
6523
+ let rendered;
6524
+ try {
6525
+ rendered = theme.render(flags);
6526
+ } catch (error) {
6527
+ throw themeError(error, source);
6528
+ }
6529
+ if (!isRendered(rendered)) {
6530
+ throw new CliError(
6531
+ `nxds ${version}'s theme.js rendered no token files, README or setup guide.`
6532
+ );
6533
+ }
6534
+ return rendered;
6535
+ }
6536
+ function storedThemeTitle(stored, module, defaultTitle) {
6537
+ if (Object.keys(stored).length === 0) return defaultTitle;
6538
+ try {
6539
+ return module?.title(stored);
6540
+ } catch {
6541
+ return void 0;
6372
6542
  }
6373
6543
  }
6374
- function mergeIndex(files, currentFile) {
6544
+ function exportedIndex(files) {
6375
6545
  const exported = files.get("design-system.json");
6376
6546
  if (!exported) throw new CliError("The release has no design-system.json.");
6377
- const merged = mergeSystemIndex(
6378
- readJson(currentFile),
6379
- JSON.parse(exported.toString("utf8"))
6547
+ return JSON.parse(exported.toString("utf8"));
6548
+ }
6549
+ var jsonBytes = (value) => Buffer.from(`${JSON.stringify(value, null, 2)}
6550
+ `);
6551
+ function applyTheme(rendered, files, rootFiles) {
6552
+ const { project } = rendered;
6553
+ files.set("tokens.json", Buffer.from(project["tokens.json"]));
6554
+ files.set("tokens.css", Buffer.from(project["tokens.css"]));
6555
+ files.set("README.md", Buffer.from(project["README.md"]));
6556
+ files.set(
6557
+ "design-system.json",
6558
+ jsonBytes({ ...exportedIndex(files), ...rendered.index })
6380
6559
  );
6560
+ rootFiles.set("SETUP.md", Buffer.from(rendered.guide(files.size)));
6561
+ }
6562
+ function mergeIndex(files, current, currentFile, retitle) {
6563
+ const merged = mergeSystemIndex(current, exportedIndex(files), { retitle });
6381
6564
  const errors = validateIndex(merged);
6382
6565
  if (errors.length > 0) {
6383
6566
  throw new UsageError(
@@ -6385,11 +6568,7 @@ function mergeIndex(files, currentFile) {
6385
6568
  ${errors.map((error) => ` ${error}`).join("\n")}`
6386
6569
  );
6387
6570
  }
6388
- files.set(
6389
- "design-system.json",
6390
- Buffer.from(`${JSON.stringify(merged, null, 2)}
6391
- `)
6392
- );
6571
+ files.set("design-system.json", jsonBytes(merged));
6393
6572
  }
6394
6573
  function writeProject(out, files) {
6395
6574
  mkdirSync2(out, { recursive: true });
@@ -6446,15 +6625,40 @@ async function exportClaudeDesign(cwd, options) {
6446
6625
  options.release ?? "latest"
6447
6626
  );
6448
6627
  try {
6449
- const files = readProject2(pkg.root);
6450
- const rootFiles = readRootFiles(pkg.root);
6451
- if (options.index) mergeIndex(files, path10.resolve(cwd, options.index));
6628
+ const { files, rootFiles, themeModule } = readRelease(pkg.root);
6629
+ const indexFile = options.index && path10.resolve(cwd, options.index);
6630
+ const current = indexFile ? readIndex(indexFile) : void 0;
6631
+ const passed = options.theme;
6632
+ const stored = current && indexFile ? storedTheme(current, indexFile) : {};
6633
+ const theme = layeredTheme(stored, passed);
6634
+ const source = {
6635
+ passed,
6636
+ indexFile: options.index,
6637
+ version: pkg.version
6638
+ };
6639
+ const defaultTitle = exportedIndex(files).title;
6640
+ const module = themeModule && (Object.keys(theme).length > 0 || Object.keys(stored).length > 0) ? await importTheme(themeModule, pkg.version) : void 0;
6641
+ let themed = {};
6642
+ if (Object.keys(theme).length > 0) {
6643
+ if (!module) throw noThemeModuleError(theme, source);
6644
+ assertThemeFlags(theme, module.flags, source);
6645
+ const rendered = renderTheme(module, theme, source);
6646
+ applyTheme(rendered, files, rootFiles);
6647
+ themed = rendered.index.theme;
6648
+ }
6649
+ if (current && indexFile) {
6650
+ const retitle = current["title"] === storedThemeTitle(stored, module, defaultTitle);
6651
+ mergeIndex(files, current, indexFile, retitle);
6652
+ }
6452
6653
  const exported = new Set(
6453
6654
  [...files.keys()].map((file) => `project/${file}`)
6454
6655
  );
6455
6656
  const removed = options.existing ? stalePaths(readPaths(path10.resolve(cwd, options.existing)), exported) : [];
6657
+ const dropped = droppedRootFiles(out, rootFiles);
6456
6658
  writeProject(out, files);
6659
+ const kept = removeDroppedRootFiles(out, dropped);
6457
6660
  writeRootFiles(out, rootFiles);
6661
+ writeManifest(out, files, rootFiles);
6458
6662
  const removedFile = path10.join(out, "removed.json");
6459
6663
  if (options.existing) {
6460
6664
  writeFileSync3(removedFile, `${JSON.stringify(removed, null, 2)}
@@ -6466,27 +6670,39 @@ async function exportClaudeDesign(cwd, options) {
6466
6670
  const lines2 = [
6467
6671
  `Exported nxds ${pkg.version} as a Claude Design System: ${files.size} files in ${path10.join(relative, "project")}.`
6468
6672
  ];
6673
+ if (Object.keys(themed).length > 0) {
6674
+ lines2.push(
6675
+ `Themed with ${formatThemeFlags(Object.entries(themed).map(([flag, value]) => ({ flag, value })))}.`
6676
+ );
6677
+ }
6469
6678
  if (rootFiles.has("coverage.md")) {
6470
6679
  lines2.push(
6471
6680
  `The props the canvas can't set are in ${path10.join(relative, "coverage.md")}.`
6472
6681
  );
6473
6682
  }
6683
+ for (const name of kept) {
6684
+ lines2.push(
6685
+ `Kept ${path10.join(relative, name)}: this release doesn't ship it, and you edited it.`
6686
+ );
6687
+ }
6474
6688
  if (removed.length > 0) {
6475
6689
  lines2.push(
6476
6690
  `Remove the ${removed.length} files the system has and this release doesn't: they're listed in ${path10.join(relative, "removed.json")}.`
6477
6691
  );
6478
6692
  }
6479
6693
  lines2.push(
6480
- rootFiles.has("SETUP.md") ? `Read ${path10.join(relative, "SETUP.md")} and follow it. It publishes the system, installs it on a canvas and checks it.` : `Publish ${relative} as a Design System artifact in Claude, with ${relative} as the root.`
6694
+ `Read ${path10.join(relative, "SETUP.md")} and follow it. It publishes the system, installs it on a canvas and checks it.`
6481
6695
  );
6482
6696
  return {
6483
- ok: true,
6697
+ ok: kept.length === 0,
6484
6698
  lines: lines2,
6485
6699
  data: {
6486
6700
  version: pkg.version,
6701
+ theme: themed,
6487
6702
  out,
6488
6703
  files: files.size,
6489
6704
  removed,
6705
+ kept: kept.map((name) => path10.join(out, name)),
6490
6706
  root: [...rootFiles.keys()].map((name) => path10.join(out, name))
6491
6707
  }
6492
6708
  };
@@ -6922,7 +7138,7 @@ function program(done, write) {
6922
7138
  // Errors are printed once, by `main`, as text or JSON.
6923
7139
  outputError: () => {
6924
7140
  }
6925
- }).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.6.0", "-v, --version", "print the version").helpOption("-h, --help", "show help").helpCommand("help [command]", "show help for a command").option("--json", "print machine-readable JSON").option("--cwd <path>", "run in another folder").addOption(
7141
+ }).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.7.0", "-v, --version", "print the version").helpOption("-h, --help", "show help").helpCommand("help [command]", "show help for a command").option("--json", "print machine-readable JSON").option("--cwd <path>", "run in another folder").addOption(
6926
7142
  new Option(
6927
7143
  "--registry <url|path>",
6928
7144
  "where to get nxds releases (default: https://nxds.dev/r)"
@@ -7004,13 +7220,13 @@ Docs: https://nxds.dev/getting-started/install`
7004
7220
  const { cwd, registry } = context(command);
7005
7221
  done(await diff(cwd, registry, items));
7006
7222
  });
7007
- cli.command("export").description(
7008
- "export nxds as a Claude Design System folder, ready to publish"
7223
+ const exportCommand = cli.command("export").description(
7224
+ "export nxds as a Claude Design System folder, ready to publish. Theme flags set the system's theme, as they do for init; with --index, one left out keeps the system's value, and an empty one, such as --density=, resets it"
7009
7225
  ).addArgument(
7010
7226
  new Argument("<target>", "what to export").choices(["claude-design"])
7011
7227
  ).option("--out <path>", `folder to write (default: ${DEFAULT_EXPORT_DIR})`).option("--release <version>", "nxds release to export (default: latest)").option(
7012
7228
  "--index <path>",
7013
- "the system's current design-system.json, to keep its title and creation date"
7229
+ "the system's current design-system.json, to keep its title, creation date and theme"
7014
7230
  ).option(
7015
7231
  "--existing <path>",
7016
7232
  "a JSON list of the system's current file paths, to list the ones this release removes"
@@ -7022,9 +7238,34 @@ Docs: https://nxds.dev/getting-started/install`
7022
7238
  "--npm-registry <url>",
7023
7239
  `npm registry to download from (default: ${DEFAULT_NPM_REGISTRY})`
7024
7240
  ).env("npm_config_registry")
7025
- ).action(async (_target, _options, command) => {
7241
+ );
7242
+ const exportThemeOptions = NXDS_THEME_FIELDS.filter(
7243
+ (field) => isExportThemeFlag(field.flag)
7244
+ ).map((field) => {
7245
+ const option = new Option(`--${field.flag} <value>`, field.description);
7246
+ exportCommand.addOption(option);
7247
+ return { flag: field.flag, key: option.attributeName() };
7248
+ });
7249
+ exportCommand.action(async (_target, _options, command) => {
7250
+ const options = command.opts();
7251
+ const theme = Object.fromEntries(
7252
+ exportThemeOptions.flatMap(({ flag, key }) => {
7253
+ const value = options[key];
7254
+ return value === void 0 ? [] : [[flag, value]];
7255
+ })
7256
+ );
7257
+ const { out, release, source, npmRegistry, index, existing, overwrite } = command.opts();
7026
7258
  done(
7027
- await exportClaudeDesign(cwdOf(command), command.opts())
7259
+ await exportClaudeDesign(cwdOf(command), {
7260
+ out,
7261
+ release,
7262
+ source,
7263
+ npmRegistry,
7264
+ index,
7265
+ existing,
7266
+ overwrite,
7267
+ theme
7268
+ })
7028
7269
  );
7029
7270
  });
7030
7271
  cli.command("doctor").description("check that nxds is wired up correctly").action(async (_options, command) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nxds-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Install nxds components, blocks and setup into your app.",
6
6
  "keywords": [
@@ -45,9 +45,9 @@
45
45
  "@types/semver": "^7.8.0",
46
46
  "tsup": "^8.5.1",
47
47
  "typescript": "^5.9.3",
48
- "@nxds/claude-design": "0.13.0",
49
- "@nxds/core": "0.12.0",
50
- "@nxds/catalog": "0.0.1"
48
+ "@nxds/catalog": "0.0.2",
49
+ "@nxds/claude-design": "0.14.0",
50
+ "@nxds/core": "0.13.0"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup",