@parity-app/cli 0.6.2 → 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.
@@ -0,0 +1,129 @@
1
+ import { a as applyLibraryEdits, f as parseLibraryEdits, p as parseLibraryManifest, u as libraryManifestExtension } from "./src-CSah1pZ-.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ import semver from "semver";
7
+ // ../core/src/jsonc.ts
8
+ /** Parses JSON with comments and trailing commas, as tsconfig files allow. Comment markers inside strings are kept. */
9
+ function parseJsonc(text) {
10
+ let output = "";
11
+ let index = 0;
12
+ while (index < text.length) {
13
+ const char = text[index];
14
+ if (char === "\"") {
15
+ const start = index++;
16
+ while (index < text.length && text[index] !== "\"") index += text[index] === "\\" ? 2 : 1;
17
+ output += text.slice(start, ++index);
18
+ } else if (char === "/" && text[index + 1] === "/") while (index < text.length && text[index] !== "\n") index++;
19
+ else if (char === "/" && text[index + 1] === "*") {
20
+ const end = text.indexOf("*/", index + 2);
21
+ index = end === -1 ? text.length : end + 2;
22
+ } else {
23
+ output += char;
24
+ index++;
25
+ }
26
+ }
27
+ return JSON.parse(output.replace(/,(\s*[}\]])/g, "$1"));
28
+ }
29
+ // ../registry/src/index.ts
30
+ const require = createRequire(import.meta.url);
31
+ /**
32
+ * The folder the registry's files are in. The published CLI bundles this module and ships the files beside it, where
33
+ * the package can't be resolved. Otherwise they're found through the package's own `package.json`, so it's the same
34
+ * whether this module runs from source or bundled into the app.
35
+ */
36
+ function registryDir() {
37
+ const bundled = join(import.meta.dirname, "libraries");
38
+ if (existsSync(bundled)) return bundled;
39
+ return join(dirname(require.resolve("@parity-app/registry/package.json")), "libraries");
40
+ }
41
+ let cache;
42
+ /** Every file in the registry, read once. One that doesn't parse is skipped with a warning. */
43
+ function registryEntries(dir = registryDir()) {
44
+ if (dir !== registryDir()) return readEntries(dir);
45
+ cache ??= readEntries(dir);
46
+ return cache;
47
+ }
48
+ async function readEntries(dir) {
49
+ const files = (await readdir(dir).catch(() => [])).filter((file) => file.endsWith(libraryManifestExtension)).sort();
50
+ const entries = [];
51
+ for (const file of files) {
52
+ const path = join(dir, file);
53
+ try {
54
+ const manifest = parseLibraryManifest(JSON.parse(await readFile(path, "utf8")));
55
+ if (!manifest.package) throw new TypeError("/package: a registry file names the package it describes");
56
+ entries.push({
57
+ file,
58
+ path,
59
+ manifest
60
+ });
61
+ } catch (error) {
62
+ console.warn(`Skipping registry file ${file}: ${errorText(error)}`);
63
+ }
64
+ }
65
+ return entries;
66
+ }
67
+ /**
68
+ * The newest file for each package the registry describes, by the version it was made from, with its hand edits
69
+ * applied. A file whose edits can't be read is used as it is, with a warning.
70
+ */
71
+ async function newestEntries(dir) {
72
+ const newest = /* @__PURE__ */ new Map();
73
+ for (const entry of await registryEntries(dir)) {
74
+ const name = entry.manifest.package.name;
75
+ const current = newest.get(name);
76
+ if (!current || compareGenerated(entry.manifest, current.manifest) > 0) newest.set(name, entry);
77
+ }
78
+ return Promise.all([...newest.values()].map(async (entry) => {
79
+ try {
80
+ return {
81
+ ...entry,
82
+ manifest: (await withEdits(entry.path, entry.manifest)).manifest
83
+ };
84
+ } catch (error) {
85
+ console.warn(`Using registry file ${entry.file} without its edits: ${errorText(error)}`);
86
+ return entry;
87
+ }
88
+ }));
89
+ }
90
+ /**
91
+ * A library file at `path` with its hand edits applied: its own `edits`, or the shared file it names beside it. Edits
92
+ * for exports it doesn't have are its `orphans`, reported rather than dropped silently. Throws when the edits can't be
93
+ * read.
94
+ */
95
+ async function withEdits(path, manifest) {
96
+ if (manifest.edits === void 0) return {
97
+ manifest,
98
+ orphans: []
99
+ };
100
+ return applyLibraryEdits(manifest, await readLibraryEdits(manifest.edits, dirname(path)));
101
+ }
102
+ /**
103
+ * Hand edits as a library file writes them: inline, or the path of a shared file, which is read from `from` and may
104
+ * have comments.
105
+ */
106
+ async function readLibraryEdits(edits, from) {
107
+ return parseLibraryEdits(typeof edits === "string" ? parseJsonc(await readFile(resolve(from, edits), "utf8")) : edits);
108
+ }
109
+ function errorText(error) {
110
+ return error instanceof Error ? error.message : String(error);
111
+ }
112
+ /** Whether a manifest describes this version of its package. A manifest without a range covers every version. */
113
+ function covers(manifest, version) {
114
+ const range = manifest.package?.range;
115
+ if (!range) return true;
116
+ if (!version) return false;
117
+ const parsed = semver.coerce(version, { includePrerelease: true });
118
+ return parsed !== null && semver.satisfies(parsed, range, { includePrerelease: true });
119
+ }
120
+ function compareGenerated(a, b) {
121
+ const left = semver.coerce(a.package?.version);
122
+ const right = semver.coerce(b.package?.version);
123
+ if (!left || !right) {
124
+ if (left) return 1;
125
+ return right ? -1 : 0;
126
+ }
127
+ return semver.compare(left, right);
128
+ }
129
+ export { registryEntries as a, registryDir as i, newestEntries as n, withEdits as o, readLibraryEdits as r, parseJsonc as s, covers as t };
@@ -0,0 +1 @@
1
+ export { t as covers, n as newestEntries, i as registryDir, a as registryEntries } from "./src-D2kRGtaF.mjs";
@@ -1,13 +1,13 @@
1
- import { i as plainObject, t as finiteNumber } from "./schema-zRNxotYl.mjs";
2
- import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-V9wTMkxO.mjs";
3
- import "./src-BIVDlIGc.mjs";
1
+ import { a as parseData, n as finiteNumber, o as plainObject, r as jsonText } from "./schema-DcQLoUPP.mjs";
2
+ import { E as collectCustomProperties, M as packageManifest, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-CSah1pZ-.mjs";
3
+ import { r as readLibraryEdits, s as parseJsonc } from "./src-D2kRGtaF.mjs";
4
4
  import { a as publishedVersions, n as packageFile, o as repositoryFiles, r as publishedSource } from "./published-CdXoUgfD.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
7
7
  import { basename, dirname, extname, join, posix, relative, resolve, sep } from "node:path";
8
8
  import { promisify } from "node:util";
9
9
  import { createHash } from "node:crypto";
10
- import { existsSync, realpathSync } from "node:fs";
10
+ import { existsSync, realpathSync, statSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { z } from "zod";
13
13
  import { pathToFileURL } from "node:url";
@@ -528,28 +528,6 @@ function slashes(path) {
528
528
  return path.replaceAll("\\", "/");
529
529
  }
530
530
  ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.NonPrimitive | ts.TypeFlags.TypeParameter;
531
- // ../project/src/jsonc.ts
532
- /** Parses JSON with comments and trailing commas, as tsconfig files allow. Comment markers inside strings are kept. */
533
- function parseJsonc(text) {
534
- let output = "";
535
- let index = 0;
536
- while (index < text.length) {
537
- const char = text[index];
538
- if (char === "\"") {
539
- const start = index++;
540
- while (index < text.length && text[index] !== "\"") index += text[index] === "\\" ? 2 : 1;
541
- output += text.slice(start, ++index);
542
- } else if (char === "/" && text[index + 1] === "/") while (index < text.length && text[index] !== "\n") index++;
543
- else if (char === "/" && text[index + 1] === "*") {
544
- const end = text.indexOf("*/", index + 2);
545
- index = end === -1 ? text.length : end + 2;
546
- } else {
547
- output += char;
548
- index++;
549
- }
550
- }
551
- return JSON.parse(output.replace(/,(\s*[}\]])/g, "$1"));
552
- }
553
531
  // ../tokens/src/dtcg-values.ts
