@nexushub/client 0.0.4 → 0.0.7

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.
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
@@ -1,639 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // tools/cli.ts
4
- import { Command } from "commander";
5
- import * as fs from "fs-extra";
6
- import path from "path";
7
- import fetch from "node-fetch";
8
- import dotenv from "dotenv";
9
- import chalk from "chalk";
10
- import ora from "ora";
11
- dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
12
- dotenv.config({ path: path.resolve(process.cwd(), ".env") });
13
- var program = new Command();
14
- program.name("nexus").description("NexusHub CLI - The Developer Toolbelt").version("1.0.0");
15
- program.command("init").description("Initialize NexusHub configuration and seed data").action(initProject);
16
- program.command("pull").description(
17
- "Pull latest content from NexusHub to local cache (Offline Mode)"
18
- ).option("-k, --api-key <key>", "NexusHub API Key").option("-p, --project-id <id>", "NexusHub Project ID").option("-u, --api-url <url>", "NexusHub API URL").option("-o, --output <path>", "Output directory (default: .nexus)").option("-f, --force", "Force overwrite existing cache").action(syncContent);
19
- program.command("seed").description("Seed content from local files to NexusHub").option("-i, --input <path>", "Input directory (default: .nexus/seed)").option("-p, --project-id <id>", "NexusHub Project ID").option("-k, --api-key <key>", "NexusHub API Key").option("-u, --api-url <url>", "NexusHub API URL").option("--dry-run", "Validate seed data without uploading").action(seedContent);
20
- program.command("deploy").description("Trigger a production deployment").option("-p, --project-id <id>", "NexusHub Project ID").option("-k, --api-key <key>", "NexusHub API Key").option("-u, --api-url <url>", "NexusHub API URL").option("-e, --environment <env>", "Deployment environment", "production").action(triggerDeploy);
21
- program.command("status").description("Check project status and content statistics").option("-p, --project-id <id>", "NexusHub Project ID").option("-k, --api-key <key>", "NexusHub API Key").option("-u, --api-url <url>", "NexusHub API URL").action(checkStatus);
22
- program.command("types").description("Generate TypeScript interfaces from your CMS Schema").option("-o, --output <path>", "Output file path", "nexus.d.ts").action(generateTypes);
23
- program.parse(process.argv);
24
- async function getContext() {
25
- const apiKey = process.env.NEXUS_API_KEY || process.env.NEXT_PUBLIC_NEXUS_KEY;
26
- const projectId = process.env.NEXUS_PROJECT_ID || process.env.NEXT_PUBLIC_NEXUS_ID;
27
- const apiUrl = process.env.NEXUS_API_URL || "https://api.nexushub.com/v1";
28
- if (!apiKey || !projectId) {
29
- console.error(chalk.red("\n\u274C Missing Configuration"));
30
- console.log(
31
- chalk.gray(
32
- "Please set NEXUS_API_KEY and NEXUS_PROJECT_ID in your .env file."
33
- )
34
- );
35
- process.exit(1);
36
- }
37
- return { apiKey, projectId, apiUrl };
38
- }
39
- async function fetchWithAuth(url, apiKey, timeout = 3e4) {
40
- const controller = new AbortController();
41
- const timeoutId = setTimeout(() => controller.abort(), timeout);
42
- try {
43
- const response = await fetch(url, {
44
- headers: {
45
- Authorization: `Bearer ${apiKey}`,
46
- "Content-Type": "application/json",
47
- "User-Agent": "NexusHub-CLI/1.0.0"
48
- },
49
- signal: controller.signal
50
- });
51
- clearTimeout(timeoutId);
52
- if (!response.ok) {
53
- throw new Error(`API Error ${response.status}: ${response.statusText}`);
54
- }
55
- return response.json();
56
- } catch (error) {
57
- clearTimeout(timeoutId);
58
- if (error instanceof Error && error.name === "AbortError") {
59
- throw new Error("Request timeout after 30 seconds");
60
- }
61
- throw error;
62
- }
63
- }
64
- async function fetchCollections(baseUrl, apiKey, projectId) {
65
- const collectionsRes = await fetchWithAuth(`${baseUrl}/collections`, apiKey);
66
- const collections = collectionsRes.data || collectionsRes;
67
- const result = {};
68
- if (!Array.isArray(collections)) {
69
- return result;
70
- }
71
- const MAX_CONCURRENT = 3;
72
- const chunks = [];
73
- for (let i = 0; i < collections.length; i += MAX_CONCURRENT) {
74
- chunks.push(collections.slice(i, i + MAX_CONCURRENT));
75
- }
76
- for (const chunk of chunks) {
77
- await Promise.all(
78
- chunk.map(async (collection) => {
79
- try {
80
- const itemsRes = await fetchWithAuth(
81
- `${baseUrl}/collections/${collection.id}/items?limit=1000`,
82
- apiKey
83
- );
84
- const itemsData = itemsRes;
85
- result[collection.slug || collection.id] = itemsData.data || itemsData.items || [];
86
- } catch (error) {
87
- console.warn(
88
- chalk.yellow(
89
- `\u26A0\uFE0F Failed to fetch collection ${collection.id}:`,
90
- error instanceof Error ? error.message : String(error)
91
- )
92
- );
93
- }
94
- })
95
- );
96
- }
97
- return result;
98
- }
99
- async function initProject() {
100
- console.log(chalk.bold.cyan("\n\u{1F3AF} NexusHub CLI - Project Initialization\n"));
101
- const nexusDir = path.join(process.cwd(), ".nexus");
102
- const seedDir = path.join(nexusDir, "seed");
103
- const collectionsDir = path.join(seedDir, "collections");
104
- try {
105
- if (fs.existsSync(nexusDir)) {
106
- console.log(chalk.yellow("\u26A0\uFE0F .nexus directory already exists."));
107
- return;
108
- }
109
- await fs.ensureDir(collectionsDir);
110
- const examplePages = [
111
- {
112
- slug: "home",
113
- title: "Welcome to Our Site",
114
- content: "This is the home page content.",
115
- seo: {
116
- title: "Home Page",
117
- description: "Welcome to our amazing website"
118
- }
119
- },
120
- {
121
- slug: "about",
122
- title: "About Us",
123
- content: "Learn more about our company.",
124
- seo: {
125
- title: "About Page",
126
- description: "Learn about our company and mission"
127
- }
128
- }
129
- ];
130
- const exampleCollection = [
131
- {
132
- title: "First Blog Post",
133
- content: "This is the content of the first blog post.",
134
- author: "Admin",
135
- published: true
136
- },
137
- {
138
- title: "Second Blog Post",
139
- content: "This is another blog post example.",
140
- author: "Admin",
141
- published: true
142
- }
143
- ];
144
- const exampleGlobals = {
145
- siteName: "My Awesome Site",
146
- navigation: [
147
- { label: "Home", href: "/" },
148
- { label: "About", href: "/about" }
149
- ],
150
- footer: {
151
- copyright: "\xA9 2024 My Site"
152
- }
153
- };
154
- await fs.writeJson(path.join(seedDir, "pages.json"), examplePages, {
155
- spaces: 2
156
- });
157
- await fs.writeJson(
158
- path.join(collectionsDir, "blog_posts.json"),
159
- exampleCollection,
160
- { spaces: 2 }
161
- );
162
- await fs.writeJson(path.join(seedDir, "globals.json"), exampleGlobals, {
163
- spaces: 2
164
- });
165
- const gitignorePath = path.join(nexusDir, ".gitignore");
166
- await fs.writeFile(gitignorePath, "cache.json\n");
167
- const readmePath = path.join(nexusDir, "README.md");
168
- const readmeContent = `# NexusHub Content Seed
169
-
170
- This directory contains seed data for your NexusHub project.
171
-
172
- ## Structure
173
-
174
- - \`pages.json\` - Static pages (home, about, contact, etc.)
175
- - \`collections/\` - Dynamic content collections
176
- - \`globals.json\` - Global site settings
177
-
178
- ## Usage
179
-
180
- 1. Edit the seed files with your content
181
- 2. Run \`npx nexus seed\` to upload to NexusHub
182
- 3. Run \`npx nexus pull\` to sync content locally
183
-
184
- ## Notes
185
-
186
- - The \`cache.json\` file is ignored by git (contains local copies)
187
- - Use the CLI to keep content in sync between local and production
188
- `;
189
- await fs.writeFile(readmePath, readmeContent);
190
- console.log(chalk.green("\u2705 Project initialized successfully!"));
191
- console.log(chalk.gray("\n\u{1F4C1} Created directory structure:"));
192
- console.log(chalk.gray(` ${nexusDir}/`));
193
- console.log(chalk.gray(` \u251C\u2500\u2500 seed/`));
194
- console.log(chalk.gray(` \u2502 \u251C\u2500\u2500 pages.json (example pages)`));
195
- console.log(chalk.gray(` \u2502 \u251C\u2500\u2500 collections/`));
196
- console.log(chalk.gray(` \u2502 \u2502 \u2514\u2500\u2500 blog_posts.json (example)`));
197
- console.log(chalk.gray(` \u2502 \u2514\u2500\u2500 globals.json (site settings)`));
198
- console.log(chalk.gray(` \u251C\u2500\u2500 .gitignore`));
199
- console.log(chalk.gray(` \u2514\u2500\u2500 README.md`));
200
- console.log(chalk.yellow("\n\u{1F680} Next steps:"));
201
- console.log(chalk.gray(" 1. Edit the seed files with your content"));
202
- console.log(chalk.gray(" 2. Set your API credentials in .env:"));
203
- console.log(chalk.gray(" NEXUS_API_KEY=your_key_here"));
204
- console.log(chalk.gray(" NEXUS_PROJECT_ID=your_project_id_here"));
205
- console.log(chalk.gray(" 3. Run `npx nexus seed` to upload content"));
206
- } catch (error) {
207
- console.error(
208
- chalk.red(`\u274C Failed to initialize project: ${error.message}`)
209
- );
210
- process.exit(1);
211
- }
212
- }
213
- async function syncContent(options) {
214
- console.log(chalk.bold.cyan("\n\u{1F680} NexusHub CLI - Content Sync\n"));
215
- const apiKey = options.apiKey || process.env.NEXUS_API_KEY || process.env.NEXT_PUBLIC_NEXUS_KEY;
216
- const projectId = options.projectId || process.env.NEXUS_PROJECT_ID || process.env.NEXT_PUBLIC_NEXUS_ID;
217
- const apiUrl = options.apiUrl || process.env.NEXUS_API_URL || process.env.NEXT_PUBLIC_NEXUS_API_URL || "https://api.nexushub.com/v1";
218
- const outputDir = options.output || ".nexus";
219
- if (!apiKey || !projectId) {
220
- console.error(chalk.red("\u274C Missing API Key or Project ID"));
221
- console.log(
222
- chalk.gray(
223
- "Set NEXUS_API_KEY and NEXUS_PROJECT_ID in .env or use --api-key and --project-id flags"
224
- )
225
- );
226
- process.exit(1);
227
- }
228
- const spinner = ora(
229
- `Fetching content for project: ${chalk.green(projectId)}...`
230
- ).start();
231
- try {
232
- const cacheDir = path.join(process.cwd(), outputDir);
233
- const cacheFile = path.join(cacheDir, "cache.json");
234
- if (await fs.pathExists(cacheFile) && !options.force) {
235
- spinner.stop();
236
- console.log(
237
- chalk.yellow(
238
- "\u26A0\uFE0F Cache file already exists. Use --force to overwrite."
239
- )
240
- );
241
- process.exit(0);
242
- }
243
- const [pages, collections, globals] = await Promise.all([
244
- fetchWithAuth(`${apiUrl}/content/${projectId}/pages`, apiKey),
245
- fetchCollections(`${apiUrl}/content/${projectId}`, apiKey, projectId),
246
- fetchWithAuth(`${apiUrl}/project/${projectId}/globals`, apiKey)
247
- ]);
248
- const cacheData = {
249
- pages: pages.data || pages,
250
- collections,
251
- globals: globals.data || globals,
252
- meta: {
253
- pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
254
- projectId,
255
- apiUrl,
256
- count: {
257
- pages: (pages.data || pages).length,
258
- collections: Object.keys(collections).length
259
- }
260
- }
261
- };
262
- await fs.ensureDir(cacheDir);
263
- await fs.writeJson(cacheFile, cacheData, { spaces: 2 });
264
- spinner.succeed(chalk.green("Content synced successfully!"));
265
- console.log(chalk.gray(` \u{1F4C1} Saved to: ${cacheFile}`));
266
- console.log(chalk.gray(` \u{1F4CA} Pages: ${cacheData.meta.count.pages}`));
267
- console.log(
268
- chalk.gray(` \u{1F4DA} Collections: ${cacheData.meta.count.collections}`)
269
- );
270
- console.log(chalk.yellow(` \u26A1 Mode: Offline / Local Cache Enabled`));
271
- } catch (error) {
272
- spinner.fail(chalk.red("Failed to sync content"));
273
- console.error(chalk.red(` Error: ${error.message}`));
274
- if (error.message.includes("401") || error.message.includes("403")) {
275
- console.log(chalk.yellow(" \u{1F511} Check your API key and permissions"));
276
- } else if (error.message.includes("404")) {
277
- console.log(
278
- chalk.yellow(" \u{1F50D} Project not found. Check your project ID")
279
- );
280
- }
281
- process.exit(1);
282
- }
283
- }
284
- async function seedContent(options) {
285
- console.log(chalk.bold.cyan("\n\u{1F331} NexusHub CLI - Content Seeding\n"));
286
- const apiKey = options.apiKey || process.env.NEXUS_API_KEY;
287
- const projectId = options.projectId || process.env.NEXUS_PROJECT_ID;
288
- const apiUrl = options.apiUrl || process.env.NEXUS_API_URL || "https://api.nexushub.com/v1";
289
- const inputDir = options.input || ".nexus/seed";
290
- const dryRun = options.dryRun || false;
291
- if (!apiKey || !projectId) {
292
- console.error(chalk.red("\u274C Missing API Key or Project ID"));
293
- process.exit(1);
294
- }
295
- const seedPath = path.join(process.cwd(), inputDir);
296
- if (!await fs.pathExists(seedPath)) {
297
- console.error(chalk.red(`\u274C Seed directory not found: ${seedPath}`));
298
- console.log(chalk.gray("Create a seed directory with your content files:"));
299
- console.log(chalk.gray(" mkdir -p .nexus/seed"));
300
- console.log(
301
- chalk.gray(" # Add pages.json, collections/ folder, globals.json")
302
- );
303
- process.exit(1);
304
- }
305
- const spinner = ora("Validating seed data...").start();
306
- try {
307
- const pagesFile = path.join(seedPath, "pages.json");
308
- const collectionsDir = path.join(seedPath, "collections");
309
- const globalsFile = path.join(seedPath, "globals.json");
310
- let pageCount = 0;
311
- let collectionCount = 0;
312
- let itemCount = 0;
313
- if (await fs.pathExists(pagesFile)) {
314
- const pages = await fs.readJson(pagesFile);
315
- pageCount = Array.isArray(pages) ? pages.length : 0;
316
- if (!dryRun && Array.isArray(pages)) {
317
- spinner.text = `Seeding ${pages.length} pages...`;
318
- await seedPages(pages, apiUrl, projectId, apiKey);
319
- }
320
- }
321
- if (await fs.pathExists(collectionsDir)) {
322
- if (!dryRun) {
323
- spinner.text = "Seeding collections...";
324
- const counts = await seedCollections(
325
- collectionsDir,
326
- apiUrl,
327
- projectId,
328
- apiKey
329
- );
330
- collectionCount = counts.collections;
331
- itemCount = counts.items;
332
- } else {
333
- const files = await fs.readdir(collectionsDir);
334
- collectionCount = files.filter((f) => f.endsWith(".json")).length;
335
- }
336
- }
337
- if (await fs.pathExists(globalsFile)) {
338
- const globals = await fs.readJson(globalsFile);
339
- if (!dryRun) {
340
- spinner.text = "Seeding globals...";
341
- await seedGlobals(globals, apiUrl, projectId, apiKey);
342
- }
343
- }
344
- if (dryRun) {
345
- spinner.succeed(chalk.green("Seed data validated successfully!"));
346
- console.log(chalk.gray(` \u{1F4C4} Pages: ${pageCount}`));
347
- console.log(chalk.gray(` \u{1F4DA} Collections: ${collectionCount}`));
348
- console.log(chalk.gray(` \u{1F4E6} Items: ${itemCount}`));
349
- } else {
350
- spinner.succeed(chalk.green("Content seeded successfully!"));
351
- }
352
- } catch (error) {
353
- spinner.fail(chalk.red("Failed to seed content"));
354
- console.error(chalk.red(` Error: ${error.message}`));
355
- process.exit(1);
356
- }
357
- }
358
- async function seedPages(pages, apiUrl, projectId, apiKey) {
359
- const results = [];
360
- for (const page of pages) {
361
- try {
362
- const response = await fetch(`${apiUrl}/content/${projectId}/pages`, {
363
- method: "POST",
364
- headers: {
365
- Authorization: `Bearer ${apiKey}`,
366
- "Content-Type": "application/json"
367
- },
368
- body: JSON.stringify(page)
369
- });
370
- if (response.ok) {
371
- results.push({ slug: page.slug, success: true });
372
- } else {
373
- const errorText = await response.text();
374
- results.push({ slug: page.slug, success: false, error: errorText });
375
- }
376
- } catch (error) {
377
- results.push({ slug: page.slug, success: false, error: error.message });
378
- }
379
- }
380
- const successful = results.filter((r) => r.success);
381
- const failed = results.filter((r) => !r.success);
382
- if (failed.length > 0) {
383
- console.warn(chalk.yellow(` \u26A0\uFE0F ${failed.length} pages failed:`));
384
- failed.forEach((f) => {
385
- console.log(chalk.gray(` - ${f.slug}: ${f.error}`));
386
- });
387
- }
388
- }
389
- async function seedCollections(collectionsDir, apiUrl, projectId, apiKey) {
390
- const files = await fs.readdir(collectionsDir);
391
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
392
- let totalItems = 0;
393
- for (const file of jsonFiles) {
394
- const collectionName = path.basename(file, ".json");
395
- const filePath = path.join(collectionsDir, file);
396
- const items = await fs.readJson(filePath);
397
- if (!Array.isArray(items)) {
398
- console.warn(
399
- chalk.yellow(` \u26A0\uFE0F ${file} does not contain a valid array of items`)
400
- );
401
- continue;
402
- }
403
- try {
404
- await fetch(`${apiUrl}/content/${projectId}/collections`, {
405
- method: "POST",
406
- headers: {
407
- Authorization: `Bearer ${apiKey}`,
408
- "Content-Type": "application/json"
409
- },
410
- body: JSON.stringify({
411
- name: collectionName,
412
- slug: collectionName,
413
- description: `Auto-generated from seed data`
414
- })
415
- });
416
- let itemCount = 0;
417
- for (const item of items) {
418
- try {
419
- await fetch(
420
- `${apiUrl}/content/${projectId}/collections/${collectionName}/items`,
421
- {
422
- method: "POST",
423
- headers: {
424
- Authorization: `Bearer ${apiKey}`,
425
- "Content-Type": "application/json"
426
- },
427
- body: JSON.stringify(item)
428
- }
429
- );
430
- itemCount++;
431
- } catch (error) {
432
- console.warn(
433
- chalk.yellow(
434
- ` Failed to create item in ${collectionName}: ${error instanceof Error ? error.message : String(error)}`
435
- )
436
- );
437
- }
438
- }
439
- console.log(
440
- chalk.gray(
441
- ` \u2705 Created collection ${collectionName} with ${itemCount} items`
442
- )
443
- );
444
- totalItems += itemCount;
445
- } catch (error) {
446
- console.warn(
447
- chalk.yellow(
448
- ` \u274C Failed to create collection ${collectionName}: ${error.message}`
449
- )
450
- );
451
- }
452
- }
453
- return { collections: jsonFiles.length, items: totalItems };
454
- }
455
- async function seedGlobals(globals, apiUrl, projectId, apiKey) {
456
- try {
457
- await fetch(`${apiUrl}/project/${projectId}/globals`, {
458
- method: "PUT",
459
- headers: {
460
- Authorization: `Bearer ${apiKey}`,
461
- "Content-Type": "application/json"
462
- },
463
- body: JSON.stringify(globals)
464
- });
465
- console.log(chalk.gray(" \u2705 Created globals"));
466
- } catch (error) {
467
- console.warn(
468
- chalk.yellow(` \u274C Failed to create globals: ${error.message}`)
469
- );
470
- }
471
- }
472
- async function triggerDeploy(options) {
473
- console.log(chalk.bold.cyan("\n\u{1F680} NexusHub CLI - Trigger Deployment\n"));
474
- const apiKey = options.apiKey || process.env.NEXUS_API_KEY;
475
- const projectId = options.projectId || process.env.NEXUS_PROJECT_ID;
476
- const apiUrl = options.apiUrl || process.env.NEXUS_API_URL || "https://api.nexushub.com/v1";
477
- const environment = options.environment || "production";
478
- if (!apiKey || !projectId) {
479
- console.error(chalk.red("\u274C Missing API Key or Project ID"));
480
- process.exit(1);
481
- }
482
- const spinner = ora(`Triggering ${environment} deployment...`).start();
483
- try {
484
- const response = await fetch(`${apiUrl}/projects/${projectId}/deploy`, {
485
- method: "POST",
486
- headers: {
487
- Authorization: `Bearer ${apiKey}`,
488
- "Content-Type": "application/json"
489
- },
490
- body: JSON.stringify({
491
- environment,
492
- trigger: "cli",
493
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
494
- })
495
- });
496
- if (response.ok) {
497
- const data = await response.json();
498
- spinner.succeed(chalk.green("Deployment triggered successfully!"));
499
- console.log(chalk.gray(` \u{1F517} Deployment ID: ${data.deploymentId}`));
500
- console.log(chalk.gray(` \u{1F30D} Environment: ${environment}`));
501
- console.log(
502
- chalk.gray(` \u23F0 Started at: ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`)
503
- );
504
- } else {
505
- throw new Error(`API Error ${response.status}: ${response.statusText}`);
506
- }
507
- } catch (error) {
508
- spinner.fail(chalk.red("Failed to trigger deployment"));
509
- console.error(chalk.red(` Error: ${error.message}`));
510
- process.exit(1);
511
- }
512
- }
513
- async function checkStatus(options) {
514
- console.log(chalk.bold.cyan("\n\u{1F4CA} NexusHub CLI - Project Status\n"));
515
- const apiKey = options.apiKey || process.env.NEXUS_API_KEY;
516
- const projectId = options.projectId || process.env.NEXUS_PROJECT_ID;
517
- const apiUrl = options.apiUrl || process.env.NEXUS_API_URL || "https://api.nexushub.com/v1";
518
- if (!apiKey || !projectId) {
519
- console.error(chalk.red("\u274C Missing API Key or Project ID"));
520
- process.exit(1);
521
- }
522
- const spinner = ora("Fetching project status...").start();
523
- try {
524
- const response = await fetch(`${apiUrl}/projects/${projectId}/status`, {
525
- headers: {
526
- Authorization: `Bearer ${apiKey}`,
527
- "Content-Type": "application/json"
528
- }
529
- });
530
- if (response.ok) {
531
- const data = await response.json();
532
- spinner.succeed(chalk.green("Project status retrieved!"));
533
- console.log(chalk.gray(` \u{1F4CB} Project: ${data.project.name}`));
534
- console.log(chalk.gray(` \u{1F194} ID: ${data.project.id}`));
535
- console.log(chalk.gray(` \u{1F4C4} Pages: ${data.stats.pages}`));
536
- console.log(chalk.gray(` \u{1F4DA} Collections: ${data.stats.collections}`));
537
- console.log(chalk.gray(` \u{1F465} Users: ${data.stats.users}`));
538
- console.log(
539
- chalk.gray(
540
- ` \u{1F552} Last Updated: ${new Date(data.project.updatedAt).toLocaleString()}`
541
- )
542
- );
543
- } else {
544
- throw new Error(`API Error ${response.status}: ${response.statusText}`);
545
- }
546
- } catch (error) {
547
- spinner.fail(chalk.red("Failed to fetch project status"));
548
- console.error(chalk.red(` Error: ${error.message}`));
549
- process.exit(1);
550
- }
551
- }
552
- async function generateTypes(options) {
553
- const { apiKey, projectId, apiUrl } = await getContext();
554
- const spinner = ora("Fetching Content Schema...").start();
555
- try {
556
- const response = await fetch(`${apiUrl}/content/${projectId}/schema`, {
557
- headers: { Authorization: `Bearer ${apiKey}` }
558
- });
559
- if (!response.ok) throw new Error(`API Error: ${response.statusText}`);
560
- const schema = await response.json();
561
- const collections = schema.collections || [];
562
- let typeDefs = `/**
563
- * NexusHub Auto-Generated Types
564
- * Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
565
- */
566
-
567
- `;
568
- typeDefs += `export interface NexusBase { id: string; createdAt: string; updatedAt: string; }
569
-
570
- `;
571
- for (const col of collections) {
572
- const name = formatInterfaceName(col.slug);
573
- typeDefs += `export interface ${name} extends NexusBase {
574
- `;
575
- for (const field of col.fields) {
576
- const tsType = mapFieldTypeToTs(field.type);
577
- const optional = field.required ? "" : "?";
578
- typeDefs += ` ${field.slug}${optional}: ${tsType};
579
- `;
580
- }
581
- typeDefs += `}
582
-
583
- `;
584
- }
585
- typeDefs += `export interface NexusCollections {
586
- `;
587
- for (const col of collections) {
588
- typeDefs += ` '${col.slug}': ${formatInterfaceName(col.slug)};
589
- `;
590
- }
591
- typeDefs += `}
592
- `;
593
- const outputPath = path.resolve(process.cwd(), options.output);
594
- await fs.writeFile(outputPath, typeDefs);
595
- spinner.succeed(chalk.green(`Types generated at ${options.output}`));
596
- if (collections.length > 0) {
597
- console.log(
598
- chalk.gray(
599
- ` \u2728 You can now use: nexus.getCollection<${formatInterfaceName(collections[0].slug)}>('${collections[0].slug}')`
600
- )
601
- );
602
- }
603
- } catch (error) {
604
- spinner.fail(chalk.red("Failed to generate types"));
605
- console.error(chalk.red(` Error: ${error.message}`));
606
- process.exit(1);
607
- }
608
- }
609
- function mapFieldTypeToTs(cmsType) {
610
- switch (cmsType) {
611
- case "text":
612
- case "rich_text":
613
- case "slug":
614
- case "image":
615
- case "url":
616
- case "email":
617
- return "string";
618
- case "number":
619
- case "price":
620
- return "number";
621
- case "boolean":
622
- return "boolean";
623
- case "json":
624
- return "Record<string, any>";
625
- case "date":
626
- return "string";
627
- // ISO Date
628
- case "reference":
629
- return "string";
630
- // ID string
631
- case "media_list":
632
- return "string[]";
633
- default:
634
- return "any";
635
- }
636
- }
637
- function formatInterfaceName(slug) {
638
- return slug.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("").replace(/s$/, "");
639
- }