ply-ui-cli 1.1.118

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,278 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/registry.ts
31
+ var registry_exports = {};
32
+ __export(registry_exports, {
33
+ FREE_REGISTRY_URL: () => FREE_REGISTRY_URL,
34
+ PRO_REGISTRY_URL: () => PRO_REGISTRY_URL,
35
+ SITE_URL: () => SITE_URL,
36
+ fetchIndex: () => fetchIndex,
37
+ fetchItem: () => fetchItem,
38
+ getLicenseKey: () => getLicenseKey,
39
+ isSafeItemName: () => isSafeItemName,
40
+ itemHaystack: () => itemHaystack,
41
+ registryIndexSchema: () => registryIndexSchema,
42
+ registryItemSchema: () => registryItemSchema,
43
+ suggestNames: () => suggestNames
44
+ });
45
+ module.exports = __toCommonJS(registry_exports);
46
+ var import_vendor = require("./vendor");
47
+
48
+ // src/integrity.ts
49
+ var import_crypto = __toESM(require("crypto"));
50
+ var TRUSTED_SIGNING_KEYS = [];
51
+ function computeItemDigest(item) {
52
+ const hash = import_crypto.default.createHash("sha256");
53
+ hash.update(item.name, "utf8");
54
+ hash.update("\0");
55
+ const sorted = [...item.files].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
56
+ for (const file of sorted) {
57
+ hash.update(file.name, "utf8");
58
+ hash.update("\0");
59
+ hash.update(file.content, "utf8");
60
+ hash.update("\0");
61
+ }
62
+ return hash.digest("hex");
63
+ }
64
+ function isIndexSignature(value) {
65
+ if (!value || typeof value !== "object") return false;
66
+ const v = value;
67
+ return typeof v.alg === "string" && typeof v.keyId === "string" && typeof v.sha256 === "string" && typeof v.signature === "string";
68
+ }
69
+ function verifyIndexSignature(rawIndex, signature) {
70
+ if (!TRUSTED_SIGNING_KEYS.length) {
71
+ return { status: "unsigned", reason: "this CLI build ships no trusted signing key" };
72
+ }
73
+ if (signature === void 0 || signature === null) {
74
+ return { status: "unsigned", reason: "the registry published no index signature" };
75
+ }
76
+ if (!isIndexSignature(signature)) {
77
+ return { status: "invalid", reason: "the index signature is malformed" };
78
+ }
79
+ if (signature.alg !== "ed25519") {
80
+ return { status: "invalid", reason: `unsupported signature algorithm '${signature.alg}'` };
81
+ }
82
+ const key = TRUSTED_SIGNING_KEYS.find((k) => k.keyId === signature.keyId);
83
+ if (!key) {
84
+ return { status: "invalid", reason: `index was signed with an untrusted key '${signature.keyId}'` };
85
+ }
86
+ const actualSha = import_crypto.default.createHash("sha256").update(rawIndex, "utf8").digest("hex");
87
+ if (actualSha !== signature.sha256) {
88
+ return { status: "invalid", reason: "the index contents do not match the signed digest" };
89
+ }
90
+ try {
91
+ const publicKey = import_crypto.default.createPublicKey({
92
+ key: Buffer.from(key.publicKey, "base64"),
93
+ format: "der",
94
+ type: "spki"
95
+ });
96
+ const ok = import_crypto.default.verify(
97
+ null,
98
+ Buffer.from(rawIndex, "utf8"),
99
+ publicKey,
100
+ Buffer.from(signature.signature, "base64")
101
+ );
102
+ return ok ? { status: "verified", keyId: signature.keyId } : { status: "invalid", reason: "the index signature failed verification" };
103
+ } catch (err) {
104
+ return { status: "invalid", reason: `could not verify the index signature (${err.message})` };
105
+ }
106
+ }
107
+
108
+ // src/registry.ts
109
+ var SITE_URL = "https://ply-ui.com";
110
+ function firstEnv(...names) {
111
+ for (const name of names) {
112
+ const v = process.env[name]?.trim();
113
+ if (v) return v;
114
+ }
115
+ }
116
+ function requireSignature() {
117
+ return process.env.PLY_REQUIRE_SIGNATURE === "1" || process.env.BASE_UI_REQUIRE_SIGNATURE === "1";
118
+ }
119
+ var FREE_REGISTRY_URL = firstEnv("PLY_REGISTRY_URL", "BASE_UI_REGISTRY_URL") || `${SITE_URL}/registry`;
120
+ var PRO_REGISTRY_URL = firstEnv("PLY_PRO_REGISTRY_URL", "BASE_UI_PRO_REGISTRY_URL") || "https://pro.ply-ui.com/registry";
121
+ var registryFileSchema = import_vendor.z.object({
122
+ name: import_vendor.z.string(),
123
+ content: import_vendor.z.string()
124
+ });
125
+ var registryItemSchema = import_vendor.z.object({
126
+ name: import_vendor.z.string(),
127
+ tier: import_vendor.z.enum(["free", "pro"]),
128
+ category: import_vendor.z.enum(["application", "page-layout", "block", "widget", "shell", "component"]).optional().default("component"),
129
+ dependencies: import_vendor.z.array(import_vendor.z.string()).default([]),
130
+ registryDependencies: import_vendor.z.array(import_vendor.z.string()).default([]),
131
+ description: import_vendor.z.string().optional().default(""),
132
+ usage: import_vendor.z.string().optional().default(""),
133
+ keywords: import_vendor.z.array(import_vendor.z.string()).optional().default([]),
134
+ files: import_vendor.z.array(registryFileSchema),
135
+ type: import_vendor.z.string().optional()
136
+ });
137
+ var registryIndexSchema = import_vendor.z.array(
138
+ registryItemSchema.omit({ files: true }).extend({
139
+ /** sha256 of the item payload, published by the build. Optional so a CLI
140
+ * newer than the deployed registry keeps working. */
141
+ digest: import_vendor.z.string().optional()
142
+ })
143
+ );
144
+ function itemHaystack(item) {
145
+ return [item.name, item.description, ...item.keywords ?? []].filter(Boolean).join(" ").toLowerCase();
146
+ }
147
+ function isSafeItemName(name) {
148
+ return /^[a-z0-9][a-z0-9-.]*$/i.test(name) && !name.includes("..");
149
+ }
150
+ function getLicenseKey() {
151
+ return firstEnv("PLY_LICENSE_KEY", "BASE_UI_LICENSE_KEY");
152
+ }
153
+ async function fetchText(url, headers = {}) {
154
+ let res;
155
+ try {
156
+ res = await fetch(url, { headers });
157
+ } catch (err) {
158
+ throw new Error(`Network error while contacting the registry (${err.message}). Check your connection and try again.`);
159
+ }
160
+ if (res.status === 401 || res.status === 403) {
161
+ throw new Error(`Your license key was rejected (HTTP ${res.status}). Check PLY_LICENSE_KEY, or contact support via ${SITE_URL}.`);
162
+ }
163
+ if (res.status === 404) {
164
+ const e = new Error("Not found");
165
+ e.code = "NOT_FOUND";
166
+ throw e;
167
+ }
168
+ if (!res.ok) {
169
+ throw new Error(`Registry request failed (HTTP ${res.status}) for ${url}.`);
170
+ }
171
+ return res.text();
172
+ }
173
+ async function fetchJson(url, headers = {}) {
174
+ return JSON.parse(await fetchText(url, headers));
175
+ }
176
+ async function fetchIndexSignature() {
177
+ try {
178
+ return await fetchJson(`${FREE_REGISTRY_URL}/index.json.sig`);
179
+ } catch {
180
+ return void 0;
181
+ }
182
+ }
183
+ async function fetchIndex() {
184
+ const raw = await fetchText(`${FREE_REGISTRY_URL}/index.json`);
185
+ const verdict = verifyIndexSignature(raw, await fetchIndexSignature());
186
+ if (verdict.status === "invalid") {
187
+ throw new Error(
188
+ `Registry index integrity check failed: ${verdict.reason}.
189
+ Nothing was written. Report this at ${SITE_URL} \u2014 do not bypass this error.`
190
+ );
191
+ }
192
+ if (verdict.status === "unsigned" && requireSignature()) {
193
+ throw new Error(
194
+ `PLY_REQUIRE_SIGNATURE=1 but the registry index could not be verified: ${verdict.reason}.`
195
+ );
196
+ }
197
+ return registryIndexSchema.parse(JSON.parse(raw));
198
+ }
199
+ async function fetchItem(name, tier, expectedDigest) {
200
+ if (!isSafeItemName(name)) {
201
+ throw new Error(`Invalid component name: '${name}'.`);
202
+ }
203
+ let data;
204
+ if (tier === "pro") {
205
+ const key = getLicenseKey();
206
+ if (!key) {
207
+ throw new Error(
208
+ `'${name}' is a Pro component. Set the PLY_LICENSE_KEY environment variable to your license key.
209
+ Get a license at ${SITE_URL}/pricing.`
210
+ );
211
+ }
212
+ data = await fetchJson(`${PRO_REGISTRY_URL}/styles/default/${name}.json`, {
213
+ authorization: `Bearer ${key}`
214
+ });
215
+ } else {
216
+ data = await fetchJson(`${FREE_REGISTRY_URL}/styles/default/${name}.json`);
217
+ }
218
+ const item = registryItemSchema.parse(data);
219
+ if (expectedDigest) {
220
+ const actual = computeItemDigest(item);
221
+ if (actual !== expectedDigest) {
222
+ throw new Error(
223
+ `Integrity check failed for '${name}': the downloaded files do not match the digest published in the registry index (expected ${expectedDigest.slice(0, 12)}\u2026, got ${actual.slice(0, 12)}\u2026). Nothing was written.`
224
+ );
225
+ }
226
+ } else if (requireSignature()) {
227
+ throw new Error(
228
+ `PLY_REQUIRE_SIGNATURE=1 but the registry index publishes no digest for '${name}'.`
229
+ );
230
+ }
231
+ return item;
232
+ }
233
+ function suggestNames(query, names, limit = 5) {
234
+ const q = query.toLowerCase();
235
+ const scored = names.map((name) => {
236
+ const n = name.toLowerCase();
237
+ let score;
238
+ if (n === q) score = 0;
239
+ else if (n.startsWith(q) || q.startsWith(n)) score = 1;
240
+ else if (n.includes(q) || q.includes(n)) score = 2;
241
+ else score = 3 + levenshtein(q, n);
242
+ return { name, score };
243
+ }).filter((s) => s.score <= 6).sort((a, b) => a.score - b.score);
244
+ return scored.slice(0, limit).map((s) => s.name);
245
+ }
246
+ function levenshtein(a, b) {
247
+ const m = a.length;
248
+ const n = b.length;
249
+ if (!m) return n;
250
+ if (!n) return m;
251
+ let prev = Array.from({ length: n + 1 }, (_, i) => i);
252
+ for (let i = 1; i <= m; i++) {
253
+ const curr = [i];
254
+ for (let j = 1; j <= n; j++) {
255
+ curr[j] = Math.min(
256
+ prev[j] + 1,
257
+ curr[j - 1] + 1,
258
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
259
+ );
260
+ }
261
+ prev = curr;
262
+ }
263
+ return prev[n];
264
+ }
265
+ // Annotate the CommonJS export names for ESM import in node:
266
+ 0 && (module.exports = {
267
+ FREE_REGISTRY_URL,
268
+ PRO_REGISTRY_URL,
269
+ SITE_URL,
270
+ fetchIndex,
271
+ fetchItem,
272
+ getLicenseKey,
273
+ isSafeItemName,
274
+ itemHaystack,
275
+ registryIndexSchema,
276
+ registryItemSchema,
277
+ suggestNames
278
+ });
package/dist/sync.js ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/sync.ts
31
+ var sync_exports = {};
32
+ __export(sync_exports, {
33
+ classifyFileStatus: () => classifyFileStatus,
34
+ computeComponentDiff: () => computeComponentDiff,
35
+ hasChanges: () => hasChanges,
36
+ hasConflicts: () => hasConflicts,
37
+ renderDiff: () => renderDiff,
38
+ statusLabel: () => statusLabel
39
+ });
40
+ module.exports = __toCommonJS(sync_exports);
41
+ var import_fs2 = __toESM(require("fs"));
42
+ var import_path2 = __toESM(require("path"));
43
+ var import_vendor = require("./vendor");
44
+ var import_registry = require("./registry");
45
+
46
+ // src/lockfile.ts
47
+ var import_crypto = __toESM(require("crypto"));
48
+ function hashContent(content) {
49
+ return import_crypto.default.createHash("sha256").update(content, "utf8").digest("hex");
50
+ }
51
+
52
+ // src/snapshot.ts
53
+ var import_fs = __toESM(require("fs"));
54
+ var import_path = __toESM(require("path"));
55
+ var SNAPSHOT_ROOT = ".base-ui/snapshots";
56
+ function snapshotPath(component, file) {
57
+ return import_path.default.resolve(process.cwd(), SNAPSHOT_ROOT, component, file);
58
+ }
59
+ function readSnapshot(component, file, expectedHash) {
60
+ const dest = snapshotPath(component, file);
61
+ if (!import_fs.default.existsSync(dest)) return void 0;
62
+ const content = import_fs.default.readFileSync(dest, "utf8");
63
+ if (expectedHash && hashContent(content) !== expectedHash) return void 0;
64
+ return content;
65
+ }
66
+
67
+ // src/brand.ts
68
+ var CLI_BIN = "ply-ui-cli";
69
+ var CLI_NPX = `npx ${CLI_BIN}`;
70
+ var MCP_PACKAGE = "ply-ui-mcp";
71
+ var MCP_NPX = `npx -y ${MCP_PACKAGE}`;
72
+
73
+ // src/sync.ts
74
+ function hasChanges(diff) {
75
+ return diff.files.some((f) => f.status !== "unchanged");
76
+ }
77
+ function hasConflicts(diff) {
78
+ return diff.files.some((f) => f.status === "conflict");
79
+ }
80
+ function classifyFileStatus(args) {
81
+ const { installedHash, localHash, upstreamHash } = args;
82
+ if (installedHash === void 0) {
83
+ if (localHash === void 0) return "new-upstream";
84
+ if (localHash === upstreamHash) {
85
+ return "unchanged";
86
+ }
87
+ return "conflict";
88
+ }
89
+ if (upstreamHash === void 0) return "removed-upstream";
90
+ if (localHash === void 0) return "missing-locally";
91
+ if (localHash === installedHash && upstreamHash === installedHash) return "unchanged";
92
+ if (localHash === installedHash) return "upstream-changed";
93
+ if (upstreamHash === installedHash) return "locally-modified";
94
+ if (localHash === upstreamHash) return "unchanged";
95
+ return "conflict";
96
+ }
97
+ async function computeComponentDiff(name, componentsRoot, lock, index) {
98
+ const lockEntry = lock.components[name];
99
+ if (!lockEntry) {
100
+ return {
101
+ name,
102
+ files: [],
103
+ error: `not tracked in ${import_path2.default.basename("base-ui-lock.json")} \u2014 run "${CLI_NPX} add ${name} --overwrite" to start tracking it.`
104
+ };
105
+ }
106
+ const indexEntry = index.find((i) => i.name === name);
107
+ if (!indexEntry) {
108
+ return { name, tier: lockEntry.tier, files: [], error: "no longer available upstream (removed from the registry)." };
109
+ }
110
+ let upstream;
111
+ try {
112
+ upstream = await (0, import_registry.fetchItem)(name, indexEntry.tier, indexEntry.digest);
113
+ } catch (err) {
114
+ return { name, tier: indexEntry.tier, files: [], error: err.message };
115
+ }
116
+ const upstreamFiles = new Map(upstream.files.map((f) => [f.name, f.content]));
117
+ const paths = /* @__PURE__ */ new Set([...Object.keys(lockEntry.files), ...upstreamFiles.keys()]);
118
+ const files = [];
119
+ for (const relPath of paths) {
120
+ const localPath = import_path2.default.join(componentsRoot, name, relPath);
121
+ const localExists = import_fs2.default.existsSync(localPath);
122
+ const localContent = localExists ? import_fs2.default.readFileSync(localPath, "utf8") : void 0;
123
+ const localHash = localContent !== void 0 ? hashContent(localContent) : void 0;
124
+ const installedHash = lockEntry.files[relPath];
125
+ const upstreamContent = upstreamFiles.get(relPath);
126
+ const upstreamHash = upstreamContent !== void 0 ? hashContent(upstreamContent) : void 0;
127
+ const status = classifyFileStatus({ installedHash, localHash, upstreamHash });
128
+ const ancestorContent = status === "conflict" ? readSnapshot(name, relPath, installedHash) : void 0;
129
+ files.push({ path: relPath, status, localContent, upstreamContent, ancestorContent });
130
+ }
131
+ files.sort((a, b) => a.path.localeCompare(b.path));
132
+ return { name, tier: indexEntry.tier, files };
133
+ }
134
+ var STATUS_LABEL = {
135
+ unchanged: "unchanged",
136
+ "upstream-changed": "updated upstream",
137
+ "locally-modified": "locally modified (no upstream change)",
138
+ conflict: "CONFLICT \u2014 both changed",
139
+ "new-upstream": "new upstream file",
140
+ "removed-upstream": "removed upstream",
141
+ "missing-locally": "upstream changed, but your local file is missing"
142
+ };
143
+ function statusLabel(status) {
144
+ return STATUS_LABEL[status];
145
+ }
146
+ var RED = (s) => `\x1B[31m${s}\x1B[0m`;
147
+ var GREEN = (s) => `\x1B[32m${s}\x1B[0m`;
148
+ var DIM = (s) => `\x1B[2m${s}\x1B[0m`;
149
+ function renderDiff(oldContent, newContent) {
150
+ const parts = (0, import_vendor.diffLines)(oldContent ?? "", newContent ?? "");
151
+ const lines = [];
152
+ for (const part of parts) {
153
+ const prefix = part.added ? "+" : part.removed ? "-" : " ";
154
+ const color = part.added ? GREEN : part.removed ? RED : DIM;
155
+ const partLines = part.value.replace(/\n$/, "").split("\n");
156
+ for (const line of partLines) {
157
+ lines.push(color(`${prefix} ${line}`));
158
+ }
159
+ }
160
+ return lines.join("\n");
161
+ }
162
+ // Annotate the CommonJS export names for ESM import in node:
163
+ 0 && (module.exports = {
164
+ classifyFileStatus,
165
+ computeComponentDiff,
166
+ hasChanges,
167
+ hasConflicts,
168
+ renderDiff,
169
+ statusLabel
170
+ });