554
532
  /** A value written as CSS. */
555
533
  const css = z.string();
@@ -935,6 +913,247 @@ new Intl.Collator("en", {
935
913
  numeric: true,
936
914
  sensitivity: "base"
937
915
  });
916
+ // ../project/src/packages.ts
917
+ /**
918
+ * The package a folder's code gets for `name`, found the way Node finds packages: in `node_modules` here or in a folder
919
+ * above, with links followed. It reads the package's own package.json, which its `exports` may leave out of reach of
920
+ * `require.resolve`, and never falls back to NODE_PATH, which pnpm's bin shims point at Parity's own dependencies.
921
+ * Throws when the manifest isn't a package's.
922
+ */
923
+ async function findPackage(from, name) {
924
+ for (let folder = resolve(from);; folder = dirname(folder)) {
925
+ const installedAt = join(folder, "node_modules", name);
926
+ const text = await readFile(join(installedAt, "package.json"), "utf8").catch(() => void 0);
927
+ if (text !== void 0) {
928
+ const manifest = readManifest(text, join(installedAt, "package.json"));
929
+ return {
930
+ dir: await realpath(installedAt),
931
+ installedAt,
932
+ manifest
933
+ };
934
+ }
935
+ if (dirname(folder) === folder) return void 0;
936
+ }
937
+ }
938
+ /** The package in a folder, or nothing when it has no package.json. Throws when the manifest isn't a package's. */
939
+ async function readPackage(dir) {
940
+ const file = join(dir, "package.json");
941
+ const text = await readFile(file, "utf8").catch(() => void 0);
942
+ return text === void 0 ? void 0 : {
943
+ dir,
944
+ manifest: readManifest(text, file)
945
+ };
946
+ }
947
+ function readManifest(text, file) {
948
+ try {
949
+ return parseData(jsonText(packageManifest), text);
950
+ } catch (error) {
951
+ throw new TypeError(`${file}: ${error instanceof Error ? error.message : String(error)}`);
952
+ }
953
+ }
954
+ /** A bare specifier's package and the entry point in it: `@acme/ui/button` is `@acme/ui` and `./button`. */
955
+ function splitSpecifier(specifier) {
956
+ const parts = specifier.split("/");
957
+ const length = specifier.startsWith("@") ? 2 : 1;
958
+ const rest = parts.slice(length).join("/");
959
+ return {
960
+ name: parts.slice(0, length).join("/"),
961
+ subpath: rest ? `./${rest}` : "."
962
+ };
963
+ }
964
+ const conditions = {
965
+ types: [
966
+ "types",
967
+ "import",
968
+ "default",
969
+ "require"
970
+ ],
971
+ runtime: [
972
+ "node",
973
+ "import",
974
+ "default"
975
+ ],
976
+ source: [
977
+ "source",
978
+ "types",
979
+ "import",
980
+ "default"
981
+ ],
982
+ style: [
983
+ "style",
984
+ "import",
985
+ "default"
986
+ ]
987
+ };
988
+ /** Which files a target may lead to for each use: declarations for types, anything but them otherwise. */
989
+ const accepts = {
990
+ types: (file) => isDeclaration(file),
991
+ runtime: (file) => !isDeclaration(file),
992
+ source: () => true,
993
+ style: (file) => !isDeclaration(file)
994
+ };
995
+ /**
996
+ * The file one of a package's entry points leads to for a use, or nothing when no file is there. `subpath` is the
997
+ * entry point as `exports` names it: `.` for the package itself, or one such as `./tailwind.css`, which can be written
998
+ * `tailwind.css`. A package that doesn't list an entry point in `exports` is read as older packages are: `types` or
999
+ * `typings` for declarations, `module` or `main` for its module, and `style` for its stylesheet.
1000
+ */
1001
+ function packageEntry(pkg, use, subpath = ".") {
1002
+ const entry = subpath === "." || subpath.startsWith("./") ? subpath : `./${subpath}`;
1003
+ for (const candidate of entryCandidates(pkg.manifest, use, entry)) {
1004
+ const file = candidate && join(pkg.dir, candidate);
1005
+ if (file && isFile(file)) return file;
1006
+ }
1007
+ }
1008
+ function entryCandidates(manifest, use, entry) {
1009
+ const root = entry === ".";
1010
+ const path = entry.slice(2);
1011
+ const exported = exportTarget(manifest.exports, entry, conditions[use], accepts[use]);
1012
+ switch (use) {
1013
+ case "types": return [
1014
+ exported,
1015
+ ...root ? [manifest.types, manifest.typings] : [],
1016
+ ...entryCandidates(manifest, "runtime", entry).map((file) => file && declarationOf(file)),
1017
+ root ? "index.d.ts" : `${path}/index.d.ts`,
1018
+ root ? void 0 : `${path}.d.ts`
1019
+ ];
1020
+ case "runtime": return [
1021
+ exported,
1022
+ ...root ? [manifest.module, ...moduleFiles(manifest.main)] : [],
1023
+ ...root || manifest.exports !== void 0 ? [] : moduleFiles(path)
1024
+ ];
1025
+ case "source": {
1026
+ const named = [exported, ...root ? [
1027
+ manifest.types,
1028
+ manifest.typings,
1029
+ manifest.module,
1030
+ manifest.main
1031
+ ] : []].find((file) => file !== void 0);
1032
+ return [...named && isBuilt(named) ? sourcesOf(named) : [named], ...root ? [
1033
+ "src/index.tsx",
1034
+ "src/index.ts",
1035
+ "src/index.jsx",
1036
+ "src/index.js",
1037
+ "index.tsx",
1038
+ "index.ts"
1039
+ ] : []];
1040
+ }
1041
+ case "style": return root ? [manifest.style, exported] : [exported, path];
1042
+ }
1043
+ }
1044
+ /** A `main` as Node reads it: the file, the file with `.js`, or a folder's `index.js`; `index.js` without one. */
1045
+ function moduleFiles(main) {
1046
+ return main ? [
1047
+ main,
1048
+ `${main}.js`,
1049
+ `${main}/index.js`
1050
+ ] : ["index.js"];
1051
+ }
1052
+ const stylesheetNames = [
1053
+ "tailwind.css",
1054
+ "styles.css",
1055
+ "style.css",
1056
+ "index.css",
1057
+ "theme.css"
1058
+ ];
1059
+ /**
1060
+ * The stylesheet a project imports to use a package, as a path from the package: its `style` field, else the CSS its
1061
+ * `exports` lists, the usual names first. Only one that leads to a file counts.
1062
+ */
1063
+ function packageStylesheet(pkg) {
1064
+ const { style, exports } = pkg.manifest;
1065
+ if (style && isFile(join(pkg.dir, style))) return style;
1066
+ const css = Object.keys(exports === void 0 ? {} : subpaths(exports)).filter((key) => key.endsWith(".css") && !key.includes("*")).map((key) => key.slice(2));
1067
+ const pick = stylesheetNames.find((name) => css.includes(name)) ?? css[0];
1068
+ return pick && packageEntry(pkg, "style", pick) ? pick : void 0;
1069
+ }
1070
+ /** `exports` by subpath: a map as written, or the package's one entry point, which is `.`. */
1071
+ function subpaths(exports) {
1072
+ if (typeof exports !== "object" || exports === null || Array.isArray(exports)) return { ".": exports };
1073
+ return Object.keys(exports).some((key) => key.startsWith(".")) ? exports : { ".": exports };
1074
+ }
1075
+ /**
1076
+ * Where `exports` leads for an entry point under these conditions: the entry point as written, else the longest
1077
+ * pattern that matches it (`./icons/*`), with what `*` stood for put in.
1078
+ */
1079
+ function exportTarget(exports, entry, wanted, accept) {
1080
+ if (exports === void 0) return void 0;
1081
+ const map = subpaths(exports);
1082
+ if (Object.hasOwn(map, entry)) return resolveTarget(map[entry], wanted, accept);
1083
+ let best;
1084
+ for (const key of Object.keys(map)) {
1085
+ const star = key.indexOf("*");
1086
+ if (star === -1) continue;
1087
+ const [prefix, suffix] = [key.slice(0, star), key.slice(star + 1)];
1088
+ if (entry.length < key.length || !entry.startsWith(prefix) || !entry.endsWith(suffix)) continue;
1089
+ if (!best || prefix.length > best.prefix.length) best = {
1090
+ key,
1091
+ prefix,
1092
+ match: entry.slice(prefix.length, entry.length - suffix.length)
1093
+ };
1094
+ }
1095
+ if (!best) return void 0;
1096
+ const { key, match } = best;
1097
+ return resolveTarget(map[key], wanted, (file) => accept(file.replaceAll("*", match)))?.replaceAll("*", match);
1098
+ }
1099
+ /** The first file a target leads to under the wanted conditions, taken in the order they're wanted. */
1100
+ function resolveTarget(target, wanted, accept) {
1101
+ if (typeof target === "string") return accept(target) ? target : void 0;
1102
+ if (target === null) return void 0;
1103
+ if (Array.isArray(target)) {
1104
+ for (const each of target) {
1105
+ const found = resolveTarget(each, wanted, accept);
1106
+ if (found) return found;
1107
+ }
1108
+ return;
1109
+ }
1110
+ for (const condition of wanted) {
1111
+ const found = Object.hasOwn(target, condition) ? resolveTarget(target[condition], wanted, accept) : void 0;
1112
+ if (found) return found;
1113
+ }
1114
+ }
1115
+ function isDeclaration(file) {
1116
+ return /\.d\.[cm]?ts$/.test(file);
1117
+ }
1118
+ /** The declarations TypeScript looks for beside a module: `index.d.ts` for `index.js`, `index.d.mts` for `index.mjs`. */
1119
+ function declarationOf(file) {
1120
+ const match = /\.([cm]?)jsx?$/.exec(file);
1121
+ return match ? `${file.slice(0, match.index)}.d.${match[1]}ts` : void 0;
1122
+ }
1123
+ /** Folders a package's build writes to, which its source isn't in. */
1124
+ const builtFolders = /* @__PURE__ */ new Set([
1125
+ "dist",
1126
+ "build",
1127
+ "lib",
1128
+ "out",
1129
+ "esm",
1130
+ "cjs"
1131
+ ]);
1132
+ /** A file a build wrote: declarations, or anything in a build folder at the package's root. */
1133
+ function isBuilt(file) {
1134
+ return isDeclaration(file) || builtFolders.has(file.replace(/^\.\//, "").split("/")[0]);
1135
+ }
1136
+ const sourceExtensions = [
1137
+ ".tsx",
1138
+ ".ts",
1139
+ ".jsx",
1140
+ ".js"
1141
+ ];
1142
+ /**
1143
+ * Where a built file's source may be, in the order to look: `dist/index.js` and `dist/index.d.ts` were built from
1144
+ * `src/index.tsx`, `src/index.ts`, `src/index.jsx` or `src/index.js`, and `dist/esm/button.js` from `src/button.tsx`.
1145
+ */
1146
+ function sourcesOf(file) {
1147
+ const parts = file.replace(/^\.\//, "").split("/");
1148
+ let start = 0;
1149
+ while (start < parts.length - 1 && builtFolders.has(parts[start])) start++;
1150
+ const rest = parts.slice(start).join("/").replace(/\.d\.[cm]?ts$|\.[cm]?jsx?$/, "");
1151
+ const stem = start > 0 ? `src/${rest}` : rest;
1152
+ return sourceExtensions.map((extension) => stem + extension);
1153
+ }
1154
+ function isFile(path) {
1155
+ return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
1156
+ }
938
1157
  // ../project/src/styles.ts
939
1158
  /**
940
1159
  * Where an app keeps its global styles when neither `components.json` nor the entry says: Vite's and Create React App's
@@ -1047,21 +1266,6 @@ async function readText(path) {
1047
1266
  function toPosix(path) {
1048
1267
  return path.replaceAll("\\", "/").replace(/^\.\//, "");
1049
1268
  }
1050
- // ../project/src/libraries.ts
1051
- /**
1052
- * Where a dependency is installed, found the way Node finds packages: in `node_modules` here or in a folder above. It
1053
- * doesn't resolve `<name>/package.json`, because a package whose `exports` leaves that out can't be resolved that way.
1054
- */
1055
- async function installedPackage(root, importName) {
1056
- for (let folder = resolve(root);; folder = dirname(folder)) {
1057
- const candidate = join(folder, "node_modules", importName);
1058
- if (await isFile(join(candidate, "package.json"))) return realpath(candidate);
1059
- if (dirname(folder) === folder) return void 0;
1060
- }
1061
- }
1062
- async function isFile(path) {
1063
- return stat(path).then((stats) => stats.isFile(), () => false);
1064
- }
1065
1269
  /** A sentence that ends this far in is kept whole, rather than its words cut down to the length. */
1066
1270
  const shortestSentence = 20;
1067
1271
  /**
@@ -3924,7 +4128,6 @@ const presets = {
3924
4128
  id: style,
3925
4129
  name: `Shadcn ${style.charAt(0).toUpperCase()}${style.slice(1)}`,
3926
4130
  description: `Every shadcn component on Base UI, in the ${style} style, kept in step with upstream.`,
3927
- styles: "tailwind.css",
3928
4131
  examples: {
3929
4132
  paths: ["apps/v4/examples/base/"],
3930
4133
  anatomy: ["apps/v4/content/docs/components/base/"]
@@ -4627,16 +4830,16 @@ async function generateLibrary(options) {
4627
4830
  }
4628
4831
  async function describePackage(options, importName) {
4629
4832
  const root = resolve(options.root);
4630
- const dir = await installedPackage(root, importName);
4631
- if (!dir) throw new Error(`${importName} isn't installed in ${root}. Install it with the project's package manager first.`);
4632
- const pkg = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
4833
+ const installed = await findPackage(root, importName);
4834
+ if (!installed) throw new Error(`${importName} isn't installed in ${root}. Install it with the project's package manager first.`);
4835
+ const { dir, manifest: pkg } = installed;
4633
4836
  const name = pkg.name ?? importName;
4634
4837
  const version = pkg.version ?? "0.0.0";
4635
4838
  const preset = presets[name];
4636
4839
  const notes = [...preset?.notes ?? []];
4637
4840
  const iconImport = preset?.icons?.import;
4638
4841
  const componentImport = iconImport ?? preset?.components;
4639
- const entry = typesEntry(dir, pkg, componentImport ? subpath(componentImport, name) : ".");
4842
+ const entry = packageEntry(installed, "types", componentImport ? splitSpecifier(componentImport).subpath : ".")?.replaceAll("\\", "/");
4640
4843
  if (!entry) notes.push("No type declarations were found, so no components could be read.");
4641
4844
  const scan = entry ? findComponents({
4642
4845
  entries: [entry],
@@ -4651,8 +4854,9 @@ async function describePackage(options, importName) {
4651
4854
  const dataIcons = preset?.icons?.render !== void 0 && preset.icons.render !== "export";
4652
4855
  const isIcons = preset?.icons !== void 0 || looksLikeIcons(scan);
4653
4856
  const tokens = /* @__PURE__ */ new Map();
4654
- const styles = preset?.styles ?? stylesheetOf(dir, pkg);
4655
- if (styles) for (const token of cssTokens(await readStylesheet(exportedFile(dir, pkg, styles)))) tokens.set(token.var, token);
4857
+ const styles = preset?.styles ?? packageStylesheet(installed);
4858
+ const stylesheet = styles && packageEntry(installed, "style", styles);
4859
+ if (stylesheet) for (const token of cssTokens(await readStylesheet(stylesheet))) tokens.set(token.var, token);
4656
4860
  let breakpoints;
4657
4861
  if (preset?.theme) try {
4658
4862
  const theme = await preset.theme(loader(root, dir));
@@ -4700,7 +4904,7 @@ async function describePackage(options, importName) {
4700
4904
  if (preset?.icons?.variants) icons.variants = preset.icons.variants;
4701
4905
  if (!dataIcons) {
4702
4906
  const load = loader(root, dir);
4703
- const modules = await styleModules(name, typeof pkg.exports === "object" && pkg.exports !== null ? Object.keys(pkg.exports) : [], iconImport ?? name, load, await iconDrawer(load));
4907
+ const modules = await styleModules(name, typeof pkg.exports === "object" && pkg.exports !== null && !Array.isArray(pkg.exports) ? Object.keys(pkg.exports) : [], iconImport ?? name, load, await iconDrawer(load));
4704
4908
  if (modules) {
4705
4909
  const main = Object.keys(modules).find((style) => modules[style] === (iconImport ?? name));
4706
4910
  icons.variants = {
@@ -4841,7 +5045,7 @@ async function reviseLibrary(generated, options) {
4841
5045
  const edits = options.edits ?? kept;
4842
5046
  if (edits !== void 0) {
4843
5047
  manifest.edits = edits;
4844
- const resolved = parseLibraryEdits(typeof edits === "string" ? parseJsonc(await readFile(resolve(options.editsBase ?? options.root, edits), "utf8")) : edits);
5048
+ const resolved = await readLibraryEdits(edits, resolve(options.editsBase ?? options.root));
4845
5049
  const { orphans } = applyLibraryEdits(parseLibraryManifest(manifest), resolved);
4846
5050
  if (orphans.length > 0) report.orphanEdits = orphans;
4847
5051
  }
@@ -4931,7 +5135,7 @@ function summarizeParts(relations) {
4931
5135
  const appExtensions = [".tsx", ".jsx"];
4932
5136
  async function describeApp(options) {
4933
5137
  const root = resolve(options.root);
4934
- const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
5138
+ const pkg = (await readPackage(root))?.manifest ?? {};
4935
5139
  const notes = [];
4936
5140
  const tsconfig = readTsconfig(root);
4937
5141
  const { files } = await appFiles(root, { componentExtensions: appExtensions }, tsconfig);
@@ -4988,8 +5192,8 @@ async function describeApp(options) {
4988
5192
  async function appManifestPath(root) {
4989
5193
  const existing = (await readdir(root)).find((file) => file.endsWith(libraryManifestExtension));
4990
5194
  if (existing) return join(root, existing);
4991
- const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
4992
- return join(root, `${(pkg.name ?? basename(root)).replace(/^@[^/]+\//, "")}${libraryManifestExtension}`);
5195
+ const name = (await readPackage(root))?.manifest.name ?? basename(root);
5196
+ return join(root, `${name.replace(/^@[^/]+\//, "")}${libraryManifestExtension}`);
4993
5197
  }
4994
5198
  /**
4995
5199
  * A generated manifest laid over the file already there: what generating finds replaces what it found before, and
@@ -5050,56 +5254,6 @@ function defaultRange(version) {
5050
5254
  const [major = "0", minor = "0"] = version.split(/[.-]/);
5051
5255
  return major === "0" ? `^0.${minor}.0` : `^${major}.0.0`;
5052
5256
  }
5053
- function subpath(specifier, packageName) {
5054
- return specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
5055
- }
5056
- /** The type declarations for one of a package's entry points, from `exports`, else `types`, else `index.d.ts`. */
5057
- function typesEntry(dir, pkg, entry) {
5058
- const exported = typeof pkg.exports === "object" && pkg.exports !== null ? pkg.exports : {};
5059
- const condition = (value) => {
5060
- if (typeof value === "string") return /\.d\.[cm]?ts$/.test(value) ? value : void 0;
5061
- if (typeof value !== "object" || value === null) return void 0;
5062
- const record = value;
5063
- for (const key of [
5064
- "types",
5065
- "import",
5066
- "default",
5067
- "require"
5068
- ]) {
5069
- const found = condition(record[key]);
5070
- if (found) return found;
5071
- }
5072
- };
5073
- const candidates = [
5074
- condition(exported[entry]),
5075
- entry === "." ? pkg.types ?? pkg.typings : void 0,
5076
- entry === "." ? "index.d.ts" : `${entry.slice(2)}/index.d.ts`,
5077
- entry === "." ? void 0 : `${entry.slice(2)}.d.ts`
5078
- ];
5079
- for (const candidate of candidates) if (candidate && existsSync(join(dir, candidate))) return join(dir, candidate).replaceAll("\\", "/");
5080
- }
5081
- /** The stylesheet a project imports to use a package: its `style` field, else the CSS its `exports` list first. */
5082
- function stylesheetOf(dir, pkg) {
5083
- if (pkg.style && existsSync(join(dir, pkg.style))) return pkg.style;
5084
- const css = (typeof pkg.exports === "object" && pkg.exports !== null ? Object.keys(pkg.exports) : []).filter((key) => key.endsWith(".css")).map((key) => key.slice(2));
5085
- const pick = [
5086
- "tailwind.css",
5087
- "styles.css",
5088
- "style.css",
5089
- "index.css",
5090
- "theme.css"
5091
- ].find((name) => css.includes(name)) ?? css[0];
5092
- if (!pick) return void 0;
5093
- const target = pkg.exports[`./${pick}`];
5094
- const file = typeof target === "string" ? target : target?.default;
5095
- return typeof file === "string" && existsSync(join(dir, file)) ? pick : void 0;
5096
- }
5097
- /** The file an export such as `tailwind.css` points at: `dist/tailwind.css` for Buildkit's styles. */
5098
- function exportedFile(dir, pkg, specifier) {
5099
- const target = (typeof pkg.exports === "object" && pkg.exports !== null ? pkg.exports : {})[`./${specifier}`];
5100
- const file = typeof target === "string" ? target : target?.default;
5101
- return join(dir, typeof file === "string" ? file : specifier);
5102
- }
5103
5257
  /** A DTCG token file the package ships, if it has one in the format; Buildkit's `tokens.json` isn't yet. */
5104
5258
  async function dtcgIn(dir) {
5105
5259
  for (const candidate of [
@@ -5230,25 +5384,8 @@ function resolveRequire(from, specifier) {
5230
5384
  }
5231
5385
  /** An ESM-only package's entry, from the `import` condition of its `exports`, which `require` can't see. */
5232
5386
  async function resolveImport(from, specifier) {
5233
- const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
5234
- const dir = await installedPackage(from, name);
5235
- if (!dir) return void 0;
5236
- const pkg = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
5237
- const exported = typeof pkg.exports === "object" && pkg.exports !== null ? pkg.exports : {};
5238
- const pick = (value) => {
5239
- if (typeof value === "string") return /\.d\.[cm]?ts$/.test(value) ? void 0 : value;
5240
- if (typeof value !== "object" || value === null) return void 0;
5241
- const record = value;
5242
- for (const key of [
5243
- "node",
5244
- "import",
5245
- "default"
5246
- ]) {
5247
- const found = pick(record[key]);
5248
- if (found) return found;
5249
- }
5250
- };
5251
- const file = pick(exported[subpath(specifier, name)]) ?? (specifier === name ? pkg.module : void 0);
5252
- return file ? join(dir, file) : void 0;
5387
+ const { name, subpath } = splitSpecifier(specifier);
5388
+ const installed = await findPackage(from, name);
5389
+ return installed && packageEntry(installed, "runtime", subpath);
5253
5390
  }
5254
5391
  export { appManifestPath, generateLibrary, manifestText, mergeManifest, parseLibraryEdits, reviseLibrary, iconVariants as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parity-app/cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Drive the Parity app from a terminal or an agent: read state, run edits, take screenshots, serve MCP.",
package/skills/SKILL.md CHANGED
@@ -27,22 +27,22 @@ The same tools are MCP tools and `parity` commands in a terminal. They act on a
27
27
  the active tab of the Parity window they used last. When the person says "this" or "what I've selected" without naming a
28
28
  project, pass `tab: "active"` (`--tab active`) and read `get_app_state` for the selection.
29
29
 
30
- | MCP | CLI | What it does |
31
- | --------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
- | `get_app_state` | `parity state` | Designs, the selection, modes, components and token names, and what Parity detected (builder and config, package manager, installed versions). Start here. |
33
- | `get_status` | `parity status` | The revision, whether anything is unsaved and which files and tokens, the last save, and undo and redo; `summary` names them. |
34
- | `get_modes` | `parity modes [design]` | Viewports (the ids `Responsive` takes), modes (the ids renders take) and what each design shows; a design adds its frames. |
35
- | `execute` | `parity exec` | Runs a JavaScript snippet against the designs. Its changes land as one undo step, merged per design with edits made meanwhile. |
36
- | `screenshot` | `parity screenshot <design>` | Renders a design in its states and modes, offscreen, and returns the images. |
37
- | `find_problems` | `parity problems <design>` | Render errors, overflow, clipping and unknown components, per frame. |
38
- | `check_accessibility` | `parity a11y <design>` | axe-core against WCAG 2.2 AA, per frame, on the layers that break each rule. |
39
- | `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
40
- | `get_tokens` | `parity tokens` | Tokens filtered by category, source or name, with values in every theme. |
41
- | `get_components` | `parity components` | Components, libraries, icon sets and exports left out or failing to render. |
42
- | `get_props` | `parity props <Component>` | Props from source, then what it goes inside and holds, its slots and an example to insert; `all` adds passed-through attributes. |
43
- | `get_tabs` | `parity tabs` | The open projects. |
44
- | `open_project` | `parity open <folder>` | Opens a project in a new tab. |
45
- | `read_skill` | `parity skill [file]` | This skill. |
30
+ | MCP | CLI | What it does |
31
+ | --------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
+ | `get_app_state` | `parity state` | Designs, the selection, modes (the primary one marked), components and token names, and what Parity detected (builder and config, package manager, installed versions). Start here. |
33
+ | `get_status` | `parity status` | The revision, whether anything is unsaved and which files and tokens, the last save, and undo and redo; `summary` names them. |
34
+ | `get_modes` | `parity modes [design]` | Viewports (the ids `Responsive` takes), modes (the ids renders take) and what each design shows; a design adds its frames. |
35
+ | `execute` | `parity exec` | Runs a JavaScript snippet against the designs. Its changes land as one undo step, merged per design with edits made meanwhile. |
36
+ | `screenshot` | `parity screenshot <design>` | Renders a design in its states and modes, offscreen, and returns the images. |
37
+ | `find_problems` | `parity problems <design>` | Render errors, overflow, clipping and unknown components, per frame. |
38
+ | `check_accessibility` | `parity a11y <design>` | axe-core against WCAG 2.2 AA, per frame, on the layers that break each rule. |
39
+ | `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
40
+ | `get_tokens` | `parity tokens` | Tokens filtered by category, source or name, with values in every theme. |
41
+ | `get_components` | `parity components` | Components, libraries, icon sets and exports left out or failing to render. |
42
+ | `get_props` | `parity props <Component>` | Props from source, then what it goes inside and holds, its slots and an example to insert; `all` adds passed-through attributes. |
43
+ | `get_tabs` | `parity tabs` | The open projects. |
44
+ | `open_project` | `parity open <folder>` | Opens a project in a new tab. |
45
+ | `read_skill` | `parity skill [file]` | This skill. |
46
46
 
47
47
  The token, component and prop tools read what designs can use without writing a snippet in either transport.
48
48
  `parity components` also says which of the app's exports aren't components and why, which components fail to draw on the