nxds-cli 0.6.1 → 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 +216 -29
  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.1"}. 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>";
@@ -6313,9 +6331,8 @@ function stalePaths(existing, exported) {
6313
6331
  var EXPORT_NAMES = /* @__PURE__ */ new Set(["project", "files.json", "removed.json"]);
6314
6332
  var isRootName = (name) => name !== "" && name !== "." && name !== ".." && path10.basename(name) === name && !EXPORT_NAMES.has(name);
6315
6333
  var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
6316
- var isEntryList = (value) => Array.isArray(value) && value.every(
6317
- (entry) => typeof entry?.path === "string" && typeof entry.sha256 === "string"
6318
- );
6334
+ var isEntry = (value) => typeof value?.path === "string" && typeof value.sha256 === "string";
6335
+ var isEntryList = (value) => Array.isArray(value) && value.every(isEntry);
6319
6336
  function readManifest(root) {
6320
6337
  const file = path10.join(root, "files.json");
6321
6338
  if (!existsSync9(file)) {
@@ -6324,12 +6341,12 @@ function readManifest(root) {
6324
6341
  );
6325
6342
  }
6326
6343
  const value = readJson(file);
6327
- if (!isEntryList(value?.project) || !isEntryList(value.root)) {
6344
+ if (!isEntryList(value?.project) || !isEntryList(value.root) || !(value.theme === void 0 || isEntry(value.theme) && value.theme.path === THEME_FILE)) {
6328
6345
  throw new CliError(
6329
- `${file} must hold project and root, each 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}.`
6330
6347
  );
6331
6348
  }
6332
- return { project: value.project, root: value.root };
6349
+ return { project: value.project, root: value.root, theme: value.theme };
6333
6350
  }
6334
6351
  function readEntries(dir, entries, prefix) {
6335
6352
  const files = /* @__PURE__ */ new Map();
@@ -6373,7 +6390,8 @@ function readRelease(root) {
6373
6390
  }
6374
6391
  return {
6375
6392
  files: readEntries(projectDir, manifest.project, "project/"),
6376
- rootFiles: readEntries(root, manifest.root, "")
6393
+ rootFiles: readEntries(root, manifest.root, ""),
6394
+ themeModule: manifest.theme ? readEntries(root, [manifest.theme], "").get(THEME_FILE) : void 0
6377
6395
  };
6378
6396
  }
6379
6397
  function droppedRootFiles(out, rootFiles) {
@@ -6417,13 +6435,132 @@ function writeManifest(out, files, rootFiles) {
6417
6435
  `
6418
6436
  );
6419
6437
  }
6420
- function mergeIndex(files, currentFile) {
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
+ );
6480
+ }
6481
+ throw new UsageError(
6482
+ `${indexFile} theme: "${flag}" isn't a theme flag nxds ${version} takes. Remove it from "theme".`
6483
+ );
6484
+ }
6485
+ }
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;
6542
+ }
6543
+ }
6544
+ function exportedIndex(files) {
6421
6545
  const exported = files.get("design-system.json");
6422
6546
  if (!exported) throw new CliError("The release has no design-system.json.");
6423
- const merged = mergeSystemIndex(
6424
- readJson(currentFile),
6425
- 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 })
6426
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 });
6427
6564
  const errors = validateIndex(merged);
6428
6565
  if (errors.length > 0) {
6429
6566
  throw new UsageError(
@@ -6431,11 +6568,7 @@ function mergeIndex(files, currentFile) {
6431
6568
  ${errors.map((error) => ` ${error}`).join("\n")}`
6432
6569
  );
6433
6570
  }
6434
- files.set(
6435
- "design-system.json",
6436
- Buffer.from(`${JSON.stringify(merged, null, 2)}
6437
- `)
6438
- );
6571
+ files.set("design-system.json", jsonBytes(merged));
6439
6572
  }
6440
6573
  function writeProject(out, files) {
6441
6574
  mkdirSync2(out, { recursive: true });
@@ -6492,8 +6625,31 @@ async function exportClaudeDesign(cwd, options) {
6492
6625
  options.release ?? "latest"
6493
6626
  );
6494
6627
  try {
6495
- const { files, rootFiles } = readRelease(pkg.root);
6496
- 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
+ }
6497
6653
  const exported = new Set(
6498
6654
  [...files.keys()].map((file) => `project/${file}`)
6499
6655
  );
@@ -6514,6 +6670,11 @@ async function exportClaudeDesign(cwd, options) {
6514
6670
  const lines2 = [
6515
6671
  `Exported nxds ${pkg.version} as a Claude Design System: ${files.size} files in ${path10.join(relative, "project")}.`
6516
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
+ }
6517
6678
  if (rootFiles.has("coverage.md")) {
6518
6679
  lines2.push(
6519
6680
  `The props the canvas can't set are in ${path10.join(relative, "coverage.md")}.`
@@ -6537,6 +6698,7 @@ async function exportClaudeDesign(cwd, options) {
6537
6698
  lines: lines2,
6538
6699
  data: {
6539
6700
  version: pkg.version,
6701
+ theme: themed,
6540
6702
  out,
6541
6703
  files: files.size,
6542
6704
  removed,
@@ -6976,7 +7138,7 @@ function program(done, write) {
6976
7138
  // Errors are printed once, by `main`, as text or JSON.
6977
7139
  outputError: () => {
6978
7140
  }
6979
- }).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.6.1", "-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(
6980
7142
  new Option(
6981
7143
  "--registry <url|path>",
6982
7144
  "where to get nxds releases (default: https://nxds.dev/r)"
@@ -7058,13 +7220,13 @@ Docs: https://nxds.dev/getting-started/install`
7058
7220
  const { cwd, registry } = context(command);
7059
7221
  done(await diff(cwd, registry, items));
7060
7222
  });
7061
- cli.command("export").description(
7062
- "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"
7063
7225
  ).addArgument(
7064
7226
  new Argument("<target>", "what to export").choices(["claude-design"])
7065
7227
  ).option("--out <path>", `folder to write (default: ${DEFAULT_EXPORT_DIR})`).option("--release <version>", "nxds release to export (default: latest)").option(
7066
7228
  "--index <path>",
7067
- "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"
7068
7230
  ).option(
7069
7231
  "--existing <path>",
7070
7232
  "a JSON list of the system's current file paths, to list the ones this release removes"
@@ -7076,9 +7238,34 @@ Docs: https://nxds.dev/getting-started/install`
7076
7238
  "--npm-registry <url>",
7077
7239
  `npm registry to download from (default: ${DEFAULT_NPM_REGISTRY})`
7078
7240
  ).env("npm_config_registry")
7079
- ).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();
7080
7258
  done(
7081
- 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
+ })
7082
7269
  );
7083
7270
  });
7084
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.1",
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/catalog": "0.0.1",
49
- "@nxds/claude-design": "0.13.2",
50
- "@nxds/core": "0.12.0"
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",