@nextsparkjs/cli 0.1.0-beta.3 → 0.1.0-beta.5

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