@nextsparkjs/cli 0.1.0-beta.1 → 0.1.0-beta.10

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/cli.js CHANGED
@@ -1,8 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ __require,
4
+ addPlugin,
5
+ addPluginCommand,
6
+ fetchPackage,
7
+ installTheme,
8
+ runPostinstall,
9
+ validateTheme
10
+ } from "./chunk-ALB2C27N.js";
2
11
 
3
12
  // src/cli.ts
4
13
  import { Command } from "commander";
5
- import chalk6 from "chalk";
14
+ import chalk15 from "chalk";
6
15
 
7
16
  // src/commands/dev.ts
8
17
  import { spawn } from "child_process";
@@ -52,8 +61,8 @@ async function devCommand(options) {
52
61
  spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
53
62
  const processes = [];
54
63
  if (options.registry) {
55
- console.log(chalk.blue("\n[Registry] Starting registry watcher..."));
56
- const registryProcess = spawn("node", ["scripts/registry-watch.js"], {
64
+ console.log(chalk.blue("\n[Registry] Starting registry builder with watch mode..."));
65
+ const registryProcess = spawn("node", ["scripts/build/registry.mjs", "--watch"], {
57
66
  cwd: coreDir,
58
67
  stdio: "inherit",
59
68
  env: {
@@ -107,21 +116,46 @@ async function devCommand(options) {
107
116
 
108
117
  // src/commands/build.ts
109
118
  import { spawn as spawn2 } from "child_process";
119
+ import { existsSync as existsSync2, readFileSync } from "fs";
120
+ import { join } from "path";
110
121
  import chalk2 from "chalk";
111
122
  import ora2 from "ora";
123
+ function loadProjectEnv(projectRoot) {
124
+ const envPath = join(projectRoot, ".env");
125
+ const envVars = {};
126
+ if (existsSync2(envPath)) {
127
+ const content = readFileSync(envPath, "utf-8");
128
+ for (const line of content.split("\n")) {
129
+ const trimmed = line.trim();
130
+ if (trimmed && !trimmed.startsWith("#")) {
131
+ const [key, ...valueParts] = trimmed.split("=");
132
+ if (key && valueParts.length > 0) {
133
+ let value = valueParts.join("=");
134
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
135
+ value = value.slice(1, -1);
136
+ }
137
+ envVars[key] = value;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return envVars;
143
+ }
112
144
  async function buildCommand(options) {
113
145
  const spinner = ora2("Preparing production build...").start();
114
146
  try {
115
147
  const coreDir = getCoreDir();
116
148
  const projectRoot = getProjectRoot();
117
149
  spinner.succeed("Core package found");
150
+ const projectEnv = loadProjectEnv(projectRoot);
118
151
  if (options.registry) {
119
152
  spinner.start("Generating registries...");
120
- await new Promise((resolve2, reject) => {
121
- const registryProcess = spawn2("node", ["scripts/registry-build.js"], {
153
+ await new Promise((resolve4, reject) => {
154
+ const registryProcess = spawn2("node", ["scripts/build/registry.mjs"], {
122
155
  cwd: coreDir,
123
156
  stdio: "pipe",
124
157
  env: {
158
+ ...projectEnv,
125
159
  ...process.env,
126
160
  NEXTSPARK_PROJECT_ROOT: projectRoot
127
161
  }
@@ -132,7 +166,7 @@ async function buildCommand(options) {
132
166
  });
133
167
  registryProcess.on("close", (code) => {
134
168
  if (code === 0) {
135
- resolve2();
169
+ resolve4();
136
170
  } else {
137
171
  reject(new Error(`Registry generation failed: ${stderr}`));
138
172
  }
@@ -146,6 +180,7 @@ async function buildCommand(options) {
146
180
  cwd: projectRoot,
147
181
  stdio: "inherit",
148
182
  env: {
183
+ ...projectEnv,
149
184
  ...process.env,
150
185
  NEXTSPARK_CORE_DIR: coreDir,
151
186
  NODE_ENV: "production"
@@ -177,8 +212,31 @@ Build failed with exit code ${code}`));
177
212
 
178
213
  // src/commands/generate.ts
179
214
  import { spawn as spawn3 } from "child_process";
215
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
216
+ import { join as join2 } from "path";
180
217
  import chalk3 from "chalk";
181
218
  import ora3 from "ora";
219
+ function loadProjectEnv2(projectRoot) {
220
+ const envPath = join2(projectRoot, ".env");
221
+ const envVars = {};
222
+ if (existsSync3(envPath)) {
223
+ const content = readFileSync2(envPath, "utf-8");
224
+ for (const line of content.split("\n")) {
225
+ const trimmed = line.trim();
226
+ if (trimmed && !trimmed.startsWith("#")) {
227
+ const [key, ...valueParts] = trimmed.split("=");
228
+ if (key && valueParts.length > 0) {
229
+ let value = valueParts.join("=");
230
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
231
+ value = value.slice(1, -1);
232
+ }
233
+ envVars[key] = value;
234
+ }
235
+ }
236
+ }
237
+ }
238
+ return envVars;
239
+ }
182
240
  async function generateCommand(options) {
183
241
  const spinner = ora3("Preparing registry generation...").start();
184
242
  try {
@@ -186,14 +244,16 @@ async function generateCommand(options) {
186
244
  const projectRoot = getProjectRoot();
187
245
  const mode = isMonorepoMode() ? "monorepo" : "npm";
188
246
  spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
189
- const scriptName = options.watch ? "registry-watch.js" : "registry-build.js";
247
+ const projectEnv = loadProjectEnv2(projectRoot);
248
+ const watchArg = options.watch ? ["--watch"] : [];
190
249
  const action = options.watch ? "Watching" : "Generating";
191
250
  console.log(chalk3.blue(`
192
251
  ${action} registries...`));
193
- const generateProcess = spawn3("node", [`scripts/${scriptName}`], {
252
+ const generateProcess = spawn3("node", ["scripts/build/registry.mjs", ...watchArg], {
194
253
  cwd: coreDir,
195
254
  stdio: "inherit",
196
255
  env: {
256
+ ...projectEnv,
197
257
  ...process.env,
198
258
  NEXTSPARK_PROJECT_ROOT: projectRoot
199
259
  }
@@ -236,19 +296,44 @@ Registry generation failed with exit code ${code}`));
236
296
 
237
297
  // src/commands/registry.ts
238
298
  import { spawn as spawn4 } from "child_process";
299
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
300
+ import { join as join3 } from "path";
239
301
  import chalk4 from "chalk";
240
302
  import ora4 from "ora";
303
+ function loadProjectEnv3(projectRoot) {
304
+ const envPath = join3(projectRoot, ".env");
305
+ const envVars = {};
306
+ if (existsSync4(envPath)) {
307
+ const content = readFileSync3(envPath, "utf-8");
308
+ for (const line of content.split("\n")) {
309
+ const trimmed = line.trim();
310
+ if (trimmed && !trimmed.startsWith("#")) {
311
+ const [key, ...valueParts] = trimmed.split("=");
312
+ if (key && valueParts.length > 0) {
313
+ let value = valueParts.join("=");
314
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
315
+ value = value.slice(1, -1);
316
+ }
317
+ envVars[key] = value;
318
+ }
319
+ }
320
+ }
321
+ }
322
+ return envVars;
323
+ }
241
324
  async function registryBuildCommand() {
242
325
  const spinner = ora4("Building registries...").start();
243
326
  try {
244
327
  const coreDir = getCoreDir();
245
328
  const projectRoot = getProjectRoot();
246
329
  const mode = isMonorepoMode() ? "monorepo" : "npm";
330
+ const projectEnv = loadProjectEnv3(projectRoot);
247
331
  spinner.text = `Building registries (${mode} mode)...`;
248
- const buildProcess = spawn4("node", ["scripts/registry-build.js"], {
332
+ const buildProcess = spawn4("node", ["scripts/build/registry.mjs"], {
249
333
  cwd: coreDir,
250
334
  stdio: "pipe",
251
335
  env: {
336
+ ...projectEnv,
252
337
  ...process.env,
253
338
  NEXTSPARK_PROJECT_ROOT: projectRoot
254
339
  }
@@ -297,10 +382,12 @@ async function registryWatchCommand() {
297
382
  const mode = isMonorepoMode() ? "monorepo" : "npm";
298
383
  spinner.succeed(`Registry watcher started (${mode} mode)`);
299
384
  console.log(chalk4.blue("\nWatching for changes... Press Ctrl+C to stop.\n"));
300
- const watchProcess = spawn4("node", ["scripts/registry-watch.js"], {
385
+ const projectEnv = loadProjectEnv3(projectRoot);
386
+ const watchProcess = spawn4("node", ["scripts/build/registry.mjs", "--watch"], {
301
387
  cwd: coreDir,
302
388
  stdio: "inherit",
303
389
  env: {
390
+ ...projectEnv,
304
391
  ...process.env,
305
392
  NEXTSPARK_PROJECT_ROOT: projectRoot
306
393
  }
@@ -335,111 +422,3251 @@ Watcher exited with code ${code}`));
335
422
  }
336
423
 
337
424
  // src/commands/init.ts
338
- import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync } from "fs";
339
- import { join } from "path";
425
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync7 } from "fs";
426
+ import { join as join8 } from "path";
427
+ import chalk12 from "chalk";
428
+ import ora8 from "ora";
429
+
430
+ // src/wizard/index.ts
431
+ import chalk11 from "chalk";
432
+ import ora7 from "ora";
433
+ import { confirm as confirm5 } from "@inquirer/prompts";
434
+ import { execSync } from "child_process";
435
+ import { existsSync as existsSync7, readdirSync } from "fs";
436
+ import { join as join7 } from "path";
437
+
438
+ // src/wizard/banner.ts
340
439
  import chalk5 from "chalk";
341
- import ora5 from "ora";
342
- function generateInitialRegistries(registriesDir) {
343
- writeFileSync(join(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
344
- import type { ComponentType } from 'react'
440
+ import { readFileSync as readFileSync4 } from "fs";
441
+ import { fileURLToPath as fileURLToPath2 } from "url";
442
+ import { dirname as dirname2, join as join4 } from "path";
443
+ var __filename2 = fileURLToPath2(import.meta.url);
444
+ var __dirname2 = dirname2(__filename2);
445
+ function getCliVersion() {
446
+ try {
447
+ const packageJsonPath = join4(__dirname2, "../../package.json");
448
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
449
+ return packageJson.version || "unknown";
450
+ } catch {
451
+ return "unknown";
452
+ }
453
+ }
454
+ var BANNER = `
455
+ _ __ __ _____ __
456
+ / | / /__ _ __/ /_/ ___/____ ____ ______/ /__
457
+ / |/ / _ \\| |/_/ __/\\__ \\/ __ \\/ __ \`/ ___/ //_/
458
+ / /| / __/> </ /_ ___/ / /_/ / /_/ / / / ,<
459
+ /_/ |_/\\___/_/|_|\\__//____/ .___/\\__,_/_/ /_/|_|
460
+ /_/
461
+ `;
462
+ function showBanner() {
463
+ const version = getCliVersion();
464
+ console.log(chalk5.cyan(BANNER));
465
+ console.log(chalk5.bold.white(" Welcome to NextSpark - The Complete SaaS Framework for Next.js"));
466
+ console.log(chalk5.gray(` Version ${version} - Create production-ready SaaS applications in minutes
467
+ `));
468
+ console.log(chalk5.gray(" " + "=".repeat(60) + "\n"));
469
+ }
470
+ function showSection(title, step, totalSteps) {
471
+ console.log("");
472
+ console.log(chalk5.cyan(` Step ${step}/${totalSteps}: ${title}`));
473
+ console.log(chalk5.gray(" " + "-".repeat(40)));
474
+ console.log("");
475
+ }
476
+ function showWarning(message) {
477
+ console.log(chalk5.yellow(` \u26A0 ${message}`));
478
+ }
479
+ function showError(message) {
480
+ console.log(chalk5.red(` \u2717 ${message}`));
481
+ }
482
+ function showInfo(message) {
483
+ console.log(chalk5.blue(` \u2139 ${message}`));
484
+ }
345
485
 
346
- export const BLOCK_REGISTRY: Record<string, {
347
- name: string
348
- slug: string
349
- componentPath: string
350
- fields?: unknown[]
351
- examples?: unknown[]
352
- }> = {}
486
+ // src/wizard/prompts/project-info.ts
487
+ import { input } from "@inquirer/prompts";
488
+ function toSlug(str) {
489
+ return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
490
+ }
491
+ function validateProjectName(name) {
492
+ if (!name || name.trim().length === 0) {
493
+ return "Project name is required";
494
+ }
495
+ if (name.trim().length < 2) {
496
+ return "Project name must be at least 2 characters";
497
+ }
498
+ if (name.trim().length > 50) {
499
+ return "Project name must be less than 50 characters";
500
+ }
501
+ return true;
502
+ }
503
+ function validateSlug(slug) {
504
+ if (!slug || slug.trim().length === 0) {
505
+ return "Slug is required";
506
+ }
507
+ if (!/^[a-z][a-z0-9-]*$/.test(slug)) {
508
+ return "Slug must start with a letter and contain only lowercase letters, numbers, and hyphens";
509
+ }
510
+ if (slug.length < 2) {
511
+ return "Slug must be at least 2 characters";
512
+ }
513
+ if (slug.length > 30) {
514
+ return "Slug must be less than 30 characters";
515
+ }
516
+ return true;
517
+ }
518
+ async function promptProjectInfo() {
519
+ showSection("Project Information", 1, 5);
520
+ const projectName = await input({
521
+ message: "What is your project name?",
522
+ default: "My SaaS App",
523
+ validate: validateProjectName
524
+ });
525
+ const suggestedSlug = toSlug(projectName);
526
+ const projectSlug = await input({
527
+ message: "Project slug (used for theme folder and URLs):",
528
+ default: suggestedSlug,
529
+ validate: validateSlug
530
+ });
531
+ const projectDescription = await input({
532
+ message: "Short description of your project:",
533
+ default: "A modern SaaS application built with NextSpark"
534
+ });
535
+ return {
536
+ projectName,
537
+ projectSlug,
538
+ projectDescription
539
+ };
540
+ }
353
541
 
354
- // Lazy-loaded block components - populated by 'nextspark generate'
355
- export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
356
- `);
357
- writeFileSync(join(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
358
- export const THEME_REGISTRY: Record<string, unknown> = {}
359
- `);
360
- writeFileSync(join(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
361
- export const ENTITY_REGISTRY: Record<string, unknown> = {}
362
- `);
363
- writeFileSync(join(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
364
- export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
365
- export function parseChildEntity(path: string) { return null }
366
- export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
367
- export function clientMetaSystemAdapter() { return {} }
368
- export type ClientEntityConfig = Record<string, unknown>
369
- `);
370
- writeFileSync(join(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
371
- export const BILLING_REGISTRY = { plans: [], features: [] }
372
- `);
373
- writeFileSync(join(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
374
- export const PLUGIN_REGISTRY: Record<string, unknown> = {}
375
- `);
376
- writeFileSync(join(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
377
- export const FLOW_REGISTRY: Record<string, unknown> = {}
378
- export const FEATURE_REGISTRY: Record<string, unknown> = {}
379
- export const TAGS_REGISTRY: Record<string, unknown> = {}
380
- export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
381
- export type FlowEntry = unknown
382
- export type FeatureEntry = unknown
383
- `);
384
- writeFileSync(join(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
385
- export const DOCS_REGISTRY = { sections: [], pages: [] }
386
- export type DocSectionMeta = { title: string; slug: string }
387
- `);
388
- writeFileSync(join(registriesDir, "index.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
389
- export * from './block-registry'
390
- export * from './theme-registry'
391
- export * from './entity-registry'
392
- export * from './entity-registry.client'
393
- export * from './billing-registry'
394
- export * from './plugin-registry'
395
- export * from './testing-registry'
396
- export * from './docs-registry'
397
- `);
542
+ // src/wizard/prompts/team-config.ts
543
+ import { select, checkbox } from "@inquirer/prompts";
544
+
545
+ // src/wizard/types.ts
546
+ var AVAILABLE_LOCALES = {
547
+ en: "English",
548
+ es: "Spanish",
549
+ fr: "French",
550
+ de: "German",
551
+ it: "Italian",
552
+ pt: "Portuguese"
553
+ };
554
+ var DEFAULT_ROLES = ["owner", "admin", "member", "viewer"];
555
+ var CURRENCIES = [
556
+ { value: "usd", label: "USD - US Dollar" },
557
+ { value: "eur", label: "EUR - Euro" },
558
+ { value: "gbp", label: "GBP - British Pound" },
559
+ { value: "cad", label: "CAD - Canadian Dollar" },
560
+ { value: "aud", label: "AUD - Australian Dollar" },
561
+ { value: "ars", label: "ARS - Argentine Peso" },
562
+ { value: "brl", label: "BRL - Brazilian Real" },
563
+ { value: "mxn", label: "MXN - Mexican Peso" }
564
+ ];
565
+
566
+ // src/wizard/prompts/team-config.ts
567
+ var TEAM_MODE_OPTIONS = [
568
+ {
569
+ name: "Multi-tenant (Multiple teams, team switching)",
570
+ value: "multi-tenant",
571
+ description: "Users can create and join multiple teams. Perfect for CRM, project management, or collaboration tools."
572
+ },
573
+ {
574
+ name: "Single-tenant (One organization, no switching)",
575
+ value: "single-tenant",
576
+ description: "All users belong to one organization. Ideal for internal tools or company-specific applications."
577
+ },
578
+ {
579
+ name: "Single-user (Personal app, no teams)",
580
+ value: "single-user",
581
+ description: "Individual user accounts without team features. Perfect for personal dashboards or blogs."
582
+ }
583
+ ];
584
+ var ROLE_OPTIONS = [
585
+ { name: "Owner (Full control, can delete team)", value: "owner", checked: true },
586
+ { name: "Admin (Can manage members and settings)", value: "admin", checked: true },
587
+ { name: "Member (Standard access)", value: "member", checked: true },
588
+ { name: "Viewer (Read-only access)", value: "viewer", checked: true }
589
+ ];
590
+ async function promptTeamConfig() {
591
+ showSection("Team Configuration", 2, 5);
592
+ const teamMode = await select({
593
+ message: "What team mode do you need?",
594
+ choices: TEAM_MODE_OPTIONS,
595
+ default: "multi-tenant"
596
+ });
597
+ const selectedOption = TEAM_MODE_OPTIONS.find((o) => o.value === teamMode);
598
+ if (selectedOption) {
599
+ showInfo(selectedOption.description);
600
+ }
601
+ let teamRoles = DEFAULT_ROLES;
602
+ if (teamMode !== "single-user") {
603
+ console.log("");
604
+ teamRoles = await checkbox({
605
+ message: "Which roles do you want to include?",
606
+ choices: ROLE_OPTIONS,
607
+ required: true
608
+ });
609
+ if (!teamRoles.includes("owner")) {
610
+ teamRoles = ["owner", ...teamRoles];
611
+ showInfo("Owner role is required and has been added automatically.");
612
+ }
613
+ console.log("");
614
+ showInfo("You can add custom roles later by editing app.config.ts");
615
+ }
616
+ return {
617
+ teamMode,
618
+ teamRoles
619
+ };
398
620
  }
399
- async function initCommand(options) {
400
- const spinner = ora5("Initializing NextSpark project...").start();
401
- const projectRoot = process.cwd();
621
+
622
+ // src/wizard/prompts/i18n-config.ts
623
+ import { select as select2, checkbox as checkbox2 } from "@inquirer/prompts";
624
+ var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
625
+ name: `${name} (${value})`,
626
+ value,
627
+ checked: value === "en"
628
+ // English selected by default
629
+ }));
630
+ async function promptI18nConfig() {
631
+ showSection("Internationalization", 3, 5);
632
+ const supportedLocales = await checkbox2({
633
+ message: "Which languages do you want to support?",
634
+ choices: LOCALE_OPTIONS,
635
+ required: true
636
+ });
637
+ if (supportedLocales.length === 0) {
638
+ showInfo("At least one language is required. English has been selected.");
639
+ supportedLocales.push("en");
640
+ }
641
+ let defaultLocale = "en";
642
+ if (supportedLocales.length === 1) {
643
+ defaultLocale = supportedLocales[0];
644
+ showInfo(`Default language set to ${AVAILABLE_LOCALES[defaultLocale]}`);
645
+ } else {
646
+ console.log("");
647
+ defaultLocale = await select2({
648
+ message: "Which should be the default language?",
649
+ choices: supportedLocales.map((locale) => ({
650
+ name: `${AVAILABLE_LOCALES[locale]} (${locale})`,
651
+ value: locale
652
+ })),
653
+ default: supportedLocales.includes("en") ? "en" : supportedLocales[0]
654
+ });
655
+ }
656
+ return {
657
+ defaultLocale,
658
+ supportedLocales
659
+ };
660
+ }
661
+
662
+ // src/wizard/prompts/billing-config.ts
663
+ import { select as select3 } from "@inquirer/prompts";
664
+ var BILLING_MODEL_OPTIONS = [
665
+ {
666
+ name: "Free (No payments)",
667
+ value: "free",
668
+ description: "All features are free. No payment processing needed."
669
+ },
670
+ {
671
+ name: "Freemium (Free + Paid tiers)",
672
+ value: "freemium",
673
+ description: "Free tier with optional paid upgrades (Free + Pro plans)."
674
+ },
675
+ {
676
+ name: "Paid (Subscription required)",
677
+ value: "paid",
678
+ description: "Subscription-based access with optional trial period."
679
+ }
680
+ ];
681
+ async function promptBillingConfig() {
682
+ showSection("Billing Configuration", 4, 5);
683
+ const billingModel = await select3({
684
+ message: "What billing model do you want to use?",
685
+ choices: BILLING_MODEL_OPTIONS,
686
+ default: "freemium"
687
+ });
688
+ const selectedOption = BILLING_MODEL_OPTIONS.find((o) => o.value === billingModel);
689
+ if (selectedOption) {
690
+ showInfo(selectedOption.description);
691
+ }
692
+ let currency = "usd";
693
+ if (billingModel !== "free") {
694
+ console.log("");
695
+ currency = await select3({
696
+ message: "What currency will you use?",
697
+ choices: CURRENCIES.map((c) => ({
698
+ name: c.label,
699
+ value: c.value
700
+ })),
701
+ default: "usd"
702
+ });
703
+ }
704
+ console.log("");
705
+ showInfo("You can customize plans and pricing in billing.config.ts");
706
+ return {
707
+ billingModel,
708
+ currency
709
+ };
710
+ }
711
+
712
+ // src/wizard/prompts/features-config.ts
713
+ import { checkbox as checkbox3 } from "@inquirer/prompts";
714
+ var FEATURE_OPTIONS = [
715
+ {
716
+ name: "Analytics Dashboard",
717
+ value: "analytics",
718
+ description: "Built-in analytics and metrics dashboard",
719
+ checked: true
720
+ },
721
+ {
722
+ name: "Team Management",
723
+ value: "teams",
724
+ description: "Team invitations, roles, and member management",
725
+ checked: true
726
+ },
727
+ {
728
+ name: "Billing & Subscriptions",
729
+ value: "billing",
730
+ description: "Stripe integration for payments and subscriptions",
731
+ checked: true
732
+ },
733
+ {
734
+ name: "API Access",
735
+ value: "api",
736
+ description: "REST API endpoints with authentication",
737
+ checked: true
738
+ },
739
+ {
740
+ name: "Documentation Site",
741
+ value: "docs",
742
+ description: "Built-in documentation system with markdown support",
743
+ checked: false
744
+ }
745
+ ];
746
+ async function promptFeaturesConfig() {
747
+ showSection("Features", 5, 5);
748
+ showInfo("Select the features you want to include in your project.");
749
+ showInfo("You can add or remove features later by editing your config files.");
750
+ console.log("");
751
+ const selectedFeatures = await checkbox3({
752
+ message: "Which features do you want to enable?",
753
+ choices: FEATURE_OPTIONS
754
+ });
755
+ const features = {
756
+ analytics: selectedFeatures.includes("analytics"),
757
+ teams: selectedFeatures.includes("teams"),
758
+ billing: selectedFeatures.includes("billing"),
759
+ api: selectedFeatures.includes("api"),
760
+ docs: selectedFeatures.includes("docs")
761
+ };
762
+ return {
763
+ features
764
+ };
765
+ }
766
+
767
+ // src/wizard/prompts/content-features-config.ts
768
+ import { checkbox as checkbox4 } from "@inquirer/prompts";
769
+ var CONTENT_FEATURE_OPTIONS = [
770
+ {
771
+ name: "Pages with Page Builder",
772
+ value: "pages",
773
+ description: "Adds the 'page' entity with full page builder support. Build custom pages using blocks.",
774
+ checked: false
775
+ },
776
+ {
777
+ name: "Blog",
778
+ value: "blog",
779
+ description: "Adds the 'post' entity for blog articles with the Post Content block for rich editorial content.",
780
+ checked: false
781
+ }
782
+ ];
783
+ async function promptContentFeaturesConfig(mode = "interactive", totalSteps = 9) {
784
+ showSection("Content Features", 6, totalSteps);
785
+ showInfo("Enable optional content features for your project.");
786
+ showInfo("These features add entities and blocks for building pages and blog posts.");
787
+ console.log("");
788
+ const selectedFeatures = await checkbox4({
789
+ message: "Which content features do you want to enable?",
790
+ choices: CONTENT_FEATURE_OPTIONS
791
+ });
792
+ console.log("");
793
+ showInfo("You can add more blocks later in contents/themes/[your-theme]/blocks/");
794
+ const contentFeatures = {
795
+ pages: selectedFeatures.includes("pages"),
796
+ blog: selectedFeatures.includes("blog")
797
+ };
798
+ return {
799
+ contentFeatures
800
+ };
801
+ }
802
+
803
+ // src/wizard/prompts/auth-config.ts
804
+ import { checkbox as checkbox5 } from "@inquirer/prompts";
805
+ var AUTH_METHOD_OPTIONS = [
806
+ {
807
+ name: "Email & Password",
808
+ value: "emailPassword",
809
+ description: "Traditional email and password authentication",
810
+ checked: true
811
+ },
812
+ {
813
+ name: "Google OAuth",
814
+ value: "googleOAuth",
815
+ description: "Sign in with Google accounts",
816
+ checked: false
817
+ }
818
+ ];
819
+ var SECURITY_OPTIONS = [
820
+ {
821
+ name: "Email Verification",
822
+ value: "emailVerification",
823
+ description: "Require users to verify their email address",
824
+ checked: true
825
+ }
826
+ ];
827
+ function getDefaultAuthConfig() {
828
+ return {
829
+ emailPassword: true,
830
+ googleOAuth: false,
831
+ emailVerification: true
832
+ };
833
+ }
834
+ async function promptAuthConfig(mode = "interactive", totalSteps = 8) {
835
+ showSection("Authentication", 6, totalSteps);
836
+ showInfo("Configure how users will authenticate to your application.");
837
+ console.log("");
838
+ const selectedMethods = await checkbox5({
839
+ message: "Which authentication methods do you want to enable?",
840
+ choices: AUTH_METHOD_OPTIONS
841
+ });
842
+ let selectedSecurity = ["emailVerification"];
843
+ if (mode === "expert") {
844
+ console.log("");
845
+ showInfo("Configure additional security features.");
846
+ console.log("");
847
+ selectedSecurity = await checkbox5({
848
+ message: "Which security features do you want to enable?",
849
+ choices: SECURITY_OPTIONS
850
+ });
851
+ }
852
+ const auth = {
853
+ emailPassword: selectedMethods.includes("emailPassword"),
854
+ googleOAuth: selectedMethods.includes("googleOAuth"),
855
+ emailVerification: selectedSecurity.includes("emailVerification")
856
+ };
857
+ return { auth };
858
+ }
859
+
860
+ // src/wizard/prompts/dashboard-config.ts
861
+ import { checkbox as checkbox6, confirm } from "@inquirer/prompts";
862
+ var DASHBOARD_FEATURE_OPTIONS = [
863
+ {
864
+ name: "Global Search",
865
+ value: "search",
866
+ description: "Search across your application from the dashboard",
867
+ checked: false
868
+ },
869
+ {
870
+ name: "Notifications",
871
+ value: "notifications",
872
+ description: "In-app notification system with bell icon",
873
+ checked: false
874
+ },
875
+ {
876
+ name: "Theme Toggle",
877
+ value: "themeToggle",
878
+ description: "Allow users to switch between light and dark themes",
879
+ checked: true
880
+ },
881
+ {
882
+ name: "Support/Help Menu",
883
+ value: "support",
884
+ description: "Help dropdown with documentation and support links",
885
+ checked: true
886
+ },
887
+ {
888
+ name: "Quick Create",
889
+ value: "quickCreate",
890
+ description: "Quick create button for creating new entities",
891
+ checked: true
892
+ },
893
+ {
894
+ name: "Superadmin Access",
895
+ value: "superadminAccess",
896
+ description: "Button to access superadmin area (only visible to superadmins)",
897
+ checked: true
898
+ },
899
+ {
900
+ name: "DevTools Access",
901
+ value: "devtoolsAccess",
902
+ description: "Button to access developer tools (only visible to developers)",
903
+ checked: true
904
+ }
905
+ ];
906
+ function getDefaultDashboardConfig() {
907
+ return {
908
+ search: false,
909
+ notifications: false,
910
+ themeToggle: true,
911
+ support: true,
912
+ quickCreate: true,
913
+ superadminAccess: true,
914
+ devtoolsAccess: true,
915
+ sidebarCollapsed: false
916
+ };
917
+ }
918
+ async function promptDashboardConfig(mode = "interactive", totalSteps = 8) {
919
+ showSection("Dashboard", 7, totalSteps);
920
+ showInfo("Configure your dashboard user interface.");
921
+ showInfo("These settings can be changed later in your theme config.");
922
+ console.log("");
923
+ const selectedFeatures = await checkbox6({
924
+ message: "Which dashboard features do you want to enable?",
925
+ choices: DASHBOARD_FEATURE_OPTIONS
926
+ });
927
+ let sidebarCollapsed = false;
928
+ if (mode === "expert") {
929
+ console.log("");
930
+ sidebarCollapsed = await confirm({
931
+ message: "Start with sidebar collapsed by default?",
932
+ default: false
933
+ });
934
+ }
935
+ const dashboard = {
936
+ search: selectedFeatures.includes("search"),
937
+ notifications: selectedFeatures.includes("notifications"),
938
+ themeToggle: selectedFeatures.includes("themeToggle"),
939
+ support: selectedFeatures.includes("support"),
940
+ quickCreate: selectedFeatures.includes("quickCreate"),
941
+ superadminAccess: selectedFeatures.includes("superadminAccess"),
942
+ devtoolsAccess: selectedFeatures.includes("devtoolsAccess"),
943
+ sidebarCollapsed
944
+ };
945
+ return { dashboard };
946
+ }
947
+
948
+ // src/wizard/prompts/dev-config.ts
949
+ import { checkbox as checkbox7, confirm as confirm2 } from "@inquirer/prompts";
950
+ var DEV_TOOL_OPTIONS = [
951
+ {
952
+ name: "Dev Keyring",
953
+ value: "devKeyring",
954
+ description: "Quick login as different users during development",
955
+ checked: true
956
+ },
957
+ {
958
+ name: "Debug Mode",
959
+ value: "debugMode",
960
+ description: "Enable verbose logging and debugging tools",
961
+ checked: false
962
+ }
963
+ ];
964
+ function getDefaultDevConfig() {
965
+ return {
966
+ devKeyring: true,
967
+ debugMode: false
968
+ };
969
+ }
970
+ async function promptDevConfig(mode = "interactive", totalSteps = 8) {
971
+ showSection("Development Tools", 8, totalSteps);
972
+ showInfo("Configure development and debugging tools.");
973
+ showWarning("These tools are disabled in production builds.");
974
+ console.log("");
975
+ let devKeyring = true;
976
+ let debugMode = false;
977
+ if (mode === "expert") {
978
+ const selectedTools = await checkbox7({
979
+ message: "Which development tools do you want to enable?",
980
+ choices: DEV_TOOL_OPTIONS
981
+ });
982
+ devKeyring = selectedTools.includes("devKeyring");
983
+ debugMode = selectedTools.includes("debugMode");
984
+ } else {
985
+ devKeyring = await confirm2({
986
+ message: "Enable dev keyring for quick user switching during development?",
987
+ default: true
988
+ });
989
+ }
990
+ const dev = {
991
+ devKeyring,
992
+ debugMode
993
+ };
994
+ return { dev };
995
+ }
996
+
997
+ // src/wizard/prompts/theme-selection.ts
998
+ import { select as select4 } from "@inquirer/prompts";
999
+ import chalk6 from "chalk";
1000
+ async function promptThemeSelection() {
1001
+ console.log("");
1002
+ console.log(chalk6.cyan(" Reference Theme Installation"));
1003
+ console.log(chalk6.gray(" " + "-".repeat(40)));
1004
+ console.log("");
1005
+ console.log(chalk6.gray(" A reference theme provides a complete example to learn from."));
1006
+ console.log(chalk6.gray(" Your custom theme (based on starter) will be your active theme."));
1007
+ console.log("");
1008
+ const theme = await select4({
1009
+ message: "Which reference theme would you like to install?",
1010
+ choices: [
1011
+ {
1012
+ name: "None (skip)",
1013
+ value: null,
1014
+ description: "Only my custom theme, no reference (add later with add:theme)"
1015
+ },
1016
+ {
1017
+ name: "Default (SaaS boilerplate)",
1018
+ value: "default",
1019
+ description: "Full-featured SaaS with dashboard, billing, AI chat"
1020
+ },
1021
+ {
1022
+ name: "Blog",
1023
+ value: "blog",
1024
+ description: "Content management and publishing platform"
1025
+ },
1026
+ {
1027
+ name: "CRM",
1028
+ value: "crm",
1029
+ description: "Customer relationship management"
1030
+ },
1031
+ {
1032
+ name: "Productivity",
1033
+ value: "productivity",
1034
+ description: "Tasks, projects, and calendar management"
1035
+ }
1036
+ ],
1037
+ default: null
1038
+ });
1039
+ return theme;
1040
+ }
1041
+
1042
+ // src/wizard/prompts/plugins-selection.ts
1043
+ import { checkbox as checkbox8 } from "@inquirer/prompts";
1044
+ import chalk7 from "chalk";
1045
+ var THEME_REQUIRED_PLUGINS = {
1046
+ "default": ["langchain"],
1047
+ "blog": [],
1048
+ "crm": [],
1049
+ "productivity": []
1050
+ };
1051
+ async function promptPluginsSelection(selectedTheme) {
1052
+ const requiredPlugins = selectedTheme ? THEME_REQUIRED_PLUGINS[selectedTheme] || [] : [];
1053
+ console.log("");
1054
+ console.log(chalk7.cyan(" Plugin Selection"));
1055
+ console.log(chalk7.gray(" " + "-".repeat(40)));
1056
+ if (requiredPlugins.length > 0) {
1057
+ console.log("");
1058
+ console.log(chalk7.gray(` Note: ${requiredPlugins.join(", ")} will be installed (required by theme)`));
1059
+ }
1060
+ console.log("");
1061
+ const plugins = await checkbox8({
1062
+ message: "Select plugins to install (Enter to skip, Space to select):",
1063
+ choices: [
1064
+ {
1065
+ name: "AI",
1066
+ value: "ai",
1067
+ description: "AI SDK with OpenAI, Anthropic, Ollama support",
1068
+ checked: false
1069
+ },
1070
+ {
1071
+ name: "LangChain",
1072
+ value: "langchain",
1073
+ description: "AI agents, chains, and advanced AI features",
1074
+ checked: requiredPlugins.includes("langchain"),
1075
+ disabled: requiredPlugins.includes("langchain") ? "(required by theme)" : false
1076
+ },
1077
+ {
1078
+ name: "Social Media Publisher",
1079
+ value: "social-media-publisher",
1080
+ description: "Multi-platform social media publishing",
1081
+ checked: false
1082
+ }
1083
+ ]
1084
+ });
1085
+ const allPlugins = [.../* @__PURE__ */ new Set([...requiredPlugins, ...plugins])];
1086
+ return allPlugins;
1087
+ }
1088
+ function getRequiredPlugins(theme) {
1089
+ if (!theme) return [];
1090
+ return THEME_REQUIRED_PLUGINS[theme] || [];
1091
+ }
1092
+
1093
+ // src/wizard/prompts/env-config.ts
1094
+ import { confirm as confirm3, input as input2 } from "@inquirer/prompts";
1095
+
1096
+ // src/wizard/prompts/git-config.ts
1097
+ import { confirm as confirm4, input as input3 } from "@inquirer/prompts";
1098
+
1099
+ // src/wizard/prompts/index.ts
1100
+ async function runAllPrompts() {
1101
+ const projectInfo = await promptProjectInfo();
1102
+ const teamConfig = await promptTeamConfig();
1103
+ const i18nConfig = await promptI18nConfig();
1104
+ const billingConfig = await promptBillingConfig();
1105
+ const featuresConfig = await promptFeaturesConfig();
1106
+ const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 9);
1107
+ const authConfig = await promptAuthConfig("interactive", 9);
1108
+ const dashboardConfig = await promptDashboardConfig("interactive", 9);
1109
+ const devConfig = await promptDevConfig("interactive", 9);
1110
+ return {
1111
+ ...projectInfo,
1112
+ ...teamConfig,
1113
+ ...i18nConfig,
1114
+ ...billingConfig,
1115
+ ...featuresConfig,
1116
+ ...contentFeaturesConfig,
1117
+ ...authConfig,
1118
+ ...dashboardConfig,
1119
+ ...devConfig
1120
+ };
1121
+ }
1122
+ async function runQuickPrompts() {
1123
+ const projectInfo = await promptProjectInfo();
1124
+ const teamConfig = await promptTeamConfig();
1125
+ const i18nConfig = await promptI18nConfig();
1126
+ const billingConfig = await promptBillingConfig();
1127
+ const featuresConfig = await promptFeaturesConfig();
1128
+ const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 9);
1129
+ const authConfig = { auth: getDefaultAuthConfig() };
1130
+ const dashboardConfig = { dashboard: getDefaultDashboardConfig() };
1131
+ const devConfig = { dev: getDefaultDevConfig() };
1132
+ return {
1133
+ ...projectInfo,
1134
+ ...teamConfig,
1135
+ ...i18nConfig,
1136
+ ...billingConfig,
1137
+ ...featuresConfig,
1138
+ ...contentFeaturesConfig,
1139
+ ...authConfig,
1140
+ ...dashboardConfig,
1141
+ ...devConfig
1142
+ };
1143
+ }
1144
+ async function runExpertPrompts() {
1145
+ const projectInfo = await promptProjectInfo();
1146
+ const teamConfig = await promptTeamConfig();
1147
+ const i18nConfig = await promptI18nConfig();
1148
+ const billingConfig = await promptBillingConfig();
1149
+ const featuresConfig = await promptFeaturesConfig();
1150
+ const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 9);
1151
+ const authConfig = await promptAuthConfig("expert", 9);
1152
+ const dashboardConfig = await promptDashboardConfig("expert", 9);
1153
+ const devConfig = await promptDevConfig("expert", 9);
1154
+ return {
1155
+ ...projectInfo,
1156
+ ...teamConfig,
1157
+ ...i18nConfig,
1158
+ ...billingConfig,
1159
+ ...featuresConfig,
1160
+ ...contentFeaturesConfig,
1161
+ ...authConfig,
1162
+ ...dashboardConfig,
1163
+ ...devConfig
1164
+ };
1165
+ }
1166
+
1167
+ // src/wizard/generators/index.ts
1168
+ import fs7 from "fs-extra";
1169
+ import path5 from "path";
1170
+ import { fileURLToPath as fileURLToPath5 } from "url";
1171
+
1172
+ // src/wizard/generators/theme-renamer.ts
1173
+ import fs from "fs-extra";
1174
+ import path from "path";
1175
+ import { fileURLToPath as fileURLToPath3 } from "url";
1176
+ var __filename3 = fileURLToPath3(import.meta.url);
1177
+ var __dirname3 = path.dirname(__filename3);
1178
+ function getTemplatesDir() {
402
1179
  try {
403
- const nextspark = join(projectRoot, ".nextspark");
404
- const registriesDir = join(nextspark, "registries");
405
- if (!existsSync2(registriesDir) || options.force) {
406
- mkdirSync(registriesDir, { recursive: true });
407
- spinner.text = "Creating .nextspark/registries/";
408
- generateInitialRegistries(registriesDir);
409
- spinner.text = "Generated initial registries";
1180
+ const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1181
+ return path.join(path.dirname(corePkgPath), "templates");
1182
+ } catch {
1183
+ const possiblePaths = [
1184
+ path.resolve(__dirname3, "../../../../../core/templates"),
1185
+ path.resolve(__dirname3, "../../../../core/templates"),
1186
+ path.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1187
+ ];
1188
+ for (const p of possiblePaths) {
1189
+ if (fs.existsSync(p)) {
1190
+ return p;
1191
+ }
410
1192
  }
411
- const tsconfigPath = join(projectRoot, "tsconfig.json");
412
- if (existsSync2(tsconfigPath)) {
413
- spinner.text = "Updating tsconfig.json paths...";
414
- const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
415
- tsconfig.compilerOptions = tsconfig.compilerOptions || {};
416
- tsconfig.compilerOptions.paths = {
417
- ...tsconfig.compilerOptions.paths,
418
- "@nextsparkjs/registries": ["./.nextspark/registries/index.ts"],
419
- "@nextsparkjs/registries/*": ["./.nextspark/registries/*"]
420
- };
421
- writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
1193
+ throw new Error("Could not find @nextsparkjs/core templates directory");
1194
+ }
1195
+ }
1196
+ function getTargetThemesDir() {
1197
+ return path.resolve(process.cwd(), "contents", "themes");
1198
+ }
1199
+ async function copyStarterTheme(config) {
1200
+ const templatesDir = getTemplatesDir();
1201
+ const starterThemePath = path.join(templatesDir, "contents", "themes", "starter");
1202
+ const targetThemesDir = getTargetThemesDir();
1203
+ const newThemePath = path.join(targetThemesDir, config.projectSlug);
1204
+ if (!await fs.pathExists(starterThemePath)) {
1205
+ throw new Error(`Starter theme not found at: ${starterThemePath}`);
1206
+ }
1207
+ if (await fs.pathExists(newThemePath)) {
1208
+ throw new Error(`Theme already exists at: ${newThemePath}. Please choose a different name or remove the existing theme.`);
1209
+ }
1210
+ await fs.ensureDir(targetThemesDir);
1211
+ await fs.copy(starterThemePath, newThemePath);
1212
+ }
1213
+ async function updateThemeConfig(config) {
1214
+ const themeConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "theme.config.ts");
1215
+ if (!await fs.pathExists(themeConfigPath)) {
1216
+ throw new Error(`theme.config.ts not found at: ${themeConfigPath}`);
1217
+ }
1218
+ let content = await fs.readFile(themeConfigPath, "utf-8");
1219
+ content = content.replace(
1220
+ /name:\s*['"]starter['"]/g,
1221
+ `name: '${config.projectSlug}'`
1222
+ );
1223
+ content = content.replace(
1224
+ /displayName:\s*['"]Starter['"]/g,
1225
+ `displayName: '${config.projectName}'`
1226
+ );
1227
+ content = content.replace(
1228
+ /description:\s*['"]Minimal starter theme for NextSpark['"]/g,
1229
+ `description: '${config.projectDescription}'`
1230
+ );
1231
+ content = content.replace(
1232
+ /export const starterThemeConfig/g,
1233
+ `export const ${toCamelCase(config.projectSlug)}ThemeConfig`
1234
+ );
1235
+ content = content.replace(
1236
+ /export default starterThemeConfig/g,
1237
+ `export default ${toCamelCase(config.projectSlug)}ThemeConfig`
1238
+ );
1239
+ await fs.writeFile(themeConfigPath, content, "utf-8");
1240
+ }
1241
+ async function updateDevConfig(config) {
1242
+ const devConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "dev.config.ts");
1243
+ if (!await fs.pathExists(devConfigPath)) {
1244
+ return;
1245
+ }
1246
+ let content = await fs.readFile(devConfigPath, "utf-8");
1247
+ content = content.replace(/STARTER THEME/g, `${config.projectName.toUpperCase()}`);
1248
+ content = content.replace(/Starter Theme/g, config.projectName);
1249
+ await fs.writeFile(devConfigPath, content, "utf-8");
1250
+ }
1251
+ async function updateAppConfig(config) {
1252
+ const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1253
+ if (!await fs.pathExists(appConfigPath)) {
1254
+ throw new Error(`app.config.ts not found at: ${appConfigPath}`);
1255
+ }
1256
+ let content = await fs.readFile(appConfigPath, "utf-8");
1257
+ content = content.replace(
1258
+ /name:\s*['"]Starter['"]/g,
1259
+ `name: '${config.projectName}'`
1260
+ );
1261
+ content = content.replace(
1262
+ /mode:\s*['"]multi-tenant['"]\s*as\s*const/g,
1263
+ `mode: '${config.teamMode}' as const`
1264
+ );
1265
+ const localesArray = config.supportedLocales.map((l) => `'${l}'`).join(", ");
1266
+ content = content.replace(
1267
+ /supportedLocales:\s*\[.*?\]/g,
1268
+ `supportedLocales: [${localesArray}]`
1269
+ );
1270
+ content = content.replace(
1271
+ /defaultLocale:\s*['"]en['"]\s*as\s*const/g,
1272
+ `defaultLocale: '${config.defaultLocale}' as const`
1273
+ );
1274
+ content = content.replace(
1275
+ /label:\s*['"]Starter['"]/g,
1276
+ `label: '${config.projectName}'`
1277
+ );
1278
+ await fs.writeFile(appConfigPath, content, "utf-8");
1279
+ }
1280
+ async function updateRolesConfig(config) {
1281
+ const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1282
+ if (!await fs.pathExists(appConfigPath)) {
1283
+ return;
1284
+ }
1285
+ let content = await fs.readFile(appConfigPath, "utf-8");
1286
+ const rolesArray = config.teamRoles.map((r) => `'${r}'`).join(", ");
1287
+ content = content.replace(
1288
+ /availableTeamRoles:\s*\[.*?\]/g,
1289
+ `availableTeamRoles: [${rolesArray}]`
1290
+ );
1291
+ await fs.writeFile(appConfigPath, content, "utf-8");
1292
+ }
1293
+ async function updateBillingConfig(config) {
1294
+ const billingConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "billing.config.ts");
1295
+ if (!await fs.pathExists(billingConfigPath)) {
1296
+ return;
1297
+ }
1298
+ let content = await fs.readFile(billingConfigPath, "utf-8");
1299
+ content = content.replace(
1300
+ /currency:\s*['"]usd['"]/g,
1301
+ `currency: '${config.currency}'`
1302
+ );
1303
+ const plansContent = generateBillingPlans(config.billingModel, config.currency);
1304
+ content = content.replace(
1305
+ /plans:\s*\[[\s\S]*?\],\s*\n\s*\/\/ ===+\s*\n\s*\/\/ ACTION MAPPINGS/,
1306
+ `plans: ${plansContent},
1307
+
1308
+ // ===========================================
1309
+ // ACTION MAPPINGS`
1310
+ );
1311
+ await fs.writeFile(billingConfigPath, content, "utf-8");
1312
+ }
1313
+ function generateBillingPlans(billingModel, currency) {
1314
+ if (billingModel === "free") {
1315
+ return "[]";
1316
+ }
1317
+ const currencySymbol = currency === "eur" ? "\u20AC" : currency === "gbp" ? "\xA3" : "$";
1318
+ if (billingModel === "freemium") {
1319
+ return `[
1320
+ // Free Plan
1321
+ {
1322
+ slug: 'free',
1323
+ name: 'billing.plans.free.name',
1324
+ description: 'billing.plans.free.description',
1325
+ type: 'free',
1326
+ visibility: 'public',
1327
+ price: { monthly: 0, yearly: 0 },
1328
+ trialDays: 0,
1329
+ features: ['basic_analytics'],
1330
+ limits: {
1331
+ team_members: 3,
1332
+ tasks: 50,
1333
+ api_calls: 1000,
1334
+ storage_gb: 1,
1335
+ },
1336
+ stripePriceIdMonthly: null,
1337
+ stripePriceIdYearly: null,
1338
+ },
1339
+ // Pro Plan - ${currencySymbol}29/month
1340
+ {
1341
+ slug: 'pro',
1342
+ name: 'billing.plans.pro.name',
1343
+ description: 'billing.plans.pro.description',
1344
+ type: 'paid',
1345
+ visibility: 'public',
1346
+ price: {
1347
+ monthly: 2900, // ${currencySymbol}29.00
1348
+ yearly: 29000, // ${currencySymbol}290.00 (16% savings)
1349
+ },
1350
+ trialDays: 14,
1351
+ features: [
1352
+ 'basic_analytics',
1353
+ 'advanced_analytics',
1354
+ 'api_access',
1355
+ 'priority_support',
1356
+ ],
1357
+ limits: {
1358
+ team_members: 15,
1359
+ tasks: 1000,
1360
+ api_calls: 100000,
1361
+ storage_gb: 50,
1362
+ },
1363
+ stripePriceIdMonthly: 'price_pro_monthly',
1364
+ stripePriceIdYearly: 'price_pro_yearly',
1365
+ },
1366
+ ]`;
1367
+ }
1368
+ return `[
1369
+ // Starter Plan - ${currencySymbol}15/month
1370
+ {
1371
+ slug: 'starter',
1372
+ name: 'billing.plans.starter.name',
1373
+ description: 'billing.plans.starter.description',
1374
+ type: 'paid',
1375
+ visibility: 'public',
1376
+ price: {
1377
+ monthly: 1500, // ${currencySymbol}15.00
1378
+ yearly: 14400, // ${currencySymbol}144.00 (20% savings)
1379
+ },
1380
+ trialDays: 7,
1381
+ features: [
1382
+ 'basic_analytics',
1383
+ 'api_access',
1384
+ ],
1385
+ limits: {
1386
+ team_members: 5,
1387
+ tasks: 200,
1388
+ api_calls: 10000,
1389
+ storage_gb: 10,
1390
+ },
1391
+ stripePriceIdMonthly: 'price_starter_monthly',
1392
+ stripePriceIdYearly: 'price_starter_yearly',
1393
+ },
1394
+ // Pro Plan - ${currencySymbol}29/month
1395
+ {
1396
+ slug: 'pro',
1397
+ name: 'billing.plans.pro.name',
1398
+ description: 'billing.plans.pro.description',
1399
+ type: 'paid',
1400
+ visibility: 'public',
1401
+ price: {
1402
+ monthly: 2900, // ${currencySymbol}29.00
1403
+ yearly: 29000, // ${currencySymbol}290.00 (16% savings)
1404
+ },
1405
+ trialDays: 14,
1406
+ features: [
1407
+ 'basic_analytics',
1408
+ 'advanced_analytics',
1409
+ 'api_access',
1410
+ 'priority_support',
1411
+ ],
1412
+ limits: {
1413
+ team_members: 15,
1414
+ tasks: 1000,
1415
+ api_calls: 100000,
1416
+ storage_gb: 50,
1417
+ },
1418
+ stripePriceIdMonthly: 'price_pro_monthly',
1419
+ stripePriceIdYearly: 'price_pro_yearly',
1420
+ },
1421
+ // Business Plan - ${currencySymbol}79/month
1422
+ {
1423
+ slug: 'business',
1424
+ name: 'billing.plans.business.name',
1425
+ description: 'billing.plans.business.description',
1426
+ type: 'paid',
1427
+ visibility: 'public',
1428
+ price: {
1429
+ monthly: 7900, // ${currencySymbol}79.00
1430
+ yearly: 79000, // ${currencySymbol}790.00 (16% savings)
1431
+ },
1432
+ trialDays: 14,
1433
+ features: [
1434
+ 'basic_analytics',
1435
+ 'advanced_analytics',
1436
+ 'api_access',
1437
+ 'sso',
1438
+ 'audit_logs',
1439
+ 'priority_support',
1440
+ 'dedicated_support',
1441
+ ],
1442
+ limits: {
1443
+ team_members: 50,
1444
+ tasks: 5000,
1445
+ api_calls: 500000,
1446
+ storage_gb: 200,
1447
+ },
1448
+ stripePriceIdMonthly: 'price_business_monthly',
1449
+ stripePriceIdYearly: 'price_business_yearly',
1450
+ },
1451
+ ]`;
1452
+ }
1453
+ async function updateMigrations(config) {
1454
+ const migrationsDir = path.join(getTargetThemesDir(), config.projectSlug, "migrations");
1455
+ if (!await fs.pathExists(migrationsDir)) {
1456
+ return;
1457
+ }
1458
+ const files = await fs.readdir(migrationsDir);
1459
+ const sqlFiles = files.filter((f) => f.endsWith(".sql"));
1460
+ for (const file of sqlFiles) {
1461
+ const filePath = path.join(migrationsDir, file);
1462
+ let content = await fs.readFile(filePath, "utf-8");
1463
+ content = content.replace(/@starter\.dev/g, `@${config.projectSlug}.dev`);
1464
+ content = content.replace(/Starter Theme/g, config.projectName);
1465
+ content = content.replace(/starter theme/g, config.projectSlug);
1466
+ await fs.writeFile(filePath, content, "utf-8");
1467
+ }
1468
+ }
1469
+ function toCamelCase(str) {
1470
+ return str.split("-").map((word, index) => {
1471
+ if (index === 0) {
1472
+ return word.toLowerCase();
422
1473
  }
423
- const envExample = join(projectRoot, ".env.example");
424
- if (!existsSync2(envExample)) {
425
- const envContent = `# NextSpark Configuration
426
- DATABASE_URL="postgresql://user:password@localhost:5432/db"
427
- BETTER_AUTH_SECRET="your-secret-here-min-32-chars"
428
- BETTER_AUTH_URL=http://localhost:3000
429
- NEXT_PUBLIC_APP_URL=http://localhost:3000
430
- `;
431
- writeFileSync(envExample, envContent);
432
- spinner.text = "Created .env.example";
1474
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
1475
+ }).join("");
1476
+ }
1477
+
1478
+ // src/wizard/generators/config-generator.ts
1479
+ import fs2 from "fs-extra";
1480
+ import path2 from "path";
1481
+ function getTargetThemesDir2() {
1482
+ return path2.resolve(process.cwd(), "contents", "themes");
1483
+ }
1484
+ async function updateAuthConfig(config) {
1485
+ const authConfigPath = path2.join(
1486
+ getTargetThemesDir2(),
1487
+ config.projectSlug,
1488
+ "config",
1489
+ "auth.config.ts"
1490
+ );
1491
+ if (!await fs2.pathExists(authConfigPath)) {
1492
+ return;
1493
+ }
1494
+ let content = await fs2.readFile(authConfigPath, "utf-8");
1495
+ content = content.replace(
1496
+ /(emailPassword:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1497
+ `$1${config.auth.emailPassword}`
1498
+ );
1499
+ content = content.replace(
1500
+ /(google:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1501
+ `$1${config.auth.googleOAuth}`
1502
+ );
1503
+ content = content.replace(
1504
+ /(emailVerification:\s*)(?:true|false)/g,
1505
+ `$1${config.auth.emailVerification}`
1506
+ );
1507
+ await fs2.writeFile(authConfigPath, content, "utf-8");
1508
+ }
1509
+ async function updateDashboardUIConfig(config) {
1510
+ const dashboardConfigPath = path2.join(
1511
+ getTargetThemesDir2(),
1512
+ config.projectSlug,
1513
+ "config",
1514
+ "dashboard.config.ts"
1515
+ );
1516
+ if (!await fs2.pathExists(dashboardConfigPath)) {
1517
+ return;
1518
+ }
1519
+ let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1520
+ content = content.replace(
1521
+ /(search:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1522
+ `$1${config.dashboard.search}`
1523
+ );
1524
+ content = content.replace(
1525
+ /(notifications:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1526
+ `$1${config.dashboard.notifications}`
1527
+ );
1528
+ content = content.replace(
1529
+ /(themeToggle:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1530
+ `$1${config.dashboard.themeToggle}`
1531
+ );
1532
+ content = content.replace(
1533
+ /(support:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1534
+ `$1${config.dashboard.support}`
1535
+ );
1536
+ content = content.replace(
1537
+ /(quickCreate:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1538
+ `$1${config.dashboard.quickCreate}`
1539
+ );
1540
+ content = content.replace(
1541
+ /(adminAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1542
+ `$1${config.dashboard.superadminAccess}`
1543
+ );
1544
+ content = content.replace(
1545
+ /(devtoolsAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1546
+ `$1${config.dashboard.devtoolsAccess}`
1547
+ );
1548
+ content = content.replace(
1549
+ /(defaultCollapsed:\s*)(?:true|false)/g,
1550
+ `$1${config.dashboard.sidebarCollapsed}`
1551
+ );
1552
+ await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1553
+ }
1554
+ async function updateDevToolsConfig(config) {
1555
+ const devConfigPath = path2.join(
1556
+ getTargetThemesDir2(),
1557
+ config.projectSlug,
1558
+ "config",
1559
+ "dev.config.ts"
1560
+ );
1561
+ if (!await fs2.pathExists(devConfigPath)) {
1562
+ return;
1563
+ }
1564
+ let content = await fs2.readFile(devConfigPath, "utf-8");
1565
+ content = content.replace(
1566
+ /(devKeyring:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1567
+ `$1${config.dev.devKeyring}`
1568
+ );
1569
+ content = content.replace(
1570
+ /(debugMode:\s*)(?:true|false)/g,
1571
+ `$1${config.dev.debugMode}`
1572
+ );
1573
+ await fs2.writeFile(devConfigPath, content, "utf-8");
1574
+ }
1575
+ async function updatePermissionsConfig(config) {
1576
+ const permissionsConfigPath = path2.join(
1577
+ getTargetThemesDir2(),
1578
+ config.projectSlug,
1579
+ "config",
1580
+ "permissions.config.ts"
1581
+ );
1582
+ if (!await fs2.pathExists(permissionsConfigPath)) {
1583
+ return;
1584
+ }
1585
+ let content = await fs2.readFile(permissionsConfigPath, "utf-8");
1586
+ const availableRoles = config.teamRoles;
1587
+ const roleArrayPattern = /roles:\s*\[(.*?)\]/g;
1588
+ content = content.replace(roleArrayPattern, (match, rolesStr) => {
1589
+ const currentRoles = rolesStr.split(",").map((r) => r.trim().replace(/['"]/g, "")).filter((r) => r.length > 0);
1590
+ const filteredRoles = currentRoles.filter((r) => availableRoles.includes(r));
1591
+ if (filteredRoles.length === 0) {
1592
+ filteredRoles.push("owner");
433
1593
  }
434
- spinner.succeed("NextSpark initialized successfully!");
435
- console.log(chalk5.blue("\nNext steps:"));
436
- console.log(" 1. Copy .env.example to .env and configure");
437
- console.log(" 2. Run: nextspark generate (to populate registries)");
438
- console.log(" 3. Run: nextspark dev");
439
- } catch (error) {
440
- spinner.fail("Initialization failed");
1594
+ return `roles: [${filteredRoles.map((r) => `'${r}'`).join(", ")}]`;
1595
+ });
1596
+ await fs2.writeFile(permissionsConfigPath, content, "utf-8");
1597
+ }
1598
+ async function updateDashboardConfig(config) {
1599
+ const dashboardConfigPath = path2.join(
1600
+ getTargetThemesDir2(),
1601
+ config.projectSlug,
1602
+ "config",
1603
+ "dashboard.config.ts"
1604
+ );
1605
+ if (!await fs2.pathExists(dashboardConfigPath)) {
1606
+ return;
1607
+ }
1608
+ let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1609
+ if (!config.features.analytics) {
1610
+ content = content.replace(
1611
+ /(id:\s*['"]analytics['"].*?enabled:\s*)true/gs,
1612
+ "$1false"
1613
+ );
1614
+ }
1615
+ if (!config.features.billing) {
1616
+ content = content.replace(
1617
+ /(id:\s*['"]billing['"].*?enabled:\s*)true/gs,
1618
+ "$1false"
1619
+ );
1620
+ }
1621
+ if (config.teamMode === "single-user") {
1622
+ content = content.replace(
1623
+ /(id:\s*['"]team['"].*?enabled:\s*)true/gs,
1624
+ "$1false"
1625
+ );
1626
+ content = content.replace(
1627
+ /(id:\s*['"]members['"].*?enabled:\s*)true/gs,
1628
+ "$1false"
1629
+ );
1630
+ }
1631
+ await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1632
+ }
1633
+ async function generateEnvExample(config) {
1634
+ const envExamplePath = path2.resolve(process.cwd(), ".env.example");
1635
+ let oauthSection = "";
1636
+ if (config.auth.googleOAuth) {
1637
+ oauthSection = `# =============================================================================
1638
+ # OAUTH PROVIDERS
1639
+ # =============================================================================
1640
+ GOOGLE_CLIENT_ID="your-google-client-id"
1641
+ GOOGLE_CLIENT_SECRET="your-google-client-secret"
1642
+ `;
1643
+ } else {
1644
+ oauthSection = `# =============================================================================
1645
+ # OAUTH (Optional - enable in auth.config.ts)
1646
+ # =============================================================================
1647
+ # GOOGLE_CLIENT_ID=""
1648
+ # GOOGLE_CLIENT_SECRET=""
1649
+ `;
1650
+ }
1651
+ const envContent = `# NextSpark Environment Configuration
1652
+ # Generated for: ${config.projectName}
1653
+
1654
+ # =============================================================================
1655
+ # DATABASE
1656
+ # =============================================================================
1657
+ DATABASE_URL="postgresql://user:password@localhost:5432/database"
1658
+
1659
+ # =============================================================================
1660
+ # AUTHENTICATION (better-auth)
1661
+ # =============================================================================
1662
+ # Generate with: openssl rand -base64 32
1663
+ BETTER_AUTH_SECRET="your-secret-key-here"
1664
+
1665
+ # =============================================================================
1666
+ # THEME
1667
+ # =============================================================================
1668
+ NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
1669
+
1670
+ # =============================================================================
1671
+ # APPLICATION
1672
+ # =============================================================================
1673
+ NEXT_PUBLIC_APP_URL="http://localhost:3000"
1674
+ NODE_ENV="development"
1675
+
1676
+ ${config.features.billing ? `# =============================================================================
1677
+ # STRIPE (Billing)
1678
+ # =============================================================================
1679
+ STRIPE_SECRET_KEY="sk_test_..."
1680
+ STRIPE_WEBHOOK_SECRET="whsec_..."
1681
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
1682
+ ` : "# Billing is disabled - no Stripe configuration needed"}
1683
+
1684
+ # =============================================================================
1685
+ # EMAIL (Resend)
1686
+ # =============================================================================
1687
+ RESEND_API_KEY="re_..."
1688
+
1689
+ ${oauthSection}`;
1690
+ if (!await fs2.pathExists(envExamplePath)) {
1691
+ await fs2.writeFile(envExamplePath, envContent, "utf-8");
1692
+ }
1693
+ }
1694
+ async function updateReadme(config) {
1695
+ const readmePath = path2.join(getTargetThemesDir2(), config.projectSlug, "README.md");
1696
+ if (!await fs2.pathExists(readmePath)) {
1697
+ return;
1698
+ }
1699
+ let content = await fs2.readFile(readmePath, "utf-8");
1700
+ content = content.replace(/# Starter Theme/g, `# ${config.projectName}`);
1701
+ content = content.replace(
1702
+ /Minimal starter theme for NextSpark/g,
1703
+ config.projectDescription
1704
+ );
1705
+ await fs2.writeFile(readmePath, content, "utf-8");
1706
+ }
1707
+ async function copyEnvExampleToEnv() {
1708
+ const projectRoot = process.cwd();
1709
+ const envExamplePath = path2.resolve(projectRoot, ".env.example");
1710
+ const envPath = path2.resolve(projectRoot, ".env");
1711
+ if (await fs2.pathExists(envExamplePath) && !await fs2.pathExists(envPath)) {
1712
+ await fs2.copy(envExamplePath, envPath);
1713
+ }
1714
+ }
1715
+
1716
+ // src/wizard/generators/messages-generator.ts
1717
+ import fs3 from "fs-extra";
1718
+ import path3 from "path";
1719
+ function getTargetThemesDir3() {
1720
+ return path3.resolve(process.cwd(), "contents", "themes");
1721
+ }
1722
+ async function removeUnusedLanguages(config) {
1723
+ const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1724
+ if (!await fs3.pathExists(messagesDir)) {
1725
+ return;
1726
+ }
1727
+ const folders = await fs3.readdir(messagesDir);
1728
+ const languageFolders = folders.filter((f) => {
1729
+ const folderPath = path3.join(messagesDir, f);
1730
+ return fs3.statSync(folderPath).isDirectory() && Object.keys(AVAILABLE_LOCALES).includes(f);
1731
+ });
1732
+ for (const folder of languageFolders) {
1733
+ if (!config.supportedLocales.includes(folder)) {
1734
+ await fs3.remove(path3.join(messagesDir, folder));
1735
+ }
1736
+ }
1737
+ }
1738
+ async function removeUnusedEntityMessages(config) {
1739
+ const entitiesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "entities");
1740
+ if (!await fs3.pathExists(entitiesDir)) {
1741
+ return;
1742
+ }
1743
+ const entityFolders = await fs3.readdir(entitiesDir);
1744
+ for (const entity of entityFolders) {
1745
+ const messagesDir = path3.join(entitiesDir, entity, "messages");
1746
+ if (!await fs3.pathExists(messagesDir)) {
1747
+ continue;
1748
+ }
1749
+ const files = await fs3.readdir(messagesDir);
1750
+ for (const file of files) {
1751
+ const locale = path3.basename(file, ".json");
1752
+ if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config.supportedLocales.includes(locale)) {
1753
+ await fs3.remove(path3.join(messagesDir, file));
1754
+ }
1755
+ }
1756
+ }
1757
+ }
1758
+ async function ensureLanguageFolders(config) {
1759
+ const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1760
+ if (!await fs3.pathExists(messagesDir)) {
1761
+ return;
1762
+ }
1763
+ const defaultLocaleDir = path3.join(messagesDir, config.defaultLocale);
1764
+ if (!await fs3.pathExists(defaultLocaleDir)) {
1765
+ const enDir = path3.join(messagesDir, "en");
1766
+ if (await fs3.pathExists(enDir)) {
1767
+ await fs3.copy(enDir, defaultLocaleDir);
1768
+ }
1769
+ }
1770
+ for (const locale of config.supportedLocales) {
1771
+ const localeDir = path3.join(messagesDir, locale);
1772
+ if (!await fs3.pathExists(localeDir) && await fs3.pathExists(defaultLocaleDir)) {
1773
+ await fs3.copy(defaultLocaleDir, localeDir);
1774
+ }
1775
+ }
1776
+ }
1777
+ async function updateMessageFiles(config) {
1778
+ const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1779
+ if (!await fs3.pathExists(messagesDir)) {
1780
+ return;
1781
+ }
1782
+ for (const locale of config.supportedLocales) {
1783
+ const commonPath = path3.join(messagesDir, locale, "common.json");
1784
+ if (await fs3.pathExists(commonPath)) {
1785
+ try {
1786
+ const content = await fs3.readJson(commonPath);
1787
+ if (content.app) {
1788
+ content.app.name = config.projectName;
1789
+ if (content.app.description) {
1790
+ content.app.description = config.projectDescription;
1791
+ }
1792
+ }
1793
+ await fs3.writeJson(commonPath, content, { spaces: 2 });
1794
+ } catch {
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+ async function processI18n(config) {
1800
+ await removeUnusedLanguages(config);
1801
+ await removeUnusedEntityMessages(config);
1802
+ await ensureLanguageFolders(config);
1803
+ await updateMessageFiles(config);
1804
+ }
1805
+
1806
+ // src/wizard/generators/content-features-generator.ts
1807
+ import fs4 from "fs-extra";
1808
+ import path4 from "path";
1809
+ import { fileURLToPath as fileURLToPath4 } from "url";
1810
+ var __filename4 = fileURLToPath4(import.meta.url);
1811
+ var __dirname4 = path4.dirname(__filename4);
1812
+ function getTemplatesDir2() {
1813
+ try {
1814
+ const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1815
+ return path4.join(path4.dirname(corePkgPath), "templates");
1816
+ } catch {
1817
+ const possiblePaths = [
1818
+ path4.resolve(__dirname4, "../../../../../core/templates"),
1819
+ path4.resolve(__dirname4, "../../../../core/templates"),
1820
+ path4.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1821
+ ];
1822
+ for (const p of possiblePaths) {
1823
+ if (fs4.existsSync(p)) {
1824
+ return p;
1825
+ }
1826
+ }
1827
+ throw new Error("Could not find @nextsparkjs/core templates directory");
1828
+ }
1829
+ }
1830
+ function getFeaturesDir() {
1831
+ return path4.join(getTemplatesDir2(), "features");
1832
+ }
1833
+ function getTargetThemeDir(projectSlug) {
1834
+ return path4.resolve(process.cwd(), "contents", "themes", projectSlug);
1835
+ }
1836
+ async function copyPagesFeature(config) {
1837
+ const featuresDir = getFeaturesDir();
1838
+ const targetThemeDir = getTargetThemeDir(config.projectSlug);
1839
+ const sourcePagesEntity = path4.join(featuresDir, "pages", "entities", "pages");
1840
+ const targetEntitiesDir = path4.join(targetThemeDir, "entities", "pages");
1841
+ if (await fs4.pathExists(sourcePagesEntity)) {
1842
+ await fs4.copy(sourcePagesEntity, targetEntitiesDir);
1843
+ } else {
1844
+ console.warn(`Warning: Pages entity not found at: ${sourcePagesEntity}`);
1845
+ }
1846
+ const sourceHeroBlock = path4.join(featuresDir, "pages", "blocks", "hero");
1847
+ const targetHeroBlock = path4.join(targetThemeDir, "blocks", "hero");
1848
+ if (await fs4.pathExists(sourceHeroBlock)) {
1849
+ await fs4.copy(sourceHeroBlock, targetHeroBlock);
1850
+ } else {
1851
+ console.warn(`Warning: Hero block not found at: ${sourceHeroBlock}`);
1852
+ }
1853
+ }
1854
+ async function copyBlogFeature(config) {
1855
+ const featuresDir = getFeaturesDir();
1856
+ const targetThemeDir = getTargetThemeDir(config.projectSlug);
1857
+ const sourcePostsEntity = path4.join(featuresDir, "blog", "entities", "posts");
1858
+ const targetPostsEntity = path4.join(targetThemeDir, "entities", "posts");
1859
+ if (await fs4.pathExists(sourcePostsEntity)) {
1860
+ await fs4.copy(sourcePostsEntity, targetPostsEntity);
1861
+ } else {
1862
+ console.warn(`Warning: Posts entity not found at: ${sourcePostsEntity}`);
1863
+ }
1864
+ const sourcePostContentBlock = path4.join(featuresDir, "blog", "blocks", "post-content");
1865
+ const targetPostContentBlock = path4.join(targetThemeDir, "blocks", "post-content");
1866
+ if (await fs4.pathExists(sourcePostContentBlock)) {
1867
+ await fs4.copy(sourcePostContentBlock, targetPostContentBlock);
1868
+ } else {
1869
+ console.warn(`Warning: Post-content block not found at: ${sourcePostContentBlock}`);
1870
+ }
1871
+ }
1872
+ async function copyContentFeatures(config) {
1873
+ if (!config.contentFeatures.pages && !config.contentFeatures.blog) {
1874
+ return;
1875
+ }
1876
+ if (config.contentFeatures.pages) {
1877
+ await copyPagesFeature(config);
1878
+ }
1879
+ if (config.contentFeatures.blog) {
1880
+ await copyBlogFeature(config);
1881
+ }
1882
+ }
1883
+
1884
+ // src/wizard/generators/theme-plugins-installer.ts
1885
+ import { existsSync as existsSync6, cpSync, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
1886
+ import { join as join6, resolve as resolve2 } from "path";
1887
+ import chalk9 from "chalk";
1888
+ import ora6 from "ora";
1889
+
1890
+ // src/commands/add-theme.ts
1891
+ import chalk8 from "chalk";
1892
+ import ora5 from "ora";
1893
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1894
+ import { join as join5 } from "path";
1895
+ async function addTheme(packageSpec, options = {}) {
1896
+ const spinner = ora5(`Adding theme ${packageSpec}`).start();
1897
+ let cleanup = null;
1898
+ try {
1899
+ const contentsDir = join5(process.cwd(), "contents");
1900
+ if (!existsSync5(contentsDir)) {
1901
+ spinner.fail('contents/ directory not found. Run "nextspark init" first.');
1902
+ return;
1903
+ }
1904
+ spinner.text = "Downloading package...";
1905
+ const { packageJson, extractedPath, cleanup: cleanupFn } = await fetchPackage(
1906
+ packageSpec,
1907
+ options.version
1908
+ );
1909
+ cleanup = cleanupFn;
1910
+ spinner.text = "Validating theme...";
1911
+ const validation = validateTheme(packageJson, extractedPath);
1912
+ if (!validation.valid) {
1913
+ spinner.fail("Invalid theme");
1914
+ validation.errors.forEach((e) => console.log(chalk8.red(` \u2717 ${e}`)));
1915
+ return;
1916
+ }
1917
+ if (validation.warnings.length > 0) {
1918
+ validation.warnings.forEach((w) => console.log(chalk8.yellow(` \u26A0 ${w}`)));
1919
+ }
1920
+ if (packageJson.requiredPlugins?.length && !options.skipPostinstall) {
1921
+ spinner.stop();
1922
+ console.log(chalk8.blue("\n Installing required plugins..."));
1923
+ const installingPlugins = /* @__PURE__ */ new Set();
1924
+ for (const plugin of packageJson.requiredPlugins) {
1925
+ if (!checkPluginExists(plugin)) {
1926
+ await addPlugin(plugin, { installingPlugins });
1927
+ }
1928
+ }
1929
+ }
1930
+ spinner.text = "Installing theme...";
1931
+ spinner.stop();
1932
+ const result = await installTheme(extractedPath, packageJson, options);
1933
+ if (!options.skipPostinstall) {
1934
+ const coreVersion = getCoreVersion();
1935
+ const context = {
1936
+ activeTheme: result.name,
1937
+ // The newly installed theme
1938
+ projectRoot: process.cwd(),
1939
+ themeName: result.name,
1940
+ coreVersion,
1941
+ timestamp: Date.now(),
1942
+ installingPlugins: /* @__PURE__ */ new Set()
1943
+ };
1944
+ await runPostinstall(packageJson, result.installedPath, context);
1945
+ }
1946
+ console.log(chalk8.green(`
1947
+ \u2713 Theme ${result.name} installed successfully!`));
1948
+ console.log(chalk8.gray(` Location: contents/themes/${result.name}/`));
1949
+ console.log(chalk8.gray(` Set NEXT_PUBLIC_ACTIVE_THEME=${result.name} to activate`));
1950
+ } catch (error) {
1951
+ spinner.fail("Failed to add theme");
1952
+ if (error instanceof Error) {
1953
+ console.log(chalk8.red(` ${error.message}`));
1954
+ }
1955
+ throw error;
1956
+ } finally {
1957
+ if (cleanup) cleanup();
1958
+ }
1959
+ }
1960
+ function checkPluginExists(pluginName) {
1961
+ const name = pluginName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
1962
+ return existsSync5(join5(process.cwd(), "contents", "plugins", name));
1963
+ }
1964
+ function getCoreVersion() {
1965
+ const pkgPath = join5(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
1966
+ if (existsSync5(pkgPath)) {
1967
+ try {
1968
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1969
+ return pkg.version || "0.0.0";
1970
+ } catch {
1971
+ return "0.0.0";
1972
+ }
1973
+ }
1974
+ return "0.0.0";
1975
+ }
1976
+ function addThemeCommand(packageSpec, options) {
1977
+ return addTheme(packageSpec, {
1978
+ force: options.force,
1979
+ skipDeps: options.noDeps,
1980
+ dryRun: options.dryRun,
1981
+ skipPostinstall: options.skipPostinstall,
1982
+ version: options.version
1983
+ });
1984
+ }
1985
+
1986
+ // src/wizard/generators/theme-plugins-installer.ts
1987
+ var THEME_PACKAGES = {
1988
+ "default": "@nextsparkjs/theme-default",
1989
+ "blog": "@nextsparkjs/theme-blog",
1990
+ "crm": "@nextsparkjs/theme-crm",
1991
+ "productivity": "@nextsparkjs/theme-productivity"
1992
+ };
1993
+ var PLUGIN_PACKAGES = {
1994
+ "ai": "@nextsparkjs/plugin-ai",
1995
+ "langchain": "@nextsparkjs/plugin-langchain",
1996
+ "social-media-publisher": "@nextsparkjs/plugin-social-media-publisher"
1997
+ };
1998
+ var THEME_REQUIRED_PLUGINS2 = {
1999
+ "default": ["langchain"],
2000
+ "blog": [],
2001
+ "crm": [],
2002
+ "productivity": []
2003
+ };
2004
+ function isMonorepoMode2() {
2005
+ const possiblePaths = [
2006
+ join6(process.cwd(), "pnpm-workspace.yaml"),
2007
+ join6(process.cwd(), "..", "pnpm-workspace.yaml"),
2008
+ join6(process.cwd(), "..", "..", "pnpm-workspace.yaml")
2009
+ ];
2010
+ return possiblePaths.some((p) => existsSync6(p));
2011
+ }
2012
+ function getMonorepoRoot() {
2013
+ const possibleRoots = [
2014
+ process.cwd(),
2015
+ join6(process.cwd(), ".."),
2016
+ join6(process.cwd(), "..", "..")
2017
+ ];
2018
+ for (const root of possibleRoots) {
2019
+ if (existsSync6(join6(root, "pnpm-workspace.yaml"))) {
2020
+ return resolve2(root);
2021
+ }
2022
+ }
2023
+ return null;
2024
+ }
2025
+ function getLocalPackageDir(type, name) {
2026
+ const monorepoRoot = getMonorepoRoot();
2027
+ if (!monorepoRoot) return null;
2028
+ const baseDir = type === "theme" ? "themes" : "plugins";
2029
+ const packageDir = join6(monorepoRoot, baseDir, name);
2030
+ if (existsSync6(packageDir) && existsSync6(join6(packageDir, "package.json"))) {
2031
+ return packageDir;
2032
+ }
2033
+ return null;
2034
+ }
2035
+ async function copyLocalTheme(name, sourceDir) {
2036
+ const targetDir = join6(process.cwd(), "contents", "themes", name);
2037
+ const themesDir = join6(process.cwd(), "contents", "themes");
2038
+ if (!existsSync6(themesDir)) {
2039
+ mkdirSync(themesDir, { recursive: true });
2040
+ }
2041
+ if (existsSync6(targetDir)) {
2042
+ console.log(chalk9.gray(` Theme ${name} already exists, skipping...`));
2043
+ return true;
2044
+ }
2045
+ cpSync(sourceDir, targetDir, { recursive: true });
2046
+ await updateTsConfigPaths(name, "theme");
2047
+ return true;
2048
+ }
2049
+ async function copyLocalPlugin(name, sourceDir) {
2050
+ const targetDir = join6(process.cwd(), "contents", "plugins", name);
2051
+ const pluginsDir = join6(process.cwd(), "contents", "plugins");
2052
+ if (!existsSync6(pluginsDir)) {
2053
+ mkdirSync(pluginsDir, { recursive: true });
2054
+ }
2055
+ if (existsSync6(targetDir)) {
2056
+ console.log(chalk9.gray(` Plugin ${name} already exists, skipping...`));
2057
+ return true;
2058
+ }
2059
+ cpSync(sourceDir, targetDir, { recursive: true });
2060
+ await updateTsConfigPaths(name, "plugin");
2061
+ return true;
2062
+ }
2063
+ async function updateTsConfigPaths(name, type) {
2064
+ const tsconfigPath = join6(process.cwd(), "tsconfig.json");
2065
+ if (!existsSync6(tsconfigPath)) {
2066
+ return;
2067
+ }
2068
+ try {
2069
+ const content = readFileSync6(tsconfigPath, "utf-8");
2070
+ const tsconfig = JSON.parse(content);
2071
+ if (!tsconfig.compilerOptions) {
2072
+ tsconfig.compilerOptions = {};
2073
+ }
2074
+ if (!tsconfig.compilerOptions.paths) {
2075
+ tsconfig.compilerOptions.paths = {};
2076
+ }
2077
+ const basePath = type === "theme" ? `contents/themes/${name}` : `contents/plugins/${name}`;
2078
+ const aliasKey = type === "theme" ? `@themes/${name}/*` : `@plugins/${name}/*`;
2079
+ tsconfig.compilerOptions.paths[aliasKey] = [`./${basePath}/*`];
2080
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
2081
+ } catch {
2082
+ }
2083
+ }
2084
+ async function installThemeViaCli(packageSpec) {
2085
+ try {
2086
+ await addTheme(packageSpec, {});
2087
+ return true;
2088
+ } catch {
2089
+ return false;
2090
+ }
2091
+ }
2092
+ async function installPluginViaCli(packageSpec) {
2093
+ try {
2094
+ await addPlugin(packageSpec, {});
2095
+ return true;
2096
+ } catch {
2097
+ return false;
2098
+ }
2099
+ }
2100
+ async function installTheme2(theme) {
2101
+ if (!theme) {
2102
+ return true;
2103
+ }
2104
+ const spinner = ora6({
2105
+ text: `Installing reference theme: ${theme}...`,
2106
+ prefixText: " "
2107
+ }).start();
2108
+ try {
2109
+ const targetDir = join6(process.cwd(), "contents", "themes", theme);
2110
+ if (existsSync6(targetDir)) {
2111
+ spinner.info(chalk9.gray(`Reference theme ${theme} already exists`));
2112
+ return true;
2113
+ }
2114
+ if (isMonorepoMode2()) {
2115
+ const localDir = getLocalPackageDir("theme", theme);
2116
+ if (localDir) {
2117
+ spinner.text = `Copying reference theme from local: ${theme}...`;
2118
+ const success2 = await copyLocalTheme(theme, localDir);
2119
+ if (success2) {
2120
+ const requiredPlugins = THEME_REQUIRED_PLUGINS2[theme] || [];
2121
+ for (const plugin of requiredPlugins) {
2122
+ const pluginDir = getLocalPackageDir("plugin", plugin);
2123
+ if (pluginDir) {
2124
+ await copyLocalPlugin(plugin, pluginDir);
2125
+ }
2126
+ }
2127
+ spinner.succeed(chalk9.green(`Reference theme ${theme} installed!`));
2128
+ return true;
2129
+ }
2130
+ }
2131
+ }
2132
+ spinner.text = `Installing reference theme: ${theme}...`;
2133
+ const packageSpec = THEME_PACKAGES[theme];
2134
+ const success = await installThemeViaCli(packageSpec);
2135
+ if (success) {
2136
+ spinner.succeed(chalk9.green(`Reference theme ${theme} installed!`));
2137
+ return true;
2138
+ } else {
2139
+ spinner.fail(chalk9.red(`Failed to install theme: ${theme}`));
2140
+ console.log(chalk9.gray(" Hint: Make sure @nextsparkjs/cli is installed or the theme package is published"));
2141
+ return false;
2142
+ }
2143
+ } catch (error) {
2144
+ spinner.fail(chalk9.red(`Failed to install theme: ${theme}`));
2145
+ if (error instanceof Error) {
2146
+ console.log(chalk9.red(` Error: ${error.message}`));
2147
+ }
2148
+ return false;
2149
+ }
2150
+ }
2151
+ async function installPlugins(plugins) {
2152
+ if (plugins.length === 0) {
2153
+ return true;
2154
+ }
2155
+ let allSuccess = true;
2156
+ for (const plugin of plugins) {
2157
+ const spinner = ora6({
2158
+ text: `Installing plugin: ${plugin}...`,
2159
+ prefixText: " "
2160
+ }).start();
2161
+ try {
2162
+ const pluginDir = join6(process.cwd(), "contents", "plugins", plugin);
2163
+ if (existsSync6(pluginDir)) {
2164
+ spinner.info(chalk9.gray(`Plugin ${plugin} already installed`));
2165
+ continue;
2166
+ }
2167
+ if (isMonorepoMode2()) {
2168
+ const localDir = getLocalPackageDir("plugin", plugin);
2169
+ if (localDir) {
2170
+ spinner.text = `Copying plugin from local: ${plugin}...`;
2171
+ const success2 = await copyLocalPlugin(plugin, localDir);
2172
+ if (success2) {
2173
+ spinner.succeed(chalk9.green(`Plugin ${plugin} installed!`));
2174
+ continue;
2175
+ }
2176
+ }
2177
+ }
2178
+ spinner.text = `Installing plugin: ${plugin}...`;
2179
+ const packageSpec = PLUGIN_PACKAGES[plugin];
2180
+ const success = await installPluginViaCli(packageSpec);
2181
+ if (success) {
2182
+ spinner.succeed(chalk9.green(`Plugin ${plugin} installed!`));
2183
+ } else {
2184
+ spinner.fail(chalk9.red(`Failed to install plugin: ${plugin}`));
2185
+ console.log(chalk9.gray(" Hint: Make sure @nextsparkjs/cli is installed or the plugin package is published"));
2186
+ allSuccess = false;
2187
+ }
2188
+ } catch (error) {
2189
+ spinner.fail(chalk9.red(`Failed to install plugin: ${plugin}`));
2190
+ if (error instanceof Error) {
2191
+ console.log(chalk9.red(` Error: ${error.message}`));
2192
+ }
2193
+ allSuccess = false;
2194
+ }
2195
+ }
2196
+ return allSuccess;
2197
+ }
2198
+ async function installThemeAndPlugins(theme, plugins) {
2199
+ if (!theme && plugins.length === 0) {
2200
+ return true;
2201
+ }
2202
+ console.log("");
2203
+ console.log(chalk9.cyan(" Installing Reference Theme & Plugins"));
2204
+ console.log(chalk9.gray(" " + "-".repeat(40)));
2205
+ console.log("");
2206
+ const themeSuccess = await installTheme2(theme);
2207
+ if (!themeSuccess && theme) {
2208
+ console.log(chalk9.yellow(" Warning: Theme installation failed, continuing with plugins..."));
2209
+ }
2210
+ const pluginsSuccess = await installPlugins(plugins);
2211
+ console.log("");
2212
+ if (themeSuccess && pluginsSuccess) {
2213
+ console.log(chalk9.green(" All installations completed successfully!"));
2214
+ } else {
2215
+ console.log(chalk9.yellow(" Some installations had issues. Check the messages above."));
2216
+ }
2217
+ return themeSuccess && pluginsSuccess;
2218
+ }
2219
+
2220
+ // src/wizard/generators/env-setup.ts
2221
+ import fs5 from "fs-extra";
2222
+
2223
+ // src/wizard/generators/git-init.ts
2224
+ import fs6 from "fs-extra";
2225
+
2226
+ // src/wizard/generators/index.ts
2227
+ var __filename5 = fileURLToPath5(import.meta.url);
2228
+ var __dirname5 = path5.dirname(__filename5);
2229
+ function getTemplatesDir3() {
2230
+ try {
2231
+ const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
2232
+ return path5.join(path5.dirname(corePkgPath), "templates");
2233
+ } catch {
2234
+ const possiblePaths = [
2235
+ path5.resolve(__dirname5, "../../../../../core/templates"),
2236
+ path5.resolve(__dirname5, "../../../../core/templates"),
2237
+ path5.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
2238
+ ];
2239
+ for (const p of possiblePaths) {
2240
+ if (fs7.existsSync(p)) {
2241
+ return p;
2242
+ }
2243
+ }
2244
+ throw new Error("Could not find @nextsparkjs/core templates directory");
2245
+ }
2246
+ }
2247
+ async function copyProjectFiles() {
2248
+ const templatesDir = getTemplatesDir3();
2249
+ const projectDir = process.cwd();
2250
+ const itemsToCopy = [
2251
+ { src: "app", dest: "app", force: true },
2252
+ { src: "public", dest: "public", force: true },
2253
+ { src: "next.config.mjs", dest: "next.config.mjs", force: true },
2254
+ { src: "tsconfig.json", dest: "tsconfig.json", force: true },
2255
+ { src: "postcss.config.mjs", dest: "postcss.config.mjs", force: true },
2256
+ { src: "i18n.ts", dest: "i18n.ts", force: true },
2257
+ { src: "npmrc", dest: ".npmrc", force: false },
2258
+ { src: "tsconfig.cypress.json", dest: "tsconfig.cypress.json", force: false },
2259
+ { src: "cypress.d.ts", dest: "cypress.d.ts", force: false },
2260
+ { src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false }
2261
+ ];
2262
+ for (const item of itemsToCopy) {
2263
+ const srcPath = path5.join(templatesDir, item.src);
2264
+ const destPath = path5.join(projectDir, item.dest);
2265
+ if (await fs7.pathExists(srcPath)) {
2266
+ if (item.force || !await fs7.pathExists(destPath)) {
2267
+ await fs7.copy(srcPath, destPath);
2268
+ }
2269
+ }
2270
+ }
2271
+ }
2272
+ async function updatePackageJson(config) {
2273
+ const packageJsonPath = path5.resolve(process.cwd(), "package.json");
2274
+ let packageJson;
2275
+ if (!await fs7.pathExists(packageJsonPath)) {
2276
+ packageJson = {
2277
+ name: config.projectSlug,
2278
+ version: "0.1.0",
2279
+ private: true,
2280
+ scripts: {},
2281
+ dependencies: {},
2282
+ devDependencies: {}
2283
+ };
2284
+ } else {
2285
+ packageJson = await fs7.readJson(packageJsonPath);
2286
+ }
2287
+ packageJson.scripts = packageJson.scripts || {};
2288
+ const scriptsToAdd = {
2289
+ "dev": "nextspark dev",
2290
+ "build": "nextspark build",
2291
+ "start": "next start",
2292
+ "lint": "next lint",
2293
+ "build:registries": "nextspark registry:build",
2294
+ "db:migrate": "nextspark db:migrate",
2295
+ "db:seed": "nextspark db:seed",
2296
+ "test:theme": `jest --config contents/themes/${config.projectSlug}/tests/jest/jest.config.ts`,
2297
+ "cy:open": `cypress open --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2298
+ "cy:run": `cypress run --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2299
+ "allure:generate": `allure generate contents/themes/${config.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config.projectSlug}/tests/cypress/allure-report`,
2300
+ "allure:open": `allure open contents/themes/${config.projectSlug}/tests/cypress/allure-report`
2301
+ };
2302
+ for (const [name, command] of Object.entries(scriptsToAdd)) {
2303
+ if (!packageJson.scripts[name]) {
2304
+ packageJson.scripts[name] = command;
2305
+ }
2306
+ }
2307
+ packageJson.dependencies = packageJson.dependencies || {};
2308
+ const depsToAdd = {
2309
+ // NextSpark
2310
+ "@nextsparkjs/core": "^0.1.0-beta.4",
2311
+ "@nextsparkjs/cli": "^0.1.0-beta.4",
2312
+ // Next.js + React
2313
+ "next": "^15.1.0",
2314
+ "react": "^19.0.0",
2315
+ "react-dom": "^19.0.0",
2316
+ // Auth
2317
+ "better-auth": "^1.4.0",
2318
+ // i18n
2319
+ "next-intl": "^4.0.2",
2320
+ // Database
2321
+ "drizzle-orm": "^0.41.0",
2322
+ "postgres": "^3.4.5",
2323
+ // State & Data
2324
+ "@tanstack/react-query": "^5.64.2",
2325
+ // Forms & Validation
2326
+ "zod": "^4.1.5",
2327
+ "react-hook-form": "^7.54.2",
2328
+ "@hookform/resolvers": "^5.0.1",
2329
+ // UI
2330
+ "tailwindcss": "^4.0.0",
2331
+ "class-variance-authority": "^0.7.1",
2332
+ "clsx": "^2.1.1",
2333
+ "tailwind-merge": "^2.6.0",
2334
+ "lucide-react": "^0.469.0",
2335
+ "sonner": "^1.7.4",
2336
+ // Utilities
2337
+ "date-fns": "^4.1.0",
2338
+ "nanoid": "^5.0.9",
2339
+ "slugify": "^1.6.6"
2340
+ };
2341
+ for (const [name, version] of Object.entries(depsToAdd)) {
2342
+ if (!packageJson.dependencies[name]) {
2343
+ packageJson.dependencies[name] = version;
2344
+ }
2345
+ }
2346
+ packageJson.devDependencies = packageJson.devDependencies || {};
2347
+ const devDepsToAdd = {
2348
+ // TypeScript
2349
+ "typescript": "^5.7.3",
2350
+ "@types/node": "^22.10.7",
2351
+ "@types/react": "^19.0.7",
2352
+ "@types/react-dom": "^19.0.3",
2353
+ // Tailwind
2354
+ "@tailwindcss/postcss": "^4.0.0",
2355
+ // ESLint
2356
+ "eslint": "^9.18.0",
2357
+ "eslint-config-next": "^15.1.0",
2358
+ "@eslint/eslintrc": "^3.2.0",
2359
+ // Database
2360
+ "drizzle-kit": "^0.31.4",
2361
+ // Jest
2362
+ "jest": "^29.7.0",
2363
+ "ts-jest": "^29.2.5",
2364
+ "ts-node": "^10.9.2",
2365
+ "@types/jest": "^29.5.14",
2366
+ "@testing-library/jest-dom": "^6.6.3",
2367
+ "@testing-library/react": "^16.3.0",
2368
+ "jest-environment-jsdom": "^29.7.0",
2369
+ // Cypress
2370
+ "cypress": "^14.0.0",
2371
+ "@testing-library/cypress": "^10.0.2",
2372
+ "@cypress/webpack-preprocessor": "^6.0.2",
2373
+ "@cypress/grep": "^4.1.0",
2374
+ "ts-loader": "^9.5.1",
2375
+ "webpack": "^5.97.0",
2376
+ "allure-cypress": "^3.0.0",
2377
+ "allure-commandline": "^2.27.0"
2378
+ };
2379
+ for (const [name, version] of Object.entries(devDepsToAdd)) {
2380
+ if (!packageJson.devDependencies[name]) {
2381
+ packageJson.devDependencies[name] = version;
2382
+ }
2383
+ }
2384
+ await fs7.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2385
+ }
2386
+ async function updateGitignore(config) {
2387
+ const gitignorePath = path5.resolve(process.cwd(), ".gitignore");
2388
+ const entriesToAdd = `
2389
+ # NextSpark
2390
+ .nextspark/
2391
+
2392
+ # Cypress (theme-based)
2393
+ contents/themes/*/tests/cypress/videos
2394
+ contents/themes/*/tests/cypress/screenshots
2395
+ contents/themes/*/tests/cypress/allure-results
2396
+ contents/themes/*/tests/cypress/allure-report
2397
+
2398
+ # Jest (theme-based)
2399
+ contents/themes/*/tests/jest/coverage
2400
+
2401
+ # Environment
2402
+ .env
2403
+ .env.local
2404
+ `;
2405
+ if (await fs7.pathExists(gitignorePath)) {
2406
+ const currentContent = await fs7.readFile(gitignorePath, "utf-8");
2407
+ if (!currentContent.includes(".nextspark/")) {
2408
+ await fs7.appendFile(gitignorePath, entriesToAdd);
2409
+ }
2410
+ } else {
2411
+ await fs7.writeFile(gitignorePath, entriesToAdd.trim());
2412
+ }
2413
+ }
2414
+ async function generateProject(config) {
2415
+ await copyProjectFiles();
2416
+ await copyStarterTheme(config);
2417
+ await copyContentFeatures(config);
2418
+ await updateThemeConfig(config);
2419
+ await updateDevConfig(config);
2420
+ await updateAppConfig(config);
2421
+ await updateBillingConfig(config);
2422
+ await updateRolesConfig(config);
2423
+ await updateMigrations(config);
2424
+ await updatePermissionsConfig(config);
2425
+ await updateDashboardConfig(config);
2426
+ await updateAuthConfig(config);
2427
+ await updateDashboardUIConfig(config);
2428
+ await updateDevToolsConfig(config);
2429
+ await processI18n(config);
2430
+ await updatePackageJson(config);
2431
+ await updateGitignore(config);
2432
+ await generateEnvExample(config);
2433
+ await updateReadme(config);
2434
+ await copyEnvExampleToEnv();
2435
+ }
2436
+
2437
+ // src/wizard/presets.ts
2438
+ var SAAS_PRESET = {
2439
+ teamMode: "multi-tenant",
2440
+ teamRoles: ["owner", "admin", "member", "viewer"],
2441
+ defaultLocale: "en",
2442
+ supportedLocales: ["en"],
2443
+ billingModel: "freemium",
2444
+ currency: "usd",
2445
+ features: {
2446
+ analytics: true,
2447
+ teams: true,
2448
+ billing: true,
2449
+ api: true,
2450
+ docs: false
2451
+ },
2452
+ contentFeatures: {
2453
+ pages: false,
2454
+ blog: false
2455
+ },
2456
+ auth: {
2457
+ emailPassword: true,
2458
+ googleOAuth: true,
2459
+ emailVerification: true
2460
+ },
2461
+ dashboard: {
2462
+ search: true,
2463
+ notifications: true,
2464
+ themeToggle: true,
2465
+ support: true,
2466
+ quickCreate: true,
2467
+ superadminAccess: true,
2468
+ devtoolsAccess: true,
2469
+ sidebarCollapsed: false
2470
+ },
2471
+ dev: {
2472
+ devKeyring: true,
2473
+ debugMode: false
2474
+ }
2475
+ };
2476
+ var BLOG_PRESET = {
2477
+ teamMode: "single-user",
2478
+ teamRoles: ["owner"],
2479
+ defaultLocale: "en",
2480
+ supportedLocales: ["en"],
2481
+ billingModel: "free",
2482
+ currency: "usd",
2483
+ features: {
2484
+ analytics: true,
2485
+ teams: false,
2486
+ billing: false,
2487
+ api: false,
2488
+ docs: true
2489
+ },
2490
+ contentFeatures: {
2491
+ pages: false,
2492
+ blog: true
2493
+ },
2494
+ auth: {
2495
+ emailPassword: true,
2496
+ googleOAuth: false,
2497
+ emailVerification: false
2498
+ },
2499
+ dashboard: {
2500
+ search: false,
2501
+ notifications: false,
2502
+ themeToggle: true,
2503
+ support: true,
2504
+ quickCreate: false,
2505
+ superadminAccess: true,
2506
+ devtoolsAccess: true,
2507
+ sidebarCollapsed: false
2508
+ },
2509
+ dev: {
2510
+ devKeyring: true,
2511
+ debugMode: false
2512
+ }
2513
+ };
2514
+ var CRM_PRESET = {
2515
+ teamMode: "single-tenant",
2516
+ teamRoles: ["owner", "admin", "member"],
2517
+ defaultLocale: "en",
2518
+ supportedLocales: ["en"],
2519
+ billingModel: "paid",
2520
+ currency: "usd",
2521
+ features: {
2522
+ analytics: true,
2523
+ teams: true,
2524
+ billing: true,
2525
+ api: true,
2526
+ docs: false
2527
+ },
2528
+ contentFeatures: {
2529
+ pages: true,
2530
+ blog: false
2531
+ },
2532
+ auth: {
2533
+ emailPassword: true,
2534
+ googleOAuth: true,
2535
+ emailVerification: true
2536
+ },
2537
+ dashboard: {
2538
+ search: true,
2539
+ notifications: true,
2540
+ themeToggle: true,
2541
+ support: true,
2542
+ quickCreate: true,
2543
+ superadminAccess: true,
2544
+ devtoolsAccess: true,
2545
+ sidebarCollapsed: false
2546
+ },
2547
+ dev: {
2548
+ devKeyring: true,
2549
+ debugMode: false
2550
+ }
2551
+ };
2552
+ var PRESETS = {
2553
+ saas: SAAS_PRESET,
2554
+ blog: BLOG_PRESET,
2555
+ crm: CRM_PRESET
2556
+ };
2557
+ var PRESET_DESCRIPTIONS = {
2558
+ saas: "Multi-tenant SaaS with freemium billing and team management",
2559
+ blog: "Single-user blog or content site with minimal features",
2560
+ crm: "Single-tenant CRM or internal tool with paid subscription"
2561
+ };
2562
+ function getPreset(name) {
2563
+ const preset = PRESETS[name];
2564
+ if (!preset) {
2565
+ throw new Error(`Unknown preset: ${name}. Available presets: ${Object.keys(PRESETS).join(", ")}`);
2566
+ }
2567
+ return preset;
2568
+ }
2569
+ function applyPreset(projectInfo, presetName) {
2570
+ const preset = getPreset(presetName);
2571
+ return {
2572
+ ...projectInfo,
2573
+ ...preset
2574
+ };
2575
+ }
2576
+
2577
+ // src/wizard/preview.ts
2578
+ import chalk10 from "chalk";
2579
+ var BOX = {
2580
+ vertical: "\u2502",
2581
+ // |
2582
+ horizontal: "\u2500",
2583
+ // -
2584
+ corner: "\u2514",
2585
+ // L
2586
+ tee: "\u251C",
2587
+ // |-
2588
+ topLeft: "\u250C",
2589
+ // top-left corner
2590
+ topRight: "\u2510",
2591
+ // top-right corner
2592
+ bottomLeft: "\u2514",
2593
+ // bottom-left corner
2594
+ bottomRight: "\u2518"
2595
+ // bottom-right corner
2596
+ };
2597
+ function getFileTree(config) {
2598
+ const files = [];
2599
+ const themeDir = `contents/themes/${config.projectSlug}`;
2600
+ files.push(`${themeDir}/config/app.config.ts`);
2601
+ files.push(`${themeDir}/config/billing.config.ts`);
2602
+ files.push(`${themeDir}/config/dashboard.config.ts`);
2603
+ files.push(`${themeDir}/config/dev.config.ts`);
2604
+ files.push(`${themeDir}/config/permissions.config.ts`);
2605
+ files.push(`${themeDir}/config/theme.config.ts`);
2606
+ files.push(`${themeDir}/config/auth.config.ts`);
2607
+ files.push(`${themeDir}/config/dashboard-ui.config.ts`);
2608
+ files.push(`${themeDir}/config/dev-tools.config.ts`);
2609
+ files.push(`${themeDir}/entities/tasks/entity.ts`);
2610
+ files.push(`${themeDir}/entities/tasks/schema.ts`);
2611
+ files.push(`${themeDir}/entities/tasks/permissions.ts`);
2612
+ files.push(`${themeDir}/blocks/hero/block.tsx`);
2613
+ files.push(`${themeDir}/blocks/hero/schema.ts`);
2614
+ files.push(`${themeDir}/blocks/hero/styles.ts`);
2615
+ for (const locale of config.supportedLocales) {
2616
+ files.push(`${themeDir}/messages/${locale}/common.json`);
2617
+ files.push(`${themeDir}/messages/${locale}/auth.json`);
2618
+ files.push(`${themeDir}/messages/${locale}/dashboard.json`);
2619
+ files.push(`${themeDir}/messages/${locale}/errors.json`);
2620
+ }
2621
+ files.push(`${themeDir}/migrations/0001_initial_schema.sql`);
2622
+ files.push(`${themeDir}/migrations/0002_auth_tables.sql`);
2623
+ files.push(`${themeDir}/migrations/0003_tasks_entity.sql`);
2624
+ files.push(`${themeDir}/styles/globals.css`);
2625
+ files.push(`${themeDir}/styles/theme.css`);
2626
+ files.push(`${themeDir}/styles/components.css`);
2627
+ files.push(`${themeDir}/tests/cypress.config.ts`);
2628
+ files.push(`${themeDir}/tests/jest/jest.config.ts`);
2629
+ files.push(`${themeDir}/tests/cypress/e2e/auth.cy.ts`);
2630
+ files.push(`${themeDir}/tests/cypress/e2e/dashboard.cy.ts`);
2631
+ files.push(`${themeDir}/tests/jest/components/hero.test.tsx`);
2632
+ return files;
2633
+ }
2634
+ function groupFilesByCategory(files) {
2635
+ const groups = {
2636
+ config: [],
2637
+ entities: [],
2638
+ blocks: [],
2639
+ messages: [],
2640
+ migrations: [],
2641
+ styles: [],
2642
+ tests: []
2643
+ };
2644
+ for (const file of files) {
2645
+ if (file.includes("/config/")) {
2646
+ groups.config.push(file);
2647
+ } else if (file.includes("/entities/")) {
2648
+ groups.entities.push(file);
2649
+ } else if (file.includes("/blocks/")) {
2650
+ groups.blocks.push(file);
2651
+ } else if (file.includes("/messages/")) {
2652
+ groups.messages.push(file);
2653
+ } else if (file.includes("/migrations/")) {
2654
+ groups.migrations.push(file);
2655
+ } else if (file.includes("/styles/")) {
2656
+ groups.styles.push(file);
2657
+ } else if (file.includes("/tests/")) {
2658
+ groups.tests.push(file);
2659
+ }
2660
+ }
2661
+ return groups;
2662
+ }
2663
+ function formatFilePath(file, themeDir) {
2664
+ return file.replace(`${themeDir}/`, "");
2665
+ }
2666
+ function showConfigPreview(config) {
2667
+ const files = getFileTree(config);
2668
+ const groups = groupFilesByCategory(files);
2669
+ const themeDir = `contents/themes/${config.projectSlug}`;
2670
+ console.log("");
2671
+ console.log(chalk10.cyan.bold(" Theme Preview"));
2672
+ console.log(chalk10.gray(" " + "=".repeat(50)));
2673
+ console.log("");
2674
+ console.log(chalk10.white.bold(` ${BOX.topLeft}${"\u2500".repeat(48)}${BOX.topRight}`));
2675
+ console.log(chalk10.white.bold(` ${BOX.vertical}`) + chalk10.cyan(` ${themeDir}/`) + " ".repeat(48 - themeDir.length - 2) + chalk10.white.bold(BOX.vertical));
2676
+ console.log(chalk10.white.bold(` ${BOX.vertical}${"\u2500".repeat(48)}${BOX.vertical}`));
2677
+ const categoryLabels = {
2678
+ config: "Configuration",
2679
+ entities: "Entities",
2680
+ blocks: "Blocks",
2681
+ messages: "Messages (i18n)",
2682
+ migrations: "Migrations",
2683
+ styles: "Styles",
2684
+ tests: "Tests"
2685
+ };
2686
+ const categoryIcons = {
2687
+ config: "*",
2688
+ entities: "@",
2689
+ blocks: "#",
2690
+ messages: "%",
2691
+ migrations: "~",
2692
+ styles: "&",
2693
+ tests: "!"
2694
+ };
2695
+ const categoryOrder = ["config", "entities", "blocks", "messages", "migrations", "styles", "tests"];
2696
+ for (const category of categoryOrder) {
2697
+ const categoryFiles = groups[category];
2698
+ if (categoryFiles.length === 0) continue;
2699
+ const label = categoryLabels[category];
2700
+ const icon = categoryIcons[category];
2701
+ console.log(chalk10.white.bold(` ${BOX.vertical} `) + chalk10.yellow(`[${icon}] ${label}`) + chalk10.gray(` (${categoryFiles.length} files)`));
2702
+ for (let i = 0; i < categoryFiles.length; i++) {
2703
+ const file = categoryFiles[i];
2704
+ const isLast = i === categoryFiles.length - 1;
2705
+ const prefix = isLast ? BOX.corner : BOX.tee;
2706
+ const formattedPath = formatFilePath(file, themeDir);
2707
+ console.log(chalk10.white.bold(` ${BOX.vertical} `) + chalk10.gray(` ${prefix}${BOX.horizontal} `) + chalk10.white(formattedPath));
2708
+ }
2709
+ console.log(chalk10.white.bold(` ${BOX.vertical}`));
2710
+ }
2711
+ console.log(chalk10.white.bold(` ${BOX.bottomLeft}${"\u2500".repeat(48)}${BOX.bottomRight}`));
2712
+ console.log("");
2713
+ console.log(chalk10.cyan.bold(" Summary"));
2714
+ console.log(chalk10.gray(" " + "-".repeat(30)));
2715
+ console.log("");
2716
+ const totalFiles = files.length;
2717
+ const estimatedSize = "~350KB";
2718
+ console.log(chalk10.white(` Total files: `) + chalk10.green.bold(totalFiles.toString()));
2719
+ console.log(chalk10.white(` Estimated size: `) + chalk10.green.bold(estimatedSize));
2720
+ console.log("");
2721
+ console.log(chalk10.gray(" By category:"));
2722
+ for (const category of categoryOrder) {
2723
+ const count = groups[category].length;
2724
+ if (count > 0) {
2725
+ const label = categoryLabels[category].padEnd(16);
2726
+ console.log(chalk10.gray(` ${label}`) + chalk10.white(count.toString().padStart(3)) + chalk10.gray(" files"));
2727
+ }
2728
+ }
2729
+ console.log("");
2730
+ console.log(chalk10.gray(" Locales configured:"));
2731
+ for (const locale of config.supportedLocales) {
2732
+ const isDefault = locale === config.defaultLocale;
2733
+ const suffix = isDefault ? chalk10.cyan(" (default)") : "";
2734
+ console.log(chalk10.gray(` - `) + chalk10.white(locale) + suffix);
2735
+ }
2736
+ console.log("");
2737
+ }
2738
+
2739
+ // src/wizard/index.ts
2740
+ function getProjectInfoFromOptions(options) {
2741
+ if (options.name && options.slug && options.description) {
2742
+ return {
2743
+ projectName: options.name,
2744
+ projectSlug: options.slug,
2745
+ projectDescription: options.description
2746
+ };
2747
+ }
2748
+ return null;
2749
+ }
2750
+ async function runWizard(options = { mode: "interactive" }) {
2751
+ showBanner();
2752
+ showModeIndicator(options);
2753
+ try {
2754
+ let selectedTheme = null;
2755
+ let selectedPlugins = [];
2756
+ if (!options.preset) {
2757
+ if (options.theme !== void 0) {
2758
+ selectedTheme = options.theme === "none" ? null : options.theme;
2759
+ showInfo(`Reference theme: ${selectedTheme || "None"}`);
2760
+ } else if (options.mode !== "quick") {
2761
+ selectedTheme = await promptThemeSelection();
2762
+ }
2763
+ if (options.plugins !== void 0) {
2764
+ selectedPlugins = options.plugins;
2765
+ if (selectedPlugins.length > 0) {
2766
+ showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
2767
+ }
2768
+ } else if (options.mode !== "quick" && !options.yes) {
2769
+ selectedPlugins = await promptPluginsSelection(selectedTheme);
2770
+ } else if (selectedTheme) {
2771
+ selectedPlugins = getRequiredPlugins(selectedTheme);
2772
+ }
2773
+ }
2774
+ let config;
2775
+ if (options.preset) {
2776
+ config = await runPresetMode(options.preset, options);
2777
+ } else {
2778
+ switch (options.mode) {
2779
+ case "quick":
2780
+ config = await runQuickPrompts();
2781
+ break;
2782
+ case "expert":
2783
+ config = await runExpertPrompts();
2784
+ break;
2785
+ case "interactive":
2786
+ default:
2787
+ config = await runAllPrompts();
2788
+ break;
2789
+ }
2790
+ }
2791
+ showConfigSummary(config);
2792
+ showConfigPreview(config);
2793
+ if (!options.yes) {
2794
+ console.log("");
2795
+ const proceed = await confirm5({
2796
+ message: "Proceed with project generation?",
2797
+ default: true
2798
+ });
2799
+ if (!proceed) {
2800
+ console.log("");
2801
+ showInfo("Project generation cancelled. No changes were made.");
2802
+ process.exit(0);
2803
+ }
2804
+ }
2805
+ await copyNpmrc();
2806
+ console.log("");
2807
+ const coreInstalled = await installCore();
2808
+ if (!coreInstalled) {
2809
+ showError("Failed to install @nextsparkjs/core. Cannot generate project.");
2810
+ process.exit(1);
2811
+ }
2812
+ console.log("");
2813
+ const spinner = ora7({
2814
+ text: "Generating your NextSpark project...",
2815
+ prefixText: " "
2816
+ }).start();
2817
+ try {
2818
+ await generateProject(config);
2819
+ spinner.succeed("Project generated successfully!");
2820
+ } catch (error) {
2821
+ spinner.fail("Failed to generate project");
2822
+ throw error;
2823
+ }
2824
+ if (selectedTheme || selectedPlugins.length > 0) {
2825
+ await installThemeAndPlugins(selectedTheme, selectedPlugins);
2826
+ }
2827
+ const installSpinner = ora7({
2828
+ text: "Installing dependencies...",
2829
+ prefixText: " "
2830
+ }).start();
2831
+ try {
2832
+ execSync("pnpm install --force", {
2833
+ cwd: process.cwd(),
2834
+ stdio: "pipe"
2835
+ });
2836
+ installSpinner.succeed("Dependencies installed!");
2837
+ } catch (error) {
2838
+ installSpinner.fail("Failed to install dependencies");
2839
+ console.log(chalk11.yellow(' Run "pnpm install" manually to install dependencies'));
2840
+ }
2841
+ const registrySpinner = ora7({
2842
+ text: "Building registries...",
2843
+ prefixText: " "
2844
+ }).start();
2845
+ try {
2846
+ const projectRoot = process.cwd();
2847
+ const registryScript = join7(projectRoot, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
2848
+ execSync(`node "${registryScript}" --build`, {
2849
+ cwd: projectRoot,
2850
+ stdio: "pipe",
2851
+ env: {
2852
+ ...process.env,
2853
+ NEXTSPARK_PROJECT_ROOT: projectRoot
2854
+ }
2855
+ });
2856
+ registrySpinner.succeed("Registries built!");
2857
+ } catch (error) {
2858
+ registrySpinner.fail("Failed to build registries");
2859
+ console.log(chalk11.yellow(' Registries will be built automatically when you run "pnpm dev"'));
2860
+ }
2861
+ showNextSteps(config, selectedTheme);
2862
+ } catch (error) {
2863
+ if (error instanceof Error) {
2864
+ if (error.message.includes("User force closed")) {
2865
+ console.log("");
2866
+ showInfo("Wizard cancelled. No changes were made.");
2867
+ process.exit(0);
2868
+ }
2869
+ showError(error.message);
2870
+ }
2871
+ process.exit(1);
2872
+ }
2873
+ }
2874
+ function showModeIndicator(options) {
2875
+ if (options.preset) {
2876
+ showInfo(`Using preset: ${chalk11.cyan(options.preset)} - ${PRESET_DESCRIPTIONS[options.preset]}`);
2877
+ console.log("");
2878
+ } else if (options.mode === "quick") {
2879
+ showInfo("Quick mode: Running essential prompts only (steps 1-5)");
2880
+ console.log("");
2881
+ } else if (options.mode === "expert") {
2882
+ showInfo("Expert mode: Running all prompts with advanced options");
2883
+ console.log("");
2884
+ }
2885
+ }
2886
+ async function runPresetMode(presetName, options) {
2887
+ if (!presetName) {
2888
+ throw new Error("Preset name is required for preset mode");
2889
+ }
2890
+ const projectInfoFromOptions = getProjectInfoFromOptions(options);
2891
+ let projectInfo;
2892
+ if (projectInfoFromOptions) {
2893
+ projectInfo = projectInfoFromOptions;
2894
+ showInfo(`Project: ${projectInfo.projectName} (${projectInfo.projectSlug})`);
2895
+ } else {
2896
+ showSection("Project Information", 1, 1);
2897
+ showInfo("Using preset defaults. Only project information is required.");
2898
+ console.log("");
2899
+ projectInfo = await promptProjectInfo();
2900
+ }
2901
+ const config = applyPreset(projectInfo, presetName);
2902
+ return config;
2903
+ }
2904
+ function showConfigSummary(config) {
2905
+ console.log("");
2906
+ console.log(chalk11.cyan(" " + "=".repeat(60)));
2907
+ console.log(chalk11.bold.white(" Configuration Summary"));
2908
+ console.log(chalk11.cyan(" " + "=".repeat(60)));
2909
+ console.log("");
2910
+ console.log(chalk11.white(" Project:"));
2911
+ console.log(chalk11.gray(` Name: ${chalk11.white(config.projectName)}`));
2912
+ console.log(chalk11.gray(` Slug: ${chalk11.white(config.projectSlug)}`));
2913
+ console.log(chalk11.gray(` Description: ${chalk11.white(config.projectDescription)}`));
2914
+ console.log("");
2915
+ console.log(chalk11.white(" Team Mode:"));
2916
+ console.log(chalk11.gray(` Mode: ${chalk11.white(config.teamMode)}`));
2917
+ console.log(chalk11.gray(` Roles: ${chalk11.white(config.teamRoles.join(", "))}`));
2918
+ console.log("");
2919
+ console.log(chalk11.white(" Internationalization:"));
2920
+ console.log(chalk11.gray(` Default: ${chalk11.white(config.defaultLocale)}`));
2921
+ console.log(chalk11.gray(` Languages: ${chalk11.white(config.supportedLocales.join(", "))}`));
2922
+ console.log("");
2923
+ console.log(chalk11.white(" Billing:"));
2924
+ console.log(chalk11.gray(` Model: ${chalk11.white(config.billingModel)}`));
2925
+ console.log(chalk11.gray(` Currency: ${chalk11.white(config.currency.toUpperCase())}`));
2926
+ console.log("");
2927
+ console.log(chalk11.white(" Features:"));
2928
+ const enabledFeatures = Object.entries(config.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
2929
+ console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledFeatures.join(", ") || "None")}`));
2930
+ console.log("");
2931
+ console.log(chalk11.white(" Authentication:"));
2932
+ const enabledAuth = Object.entries(config.auth).filter(([_, enabled]) => enabled).map(([method]) => formatAuthMethod(method));
2933
+ console.log(chalk11.gray(` Methods: ${chalk11.white(enabledAuth.join(", ") || "None")}`));
2934
+ console.log("");
2935
+ console.log(chalk11.white(" Dashboard:"));
2936
+ const enabledDashboard = Object.entries(config.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
2937
+ console.log(chalk11.gray(` Features: ${chalk11.white(enabledDashboard.join(", ") || "None")}`));
2938
+ console.log("");
2939
+ console.log(chalk11.white(" Dev Tools:"));
2940
+ const enabledDevTools = Object.entries(config.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
2941
+ console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledDevTools.join(", ") || "None")}`));
2942
+ }
2943
+ function formatAuthMethod(method) {
2944
+ const mapping = {
2945
+ emailPassword: "Email/Password",
2946
+ googleOAuth: "Google",
2947
+ emailVerification: "Email Verification"
2948
+ };
2949
+ return mapping[method] || method;
2950
+ }
2951
+ function formatDashboardFeature(feature) {
2952
+ const mapping = {
2953
+ search: "Search",
2954
+ notifications: "Notifications",
2955
+ themeToggle: "Theme Toggle",
2956
+ sidebarCollapsed: "Sidebar Collapsed"
2957
+ };
2958
+ return mapping[feature] || feature;
2959
+ }
2960
+ function formatDevTool(tool) {
2961
+ const mapping = {
2962
+ devKeyring: "Dev Keyring",
2963
+ debugMode: "Debug Mode"
2964
+ };
2965
+ return mapping[tool] || tool;
2966
+ }
2967
+ function showNextSteps(config, referenceTheme = null) {
2968
+ console.log("");
2969
+ console.log(chalk11.cyan(" " + "=".repeat(60)));
2970
+ console.log(chalk11.bold.green(" \u2728 NextSpark project ready!"));
2971
+ console.log(chalk11.cyan(" " + "=".repeat(60)));
2972
+ console.log("");
2973
+ console.log(chalk11.bold.white(" Next steps:"));
2974
+ console.log("");
2975
+ console.log(chalk11.white(" 1. Configure your .env file:"));
2976
+ console.log(chalk11.gray(" Edit these values in .env:"));
2977
+ console.log("");
2978
+ console.log(chalk11.yellow(" DATABASE_URL"));
2979
+ console.log(chalk11.gray(" PostgreSQL connection string"));
2980
+ console.log(chalk11.dim(" Example: postgresql://user:pass@localhost:5432/mydb"));
2981
+ console.log("");
2982
+ console.log(chalk11.yellow(" BETTER_AUTH_SECRET"));
2983
+ console.log(chalk11.gray(" Generate with:"));
2984
+ console.log(chalk11.cyan(" openssl rand -base64 32"));
2985
+ console.log("");
2986
+ console.log(chalk11.white(" 2. Run database migrations:"));
2987
+ console.log(chalk11.cyan(" pnpm db:migrate"));
2988
+ console.log("");
2989
+ console.log(chalk11.white(" 3. Start the development server:"));
2990
+ console.log(chalk11.cyan(" pnpm dev"));
2991
+ console.log("");
2992
+ console.log(chalk11.gray(" " + "-".repeat(60)));
2993
+ console.log(chalk11.gray(` Theme: ${chalk11.white(`contents/themes/${config.projectSlug}/`)}`));
2994
+ console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config.projectSlug}`)}`));
2995
+ if (referenceTheme) {
2996
+ console.log(chalk11.gray(` Reference: ${chalk11.white(`contents/themes/${referenceTheme}/`)}`));
2997
+ }
2998
+ console.log(chalk11.gray(" Docs: https://nextspark.dev/docs"));
2999
+ console.log("");
3000
+ }
3001
+ function findLocalCoreTarball() {
3002
+ const cwd = process.cwd();
3003
+ try {
3004
+ const files = readdirSync(cwd);
3005
+ const coreTarball = files.find(
3006
+ (f) => f.includes("nextsparkjs-core") && f.endsWith(".tgz")
3007
+ );
3008
+ if (coreTarball) {
3009
+ return join7(cwd, coreTarball);
3010
+ }
3011
+ } catch {
3012
+ }
3013
+ return null;
3014
+ }
3015
+ function isCoreInstalled() {
3016
+ const corePath = join7(process.cwd(), "node_modules", "@nextsparkjs", "core");
3017
+ return existsSync7(corePath);
3018
+ }
3019
+ async function installCore() {
3020
+ if (isCoreInstalled()) {
3021
+ return true;
3022
+ }
3023
+ const spinner = ora7({
3024
+ text: "Installing @nextsparkjs/core...",
3025
+ prefixText: " "
3026
+ }).start();
3027
+ try {
3028
+ const localTarball = findLocalCoreTarball();
3029
+ let packageSpec = "@nextsparkjs/core";
3030
+ if (localTarball) {
3031
+ packageSpec = localTarball;
3032
+ spinner.text = "Installing @nextsparkjs/core from local tarball...";
3033
+ }
3034
+ const useYarn = existsSync7(join7(process.cwd(), "yarn.lock"));
3035
+ const usePnpm = existsSync7(join7(process.cwd(), "pnpm-lock.yaml"));
3036
+ let installCmd;
3037
+ if (usePnpm) {
3038
+ installCmd = `pnpm add ${packageSpec}`;
3039
+ } else if (useYarn) {
3040
+ installCmd = `yarn add ${packageSpec}`;
3041
+ } else {
3042
+ installCmd = `npm install ${packageSpec}`;
3043
+ }
3044
+ execSync(installCmd, {
3045
+ stdio: "pipe",
3046
+ cwd: process.cwd()
3047
+ });
3048
+ spinner.succeed(chalk11.green("@nextsparkjs/core installed successfully!"));
3049
+ return true;
3050
+ } catch (error) {
3051
+ spinner.fail(chalk11.red("Failed to install @nextsparkjs/core"));
3052
+ if (error instanceof Error) {
3053
+ console.log(chalk11.red(` Error: ${error.message}`));
3054
+ }
3055
+ console.log(chalk11.gray(" Hint: Make sure the package is available (npm registry or local tarball)"));
3056
+ return false;
3057
+ }
3058
+ }
3059
+ async function copyNpmrc() {
3060
+ const npmrcPath = join7(process.cwd(), ".npmrc");
3061
+ if (existsSync7(npmrcPath)) {
3062
+ return;
3063
+ }
3064
+ const npmrcContent = `# Hoist @nextsparkjs/core dependencies so they're accessible from the project
3065
+ # This is required for pnpm to make peer dependencies available
3066
+ public-hoist-pattern[]=*
3067
+ `;
3068
+ const { writeFileSync: writeFileSync3 } = await import("fs");
3069
+ writeFileSync3(npmrcPath, npmrcContent);
3070
+ }
3071
+
3072
+ // src/commands/init.ts
3073
+ function getWizardMode(options) {
3074
+ if (options.quick) return "quick";
3075
+ if (options.expert) return "expert";
3076
+ return "interactive";
3077
+ }
3078
+ function parsePlugins(pluginsStr) {
3079
+ if (!pluginsStr) return void 0;
3080
+ return pluginsStr.split(",").map((p) => p.trim());
3081
+ }
3082
+ function hasExistingProject() {
3083
+ const projectRoot = process.cwd();
3084
+ return existsSync8(join8(projectRoot, "contents")) || existsSync8(join8(projectRoot, ".nextspark"));
3085
+ }
3086
+ function generateInitialRegistries(registriesDir) {
3087
+ writeFileSync2(join8(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
3088
+ import type { ComponentType } from 'react'
3089
+
3090
+ export const BLOCK_REGISTRY: Record<string, {
3091
+ name: string
3092
+ slug: string
3093
+ componentPath: string
3094
+ fields?: unknown[]
3095
+ examples?: unknown[]
3096
+ }> = {}
3097
+
3098
+ export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
3099
+ `);
3100
+ writeFileSync2(join8(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
3101
+ export const THEME_REGISTRY: Record<string, unknown> = {}
3102
+ `);
3103
+ writeFileSync2(join8(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
3104
+ export const ENTITY_REGISTRY: Record<string, unknown> = {}
3105
+ `);
3106
+ writeFileSync2(join8(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
3107
+ export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
3108
+ export function parseChildEntity(path: string) { return null }
3109
+ export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
3110
+ export function clientMetaSystemAdapter() { return {} }
3111
+ export type ClientEntityConfig = Record<string, unknown>
3112
+ `);
3113
+ writeFileSync2(join8(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
3114
+ export const BILLING_REGISTRY = { plans: [], features: [] }
3115
+ `);
3116
+ writeFileSync2(join8(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
3117
+ export const PLUGIN_REGISTRY: Record<string, unknown> = {}
3118
+ `);
3119
+ writeFileSync2(join8(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
3120
+ export const FLOW_REGISTRY: Record<string, unknown> = {}
3121
+ export const FEATURE_REGISTRY: Record<string, unknown> = {}
3122
+ export const TAGS_REGISTRY: Record<string, unknown> = {}
3123
+ export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
3124
+ export type FlowEntry = unknown
3125
+ export type FeatureEntry = unknown
3126
+ `);
3127
+ writeFileSync2(join8(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
3128
+ export const DOCS_REGISTRY = { sections: [], pages: [] }
3129
+ export type DocSectionMeta = { title: string; slug: string }
3130
+ `);
3131
+ writeFileSync2(join8(registriesDir, "index.ts"), `// Auto-generated by nextspark init
3132
+ export * from './block-registry'
3133
+ export * from './theme-registry'
3134
+ export * from './entity-registry'
3135
+ export * from './entity-registry.client'
3136
+ export * from './billing-registry'
3137
+ export * from './plugin-registry'
3138
+ export * from './testing-registry'
3139
+ export * from './docs-registry'
3140
+ `);
3141
+ }
3142
+ async function simpleInit(options) {
3143
+ const spinner = ora8("Initializing NextSpark project...").start();
3144
+ const projectRoot = process.cwd();
3145
+ try {
3146
+ const nextspark = join8(projectRoot, ".nextspark");
3147
+ const registriesDir = join8(nextspark, "registries");
3148
+ if (!existsSync8(registriesDir) || options.force) {
3149
+ mkdirSync2(registriesDir, { recursive: true });
3150
+ spinner.text = "Creating .nextspark/registries/";
3151
+ generateInitialRegistries(registriesDir);
3152
+ spinner.text = "Generated initial registries";
3153
+ }
3154
+ const tsconfigPath = join8(projectRoot, "tsconfig.json");
3155
+ if (existsSync8(tsconfigPath)) {
3156
+ spinner.text = "Updating tsconfig.json paths...";
3157
+ const tsconfig = JSON.parse(readFileSync7(tsconfigPath, "utf-8"));
3158
+ tsconfig.compilerOptions = tsconfig.compilerOptions || {};
3159
+ tsconfig.compilerOptions.paths = {
3160
+ ...tsconfig.compilerOptions.paths,
3161
+ "@nextsparkjs/registries": ["./.nextspark/registries/index.ts"],
3162
+ "@nextsparkjs/registries/*": ["./.nextspark/registries/*"]
3163
+ };
3164
+ writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2));
3165
+ }
3166
+ const envExample = join8(projectRoot, ".env.example");
3167
+ if (!existsSync8(envExample)) {
3168
+ const envContent = `# NextSpark Configuration
3169
+ DATABASE_URL="postgresql://user:password@localhost:5432/db"
3170
+ BETTER_AUTH_SECRET="your-secret-here-min-32-chars"
3171
+ BETTER_AUTH_URL=http://localhost:3000
3172
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
3173
+ `;
3174
+ writeFileSync2(envExample, envContent);
3175
+ spinner.text = "Created .env.example";
3176
+ }
3177
+ spinner.succeed("NextSpark initialized successfully!");
3178
+ console.log(chalk12.blue("\nNext steps:"));
3179
+ console.log(" 1. Copy .env.example to .env and configure");
3180
+ console.log(" 2. Run: nextspark generate");
3181
+ console.log(" 3. Run: nextspark dev");
3182
+ } catch (error) {
3183
+ spinner.fail("Initialization failed");
3184
+ if (error instanceof Error) {
3185
+ console.error(chalk12.red(error.message));
3186
+ }
3187
+ process.exit(1);
3188
+ }
3189
+ }
3190
+ async function initCommand(options) {
3191
+ if (options.registriesOnly) {
3192
+ await simpleInit(options);
3193
+ return;
3194
+ }
3195
+ if (hasExistingProject() && !options.wizard && !options.preset && !options.theme) {
3196
+ console.log(chalk12.yellow("Existing NextSpark project detected."));
3197
+ console.log(chalk12.gray("Use --wizard to run the full wizard, or --registries-only for just registries."));
3198
+ await simpleInit(options);
3199
+ return;
3200
+ }
3201
+ const wizardOptions = {
3202
+ mode: getWizardMode(options),
3203
+ preset: options.preset,
3204
+ theme: options.theme,
3205
+ plugins: parsePlugins(options.plugins),
3206
+ yes: options.yes,
3207
+ name: options.name,
3208
+ slug: options.slug,
3209
+ description: options.description
3210
+ };
3211
+ await runWizard(wizardOptions);
3212
+ }
3213
+
3214
+ // src/commands/doctor.ts
3215
+ import chalk14 from "chalk";
3216
+
3217
+ // src/doctor/index.ts
3218
+ import chalk13 from "chalk";
3219
+
3220
+ // src/doctor/checks/dependencies.ts
3221
+ import fs8 from "fs-extra";
3222
+ import path6 from "path";
3223
+ async function checkDependencies() {
3224
+ const cwd = process.cwd();
3225
+ const nodeModulesPath = path6.join(cwd, "node_modules");
3226
+ const packageJsonPath = path6.join(cwd, "package.json");
3227
+ if (!await fs8.pathExists(packageJsonPath)) {
3228
+ return {
3229
+ name: "Dependencies",
3230
+ status: "fail",
3231
+ message: "package.json not found",
3232
+ fix: "Ensure you are in a NextSpark project directory"
3233
+ };
3234
+ }
3235
+ if (!await fs8.pathExists(nodeModulesPath)) {
3236
+ return {
3237
+ name: "Dependencies",
3238
+ status: "fail",
3239
+ message: "node_modules not found",
3240
+ fix: "Run: pnpm install"
3241
+ };
3242
+ }
3243
+ let packageJson;
3244
+ try {
3245
+ packageJson = await fs8.readJson(packageJsonPath);
3246
+ } catch {
3247
+ return {
3248
+ name: "Dependencies",
3249
+ status: "fail",
3250
+ message: "Failed to read package.json",
3251
+ fix: "Ensure package.json is valid JSON"
3252
+ };
3253
+ }
3254
+ const allDependencies = {
3255
+ ...packageJson.dependencies,
3256
+ ...packageJson.devDependencies
3257
+ };
3258
+ const missingDeps = [];
3259
+ const criticalDeps = ["next", "react", "react-dom"];
3260
+ for (const dep of criticalDeps) {
3261
+ if (allDependencies[dep]) {
3262
+ const depPath = path6.join(nodeModulesPath, dep);
3263
+ if (!await fs8.pathExists(depPath)) {
3264
+ missingDeps.push(dep);
3265
+ }
3266
+ }
3267
+ }
3268
+ const nextsparksCorePath = path6.join(nodeModulesPath, "@nextsparkjs", "core");
3269
+ const hasNextSparkCore = await fs8.pathExists(nextsparksCorePath);
3270
+ if (missingDeps.length > 0) {
3271
+ return {
3272
+ name: "Dependencies",
3273
+ status: "fail",
3274
+ message: `Missing packages: ${missingDeps.join(", ")}`,
3275
+ fix: "Run: pnpm install"
3276
+ };
3277
+ }
3278
+ if (!hasNextSparkCore && allDependencies["@nextsparkjs/core"]) {
3279
+ return {
3280
+ name: "Dependencies",
3281
+ status: "warn",
3282
+ message: "@nextsparkjs/core not installed",
3283
+ fix: "Run: pnpm install"
3284
+ };
3285
+ }
3286
+ return {
3287
+ name: "Dependencies",
3288
+ status: "pass",
3289
+ message: "All packages installed"
3290
+ };
3291
+ }
3292
+
3293
+ // src/doctor/checks/config.ts
3294
+ import fs9 from "fs-extra";
3295
+ import path7 from "path";
3296
+ var REQUIRED_CONFIG_FILES = [
3297
+ "next.config.ts",
3298
+ "tailwind.config.ts",
3299
+ "tsconfig.json"
3300
+ ];
3301
+ async function checkConfigs() {
3302
+ const cwd = process.cwd();
3303
+ const missingFiles = [];
3304
+ const invalidFiles = [];
3305
+ for (const file of REQUIRED_CONFIG_FILES) {
3306
+ const filePath = path7.join(cwd, file);
3307
+ if (file.endsWith(".ts")) {
3308
+ const jsPath = path7.join(cwd, file.replace(".ts", ".js"));
3309
+ const mjsPath = path7.join(cwd, file.replace(".ts", ".mjs"));
3310
+ const tsExists = await fs9.pathExists(filePath);
3311
+ const jsExists = await fs9.pathExists(jsPath);
3312
+ const mjsExists = await fs9.pathExists(mjsPath);
3313
+ if (!tsExists && !jsExists && !mjsExists) {
3314
+ if (!file.includes("next.config") && !file.includes("tailwind.config")) {
3315
+ missingFiles.push(file);
3316
+ }
3317
+ }
3318
+ continue;
3319
+ }
3320
+ if (!await fs9.pathExists(filePath)) {
3321
+ missingFiles.push(file);
3322
+ }
3323
+ }
3324
+ const tsconfigPath = path7.join(cwd, "tsconfig.json");
3325
+ if (await fs9.pathExists(tsconfigPath)) {
3326
+ try {
3327
+ const content = await fs9.readFile(tsconfigPath, "utf-8");
3328
+ JSON.parse(content);
3329
+ } catch {
3330
+ invalidFiles.push("tsconfig.json");
3331
+ }
3332
+ } else {
3333
+ missingFiles.push("tsconfig.json");
3334
+ }
3335
+ const configDir = path7.join(cwd, "config");
3336
+ if (await fs9.pathExists(configDir)) {
3337
+ const configFiles = await fs9.readdir(configDir);
3338
+ const tsConfigFiles = configFiles.filter((f) => f.endsWith(".ts"));
3339
+ for (const file of tsConfigFiles) {
3340
+ const filePath = path7.join(configDir, file);
3341
+ try {
3342
+ const content = await fs9.readFile(filePath, "utf-8");
3343
+ if (content.trim().length === 0) {
3344
+ invalidFiles.push(`config/${file}`);
3345
+ }
3346
+ } catch {
3347
+ invalidFiles.push(`config/${file}`);
3348
+ }
3349
+ }
3350
+ }
3351
+ if (invalidFiles.length > 0) {
3352
+ return {
3353
+ name: "Configuration",
3354
+ status: "fail",
3355
+ message: `Invalid config files: ${invalidFiles.join(", ")}`,
3356
+ fix: "Check the syntax of the invalid configuration files"
3357
+ };
3358
+ }
3359
+ if (missingFiles.length > 0) {
3360
+ return {
3361
+ name: "Configuration",
3362
+ status: "warn",
3363
+ message: `Missing config files: ${missingFiles.join(", ")}`,
3364
+ fix: "Run: npx nextspark init to regenerate configuration"
3365
+ };
3366
+ }
3367
+ return {
3368
+ name: "Configuration",
3369
+ status: "pass",
3370
+ message: "All config files valid"
3371
+ };
3372
+ }
3373
+
3374
+ // src/doctor/checks/database.ts
3375
+ import fs10 from "fs-extra";
3376
+ import path8 from "path";
3377
+ function parseEnvFile(content) {
3378
+ const result = {};
3379
+ const lines = content.split("\n");
3380
+ for (const line of lines) {
3381
+ const trimmed = line.trim();
3382
+ if (!trimmed || trimmed.startsWith("#")) {
3383
+ continue;
3384
+ }
3385
+ const equalsIndex = trimmed.indexOf("=");
3386
+ if (equalsIndex > 0) {
3387
+ const key = trimmed.substring(0, equalsIndex).trim();
3388
+ let value = trimmed.substring(equalsIndex + 1).trim();
3389
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3390
+ value = value.slice(1, -1);
3391
+ }
3392
+ result[key] = value;
3393
+ }
3394
+ }
3395
+ return result;
3396
+ }
3397
+ async function checkDatabase() {
3398
+ const cwd = process.cwd();
3399
+ const envPath = path8.join(cwd, ".env");
3400
+ const envLocalPath = path8.join(cwd, ".env.local");
3401
+ let envVars = {};
3402
+ if (await fs10.pathExists(envLocalPath)) {
3403
+ try {
3404
+ const content = await fs10.readFile(envLocalPath, "utf-8");
3405
+ envVars = { ...envVars, ...parseEnvFile(content) };
3406
+ } catch {
3407
+ }
3408
+ }
3409
+ if (await fs10.pathExists(envPath)) {
3410
+ try {
3411
+ const content = await fs10.readFile(envPath, "utf-8");
3412
+ envVars = { ...envVars, ...parseEnvFile(content) };
3413
+ } catch {
3414
+ }
3415
+ }
3416
+ const databaseUrl = envVars["DATABASE_URL"] || process.env.DATABASE_URL;
3417
+ if (!await fs10.pathExists(envPath) && !await fs10.pathExists(envLocalPath)) {
3418
+ return {
3419
+ name: "Database",
3420
+ status: "warn",
3421
+ message: "No .env file found",
3422
+ fix: "Copy .env.example to .env and configure DATABASE_URL"
3423
+ };
3424
+ }
3425
+ if (!databaseUrl) {
3426
+ return {
3427
+ name: "Database",
3428
+ status: "warn",
3429
+ message: "DATABASE_URL not configured",
3430
+ fix: "Add DATABASE_URL to .env"
3431
+ };
3432
+ }
3433
+ if (databaseUrl.includes("your-") || databaseUrl.includes("example") || databaseUrl.includes("placeholder") || databaseUrl === "postgresql://") {
3434
+ return {
3435
+ name: "Database",
3436
+ status: "warn",
3437
+ message: "DATABASE_URL appears to be a placeholder",
3438
+ fix: "Update DATABASE_URL in .env with your actual database connection string"
3439
+ };
3440
+ }
3441
+ const isValidFormat = databaseUrl.startsWith("postgresql://") || databaseUrl.startsWith("postgres://") || databaseUrl.startsWith("mysql://") || databaseUrl.startsWith("sqlite:");
3442
+ if (!isValidFormat) {
3443
+ return {
3444
+ name: "Database",
3445
+ status: "warn",
3446
+ message: "DATABASE_URL format may be invalid",
3447
+ fix: "Ensure DATABASE_URL is a valid database connection string"
3448
+ };
3449
+ }
3450
+ return {
3451
+ name: "Database",
3452
+ status: "pass",
3453
+ message: "DATABASE_URL configured"
3454
+ };
3455
+ }
3456
+
3457
+ // src/doctor/checks/imports.ts
3458
+ import fs11 from "fs-extra";
3459
+ import path9 from "path";
3460
+ async function checkCorePackage(cwd) {
3461
+ const nodeModulesPath = path9.join(cwd, "node_modules", "@nextsparkjs", "core");
3462
+ if (!await fs11.pathExists(nodeModulesPath)) {
3463
+ return { accessible: false, reason: "@nextsparkjs/core not found in node_modules" };
3464
+ }
3465
+ const packageJsonPath = path9.join(nodeModulesPath, "package.json");
3466
+ if (!await fs11.pathExists(packageJsonPath)) {
3467
+ return { accessible: false, reason: "@nextsparkjs/core package.json not found" };
3468
+ }
3469
+ try {
3470
+ const packageJson = await fs11.readJson(packageJsonPath);
3471
+ const mainEntry = packageJson.main || packageJson.module || "./dist/index.js";
3472
+ const mainPath = path9.join(nodeModulesPath, mainEntry);
3473
+ if (!await fs11.pathExists(mainPath)) {
3474
+ return { accessible: false, reason: "@nextsparkjs/core entry point not found" };
3475
+ }
3476
+ } catch {
3477
+ return { accessible: false, reason: "Failed to read @nextsparkjs/core package.json" };
3478
+ }
3479
+ return { accessible: true };
3480
+ }
3481
+ async function scanForBrokenImports(cwd) {
3482
+ const brokenImports = [];
3483
+ const srcDirs = ["src", "app", "pages", "components", "lib"];
3484
+ const existingDirs = [];
3485
+ for (const dir of srcDirs) {
3486
+ const dirPath = path9.join(cwd, dir);
3487
+ if (await fs11.pathExists(dirPath)) {
3488
+ existingDirs.push(dirPath);
3489
+ }
3490
+ }
3491
+ const maxFilesToScan = 50;
3492
+ let filesScanned = 0;
3493
+ for (const dir of existingDirs) {
3494
+ if (filesScanned >= maxFilesToScan) break;
3495
+ try {
3496
+ const files = await fs11.readdir(dir, { withFileTypes: true });
3497
+ for (const file of files) {
3498
+ if (filesScanned >= maxFilesToScan) break;
3499
+ if (file.isFile() && (file.name.endsWith(".ts") || file.name.endsWith(".tsx"))) {
3500
+ const filePath = path9.join(dir, file.name);
3501
+ try {
3502
+ const content = await fs11.readFile(filePath, "utf-8");
3503
+ const importMatches = content.match(/from ['"](@?[^'"]+)['"]/g);
3504
+ if (importMatches) {
3505
+ for (const match of importMatches) {
3506
+ const importPath = match.replace(/from ['"]/g, "").replace(/['"]/g, "");
3507
+ if (importPath.startsWith(".")) {
3508
+ const absoluteImportPath = path9.resolve(path9.dirname(filePath), importPath);
3509
+ const possiblePaths = [
3510
+ absoluteImportPath,
3511
+ `${absoluteImportPath}.ts`,
3512
+ `${absoluteImportPath}.tsx`,
3513
+ `${absoluteImportPath}.js`,
3514
+ `${absoluteImportPath}.jsx`,
3515
+ path9.join(absoluteImportPath, "index.ts"),
3516
+ path9.join(absoluteImportPath, "index.tsx"),
3517
+ path9.join(absoluteImportPath, "index.js")
3518
+ ];
3519
+ const exists = await Promise.any(
3520
+ possiblePaths.map(async (p) => {
3521
+ if (await fs11.pathExists(p)) return true;
3522
+ throw new Error("Not found");
3523
+ })
3524
+ ).catch(() => false);
3525
+ if (!exists) {
3526
+ brokenImports.push(`${file.name}: ${importPath}`);
3527
+ }
3528
+ }
3529
+ }
3530
+ }
3531
+ filesScanned++;
3532
+ } catch {
3533
+ }
3534
+ }
3535
+ }
3536
+ } catch {
3537
+ }
3538
+ }
3539
+ return brokenImports;
3540
+ }
3541
+ async function checkImports() {
3542
+ const cwd = process.cwd();
3543
+ const coreCheck = await checkCorePackage(cwd);
3544
+ if (!coreCheck.accessible) {
3545
+ const packageJsonPath = path9.join(cwd, "package.json");
3546
+ let hasCoreDep = false;
3547
+ if (await fs11.pathExists(packageJsonPath)) {
3548
+ try {
3549
+ const packageJson = await fs11.readJson(packageJsonPath);
3550
+ hasCoreDep = !!(packageJson.dependencies?.["@nextsparkjs/core"] || packageJson.devDependencies?.["@nextsparkjs/core"]);
3551
+ } catch {
3552
+ }
3553
+ }
3554
+ if (hasCoreDep) {
3555
+ return {
3556
+ name: "Imports",
3557
+ status: "fail",
3558
+ message: coreCheck.reason || "@nextsparkjs/core not accessible",
3559
+ fix: "Run: pnpm install"
3560
+ };
3561
+ }
3562
+ }
3563
+ try {
3564
+ const brokenImports = await scanForBrokenImports(cwd);
3565
+ if (brokenImports.length > 0) {
3566
+ const displayImports = brokenImports.slice(0, 3).join(", ");
3567
+ const remaining = brokenImports.length > 3 ? ` (+${brokenImports.length - 3} more)` : "";
3568
+ return {
3569
+ name: "Imports",
3570
+ status: "warn",
3571
+ message: `Potentially broken imports: ${displayImports}${remaining}`,
3572
+ fix: "Check the import paths in the mentioned files"
3573
+ };
3574
+ }
3575
+ } catch {
3576
+ }
3577
+ return {
3578
+ name: "Imports",
3579
+ status: "pass",
3580
+ message: "No broken imports found"
3581
+ };
3582
+ }
3583
+
3584
+ // src/doctor/index.ts
3585
+ var STATUS_ICONS = {
3586
+ pass: chalk13.green("\u2713"),
3587
+ warn: chalk13.yellow("\u26A0"),
3588
+ fail: chalk13.red("\u2717")
3589
+ };
3590
+ function formatResult(result) {
3591
+ const icon = STATUS_ICONS[result.status];
3592
+ const nameColor = result.status === "fail" ? chalk13.red : result.status === "warn" ? chalk13.yellow : chalk13.white;
3593
+ const name = nameColor(result.name.padEnd(18));
3594
+ const message = chalk13.gray(result.message);
3595
+ let output = `${icon} ${name} ${message}`;
3596
+ if (result.fix && result.status !== "pass") {
3597
+ output += `
3598
+ ${" ".repeat(22)}${chalk13.cyan("\u2192")} ${chalk13.cyan(result.fix)}`;
3599
+ }
3600
+ return output;
3601
+ }
3602
+ function showHeader() {
3603
+ console.log("");
3604
+ console.log(chalk13.cyan("\u{1FA7A} NextSpark Health Check"));
3605
+ console.log("");
3606
+ }
3607
+ function showSummary(results) {
3608
+ const passed = results.filter((r) => r.status === "pass").length;
3609
+ const warnings = results.filter((r) => r.status === "warn").length;
3610
+ const failed = results.filter((r) => r.status === "fail").length;
3611
+ console.log("");
3612
+ console.log(chalk13.gray("-".repeat(50)));
3613
+ const summary = [
3614
+ chalk13.green(`${passed} passed`),
3615
+ warnings > 0 ? chalk13.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
3616
+ failed > 0 ? chalk13.red(`${failed} failed`) : null
3617
+ ].filter(Boolean).join(", ");
3618
+ console.log(`Summary: ${summary}`);
3619
+ console.log("");
3620
+ if (failed > 0) {
3621
+ process.exitCode = 1;
3622
+ }
3623
+ }
3624
+ async function runHealthCheck() {
3625
+ const results = [];
3626
+ const checks = [
3627
+ { name: "Dependencies", fn: checkDependencies },
3628
+ { name: "Configuration", fn: checkConfigs },
3629
+ { name: "Database", fn: checkDatabase },
3630
+ { name: "Imports", fn: checkImports }
3631
+ ];
3632
+ for (const check of checks) {
3633
+ try {
3634
+ const result = await check.fn();
3635
+ results.push(result);
3636
+ } catch (error) {
3637
+ results.push({
3638
+ name: check.name,
3639
+ status: "fail",
3640
+ message: error instanceof Error ? error.message : "Unknown error occurred",
3641
+ fix: "Check the error message and try again"
3642
+ });
3643
+ }
3644
+ }
3645
+ return results;
3646
+ }
3647
+ async function runDoctorCommand() {
3648
+ showHeader();
3649
+ const results = await runHealthCheck();
3650
+ for (const result of results) {
3651
+ console.log(formatResult(result));
3652
+ }
3653
+ showSummary(results);
3654
+ }
3655
+ var isDirectExecution = process.argv[1]?.includes("doctor") || process.argv.includes("doctor");
3656
+ if (isDirectExecution && typeof __require !== "undefined") {
3657
+ runDoctorCommand().catch((error) => {
3658
+ console.error(chalk13.red("An unexpected error occurred:"), error.message);
3659
+ process.exit(1);
3660
+ });
3661
+ }
3662
+
3663
+ // src/commands/doctor.ts
3664
+ async function doctorCommand() {
3665
+ try {
3666
+ await runDoctorCommand();
3667
+ } catch (error) {
441
3668
  if (error instanceof Error) {
442
- console.error(chalk5.red(error.message));
3669
+ console.error(chalk14.red(`Error: ${error.message}`));
443
3670
  }
444
3671
  process.exit(1);
445
3672
  }
@@ -447,7 +3674,7 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000
447
3674
 
448
3675
  // src/cli.ts
449
3676
  var program = new Command();
450
- program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version("0.3.0");
3677
+ program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version("0.1.0-beta.4");
451
3678
  program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
452
3679
  program.command("build").description("Build for production").option("--no-registry", "Skip registry generation before build").action(buildCommand);
453
3680
  program.command("generate").description("Generate all registries").option("-w, --watch", "Watch for changes").action(generateCommand);
@@ -456,9 +3683,12 @@ registry.command("build").description("Build all registries").action(registryBui
456
3683
  registry.command("watch").description("Watch and rebuild registries on changes").action(registryWatchCommand);
457
3684
  program.command("registry:build").description("Build all registries (alias)").action(registryBuildCommand);
458
3685
  program.command("registry:watch").description("Watch and rebuild registries (alias)").action(registryWatchCommand);
459
- program.command("init").description("Initialize NextSpark in current project").option("-f, --force", "Overwrite existing configuration").action(initCommand);
3686
+ program.command("init").description("Initialize NextSpark project").option("-f, --force", "Overwrite existing configuration").option("--wizard", "Run full project wizard").option("--quick", "Quick wizard mode (essential steps only)").option("--expert", "Expert wizard mode (all options)").option("--preset <name>", "Use preset configuration (saas, blog, crm)").option("--theme <name>", "Pre-select theme (default, blog, crm, productivity, none)").option("--plugins <list>", "Pre-select plugins (comma-separated)").option("-y, --yes", "Skip confirmations").option("--registries-only", "Only create registries (no wizard)").option("--name <name>", "Project name (non-interactive mode)").option("--slug <slug>", "Project slug (non-interactive mode)").option("--description <desc>", "Project description (non-interactive mode)").action(initCommand);
3687
+ program.command("add:plugin <package>").description("Add a plugin to your project").option("-v, --version <version>", "Specific version to install").option("-f, --force", "Overwrite if already exists").option("--skip-postinstall", "Skip postinstall hooks").option("--no-deps", "Skip installing dependencies").option("--dry-run", "Show what would be done without making changes").action(addPluginCommand);
3688
+ program.command("add:theme <package>").description("Add a theme to your project").option("-v, --version <version>", "Specific version to install").option("-f, --force", "Overwrite if already exists").option("--skip-postinstall", "Skip postinstall hooks").option("--no-deps", "Skip installing dependencies").option("--dry-run", "Show what would be done without making changes").action(addThemeCommand);
3689
+ program.command("doctor").description("Run health check on NextSpark project").action(doctorCommand);
460
3690
  program.showHelpAfterError();
461
3691
  program.configureOutput({
462
- writeErr: (str) => process.stderr.write(chalk6.red(str))
3692
+ writeErr: (str) => process.stderr.write(chalk15.red(str))
463
3693
  });
464
3694
  program.parse();