vectorvesper 1.2.0 → 2.0.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.
- package/dist/chunk-32GDEAJM.js +498 -0
- package/dist/hooks.json +886 -0
- package/dist/index.js +295 -720
- package/dist/mcp-DOUIUZGA.js +24 -0
- package/dist/server-7WN7N3ZG.js +424 -0
- package/package.json +4 -2
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
var verboseMode = false;
|
|
4
|
+
function setVerbose(enabled) {
|
|
5
|
+
verboseMode = enabled;
|
|
6
|
+
}
|
|
7
|
+
var logger = {
|
|
8
|
+
/** Standard info message */
|
|
9
|
+
info(message) {
|
|
10
|
+
console.log(message);
|
|
11
|
+
},
|
|
12
|
+
/** Success message with green checkmark */
|
|
13
|
+
success(message) {
|
|
14
|
+
console.log(`${pc.green("\u2714")} ${message}`);
|
|
15
|
+
},
|
|
16
|
+
/** Warning message */
|
|
17
|
+
warn(message) {
|
|
18
|
+
console.log(pc.yellow(`\u26A0\uFE0F ${message}`));
|
|
19
|
+
},
|
|
20
|
+
/** Error message */
|
|
21
|
+
error(message) {
|
|
22
|
+
console.error(pc.red(`\u274C ${message}`));
|
|
23
|
+
},
|
|
24
|
+
/** Debug message — only shown when --verbose is active */
|
|
25
|
+
debug(message) {
|
|
26
|
+
if (verboseMode) {
|
|
27
|
+
console.log(pc.dim(` [debug] ${message}`));
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
/** Blank line */
|
|
31
|
+
break() {
|
|
32
|
+
console.log("");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/utils/project.ts
|
|
37
|
+
import fs from "fs";
|
|
38
|
+
import path from "path";
|
|
39
|
+
function detectProject(cwd = process.cwd()) {
|
|
40
|
+
const rootPath = cwd;
|
|
41
|
+
let packageManager = "npm";
|
|
42
|
+
if (fs.existsSync(path.join(rootPath, "pnpm-lock.yaml"))) {
|
|
43
|
+
packageManager = "pnpm";
|
|
44
|
+
} else if (fs.existsSync(path.join(rootPath, "yarn.lock"))) {
|
|
45
|
+
packageManager = "yarn";
|
|
46
|
+
} else if (fs.existsSync(path.join(rootPath, "bun.lockb")) || fs.existsSync(path.join(rootPath, "bun.lock"))) {
|
|
47
|
+
packageManager = "bun";
|
|
48
|
+
}
|
|
49
|
+
const isTypeScript = fs.existsSync(path.join(rootPath, "tsconfig.json"));
|
|
50
|
+
const hasSrcDir = fs.existsSync(path.join(rootPath, "src"));
|
|
51
|
+
const pkgPath = path.join(rootPath, "package.json");
|
|
52
|
+
let dependencies = {};
|
|
53
|
+
let devDependencies = {};
|
|
54
|
+
if (fs.existsSync(pkgPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
57
|
+
dependencies = pkg.dependencies || {};
|
|
58
|
+
devDependencies = pkg.devDependencies || {};
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
63
|
+
const tailwindConfigs = [
|
|
64
|
+
"tailwind.config.js",
|
|
65
|
+
"tailwind.config.ts",
|
|
66
|
+
"tailwind.config.cjs",
|
|
67
|
+
"tailwind.config.mjs"
|
|
68
|
+
];
|
|
69
|
+
const hasTailwindConfig = tailwindConfigs.some(
|
|
70
|
+
(config) => fs.existsSync(path.join(rootPath, config))
|
|
71
|
+
);
|
|
72
|
+
const hasTailwindDep = "tailwindcss" in allDeps || "@tailwindcss/postcss" in allDeps || "@tailwindcss/vite" in allDeps;
|
|
73
|
+
const hasTailwind = hasTailwindConfig || hasTailwindDep;
|
|
74
|
+
let framework = "unknown";
|
|
75
|
+
let nextRouter;
|
|
76
|
+
if (dependencies["next"] || devDependencies["next"]) {
|
|
77
|
+
framework = "next";
|
|
78
|
+
const searchPath = hasSrcDir ? path.join(rootPath, "src") : rootPath;
|
|
79
|
+
if (fs.existsSync(path.join(searchPath, "app"))) {
|
|
80
|
+
nextRouter = "app";
|
|
81
|
+
} else if (fs.existsSync(path.join(searchPath, "pages"))) {
|
|
82
|
+
nextRouter = "pages";
|
|
83
|
+
} else {
|
|
84
|
+
nextRouter = "app";
|
|
85
|
+
}
|
|
86
|
+
} else if (dependencies["vite"] || devDependencies["vite"] || fs.existsSync(path.join(rootPath, "vite.config.ts")) || fs.existsSync(path.join(rootPath, "vite.config.js"))) {
|
|
87
|
+
framework = "vite";
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
packageManager,
|
|
91
|
+
isTypeScript,
|
|
92
|
+
framework,
|
|
93
|
+
nextRouter,
|
|
94
|
+
hasSrcDir,
|
|
95
|
+
hasTailwind,
|
|
96
|
+
rootPath
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/utils/config.ts
|
|
101
|
+
import fs2 from "fs";
|
|
102
|
+
import path2 from "path";
|
|
103
|
+
import os from "os";
|
|
104
|
+
|
|
105
|
+
// src/utils/types.ts
|
|
106
|
+
import { z } from "zod";
|
|
107
|
+
var SLUG_REGEX = /^[a-z0-9][a-z0-9-]*$/;
|
|
108
|
+
function validateSlug(slug) {
|
|
109
|
+
if (!SLUG_REGEX.test(slug)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Invalid component slug "${slug}". Slugs must contain only lowercase letters, numbers, and hyphens, and cannot start with a hyphen.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var ComponentFileSchema = z.object({
|
|
116
|
+
path: z.string(),
|
|
117
|
+
target: z.string(),
|
|
118
|
+
type: z.enum(["component", "types", "shader", "lib", "docs"]),
|
|
119
|
+
content: z.string()
|
|
120
|
+
});
|
|
121
|
+
var RegistryComponentSchema = z.object({
|
|
122
|
+
slug: z.string(),
|
|
123
|
+
name: z.string(),
|
|
124
|
+
title: z.string(),
|
|
125
|
+
description: z.string(),
|
|
126
|
+
version: z.string(),
|
|
127
|
+
tier: z.enum(["free", "pro"]),
|
|
128
|
+
category: z.string(),
|
|
129
|
+
type: z.string(),
|
|
130
|
+
frameworks: z.array(z.string()),
|
|
131
|
+
files: z.array(ComponentFileSchema),
|
|
132
|
+
dependencies: z.array(z.string()),
|
|
133
|
+
devDependencies: z.array(z.string()),
|
|
134
|
+
registryDependencies: z.array(z.string()),
|
|
135
|
+
requiresClient: z.boolean(),
|
|
136
|
+
supportsReducedMotion: z.boolean(),
|
|
137
|
+
usesWebGL: z.boolean(),
|
|
138
|
+
usesPointer: z.boolean(),
|
|
139
|
+
usesScroll: z.boolean(),
|
|
140
|
+
// Optional metadata — kept in sync with the registry builder schema.
|
|
141
|
+
// Declared here (rather than relying on .passthrough()) so the CLI can
|
|
142
|
+
// read them with full type-safety for post-install guidance.
|
|
143
|
+
entry: z.string().optional(),
|
|
144
|
+
exportName: z.string().optional(),
|
|
145
|
+
usesTailwind: z.boolean().optional(),
|
|
146
|
+
fallbacks: z.array(z.string()).optional(),
|
|
147
|
+
recipes: z.array(z.string()).optional(),
|
|
148
|
+
docsUrl: z.string().optional(),
|
|
149
|
+
previewUrl: z.string().optional()
|
|
150
|
+
}).passthrough();
|
|
151
|
+
var IndexComponentSchema = z.object({
|
|
152
|
+
slug: z.string(),
|
|
153
|
+
name: z.string(),
|
|
154
|
+
title: z.string(),
|
|
155
|
+
description: z.string(),
|
|
156
|
+
version: z.string(),
|
|
157
|
+
tier: z.enum(["free", "pro"]),
|
|
158
|
+
category: z.string(),
|
|
159
|
+
type: z.string(),
|
|
160
|
+
frameworks: z.array(z.string()),
|
|
161
|
+
files: z.array(z.object({
|
|
162
|
+
path: z.string(),
|
|
163
|
+
target: z.string(),
|
|
164
|
+
type: z.enum(["component", "types", "shader", "lib", "docs"])
|
|
165
|
+
})),
|
|
166
|
+
dependencies: z.array(z.string()),
|
|
167
|
+
devDependencies: z.array(z.string()),
|
|
168
|
+
registryDependencies: z.array(z.string()),
|
|
169
|
+
requiresClient: z.boolean(),
|
|
170
|
+
supportsReducedMotion: z.boolean(),
|
|
171
|
+
usesWebGL: z.boolean(),
|
|
172
|
+
usesPointer: z.boolean(),
|
|
173
|
+
usesScroll: z.boolean(),
|
|
174
|
+
entry: z.string().optional(),
|
|
175
|
+
exportName: z.string().optional(),
|
|
176
|
+
usesTailwind: z.boolean().optional(),
|
|
177
|
+
// These are emitted by the registry build and were reaching consumers only
|
|
178
|
+
// through .passthrough(), i.e. typed as unknown. Declared optional rather
|
|
179
|
+
// than required so an older published registry still validates.
|
|
180
|
+
fallbacks: z.array(z.string()).optional(),
|
|
181
|
+
recipes: z.array(z.string()).optional(),
|
|
182
|
+
docsUrl: z.string().optional(),
|
|
183
|
+
previewUrl: z.string().optional()
|
|
184
|
+
}).passthrough();
|
|
185
|
+
var RegistryIndexSchema = z.object({
|
|
186
|
+
version: z.string(),
|
|
187
|
+
components: z.array(IndexComponentSchema)
|
|
188
|
+
});
|
|
189
|
+
var VVConfigSchema = z.object({
|
|
190
|
+
tsx: z.boolean(),
|
|
191
|
+
framework: z.enum(["next", "vite", "unknown"]),
|
|
192
|
+
aliases: z.object({
|
|
193
|
+
vv: z.string()
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
var VVGlobalConfigSchema = z.object({
|
|
197
|
+
// Auth — license key for paid components
|
|
198
|
+
auth: z.object({
|
|
199
|
+
token: z.string().optional(),
|
|
200
|
+
email: z.string().optional(),
|
|
201
|
+
tier: z.enum(["free", "pro", "team"]).optional(),
|
|
202
|
+
expiresAt: z.string().optional()
|
|
203
|
+
}).optional(),
|
|
204
|
+
// Preferences
|
|
205
|
+
telemetry: z.boolean().optional()
|
|
206
|
+
}).passthrough();
|
|
207
|
+
var ManifestEntrySchema = z.object({
|
|
208
|
+
version: z.string(),
|
|
209
|
+
installedAt: z.string(),
|
|
210
|
+
installedOn: z.string(),
|
|
211
|
+
files: z.array(z.string())
|
|
212
|
+
});
|
|
213
|
+
var ManifestSchema = z.object({
|
|
214
|
+
components: z.record(z.string(), ManifestEntrySchema)
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// src/utils/config.ts
|
|
218
|
+
var CONFIG_FILE_NAME = "vv.config.json";
|
|
219
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
220
|
+
return path2.join(cwd, CONFIG_FILE_NAME);
|
|
221
|
+
}
|
|
222
|
+
function loadConfig(cwd = process.cwd()) {
|
|
223
|
+
const configPath = getConfigPath(cwd);
|
|
224
|
+
if (!fs2.existsSync(configPath)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
let raw;
|
|
228
|
+
try {
|
|
229
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
230
|
+
raw = JSON.parse(content);
|
|
231
|
+
} catch {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Failed to parse vv.config.json \u2014 the file contains invalid JSON. Delete it and run "npx vectorvesper init" to regenerate.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const result = VVConfigSchema.safeParse(raw);
|
|
237
|
+
if (!result.success) {
|
|
238
|
+
const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Invalid vv.config.json \u2014 schema validation failed:
|
|
241
|
+
${issues}
|
|
242
|
+
Delete the file and run "npx vectorvesper init" to regenerate.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return result.data;
|
|
246
|
+
}
|
|
247
|
+
function writeConfig(config, cwd = process.cwd()) {
|
|
248
|
+
const configPath = getConfigPath(cwd);
|
|
249
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
250
|
+
}
|
|
251
|
+
var GLOBAL_CONFIG_DIR = path2.join(os.homedir(), ".vv");
|
|
252
|
+
var GLOBAL_CONFIG_PATH = path2.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
253
|
+
function getGlobalConfigPath() {
|
|
254
|
+
return GLOBAL_CONFIG_PATH;
|
|
255
|
+
}
|
|
256
|
+
function loadGlobalConfig() {
|
|
257
|
+
if (!fs2.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
258
|
+
return {};
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const content = fs2.readFileSync(GLOBAL_CONFIG_PATH, "utf-8");
|
|
262
|
+
const raw = JSON.parse(content);
|
|
263
|
+
const result = VVGlobalConfigSchema.safeParse(raw);
|
|
264
|
+
if (!result.success) {
|
|
265
|
+
logger.debug(`Global config validation failed: ${result.error.message}`);
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
return result.data;
|
|
269
|
+
} catch {
|
|
270
|
+
logger.debug("Failed to parse global config, using defaults.");
|
|
271
|
+
return {};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function saveGlobalConfig(config) {
|
|
275
|
+
if (!fs2.existsSync(GLOBAL_CONFIG_DIR)) {
|
|
276
|
+
fs2.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
fs2.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
279
|
+
}
|
|
280
|
+
function getAuthToken() {
|
|
281
|
+
const config = loadGlobalConfig();
|
|
282
|
+
return config.auth?.token;
|
|
283
|
+
}
|
|
284
|
+
function resolveAlias(alias, hasSrcDir) {
|
|
285
|
+
if (alias.startsWith("@/")) {
|
|
286
|
+
return alias.replace("@/", hasSrcDir ? "src/" : "");
|
|
287
|
+
}
|
|
288
|
+
if (alias.startsWith("~/")) {
|
|
289
|
+
return alias.replace("~/", hasSrcDir ? "src/" : "");
|
|
290
|
+
}
|
|
291
|
+
return alias;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/utils/registry.ts
|
|
295
|
+
import fs3 from "fs";
|
|
296
|
+
import path3 from "path";
|
|
297
|
+
import { fileURLToPath } from "url";
|
|
298
|
+
var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/vectorvesper/vv-components/main/r";
|
|
299
|
+
var DEFAULT_PRO_API_URL = "https://vectorvesper.dev/api/cli/registry";
|
|
300
|
+
var FETCH_TIMEOUT_MS = 15e3;
|
|
301
|
+
var MAX_RETRIES = 1;
|
|
302
|
+
var RETRY_DELAY_MS = 1e3;
|
|
303
|
+
function getRegistryBaseUrl() {
|
|
304
|
+
return process.env.VV_REGISTRY_URL || DEFAULT_REGISTRY_URL;
|
|
305
|
+
}
|
|
306
|
+
function getProApiBaseUrl() {
|
|
307
|
+
return process.env.VV_PRO_API_URL || DEFAULT_PRO_API_URL;
|
|
308
|
+
}
|
|
309
|
+
function handleHttpError(status, url) {
|
|
310
|
+
switch (status) {
|
|
311
|
+
case 401:
|
|
312
|
+
throw new Error(
|
|
313
|
+
`Authentication required. This is a Pro component.
|
|
314
|
+
\u{1F449} Run "npx vectorvesper login <token>" \u2014 get a token at https://vectorvesper.dev/account
|
|
315
|
+
\u{1F511} Pro membership: https://vectorvesper.dev/pricing`
|
|
316
|
+
);
|
|
317
|
+
case 402:
|
|
318
|
+
throw new Error(
|
|
319
|
+
`This is a Pro component and requires an active membership.
|
|
320
|
+
\u{1F449} Upgrade at https://vectorvesper.dev/pricing
|
|
321
|
+
\u{1F4A1} Run "npx vectorvesper list" to see all free components.`
|
|
322
|
+
);
|
|
323
|
+
case 403:
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Access denied. Your membership doesn't include this component, or it has lapsed.
|
|
326
|
+
\u{1F449} Check your account at https://vectorvesper.dev/account
|
|
327
|
+
\u{1F4A1} Run "npx vectorvesper whoami" to see your current access.`
|
|
328
|
+
);
|
|
329
|
+
case 404:
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Component not found in the registry.
|
|
332
|
+
\u{1F449} Run "npx vectorvesper list" to see all available components.`
|
|
333
|
+
);
|
|
334
|
+
case 429:
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Rate limited by the registry. Please wait a moment and try again.`
|
|
337
|
+
);
|
|
338
|
+
default:
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Registry request failed: HTTP ${status}
|
|
341
|
+
URL: ${url}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function fetchWithRetry(url) {
|
|
346
|
+
const token = getAuthToken();
|
|
347
|
+
const headers = {
|
|
348
|
+
"User-Agent": "vv-cli",
|
|
349
|
+
"Accept": "application/json"
|
|
350
|
+
};
|
|
351
|
+
const isPublicRegistry = url.includes("raw.githubusercontent.com");
|
|
352
|
+
if (token && !isPublicRegistry) {
|
|
353
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
354
|
+
logger.debug(`Using auth token for request`);
|
|
355
|
+
}
|
|
356
|
+
let lastError = null;
|
|
357
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
358
|
+
try {
|
|
359
|
+
if (attempt > 0) {
|
|
360
|
+
logger.debug(`Retry attempt ${attempt} for ${url}`);
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt));
|
|
362
|
+
}
|
|
363
|
+
logger.debug(`Fetching: ${url}`);
|
|
364
|
+
const response = await fetch(url, {
|
|
365
|
+
headers,
|
|
366
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
367
|
+
});
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
if (response.status >= 400 && response.status < 500) {
|
|
370
|
+
handleHttpError(response.status, url);
|
|
371
|
+
}
|
|
372
|
+
throw new Error(`HTTP ${response.status}`);
|
|
373
|
+
}
|
|
374
|
+
return await response.text();
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (error instanceof Error) {
|
|
377
|
+
if (error.message.includes("Authentication required") || error.message.includes("Pro component") || error.message.includes("Access denied") || error.message.includes("not found in the registry")) {
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
if (error.name === "TimeoutError" || error.name === "AbortError") {
|
|
381
|
+
lastError = new Error(
|
|
382
|
+
`Request timed out after ${FETCH_TIMEOUT_MS / 1e3}s. Check your internet connection or try again.`
|
|
383
|
+
);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
lastError = error;
|
|
387
|
+
} else {
|
|
388
|
+
lastError = new Error(String(error));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
throw lastError || new Error(`Failed to fetch: ${url}`);
|
|
393
|
+
}
|
|
394
|
+
async function fetchRegistryData(urlOrPath) {
|
|
395
|
+
if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) {
|
|
396
|
+
if (urlOrPath.startsWith("http://") && !urlOrPath.includes("localhost") && !urlOrPath.includes("127.0.0.1")) {
|
|
397
|
+
logger.warn("Using insecure HTTP for registry. Consider switching to HTTPS.");
|
|
398
|
+
}
|
|
399
|
+
return fetchWithRetry(urlOrPath);
|
|
400
|
+
}
|
|
401
|
+
let cleanPath = urlOrPath;
|
|
402
|
+
if (urlOrPath.startsWith("file://")) {
|
|
403
|
+
cleanPath = fileURLToPath(urlOrPath);
|
|
404
|
+
}
|
|
405
|
+
cleanPath = path3.resolve(cleanPath);
|
|
406
|
+
if (!fs3.existsSync(cleanPath)) {
|
|
407
|
+
throw new Error(`Registry path not found: ${cleanPath}`);
|
|
408
|
+
}
|
|
409
|
+
logger.debug(`Reading local registry: ${cleanPath}`);
|
|
410
|
+
return fs3.readFileSync(cleanPath, "utf-8");
|
|
411
|
+
}
|
|
412
|
+
async function fetchRegistryIndex() {
|
|
413
|
+
const baseUrl = getRegistryBaseUrl();
|
|
414
|
+
const url = baseUrl.endsWith("/") ? `${baseUrl}registry.json` : `${baseUrl}/registry.json`;
|
|
415
|
+
const content = await fetchRegistryData(url);
|
|
416
|
+
let raw;
|
|
417
|
+
try {
|
|
418
|
+
raw = JSON.parse(content);
|
|
419
|
+
} catch {
|
|
420
|
+
throw new Error("Failed to parse registry index \u2014 received invalid JSON.");
|
|
421
|
+
}
|
|
422
|
+
const result = RegistryIndexSchema.safeParse(raw);
|
|
423
|
+
if (!result.success) {
|
|
424
|
+
logger.debug(`Registry index validation errors: ${result.error.message}`);
|
|
425
|
+
throw new Error("Registry index failed validation. The registry may be corrupted or incompatible with this CLI version.");
|
|
426
|
+
}
|
|
427
|
+
return result.data;
|
|
428
|
+
}
|
|
429
|
+
var indexCache = null;
|
|
430
|
+
async function getCachedIndex() {
|
|
431
|
+
if (indexCache) return indexCache;
|
|
432
|
+
try {
|
|
433
|
+
indexCache = await fetchRegistryIndex();
|
|
434
|
+
} catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return indexCache;
|
|
438
|
+
}
|
|
439
|
+
async function getComponentTier(slug) {
|
|
440
|
+
const index = await getCachedIndex();
|
|
441
|
+
return index?.components.find((c) => c.slug === slug)?.tier;
|
|
442
|
+
}
|
|
443
|
+
async function fetchComponent(slug) {
|
|
444
|
+
validateSlug(slug);
|
|
445
|
+
const tier = await getComponentTier(slug);
|
|
446
|
+
return tier === "pro" ? fetchProComponent(slug) : fetchFreeComponent(slug);
|
|
447
|
+
}
|
|
448
|
+
async function fetchFreeComponent(slug) {
|
|
449
|
+
const baseUrl = getRegistryBaseUrl();
|
|
450
|
+
const url = baseUrl.endsWith("/") ? `${baseUrl}${slug}.json` : `${baseUrl}/${slug}.json`;
|
|
451
|
+
return parseComponent(slug, await fetchRegistryData(url));
|
|
452
|
+
}
|
|
453
|
+
async function fetchProComponent(slug) {
|
|
454
|
+
if (!getAuthToken()) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`"${slug}" is a Pro component and needs authentication.
|
|
457
|
+
\u{1F449} Run "npx vectorvesper login <token>" \u2014 get a token at https://vectorvesper.dev/account
|
|
458
|
+
\u{1F511} Pro membership: https://vectorvesper.dev/pricing`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
const base = getProApiBaseUrl();
|
|
462
|
+
const url = base.endsWith("/") ? `${base}${slug}` : `${base}/${slug}`;
|
|
463
|
+
return parseComponent(slug, await fetchRegistryData(url));
|
|
464
|
+
}
|
|
465
|
+
function parseComponent(slug, content) {
|
|
466
|
+
let raw;
|
|
467
|
+
try {
|
|
468
|
+
raw = JSON.parse(content);
|
|
469
|
+
} catch {
|
|
470
|
+
throw new Error(`Failed to parse component "${slug}" \u2014 received invalid JSON.`);
|
|
471
|
+
}
|
|
472
|
+
const result = RegistryComponentSchema.safeParse(raw);
|
|
473
|
+
if (!result.success) {
|
|
474
|
+
const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
475
|
+
logger.debug(`Component "${slug}" validation errors:
|
|
476
|
+
${issues}`);
|
|
477
|
+
throw new Error(
|
|
478
|
+
`Component "${slug}" failed validation. This may indicate an incompatible registry version. Try updating the CLI.`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
return result.data;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export {
|
|
485
|
+
setVerbose,
|
|
486
|
+
logger,
|
|
487
|
+
detectProject,
|
|
488
|
+
validateSlug,
|
|
489
|
+
loadConfig,
|
|
490
|
+
writeConfig,
|
|
491
|
+
getGlobalConfigPath,
|
|
492
|
+
loadGlobalConfig,
|
|
493
|
+
saveGlobalConfig,
|
|
494
|
+
getAuthToken,
|
|
495
|
+
resolveAlias,
|
|
496
|
+
fetchRegistryIndex,
|
|
497
|
+
fetchComponent
|
|
498
|
+
};
|