nxds-cli 0.3.0 → 0.4.1
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.
- package/dist/index.js +420 -23
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command, CommanderError, Option } from "commander";
|
|
5
|
-
import
|
|
4
|
+
import { Argument, Command, CommanderError, Option } from "commander";
|
|
5
|
+
import path13 from "path";
|
|
6
6
|
|
|
7
7
|
// ../catalog/scripts/catalog-schema.ts
|
|
8
8
|
import path from "path";
|
|
@@ -178,17 +178,42 @@ function readJson(file) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// src/url.ts
|
|
182
|
+
function httpUrl(value) {
|
|
183
|
+
const url = URL.canParse(value) ? new URL(value) : void 0;
|
|
184
|
+
return url?.protocol === "http:" || url?.protocol === "https:" ? url : void 0;
|
|
185
|
+
}
|
|
186
|
+
function assertRegistryUrl(flag, value, privateRegistryHint) {
|
|
187
|
+
const url = httpUrl(value);
|
|
188
|
+
if (!url) {
|
|
189
|
+
throw new UsageError(
|
|
190
|
+
`${flag} is not a valid http or https URL. ${privateRegistryHint}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (url.username || url.password || /[?#]/.test(value)) {
|
|
194
|
+
throw new UsageError(
|
|
195
|
+
`${flag} can't include a user name, password, query or fragment. nxds-cli sends no registry login, and it adds file paths to the URL. ${privateRegistryHint}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
181
200
|
// src/catalog.ts
|
|
182
201
|
var SETUP = "setup";
|
|
183
202
|
var DEFAULT_REGISTRY = "https://nxds.dev/r";
|
|
184
203
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
185
204
|
var dispatcher = new EnvHttpProxyAgent();
|
|
186
205
|
function isUrl(registry) {
|
|
187
|
-
return
|
|
206
|
+
return /^\s*[a-z][a-z\d+.-]+:/i.test(registry);
|
|
188
207
|
}
|
|
189
208
|
function resolveRegistry(option) {
|
|
190
209
|
if (!option) return DEFAULT_REGISTRY;
|
|
191
|
-
|
|
210
|
+
if (!isUrl(option)) return path2.resolve(option);
|
|
211
|
+
assertRegistryUrl(
|
|
212
|
+
"--registry (or NXDS_REGISTRY_URL)",
|
|
213
|
+
option,
|
|
214
|
+
"For a private registry, download its latest.json and <version>.json files to a folder, and pass the folder with --registry."
|
|
215
|
+
);
|
|
216
|
+
return option.replace(/\/+$/, "");
|
|
192
217
|
}
|
|
193
218
|
function timeoutError(url, error) {
|
|
194
219
|
if (!(error instanceof Error) || error.name !== "TimeoutError") return null;
|
|
@@ -244,7 +269,7 @@ async function fetchRelease(registry, version) {
|
|
|
244
269
|
if (isNewerSchema(value)) {
|
|
245
270
|
const release = version === "latest" ? "The latest nxds release" : `nxds ${version}`;
|
|
246
271
|
throw new CliError(
|
|
247
|
-
`${release} needs a newer nxds-cli than ${"0.
|
|
272
|
+
`${release} needs a newer nxds-cli than ${"0.4.1"}. Run \`${CLI_COMMAND}\` to get the newest one.`
|
|
248
273
|
);
|
|
249
274
|
}
|
|
250
275
|
const invalid = (problems) => new CliError(
|
|
@@ -1556,13 +1581,361 @@ function report(checks) {
|
|
|
1556
1581
|
};
|
|
1557
1582
|
}
|
|
1558
1583
|
|
|
1584
|
+
// ../claude-design/src/system-index.ts
|
|
1585
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1586
|
+
var PAGE_PATHS = ["manifest.json", "api/"];
|
|
1587
|
+
function mergeSystemIndex(current, exported) {
|
|
1588
|
+
return {
|
|
1589
|
+
...current,
|
|
1590
|
+
namespace: exported.namespace,
|
|
1591
|
+
editing: exported.editing,
|
|
1592
|
+
libraries: exported.libraries,
|
|
1593
|
+
lastChange: exported.lastChange
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function isPageGenerated(file) {
|
|
1597
|
+
return PAGE_PATHS.some((page) => file === page || file.startsWith(page));
|
|
1598
|
+
}
|
|
1599
|
+
function validateIndex(index) {
|
|
1600
|
+
const where = "design-system.json";
|
|
1601
|
+
const errors = [];
|
|
1602
|
+
if (index["v"] !== 3 || index["layout"] !== "files") {
|
|
1603
|
+
errors.push(`${where}: needs "v": 3 and "layout": "files".`);
|
|
1604
|
+
}
|
|
1605
|
+
if (!index["createdOnFiles"] && !index["convertedFrom"]) {
|
|
1606
|
+
errors.push(`${where}: needs the createdOnFiles marker.`);
|
|
1607
|
+
}
|
|
1608
|
+
if (typeof index["title"] !== "string" || !index["title"]) {
|
|
1609
|
+
errors.push(`${where}: needs a title.`);
|
|
1610
|
+
}
|
|
1611
|
+
if (typeof index["namespace"] !== "string" || !IDENTIFIER.test(index["namespace"])) {
|
|
1612
|
+
errors.push(`${where}: namespace must be a JS identifier.`);
|
|
1613
|
+
}
|
|
1614
|
+
const lastChange = index["lastChange"];
|
|
1615
|
+
if (!lastChange || typeof lastChange["by"] !== "string" || Number.isNaN(Date.parse(String(lastChange["at"])))) {
|
|
1616
|
+
errors.push(`${where}: lastChange needs "by" and an ISO-8601 "at".`);
|
|
1617
|
+
}
|
|
1618
|
+
return errors;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// src/commands/export.ts
|
|
1622
|
+
import { createHash } from "crypto";
|
|
1623
|
+
import {
|
|
1624
|
+
existsSync as existsSync9,
|
|
1625
|
+
lstatSync,
|
|
1626
|
+
mkdirSync as mkdirSync2,
|
|
1627
|
+
mkdtempSync,
|
|
1628
|
+
readdirSync,
|
|
1629
|
+
readFileSync as readFileSync5,
|
|
1630
|
+
renameSync,
|
|
1631
|
+
rmSync,
|
|
1632
|
+
writeFileSync as writeFileSync3
|
|
1633
|
+
} from "fs";
|
|
1634
|
+
import { tmpdir } from "os";
|
|
1635
|
+
import path10 from "path";
|
|
1636
|
+
import { x as extract } from "tar";
|
|
1637
|
+
import { EnvHttpProxyAgent as EnvHttpProxyAgent2, fetch as fetch2 } from "undici";
|
|
1638
|
+
var CLAUDE_DESIGN_PACKAGE = "@nxds/claude-design";
|
|
1639
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
1640
|
+
var DEFAULT_EXPORT_DIR = "nxds-design-system";
|
|
1641
|
+
var FETCH_TIMEOUT_MS2 = 6e4;
|
|
1642
|
+
var dispatcher2 = new EnvHttpProxyAgent2();
|
|
1643
|
+
function redactUrl(url) {
|
|
1644
|
+
const redacted = httpUrl(url);
|
|
1645
|
+
if (!redacted) return "<invalid URL>";
|
|
1646
|
+
redacted.username = "";
|
|
1647
|
+
redacted.password = "";
|
|
1648
|
+
redacted.search = "";
|
|
1649
|
+
return redacted.href;
|
|
1650
|
+
}
|
|
1651
|
+
async function download(url) {
|
|
1652
|
+
const shown = redactUrl(url);
|
|
1653
|
+
let response;
|
|
1654
|
+
try {
|
|
1655
|
+
response = await fetch2(url, {
|
|
1656
|
+
dispatcher: dispatcher2,
|
|
1657
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
|
|
1658
|
+
});
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
const { message, cause } = error;
|
|
1661
|
+
const reason = (cause instanceof Error ? cause.message : message).replaceAll(url, () => shown);
|
|
1662
|
+
throw new CliError(
|
|
1663
|
+
`Could not reach ${shown} (${reason}). Check your connection, or set HTTPS_PROXY if you are behind a proxy, then run the command again.`
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
if (!response.ok) {
|
|
1667
|
+
throw new CliError(
|
|
1668
|
+
`${shown} answered ${response.status}. Run the command again later.`
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
return Buffer.from(await response.arrayBuffer());
|
|
1672
|
+
}
|
|
1673
|
+
function packageUrl(registry) {
|
|
1674
|
+
return `${registry.replace(/\/+$/, "")}/${CLAUDE_DESIGN_PACKAGE.replace("/", "%2f")}`;
|
|
1675
|
+
}
|
|
1676
|
+
async function resolveTarball(registry, release) {
|
|
1677
|
+
assertRegistryUrl(
|
|
1678
|
+
"--npm-registry (or npm_config_registry)",
|
|
1679
|
+
registry,
|
|
1680
|
+
`For a private registry, unpack the tarball from \`npm pack ${CLAUDE_DESIGN_PACKAGE}\` and pass its package folder with --source.`
|
|
1681
|
+
);
|
|
1682
|
+
const url = packageUrl(registry);
|
|
1683
|
+
const body = (await download(url)).toString("utf8");
|
|
1684
|
+
let packument;
|
|
1685
|
+
try {
|
|
1686
|
+
packument = JSON.parse(body);
|
|
1687
|
+
} catch {
|
|
1688
|
+
throw new CliError(
|
|
1689
|
+
`${redactUrl(url)} didn't answer with JSON. Check --npm-registry, or your proxy, then run the command again.`
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
const version = packument["dist-tags"]?.[release] ?? release;
|
|
1693
|
+
const dist = packument.versions?.[version]?.dist;
|
|
1694
|
+
if (!dist?.tarball || !dist.integrity) {
|
|
1695
|
+
const known = Object.keys(packument.versions ?? {}).slice(-5).join(", ");
|
|
1696
|
+
throw new UsageError(
|
|
1697
|
+
`${CLAUDE_DESIGN_PACKAGE} has no release ${release}. Recent releases: ${known || "none"}.`
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
return { version, tarball: dist.tarball, integrity: dist.integrity };
|
|
1701
|
+
}
|
|
1702
|
+
function checkIntegrity(bytes, integrity, tarball) {
|
|
1703
|
+
const expected = integrity.split(/\s+/).find((entry) => entry.startsWith("sha512-"));
|
|
1704
|
+
if (!expected) {
|
|
1705
|
+
throw new CliError(
|
|
1706
|
+
`${redactUrl(tarball)} has no sha512 integrity to check it against.`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
const actual = `sha512-${createHash("sha512").update(bytes).digest("base64")}`;
|
|
1710
|
+
if (actual !== expected) {
|
|
1711
|
+
throw new CliError(
|
|
1712
|
+
`${redactUrl(tarball)} doesn't match its published integrity. Run the command again; if it keeps failing, report it.`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
async function fetchPackage(registry, release) {
|
|
1717
|
+
const { version, tarball, integrity } = await resolveTarball(
|
|
1718
|
+
registry,
|
|
1719
|
+
release
|
|
1720
|
+
);
|
|
1721
|
+
const bytes = await download(tarball);
|
|
1722
|
+
checkIntegrity(bytes, integrity, tarball);
|
|
1723
|
+
const folder = mkdtempSync(path10.join(tmpdir(), "nxds-claude-design-"));
|
|
1724
|
+
const cleanup = () => rmSync(folder, { recursive: true, force: true });
|
|
1725
|
+
try {
|
|
1726
|
+
const file = path10.join(folder, "package.tgz");
|
|
1727
|
+
writeFileSync3(file, bytes);
|
|
1728
|
+
const unpacked = path10.join(folder, "package");
|
|
1729
|
+
mkdirSync2(unpacked);
|
|
1730
|
+
await extract({
|
|
1731
|
+
file,
|
|
1732
|
+
cwd: unpacked,
|
|
1733
|
+
strip: 1,
|
|
1734
|
+
filter: (_, entry) => "type" in entry && (entry.type === "File" || entry.type === "Directory")
|
|
1735
|
+
});
|
|
1736
|
+
return { version, root: unpacked, cleanup };
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
cleanup();
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function localPackage(source) {
|
|
1743
|
+
if (!existsSync9(path10.join(source, "project"))) {
|
|
1744
|
+
throw new UsageError(
|
|
1745
|
+
`${source} has no project/ folder. Point --source at a built @nxds/claude-design dist folder.`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
const manifest = existsSync9(path10.join(source, "package.json")) ? readJson(path10.join(source, "package.json")) : {};
|
|
1749
|
+
return {
|
|
1750
|
+
version: manifest.version ?? "local",
|
|
1751
|
+
root: source,
|
|
1752
|
+
cleanup: () => {
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
function assertWritable(out, overwrite) {
|
|
1757
|
+
if (!existsSync9(out) || readdirSync(out).length === 0 || overwrite) return;
|
|
1758
|
+
throw new UsageError(
|
|
1759
|
+
`${out} already has files. Pass --overwrite to replace its project/ folder, or choose another --out.`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
function readPaths(file) {
|
|
1763
|
+
const value = readJson(file);
|
|
1764
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
1765
|
+
throw new UsageError(
|
|
1766
|
+
`${file} must be a JSON array of the system's file paths.`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
return value;
|
|
1770
|
+
}
|
|
1771
|
+
function stalePaths(existing, exported) {
|
|
1772
|
+
return existing.filter((file) => file.startsWith("project/")).filter((file) => !isPageGenerated(file.slice("project/".length))).filter((file) => !exported.has(file)).sort();
|
|
1773
|
+
}
|
|
1774
|
+
function readManifest(root) {
|
|
1775
|
+
const file = path10.join(root, "files.json");
|
|
1776
|
+
if (!existsSync9(file)) {
|
|
1777
|
+
throw new CliError(
|
|
1778
|
+
`${root} has no files.json. Export from an @nxds/claude-design release or a built dist folder.`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const value = readJson(file);
|
|
1782
|
+
if (!Array.isArray(value) || !value.every(
|
|
1783
|
+
(entry) => typeof entry?.path === "string" && typeof entry.sha256 === "string"
|
|
1784
|
+
)) {
|
|
1785
|
+
throw new CliError(
|
|
1786
|
+
`${file} must be a JSON array of { path, sha256 } entries.`
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
return value;
|
|
1790
|
+
}
|
|
1791
|
+
function readProject2(root) {
|
|
1792
|
+
const projectDir = path10.join(root, "project");
|
|
1793
|
+
const files = /* @__PURE__ */ new Map();
|
|
1794
|
+
for (const entry of readManifest(root)) {
|
|
1795
|
+
const file = path10.join(projectDir, entry.path);
|
|
1796
|
+
const relative = path10.relative(projectDir, file);
|
|
1797
|
+
if (relative.split(path10.sep)[0] === ".." || path10.isAbsolute(relative)) {
|
|
1798
|
+
throw new CliError(`files.json lists ${entry.path}, outside project/.`);
|
|
1799
|
+
}
|
|
1800
|
+
if (!existsSync9(file) || !lstatSync(file).isFile()) {
|
|
1801
|
+
throw new CliError(
|
|
1802
|
+
`project/${entry.path} is missing from the release, or isn't a regular file.`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
const bytes = readFileSync5(file);
|
|
1806
|
+
if (createHash("sha256").update(bytes).digest("hex") !== entry.sha256) {
|
|
1807
|
+
throw new CliError(
|
|
1808
|
+
`project/${entry.path} doesn't match the release's files.json, so nothing was written.`
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
files.set(entry.path, bytes);
|
|
1812
|
+
}
|
|
1813
|
+
return files;
|
|
1814
|
+
}
|
|
1815
|
+
function mergeIndex(files, currentFile) {
|
|
1816
|
+
const exported = files.get("design-system.json");
|
|
1817
|
+
if (!exported) throw new CliError("The release has no design-system.json.");
|
|
1818
|
+
const merged = mergeSystemIndex(
|
|
1819
|
+
readJson(currentFile),
|
|
1820
|
+
JSON.parse(exported.toString("utf8"))
|
|
1821
|
+
);
|
|
1822
|
+
const errors = validateIndex(merged);
|
|
1823
|
+
if (errors.length > 0) {
|
|
1824
|
+
throw new UsageError(
|
|
1825
|
+
`${currentFile} merges into an index the page can't read, so nothing was written:
|
|
1826
|
+
${errors.map((error) => ` ${error}`).join("\n")}`
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
files.set(
|
|
1830
|
+
"design-system.json",
|
|
1831
|
+
Buffer.from(`${JSON.stringify(merged, null, 2)}
|
|
1832
|
+
`)
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
function writeProject(out, files) {
|
|
1836
|
+
mkdirSync2(out, { recursive: true });
|
|
1837
|
+
const staging = makeStaging(path10.dirname(out), out);
|
|
1838
|
+
const next = path10.join(staging, "next");
|
|
1839
|
+
const previous = path10.join(staging, "previous");
|
|
1840
|
+
const projectDir = path10.join(out, "project");
|
|
1841
|
+
try {
|
|
1842
|
+
for (const [file, bytes] of files) {
|
|
1843
|
+
mkdirSync2(path10.dirname(path10.join(next, file)), { recursive: true });
|
|
1844
|
+
writeFileSync3(path10.join(next, file), bytes);
|
|
1845
|
+
}
|
|
1846
|
+
if (existsSync9(projectDir)) renameSync(projectDir, previous);
|
|
1847
|
+
renameSync(next, projectDir);
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
restore(previous, projectDir, error);
|
|
1850
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1851
|
+
throw renameError(error, out);
|
|
1852
|
+
}
|
|
1853
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1854
|
+
}
|
|
1855
|
+
function makeStaging(parent, out) {
|
|
1856
|
+
try {
|
|
1857
|
+
return mkdtempSync(path10.join(parent, ".nxds-project-"));
|
|
1858
|
+
} catch (error) {
|
|
1859
|
+
const { code, message } = error;
|
|
1860
|
+
if (code !== "EACCES" && code !== "EPERM" && code !== "EROFS") throw error;
|
|
1861
|
+
throw new UsageError(
|
|
1862
|
+
`The export stages project/ in ${parent}, beside ${out}, so ${parent} must be writable (${message}).`
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
function restore(previous, projectDir, cause) {
|
|
1867
|
+
if (!existsSync9(previous)) return;
|
|
1868
|
+
try {
|
|
1869
|
+
renameSync(previous, projectDir);
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
throw new CliError(
|
|
1872
|
+
`The export failed (${cause.message}) and couldn't put the old project/ back (${error.message}). It is in ${previous}.`
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
function renameError(error, out) {
|
|
1877
|
+
if (error.code !== "EXDEV") return error;
|
|
1878
|
+
return new UsageError(
|
|
1879
|
+
`${out} is on another filesystem from its parent folder, so project/ can't be moved into it. Pass an --out folder inside ${out}.`
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
async function exportClaudeDesign(cwd, options) {
|
|
1883
|
+
const out = path10.resolve(cwd, options.out ?? DEFAULT_EXPORT_DIR);
|
|
1884
|
+
assertWritable(out, options.overwrite);
|
|
1885
|
+
const pkg = options.source ? localPackage(path10.resolve(cwd, options.source)) : await fetchPackage(
|
|
1886
|
+
options.npmRegistry ?? DEFAULT_NPM_REGISTRY,
|
|
1887
|
+
options.release ?? "latest"
|
|
1888
|
+
);
|
|
1889
|
+
try {
|
|
1890
|
+
const files = readProject2(pkg.root);
|
|
1891
|
+
if (options.index) mergeIndex(files, path10.resolve(cwd, options.index));
|
|
1892
|
+
const exported = new Set(
|
|
1893
|
+
[...files.keys()].map((file) => `project/${file}`)
|
|
1894
|
+
);
|
|
1895
|
+
const removed = options.existing ? stalePaths(readPaths(path10.resolve(cwd, options.existing)), exported) : [];
|
|
1896
|
+
const coverageFile = path10.join(pkg.root, "coverage.md");
|
|
1897
|
+
const coverage = existsSync9(coverageFile) ? readFileSync5(coverageFile) : void 0;
|
|
1898
|
+
writeProject(out, files);
|
|
1899
|
+
if (coverage) writeFileSync3(path10.join(out, "coverage.md"), coverage);
|
|
1900
|
+
if (options.existing) {
|
|
1901
|
+
writeFileSync3(
|
|
1902
|
+
path10.join(out, "removed.json"),
|
|
1903
|
+
`${JSON.stringify(removed, null, 2)}
|
|
1904
|
+
`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
const relative = path10.relative(cwd, out) || ".";
|
|
1908
|
+
const lines2 = [
|
|
1909
|
+
`Exported nxds ${pkg.version} as a Claude Design System: ${files.size} files in ${path10.join(relative, "project")}.`,
|
|
1910
|
+
`Publish ${relative} as a Design System artifact in Claude, with ${relative} as the root.`
|
|
1911
|
+
];
|
|
1912
|
+
if (removed.length > 0) {
|
|
1913
|
+
lines2.push(
|
|
1914
|
+
`Remove the ${removed.length} files the system has and this release doesn't: they're listed in ${path10.join(relative, "removed.json")}.`
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
if (coverage) {
|
|
1918
|
+
lines2.push(
|
|
1919
|
+
`The props the canvas can't set are in ${path10.join(relative, "coverage.md")}.`
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
ok: true,
|
|
1924
|
+
lines: lines2,
|
|
1925
|
+
data: { version: pkg.version, out, files: files.size, removed }
|
|
1926
|
+
};
|
|
1927
|
+
} finally {
|
|
1928
|
+
pkg.cleanup();
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1559
1932
|
// src/commands/install.ts
|
|
1560
|
-
import { existsSync as
|
|
1561
|
-
import
|
|
1933
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
1934
|
+
import path12 from "path";
|
|
1562
1935
|
|
|
1563
1936
|
// src/install.ts
|
|
1564
|
-
import { existsSync as
|
|
1565
|
-
import
|
|
1937
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
1938
|
+
import path11 from "path";
|
|
1566
1939
|
function emptyInstallResult() {
|
|
1567
1940
|
return {
|
|
1568
1941
|
items: [],
|
|
@@ -1578,11 +1951,11 @@ function emptyInstallResult() {
|
|
|
1578
1951
|
}
|
|
1579
1952
|
function addCssImports(project, config, files, dryRun) {
|
|
1580
1953
|
if (files.length === 0) return [];
|
|
1581
|
-
const entry =
|
|
1582
|
-
const current =
|
|
1954
|
+
const entry = path11.join(project.root, config.css);
|
|
1955
|
+
const current = existsSync10(entry) ? readFileSync6(entry, "utf8") : "";
|
|
1583
1956
|
const added = [...new Set(files)].map((file) => cssImportLine(config, file)).filter((line) => !current.includes(line));
|
|
1584
1957
|
if (added.length > 0 && !dryRun) {
|
|
1585
|
-
|
|
1958
|
+
writeFileSync4(entry, `${current.trimEnd()}
|
|
1586
1959
|
${added.join("\n")}
|
|
1587
1960
|
`);
|
|
1588
1961
|
}
|
|
@@ -1737,9 +2110,9 @@ async function init(cwd, registry, options) {
|
|
|
1737
2110
|
);
|
|
1738
2111
|
if (!options.dryRun) {
|
|
1739
2112
|
for (const change of wiring.changes) {
|
|
1740
|
-
const target =
|
|
1741
|
-
|
|
1742
|
-
|
|
2113
|
+
const target = path12.join(project.root, change.path);
|
|
2114
|
+
mkdirSync3(path12.dirname(target), { recursive: true });
|
|
2115
|
+
writeFileSync5(target, change.content);
|
|
1743
2116
|
}
|
|
1744
2117
|
writeConfig(cwd, config);
|
|
1745
2118
|
}
|
|
@@ -1848,7 +2221,7 @@ async function update(cwd, registry, options) {
|
|
|
1848
2221
|
};
|
|
1849
2222
|
}
|
|
1850
2223
|
const orphaned = [...installedBefore].filter(
|
|
1851
|
-
(file) => !current.has(file) &&
|
|
2224
|
+
(file) => !current.has(file) && existsSync11(path12.join(cwd, config.dir, file))
|
|
1852
2225
|
);
|
|
1853
2226
|
const previousFiles = previous && releaseFiles(previous, config);
|
|
1854
2227
|
config.items = known;
|
|
@@ -1899,12 +2272,13 @@ async function update(cwd, registry, options) {
|
|
|
1899
2272
|
}
|
|
1900
2273
|
|
|
1901
2274
|
// src/cli.ts
|
|
2275
|
+
function cwdOf(command) {
|
|
2276
|
+
const { cwd } = command.optsWithGlobals();
|
|
2277
|
+
return path13.resolve(cwd ?? process.cwd());
|
|
2278
|
+
}
|
|
1902
2279
|
function context(command) {
|
|
1903
|
-
const {
|
|
1904
|
-
return {
|
|
1905
|
-
cwd: path12.resolve(cwd ?? process.cwd()),
|
|
1906
|
-
registry: resolveRegistry(registry)
|
|
1907
|
-
};
|
|
2280
|
+
const { registry } = command.optsWithGlobals();
|
|
2281
|
+
return { cwd: cwdOf(command), registry: resolveRegistry(registry) };
|
|
1908
2282
|
}
|
|
1909
2283
|
function installOptions(command) {
|
|
1910
2284
|
const flags = command.opts();
|
|
@@ -1924,7 +2298,7 @@ function program(done, write) {
|
|
|
1924
2298
|
// Errors are printed once, by `main`, as text or JSON.
|
|
1925
2299
|
outputError: () => {
|
|
1926
2300
|
}
|
|
1927
|
-
}).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.
|
|
2301
|
+
}).exitOverride().name(CLI_COMMAND).description("Add nxds to your app.").version("0.4.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(
|
|
1928
2302
|
new Option(
|
|
1929
2303
|
"--registry <url|path>",
|
|
1930
2304
|
"where to get nxds releases (default: https://nxds.dev/r)"
|
|
@@ -1978,12 +2352,35 @@ Docs: https://nxds.dev/getting-started/install`
|
|
|
1978
2352
|
done(await view(cwd, registry, item));
|
|
1979
2353
|
});
|
|
1980
2354
|
cli.command("info").description("show what the CLI detects in this app").action((_options, command) => {
|
|
1981
|
-
done(info(
|
|
2355
|
+
done(info(cwdOf(command)));
|
|
1982
2356
|
});
|
|
1983
2357
|
cli.command("diff").description("compare your nxds files with the latest release").argument("[items...]", "item names (default: every installed item)").action(async (items, _options, command) => {
|
|
1984
2358
|
const { cwd, registry } = context(command);
|
|
1985
2359
|
done(await diff(cwd, registry, items));
|
|
1986
2360
|
});
|
|
2361
|
+
cli.command("export").description(
|
|
2362
|
+
"export nxds as a Claude Design System folder, ready to publish"
|
|
2363
|
+
).addArgument(
|
|
2364
|
+
new Argument("<target>", "what to export").choices(["claude-design"])
|
|
2365
|
+
).option("--out <path>", `folder to write (default: ${DEFAULT_EXPORT_DIR})`).option("--release <version>", "nxds release to export (default: latest)").option(
|
|
2366
|
+
"--index <path>",
|
|
2367
|
+
"the system's current design-system.json, to keep its title and creation date"
|
|
2368
|
+
).option(
|
|
2369
|
+
"--existing <path>",
|
|
2370
|
+
"a JSON list of the system's current file paths, to list the ones this release removes"
|
|
2371
|
+
).option("--overwrite", "replace the project/ folder in --out", false).option(
|
|
2372
|
+
"--source <path>",
|
|
2373
|
+
"a built @nxds/claude-design dist folder, instead of the npm release"
|
|
2374
|
+
).addOption(
|
|
2375
|
+
new Option(
|
|
2376
|
+
"--npm-registry <url>",
|
|
2377
|
+
`npm registry to download from (default: ${DEFAULT_NPM_REGISTRY})`
|
|
2378
|
+
).env("npm_config_registry")
|
|
2379
|
+
).action(async (_target, _options, command) => {
|
|
2380
|
+
done(
|
|
2381
|
+
await exportClaudeDesign(cwdOf(command), command.opts())
|
|
2382
|
+
);
|
|
2383
|
+
});
|
|
1987
2384
|
cli.command("doctor").description("check that nxds is wired up correctly").action(async (_options, command) => {
|
|
1988
2385
|
const { cwd, registry } = context(command);
|
|
1989
2386
|
done(await doctor(cwd, registry));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nxds-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Install nxds components, blocks and setup into your app.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"commander": "^14.0.3",
|
|
37
37
|
"node-diff3": "^3.2.1",
|
|
38
38
|
"semver": "^7.8.5",
|
|
39
|
+
"tar": "^7.5.22",
|
|
39
40
|
"undici": "^7.30.1",
|
|
40
41
|
"zod": "^4.6.7"
|
|
41
42
|
},
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"@types/semver": "^7.8.0",
|
|
45
46
|
"tsup": "^8.5.1",
|
|
46
47
|
"typescript": "^5.9.3",
|
|
48
|
+
"@nxds/claude-design": "0.8.1",
|
|
47
49
|
"@nxds/catalog": "0.0.0"
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|