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

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
@@ -7,11 +7,13 @@ import {
7
7
  installTheme,
8
8
  runPostinstall,
9
9
  validateTheme
10
- } from "./chunk-ALB2C27N.js";
10
+ } from "./chunk-DXL5CEZD.js";
11
11
 
12
12
  // src/cli.ts
13
+ import { config } from "dotenv";
13
14
  import { Command } from "commander";
14
- import chalk15 from "chalk";
15
+ import chalk20 from "chalk";
16
+ import { readFileSync as readFileSync11 } from "fs";
15
17
 
16
18
  // src/commands/dev.ts
17
19
  import { spawn } from "child_process";
@@ -20,7 +22,7 @@ import ora from "ora";
20
22
 
21
23
  // src/utils/paths.ts
22
24
  import { existsSync } from "fs";
23
- import { resolve, dirname } from "path";
25
+ import { resolve, join, dirname } from "path";
24
26
  import { fileURLToPath } from "url";
25
27
  var __filename = fileURLToPath(import.meta.url);
26
28
  var __dirname = dirname(__filename);
@@ -50,6 +52,16 @@ function isMonorepoMode() {
50
52
  const npmCorePath = resolve(cwd, "node_modules", "@nextsparkjs", "core");
51
53
  return !existsSync(npmCorePath);
52
54
  }
55
+ function getAIWorkflowDir() {
56
+ const cwd = process.cwd();
57
+ const nmPath = join(cwd, "node_modules", "@nextsparkjs", "ai-workflow");
58
+ if (existsSync(nmPath)) return nmPath;
59
+ const webNmPath = join(cwd, "web", "node_modules", "@nextsparkjs", "ai-workflow");
60
+ if (existsSync(webNmPath)) return webNmPath;
61
+ const monoPath = join(cwd, "packages", "ai-workflow");
62
+ if (existsSync(monoPath)) return monoPath;
63
+ return null;
64
+ }
53
65
 
54
66
  // src/commands/dev.ts
55
67
  async function devCommand(options) {
@@ -80,6 +92,7 @@ async function devCommand(options) {
80
92
  const devProcess = spawn("npx", ["next", "dev", "-p", options.port], {
81
93
  cwd: projectRoot,
82
94
  stdio: "inherit",
95
+ shell: true,
83
96
  env: {
84
97
  ...process.env,
85
98
  NEXTSPARK_CORE_DIR: coreDir
@@ -117,11 +130,11 @@ async function devCommand(options) {
117
130
  // src/commands/build.ts
118
131
  import { spawn as spawn2 } from "child_process";
119
132
  import { existsSync as existsSync2, readFileSync } from "fs";
120
- import { join } from "path";
133
+ import { join as join2 } from "path";
121
134
  import chalk2 from "chalk";
122
135
  import ora2 from "ora";
123
136
  function loadProjectEnv(projectRoot) {
124
- const envPath = join(projectRoot, ".env");
137
+ const envPath = join2(projectRoot, ".env");
125
138
  const envVars = {};
126
139
  if (existsSync2(envPath)) {
127
140
  const content = readFileSync(envPath, "utf-8");
@@ -179,6 +192,7 @@ async function buildCommand(options) {
179
192
  const buildProcess = spawn2("npx", ["next", "build"], {
180
193
  cwd: projectRoot,
181
194
  stdio: "inherit",
195
+ shell: true,
182
196
  env: {
183
197
  ...projectEnv,
184
198
  ...process.env,
@@ -213,11 +227,11 @@ Build failed with exit code ${code}`));
213
227
  // src/commands/generate.ts
214
228
  import { spawn as spawn3 } from "child_process";
215
229
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
216
- import { join as join2 } from "path";
230
+ import { join as join3 } from "path";
217
231
  import chalk3 from "chalk";
218
232
  import ora3 from "ora";
219
233
  function loadProjectEnv2(projectRoot) {
220
- const envPath = join2(projectRoot, ".env");
234
+ const envPath = join3(projectRoot, ".env");
221
235
  const envVars = {};
222
236
  if (existsSync3(envPath)) {
223
237
  const content = readFileSync2(envPath, "utf-8");
@@ -297,11 +311,11 @@ Registry generation failed with exit code ${code}`));
297
311
  // src/commands/registry.ts
298
312
  import { spawn as spawn4 } from "child_process";
299
313
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
300
- import { join as join3 } from "path";
314
+ import { join as join4 } from "path";
301
315
  import chalk4 from "chalk";
302
316
  import ora4 from "ora";
303
317
  function loadProjectEnv3(projectRoot) {
304
- const envPath = join3(projectRoot, ".env");
318
+ const envPath = join4(projectRoot, ".env");
305
319
  const envVars = {};
306
320
  if (existsSync4(envPath)) {
307
321
  const content = readFileSync3(envPath, "utf-8");
@@ -423,33 +437,44 @@ Watcher exited with code ${code}`));
423
437
 
424
438
  // src/commands/init.ts
425
439
  import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync7 } from "fs";
426
- import { join as join8 } from "path";
440
+ import { join as join9 } from "path";
427
441
  import chalk12 from "chalk";
428
442
  import ora8 from "ora";
429
443
 
430
444
  // src/wizard/index.ts
431
445
  import chalk11 from "chalk";
432
446
  import ora7 from "ora";
433
- import { confirm as confirm5 } from "@inquirer/prompts";
447
+ import { confirm as confirm5, select as select7 } from "@inquirer/prompts";
434
448
  import { execSync } from "child_process";
435
449
  import { existsSync as existsSync7, readdirSync } from "fs";
436
- import { join as join7 } from "path";
450
+ import { join as join8 } from "path";
437
451
 
438
452
  // src/wizard/banner.ts
439
453
  import chalk5 from "chalk";
440
454
  import { readFileSync as readFileSync4 } from "fs";
441
455
  import { fileURLToPath as fileURLToPath2 } from "url";
442
- import { dirname as dirname2, join as join4 } from "path";
456
+ import { dirname as dirname2, join as join5 } from "path";
443
457
  var __filename2 = fileURLToPath2(import.meta.url);
444
458
  var __dirname2 = dirname2(__filename2);
445
459
  function getCliVersion() {
446
- try {
447
- const packageJsonPath = join4(__dirname2, "../../package.json");
448
- const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
449
- return packageJson.version || "unknown";
450
- } catch {
451
- return "unknown";
460
+ const possiblePaths = [
461
+ join5(__dirname2, "../package.json"),
462
+ // from dist/
463
+ join5(__dirname2, "../../package.json"),
464
+ // from dist/wizard/ or src/wizard/
465
+ join5(__dirname2, "../../../package.json")
466
+ // fallback
467
+ ];
468
+ for (const packageJsonPath of possiblePaths) {
469
+ try {
470
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
471
+ if (packageJson.name === "@nextsparkjs/cli" && packageJson.version) {
472
+ return packageJson.version;
473
+ }
474
+ } catch {
475
+ }
452
476
  }
477
+ return "unknown";
453
478
  }
454
479
  var BANNER = `
455
480
  _ __ __ _____ __
@@ -473,6 +498,9 @@ function showSection(title, step, totalSteps) {
473
498
  console.log(chalk5.gray(" " + "-".repeat(40)));
474
499
  console.log("");
475
500
  }
501
+ function showSuccess(message) {
502
+ console.log(chalk5.green(` \u2713 ${message}`));
503
+ }
476
504
  function showWarning(message) {
477
505
  console.log(chalk5.yellow(` \u26A0 ${message}`));
478
506
  }
@@ -516,7 +544,7 @@ function validateSlug(slug) {
516
544
  return true;
517
545
  }
518
546
  async function promptProjectInfo() {
519
- showSection("Project Information", 1, 5);
547
+ showSection("Project Information", 2, 10);
520
548
  const projectName = await input({
521
549
  message: "What is your project name?",
522
550
  default: "My SaaS App",
@@ -539,8 +567,43 @@ async function promptProjectInfo() {
539
567
  };
540
568
  }
541
569
 
570
+ // src/wizard/prompts/project-type.ts
571
+ import { select } from "@inquirer/prompts";
572
+ var PROJECT_TYPE_OPTIONS = [
573
+ {
574
+ name: "Web only",
575
+ value: "web",
576
+ description: "Next.js web application with NextSpark. Standard flat project structure."
577
+ },
578
+ {
579
+ name: "Web + Mobile",
580
+ value: "web-mobile",
581
+ description: "Monorepo with Next.js web app and Expo mobile app sharing the same backend."
582
+ }
583
+ ];
584
+ async function promptProjectType() {
585
+ showSection("Project Type", 1, 10);
586
+ const projectType = await select({
587
+ message: "What type of project do you want to create?",
588
+ choices: PROJECT_TYPE_OPTIONS,
589
+ default: "web"
590
+ });
591
+ const selectedOption = PROJECT_TYPE_OPTIONS.find((o) => o.value === projectType);
592
+ if (selectedOption) {
593
+ showInfo(selectedOption.description);
594
+ }
595
+ if (projectType === "web-mobile") {
596
+ console.log("");
597
+ showInfo("Your project will be a pnpm monorepo with web/ and mobile/ directories.");
598
+ showInfo("The mobile app will use Expo and share the same backend API.");
599
+ }
600
+ return {
601
+ projectType
602
+ };
603
+ }
604
+
542
605
  // src/wizard/prompts/team-config.ts
543
- import { select, checkbox } from "@inquirer/prompts";
606
+ import { select as select2, checkbox } from "@inquirer/prompts";
544
607
 
545
608
  // src/wizard/types.ts
546
609
  var AVAILABLE_LOCALES = {
@@ -588,8 +651,8 @@ var ROLE_OPTIONS = [
588
651
  { name: "Viewer (Read-only access)", value: "viewer", checked: true }
589
652
  ];
590
653
  async function promptTeamConfig() {
591
- showSection("Team Configuration", 2, 5);
592
- const teamMode = await select({
654
+ showSection("Team Configuration", 3, 10);
655
+ const teamMode = await select2({
593
656
  message: "What team mode do you need?",
594
657
  choices: TEAM_MODE_OPTIONS,
595
658
  default: "multi-tenant"
@@ -620,7 +683,7 @@ async function promptTeamConfig() {
620
683
  }
621
684
 
622
685
  // src/wizard/prompts/i18n-config.ts
623
- import { select as select2, checkbox as checkbox2 } from "@inquirer/prompts";
686
+ import { select as select3, checkbox as checkbox2 } from "@inquirer/prompts";
624
687
  var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
625
688
  name: `${name} (${value})`,
626
689
  value,
@@ -628,7 +691,7 @@ var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
628
691
  // English selected by default
629
692
  }));
630
693
  async function promptI18nConfig() {
631
- showSection("Internationalization", 3, 5);
694
+ showSection("Internationalization", 4, 10);
632
695
  const supportedLocales = await checkbox2({
633
696
  message: "Which languages do you want to support?",
634
697
  choices: LOCALE_OPTIONS,
@@ -644,7 +707,7 @@ async function promptI18nConfig() {
644
707
  showInfo(`Default language set to ${AVAILABLE_LOCALES[defaultLocale]}`);
645
708
  } else {
646
709
  console.log("");
647
- defaultLocale = await select2({
710
+ defaultLocale = await select3({
648
711
  message: "Which should be the default language?",
649
712
  choices: supportedLocales.map((locale) => ({
650
713
  name: `${AVAILABLE_LOCALES[locale]} (${locale})`,
@@ -660,7 +723,7 @@ async function promptI18nConfig() {
660
723
  }
661
724
 
662
725
  // src/wizard/prompts/billing-config.ts
663
- import { select as select3 } from "@inquirer/prompts";
726
+ import { select as select4 } from "@inquirer/prompts";
664
727
  var BILLING_MODEL_OPTIONS = [
665
728
  {
666
729
  name: "Free (No payments)",
@@ -679,8 +742,8 @@ var BILLING_MODEL_OPTIONS = [
679
742
  }
680
743
  ];
681
744
  async function promptBillingConfig() {
682
- showSection("Billing Configuration", 4, 5);
683
- const billingModel = await select3({
745
+ showSection("Billing Configuration", 5, 10);
746
+ const billingModel = await select4({
684
747
  message: "What billing model do you want to use?",
685
748
  choices: BILLING_MODEL_OPTIONS,
686
749
  default: "freemium"
@@ -692,7 +755,7 @@ async function promptBillingConfig() {
692
755
  let currency = "usd";
693
756
  if (billingModel !== "free") {
694
757
  console.log("");
695
- currency = await select3({
758
+ currency = await select4({
696
759
  message: "What currency will you use?",
697
760
  choices: CURRENCIES.map((c) => ({
698
761
  name: c.label,
@@ -744,7 +807,7 @@ var FEATURE_OPTIONS = [
744
807
  }
745
808
  ];
746
809
  async function promptFeaturesConfig() {
747
- showSection("Features", 5, 5);
810
+ showSection("Features", 6, 10);
748
811
  showInfo("Select the features you want to include in your project.");
749
812
  showInfo("You can add or remove features later by editing your config files.");
750
813
  console.log("");
@@ -781,7 +844,7 @@ var CONTENT_FEATURE_OPTIONS = [
781
844
  }
782
845
  ];
783
846
  async function promptContentFeaturesConfig(mode = "interactive", totalSteps = 9) {
784
- showSection("Content Features", 6, totalSteps);
847
+ showSection("Content Features", 7, totalSteps);
785
848
  showInfo("Enable optional content features for your project.");
786
849
  showInfo("These features add entities and blocks for building pages and blog posts.");
787
850
  console.log("");
@@ -801,7 +864,24 @@ async function promptContentFeaturesConfig(mode = "interactive", totalSteps = 9)
801
864
  }
802
865
 
803
866
  // src/wizard/prompts/auth-config.ts
804
- import { checkbox as checkbox5 } from "@inquirer/prompts";
867
+ import { checkbox as checkbox5, input as input2, select as select5 } from "@inquirer/prompts";
868
+ var REGISTRATION_MODE_OPTIONS = [
869
+ {
870
+ name: "Open (anyone can register)",
871
+ value: "open",
872
+ description: "Email+password and Google OAuth signup available to everyone"
873
+ },
874
+ {
875
+ name: "Domain-Restricted (Google OAuth only for specific domains)",
876
+ value: "domain-restricted",
877
+ description: "Only Google OAuth for allowed email domains (e.g., @yourcompany.com)"
878
+ },
879
+ {
880
+ name: "Invitation-Only (registration via invite link)",
881
+ value: "invitation-only",
882
+ description: "Users can only register when invited by an existing user"
883
+ }
884
+ ];
805
885
  var AUTH_METHOD_OPTIONS = [
806
886
  {
807
887
  name: "Email & Password",
@@ -826,19 +906,50 @@ var SECURITY_OPTIONS = [
826
906
  ];
827
907
  function getDefaultAuthConfig() {
828
908
  return {
909
+ registrationMode: "open",
829
910
  emailPassword: true,
830
911
  googleOAuth: false,
831
912
  emailVerification: true
832
913
  };
833
914
  }
834
915
  async function promptAuthConfig(mode = "interactive", totalSteps = 8) {
835
- showSection("Authentication", 6, totalSteps);
916
+ showSection("Authentication", 8, totalSteps);
836
917
  showInfo("Configure how users will authenticate to your application.");
837
918
  console.log("");
838
- const selectedMethods = await checkbox5({
839
- message: "Which authentication methods do you want to enable?",
840
- choices: AUTH_METHOD_OPTIONS
919
+ const registrationMode = await select5({
920
+ message: "How should new users register?",
921
+ choices: REGISTRATION_MODE_OPTIONS,
922
+ default: "open"
841
923
  });
924
+ console.log("");
925
+ let emailPassword = true;
926
+ let googleOAuth = false;
927
+ let allowedDomains;
928
+ if (registrationMode === "domain-restricted") {
929
+ googleOAuth = true;
930
+ emailPassword = false;
931
+ showInfo("Domain-restricted mode: Google OAuth enabled, email login hidden on login page.");
932
+ console.log("");
933
+ const domainsInput = await input2({
934
+ message: "Allowed email domains (comma-separated, e.g. yourcompany.com, partner.org):",
935
+ validate: (value) => {
936
+ if (!value.trim()) return "At least one domain is required for domain-restricted mode";
937
+ const domains = value.split(",").map((d) => d.trim()).filter(Boolean);
938
+ const invalid = domains.find((d) => !d.includes("."));
939
+ if (invalid) return `Invalid domain: "${invalid}" (must contain a dot)`;
940
+ return true;
941
+ }
942
+ });
943
+ allowedDomains = domainsInput.split(",").map((d) => d.trim().toLowerCase()).filter(Boolean);
944
+ console.log("");
945
+ } else {
946
+ const selectedMethods = await checkbox5({
947
+ message: "Which authentication methods do you want to enable?",
948
+ choices: AUTH_METHOD_OPTIONS
949
+ });
950
+ emailPassword = selectedMethods.includes("emailPassword");
951
+ googleOAuth = selectedMethods.includes("googleOAuth");
952
+ }
842
953
  let selectedSecurity = ["emailVerification"];
843
954
  if (mode === "expert") {
844
955
  console.log("");
@@ -850,9 +961,11 @@ async function promptAuthConfig(mode = "interactive", totalSteps = 8) {
850
961
  });
851
962
  }
852
963
  const auth = {
853
- emailPassword: selectedMethods.includes("emailPassword"),
854
- googleOAuth: selectedMethods.includes("googleOAuth"),
855
- emailVerification: selectedSecurity.includes("emailVerification")
964
+ registrationMode,
965
+ emailPassword,
966
+ googleOAuth,
967
+ emailVerification: selectedSecurity.includes("emailVerification"),
968
+ allowedDomains
856
969
  };
857
970
  return { auth };
858
971
  }
@@ -916,7 +1029,7 @@ function getDefaultDashboardConfig() {
916
1029
  };
917
1030
  }
918
1031
  async function promptDashboardConfig(mode = "interactive", totalSteps = 8) {
919
- showSection("Dashboard", 7, totalSteps);
1032
+ showSection("Dashboard", 9, totalSteps);
920
1033
  showInfo("Configure your dashboard user interface.");
921
1034
  showInfo("These settings can be changed later in your theme config.");
922
1035
  console.log("");
@@ -968,7 +1081,7 @@ function getDefaultDevConfig() {
968
1081
  };
969
1082
  }
970
1083
  async function promptDevConfig(mode = "interactive", totalSteps = 8) {
971
- showSection("Development Tools", 8, totalSteps);
1084
+ showSection("Development Tools", 10, totalSteps);
972
1085
  showInfo("Configure development and debugging tools.");
973
1086
  showWarning("These tools are disabled in production builds.");
974
1087
  console.log("");
@@ -995,7 +1108,7 @@ async function promptDevConfig(mode = "interactive", totalSteps = 8) {
995
1108
  }
996
1109
 
997
1110
  // src/wizard/prompts/theme-selection.ts
998
- import { select as select4 } from "@inquirer/prompts";
1111
+ import { select as select6 } from "@inquirer/prompts";
999
1112
  import chalk6 from "chalk";
1000
1113
  async function promptThemeSelection() {
1001
1114
  console.log("");
@@ -1005,7 +1118,7 @@ async function promptThemeSelection() {
1005
1118
  console.log(chalk6.gray(" A reference theme provides a complete example to learn from."));
1006
1119
  console.log(chalk6.gray(" Your custom theme (based on starter) will be your active theme."));
1007
1120
  console.log("");
1008
- const theme = await select4({
1121
+ const theme = await select6({
1009
1122
  message: "Which reference theme would you like to install?",
1010
1123
  choices: [
1011
1124
  {
@@ -1091,24 +1204,26 @@ function getRequiredPlugins(theme) {
1091
1204
  }
1092
1205
 
1093
1206
  // src/wizard/prompts/env-config.ts
1094
- import { confirm as confirm3, input as input2 } from "@inquirer/prompts";
1207
+ import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
1095
1208
 
1096
1209
  // src/wizard/prompts/git-config.ts
1097
- import { confirm as confirm4, input as input3 } from "@inquirer/prompts";
1210
+ import { confirm as confirm4, input as input4 } from "@inquirer/prompts";
1098
1211
 
1099
1212
  // src/wizard/prompts/index.ts
1100
1213
  async function runAllPrompts() {
1214
+ const projectTypeConfig = await promptProjectType();
1101
1215
  const projectInfo = await promptProjectInfo();
1102
1216
  const teamConfig = await promptTeamConfig();
1103
1217
  const i18nConfig = await promptI18nConfig();
1104
1218
  const billingConfig = await promptBillingConfig();
1105
1219
  const featuresConfig = await promptFeaturesConfig();
1106
- const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 9);
1107
- const authConfig = await promptAuthConfig("interactive", 9);
1108
- const dashboardConfig = await promptDashboardConfig("interactive", 9);
1109
- const devConfig = await promptDevConfig("interactive", 9);
1220
+ const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 10);
1221
+ const authConfig = await promptAuthConfig("interactive", 10);
1222
+ const dashboardConfig = await promptDashboardConfig("interactive", 10);
1223
+ const devConfig = await promptDevConfig("interactive", 10);
1110
1224
  return {
1111
1225
  ...projectInfo,
1226
+ ...projectTypeConfig,
1112
1227
  ...teamConfig,
1113
1228
  ...i18nConfig,
1114
1229
  ...billingConfig,
@@ -1120,17 +1235,19 @@ async function runAllPrompts() {
1120
1235
  };
1121
1236
  }
1122
1237
  async function runQuickPrompts() {
1238
+ const projectTypeConfig = await promptProjectType();
1123
1239
  const projectInfo = await promptProjectInfo();
1124
1240
  const teamConfig = await promptTeamConfig();
1125
1241
  const i18nConfig = await promptI18nConfig();
1126
1242
  const billingConfig = await promptBillingConfig();
1127
1243
  const featuresConfig = await promptFeaturesConfig();
1128
- const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 9);
1244
+ const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 10);
1129
1245
  const authConfig = { auth: getDefaultAuthConfig() };
1130
1246
  const dashboardConfig = { dashboard: getDefaultDashboardConfig() };
1131
1247
  const devConfig = { dev: getDefaultDevConfig() };
1132
1248
  return {
1133
1249
  ...projectInfo,
1250
+ ...projectTypeConfig,
1134
1251
  ...teamConfig,
1135
1252
  ...i18nConfig,
1136
1253
  ...billingConfig,
@@ -1142,17 +1259,19 @@ async function runQuickPrompts() {
1142
1259
  };
1143
1260
  }
1144
1261
  async function runExpertPrompts() {
1262
+ const projectTypeConfig = await promptProjectType();
1145
1263
  const projectInfo = await promptProjectInfo();
1146
1264
  const teamConfig = await promptTeamConfig();
1147
1265
  const i18nConfig = await promptI18nConfig();
1148
1266
  const billingConfig = await promptBillingConfig();
1149
1267
  const featuresConfig = await promptFeaturesConfig();
1150
- const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 9);
1151
- const authConfig = await promptAuthConfig("expert", 9);
1152
- const dashboardConfig = await promptDashboardConfig("expert", 9);
1153
- const devConfig = await promptDevConfig("expert", 9);
1268
+ const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 10);
1269
+ const authConfig = await promptAuthConfig("expert", 10);
1270
+ const dashboardConfig = await promptDashboardConfig("expert", 10);
1271
+ const devConfig = await promptDevConfig("expert", 10);
1154
1272
  return {
1155
1273
  ...projectInfo,
1274
+ ...projectTypeConfig,
1156
1275
  ...teamConfig,
1157
1276
  ...i18nConfig,
1158
1277
  ...billingConfig,
@@ -1165,9 +1284,9 @@ async function runExpertPrompts() {
1165
1284
  }
1166
1285
 
1167
1286
  // src/wizard/generators/index.ts
1168
- import fs7 from "fs-extra";
1169
- import path5 from "path";
1170
- import { fileURLToPath as fileURLToPath5 } from "url";
1287
+ import fs8 from "fs-extra";
1288
+ import path7 from "path";
1289
+ import { fileURLToPath as fileURLToPath7 } from "url";
1171
1290
 
1172
1291
  // src/wizard/generators/theme-renamer.ts
1173
1292
  import fs from "fs-extra";
@@ -1176,31 +1295,31 @@ import { fileURLToPath as fileURLToPath3 } from "url";
1176
1295
  var __filename3 = fileURLToPath3(import.meta.url);
1177
1296
  var __dirname3 = path.dirname(__filename3);
1178
1297
  function getTemplatesDir() {
1179
- try {
1180
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1181
- return path.join(path.dirname(corePkgPath), "templates");
1182
- } catch {
1183
- const possiblePaths = [
1184
- path.resolve(__dirname3, "../../../../../core/templates"),
1185
- path.resolve(__dirname3, "../../../../core/templates"),
1186
- path.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1187
- ];
1188
- for (const p of possiblePaths) {
1189
- if (fs.existsSync(p)) {
1190
- return p;
1191
- }
1298
+ const rootDir = process.cwd();
1299
+ const possiblePaths = [
1300
+ // From project root node_modules (most common for installed packages)
1301
+ path.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
1302
+ // From CLI dist folder for development
1303
+ path.resolve(__dirname3, "../../core/templates"),
1304
+ // Legacy paths for different build structures
1305
+ path.resolve(__dirname3, "../../../../../core/templates"),
1306
+ path.resolve(__dirname3, "../../../../core/templates")
1307
+ ];
1308
+ for (const p of possiblePaths) {
1309
+ if (fs.existsSync(p)) {
1310
+ return p;
1192
1311
  }
1193
- throw new Error("Could not find @nextsparkjs/core templates directory");
1194
1312
  }
1313
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
1195
1314
  }
1196
1315
  function getTargetThemesDir() {
1197
1316
  return path.resolve(process.cwd(), "contents", "themes");
1198
1317
  }
1199
- async function copyStarterTheme(config) {
1318
+ async function copyStarterTheme(config2) {
1200
1319
  const templatesDir = getTemplatesDir();
1201
1320
  const starterThemePath = path.join(templatesDir, "contents", "themes", "starter");
1202
1321
  const targetThemesDir = getTargetThemesDir();
1203
- const newThemePath = path.join(targetThemesDir, config.projectSlug);
1322
+ const newThemePath = path.join(targetThemesDir, config2.projectSlug);
1204
1323
  if (!await fs.pathExists(starterThemePath)) {
1205
1324
  throw new Error(`Starter theme not found at: ${starterThemePath}`);
1206
1325
  }
@@ -1210,97 +1329,97 @@ async function copyStarterTheme(config) {
1210
1329
  await fs.ensureDir(targetThemesDir);
1211
1330
  await fs.copy(starterThemePath, newThemePath);
1212
1331
  }
1213
- async function updateThemeConfig(config) {
1214
- const themeConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "theme.config.ts");
1332
+ async function updateThemeConfig(config2) {
1333
+ const themeConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "theme.config.ts");
1215
1334
  if (!await fs.pathExists(themeConfigPath)) {
1216
1335
  throw new Error(`theme.config.ts not found at: ${themeConfigPath}`);
1217
1336
  }
1218
1337
  let content = await fs.readFile(themeConfigPath, "utf-8");
1219
1338
  content = content.replace(
1220
1339
  /name:\s*['"]starter['"]/g,
1221
- `name: '${config.projectSlug}'`
1340
+ `name: '${config2.projectSlug}'`
1222
1341
  );
1223
1342
  content = content.replace(
1224
1343
  /displayName:\s*['"]Starter['"]/g,
1225
- `displayName: '${config.projectName}'`
1344
+ `displayName: '${config2.projectName}'`
1226
1345
  );
1227
1346
  content = content.replace(
1228
1347
  /description:\s*['"]Minimal starter theme for NextSpark['"]/g,
1229
- `description: '${config.projectDescription}'`
1348
+ `description: '${config2.projectDescription}'`
1230
1349
  );
1231
1350
  content = content.replace(
1232
1351
  /export const starterThemeConfig/g,
1233
- `export const ${toCamelCase(config.projectSlug)}ThemeConfig`
1352
+ `export const ${toCamelCase(config2.projectSlug)}ThemeConfig`
1234
1353
  );
1235
1354
  content = content.replace(
1236
1355
  /export default starterThemeConfig/g,
1237
- `export default ${toCamelCase(config.projectSlug)}ThemeConfig`
1356
+ `export default ${toCamelCase(config2.projectSlug)}ThemeConfig`
1238
1357
  );
1239
1358
  await fs.writeFile(themeConfigPath, content, "utf-8");
1240
1359
  }
1241
- async function updateDevConfig(config) {
1242
- const devConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "dev.config.ts");
1360
+ async function updateDevConfig(config2) {
1361
+ const devConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "dev.config.ts");
1243
1362
  if (!await fs.pathExists(devConfigPath)) {
1244
1363
  return;
1245
1364
  }
1246
1365
  let content = await fs.readFile(devConfigPath, "utf-8");
1247
- content = content.replace(/STARTER THEME/g, `${config.projectName.toUpperCase()}`);
1248
- content = content.replace(/Starter Theme/g, config.projectName);
1366
+ content = content.replace(/STARTER THEME/g, `${config2.projectName.toUpperCase()}`);
1367
+ content = content.replace(/Starter Theme/g, config2.projectName);
1249
1368
  await fs.writeFile(devConfigPath, content, "utf-8");
1250
1369
  }
1251
- async function updateAppConfig(config) {
1252
- const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1370
+ async function updateAppConfig(config2) {
1371
+ const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
1253
1372
  if (!await fs.pathExists(appConfigPath)) {
1254
1373
  throw new Error(`app.config.ts not found at: ${appConfigPath}`);
1255
1374
  }
1256
1375
  let content = await fs.readFile(appConfigPath, "utf-8");
1257
1376
  content = content.replace(
1258
1377
  /name:\s*['"]Starter['"]/g,
1259
- `name: '${config.projectName}'`
1378
+ `name: '${config2.projectName}'`
1260
1379
  );
1261
1380
  content = content.replace(
1262
1381
  /mode:\s*['"]multi-tenant['"]\s*as\s*const/g,
1263
- `mode: '${config.teamMode}' as const`
1382
+ `mode: '${config2.teamMode}' as const`
1264
1383
  );
1265
- const localesArray = config.supportedLocales.map((l) => `'${l}'`).join(", ");
1384
+ const localesArray = config2.supportedLocales.map((l) => `'${l}'`).join(", ");
1266
1385
  content = content.replace(
1267
1386
  /supportedLocales:\s*\[.*?\]/g,
1268
1387
  `supportedLocales: [${localesArray}]`
1269
1388
  );
1270
1389
  content = content.replace(
1271
1390
  /defaultLocale:\s*['"]en['"]\s*as\s*const/g,
1272
- `defaultLocale: '${config.defaultLocale}' as const`
1391
+ `defaultLocale: '${config2.defaultLocale}' as const`
1273
1392
  );
1274
1393
  content = content.replace(
1275
1394
  /label:\s*['"]Starter['"]/g,
1276
- `label: '${config.projectName}'`
1395
+ `label: '${config2.projectName}'`
1277
1396
  );
1278
1397
  await fs.writeFile(appConfigPath, content, "utf-8");
1279
1398
  }
1280
- async function updateRolesConfig(config) {
1281
- const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1399
+ async function updateRolesConfig(config2) {
1400
+ const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
1282
1401
  if (!await fs.pathExists(appConfigPath)) {
1283
1402
  return;
1284
1403
  }
1285
1404
  let content = await fs.readFile(appConfigPath, "utf-8");
1286
- const rolesArray = config.teamRoles.map((r) => `'${r}'`).join(", ");
1405
+ const rolesArray = config2.teamRoles.map((r) => `'${r}'`).join(", ");
1287
1406
  content = content.replace(
1288
1407
  /availableTeamRoles:\s*\[.*?\]/g,
1289
1408
  `availableTeamRoles: [${rolesArray}]`
1290
1409
  );
1291
1410
  await fs.writeFile(appConfigPath, content, "utf-8");
1292
1411
  }
1293
- async function updateBillingConfig(config) {
1294
- const billingConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "billing.config.ts");
1412
+ async function updateBillingConfig(config2) {
1413
+ const billingConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "billing.config.ts");
1295
1414
  if (!await fs.pathExists(billingConfigPath)) {
1296
1415
  return;
1297
1416
  }
1298
1417
  let content = await fs.readFile(billingConfigPath, "utf-8");
1299
1418
  content = content.replace(
1300
1419
  /currency:\s*['"]usd['"]/g,
1301
- `currency: '${config.currency}'`
1420
+ `currency: '${config2.currency}'`
1302
1421
  );
1303
- const plansContent = generateBillingPlans(config.billingModel, config.currency);
1422
+ const plansContent = generateBillingPlans(config2.billingModel, config2.currency);
1304
1423
  content = content.replace(
1305
1424
  /plans:\s*\[[\s\S]*?\],\s*\n\s*\/\/ ===+\s*\n\s*\/\/ ACTION MAPPINGS/,
1306
1425
  `plans: ${plansContent},
@@ -1450,8 +1569,8 @@ function generateBillingPlans(billingModel, currency) {
1450
1569
  },
1451
1570
  ]`;
1452
1571
  }
1453
- async function updateMigrations(config) {
1454
- const migrationsDir = path.join(getTargetThemesDir(), config.projectSlug, "migrations");
1572
+ async function updateMigrations(config2) {
1573
+ const migrationsDir = path.join(getTargetThemesDir(), config2.projectSlug, "migrations");
1455
1574
  if (!await fs.pathExists(migrationsDir)) {
1456
1575
  return;
1457
1576
  }
@@ -1460,12 +1579,39 @@ async function updateMigrations(config) {
1460
1579
  for (const file of sqlFiles) {
1461
1580
  const filePath = path.join(migrationsDir, file);
1462
1581
  let content = await fs.readFile(filePath, "utf-8");
1463
- content = content.replace(/@starter\.dev/g, `@${config.projectSlug}.dev`);
1464
- content = content.replace(/Starter Theme/g, config.projectName);
1465
- content = content.replace(/starter theme/g, config.projectSlug);
1582
+ content = content.replace(/@starter\.dev/g, `@${config2.projectSlug}.dev`);
1583
+ content = content.replace(/Starter Theme/g, config2.projectName);
1584
+ content = content.replace(/starter theme/g, config2.projectSlug);
1466
1585
  await fs.writeFile(filePath, content, "utf-8");
1467
1586
  }
1468
1587
  }
1588
+ async function updateTestFiles(config2) {
1589
+ const testsDir = path.join(getTargetThemesDir(), config2.projectSlug, "tests");
1590
+ if (!await fs.pathExists(testsDir)) {
1591
+ return;
1592
+ }
1593
+ const processDir = async (dir) => {
1594
+ const items = await fs.readdir(dir);
1595
+ for (const item of items) {
1596
+ const itemPath = path.join(dir, item);
1597
+ const stat = await fs.stat(itemPath);
1598
+ if (stat.isDirectory()) {
1599
+ await processDir(itemPath);
1600
+ } else if (item.endsWith(".ts") || item.endsWith(".tsx")) {
1601
+ let content = await fs.readFile(itemPath, "utf-8");
1602
+ const hasChanges = content.includes("@/contents/themes/starter/");
1603
+ if (hasChanges) {
1604
+ content = content.replace(
1605
+ /@\/contents\/themes\/starter\//g,
1606
+ `@/contents/themes/${config2.projectSlug}/`
1607
+ );
1608
+ await fs.writeFile(itemPath, content, "utf-8");
1609
+ }
1610
+ }
1611
+ }
1612
+ };
1613
+ await processDir(testsDir);
1614
+ }
1469
1615
  function toCamelCase(str) {
1470
1616
  return str.split("-").map((word, index) => {
1471
1617
  if (index === 0) {
@@ -1481,35 +1627,38 @@ import path2 from "path";
1481
1627
  function getTargetThemesDir2() {
1482
1628
  return path2.resolve(process.cwd(), "contents", "themes");
1483
1629
  }
1484
- async function updateAuthConfig(config) {
1485
- const authConfigPath = path2.join(
1630
+ async function updateAuthConfig(config2) {
1631
+ const appConfigPath = path2.join(
1486
1632
  getTargetThemesDir2(),
1487
- config.projectSlug,
1633
+ config2.projectSlug,
1488
1634
  "config",
1489
- "auth.config.ts"
1635
+ "app.config.ts"
1490
1636
  );
1491
- if (!await fs2.pathExists(authConfigPath)) {
1637
+ if (!await fs2.pathExists(appConfigPath)) {
1492
1638
  return;
1493
1639
  }
1494
- let content = await fs2.readFile(authConfigPath, "utf-8");
1495
- content = content.replace(
1496
- /(emailPassword:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1497
- `$1${config.auth.emailPassword}`
1498
- );
1640
+ let content = await fs2.readFile(appConfigPath, "utf-8");
1499
1641
  content = content.replace(
1500
- /(google:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1501
- `$1${config.auth.googleOAuth}`
1642
+ /(mode:\s*)'open'(\s*as\s*const)/,
1643
+ `$1'${config2.auth.registrationMode}'$2`
1502
1644
  );
1503
1645
  content = content.replace(
1504
- /(emailVerification:\s*)(?:true|false)/g,
1505
- `$1${config.auth.emailVerification}`
1646
+ /(google:\s*{\s*enabled:\s*)(?:true|false)/s,
1647
+ `$1${config2.auth.googleOAuth}`
1506
1648
  );
1507
- await fs2.writeFile(authConfigPath, content, "utf-8");
1649
+ if (config2.auth.registrationMode === "domain-restricted") {
1650
+ const domains = config2.auth.allowedDomains?.length ? config2.auth.allowedDomains.map((d) => `'${d}'`).join(", ") : "'yourcompany.com'";
1651
+ content = content.replace(
1652
+ /\/\/\s*allowedDomains:\s*\['yourcompany\.com'\],\s*\/\/\s*Only for 'domain-restricted' mode/,
1653
+ `allowedDomains: [${domains}],`
1654
+ );
1655
+ }
1656
+ await fs2.writeFile(appConfigPath, content, "utf-8");
1508
1657
  }
1509
- async function updateDashboardUIConfig(config) {
1658
+ async function updateDashboardUIConfig(config2) {
1510
1659
  const dashboardConfigPath = path2.join(
1511
1660
  getTargetThemesDir2(),
1512
- config.projectSlug,
1661
+ config2.projectSlug,
1513
1662
  "config",
1514
1663
  "dashboard.config.ts"
1515
1664
  );
@@ -1519,42 +1668,42 @@ async function updateDashboardUIConfig(config) {
1519
1668
  let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1520
1669
  content = content.replace(
1521
1670
  /(search:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1522
- `$1${config.dashboard.search}`
1671
+ `$1${config2.dashboard.search}`
1523
1672
  );
1524
1673
  content = content.replace(
1525
1674
  /(notifications:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1526
- `$1${config.dashboard.notifications}`
1675
+ `$1${config2.dashboard.notifications}`
1527
1676
  );
1528
1677
  content = content.replace(
1529
1678
  /(themeToggle:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1530
- `$1${config.dashboard.themeToggle}`
1679
+ `$1${config2.dashboard.themeToggle}`
1531
1680
  );
1532
1681
  content = content.replace(
1533
1682
  /(support:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1534
- `$1${config.dashboard.support}`
1683
+ `$1${config2.dashboard.support}`
1535
1684
  );
1536
1685
  content = content.replace(
1537
1686
  /(quickCreate:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1538
- `$1${config.dashboard.quickCreate}`
1687
+ `$1${config2.dashboard.quickCreate}`
1539
1688
  );
1540
1689
  content = content.replace(
1541
1690
  /(adminAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1542
- `$1${config.dashboard.superadminAccess}`
1691
+ `$1${config2.dashboard.superadminAccess}`
1543
1692
  );
1544
1693
  content = content.replace(
1545
1694
  /(devtoolsAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1546
- `$1${config.dashboard.devtoolsAccess}`
1695
+ `$1${config2.dashboard.devtoolsAccess}`
1547
1696
  );
1548
1697
  content = content.replace(
1549
1698
  /(defaultCollapsed:\s*)(?:true|false)/g,
1550
- `$1${config.dashboard.sidebarCollapsed}`
1699
+ `$1${config2.dashboard.sidebarCollapsed}`
1551
1700
  );
1552
1701
  await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1553
1702
  }
1554
- async function updateDevToolsConfig(config) {
1703
+ async function updateDevToolsConfig(config2) {
1555
1704
  const devConfigPath = path2.join(
1556
1705
  getTargetThemesDir2(),
1557
- config.projectSlug,
1706
+ config2.projectSlug,
1558
1707
  "config",
1559
1708
  "dev.config.ts"
1560
1709
  );
@@ -1564,18 +1713,18 @@ async function updateDevToolsConfig(config) {
1564
1713
  let content = await fs2.readFile(devConfigPath, "utf-8");
1565
1714
  content = content.replace(
1566
1715
  /(devKeyring:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1567
- `$1${config.dev.devKeyring}`
1716
+ `$1${config2.dev.devKeyring}`
1568
1717
  );
1569
1718
  content = content.replace(
1570
1719
  /(debugMode:\s*)(?:true|false)/g,
1571
- `$1${config.dev.debugMode}`
1720
+ `$1${config2.dev.debugMode}`
1572
1721
  );
1573
1722
  await fs2.writeFile(devConfigPath, content, "utf-8");
1574
1723
  }
1575
- async function updatePermissionsConfig(config) {
1724
+ async function updatePermissionsConfig(config2) {
1576
1725
  const permissionsConfigPath = path2.join(
1577
1726
  getTargetThemesDir2(),
1578
- config.projectSlug,
1727
+ config2.projectSlug,
1579
1728
  "config",
1580
1729
  "permissions.config.ts"
1581
1730
  );
@@ -1583,7 +1732,7 @@ async function updatePermissionsConfig(config) {
1583
1732
  return;
1584
1733
  }
1585
1734
  let content = await fs2.readFile(permissionsConfigPath, "utf-8");
1586
- const availableRoles = config.teamRoles;
1735
+ const availableRoles = config2.teamRoles;
1587
1736
  const roleArrayPattern = /roles:\s*\[(.*?)\]/g;
1588
1737
  content = content.replace(roleArrayPattern, (match, rolesStr) => {
1589
1738
  const currentRoles = rolesStr.split(",").map((r) => r.trim().replace(/['"]/g, "")).filter((r) => r.length > 0);
@@ -1595,10 +1744,48 @@ async function updatePermissionsConfig(config) {
1595
1744
  });
1596
1745
  await fs2.writeFile(permissionsConfigPath, content, "utf-8");
1597
1746
  }
1598
- async function updateDashboardConfig(config) {
1747
+ async function updateEntityPermissions(config2) {
1748
+ const permissionsConfigPath = path2.join(
1749
+ getTargetThemesDir2(),
1750
+ config2.projectSlug,
1751
+ "config",
1752
+ "permissions.config.ts"
1753
+ );
1754
+ if (!await fs2.pathExists(permissionsConfigPath)) {
1755
+ return;
1756
+ }
1757
+ let content = await fs2.readFile(permissionsConfigPath, "utf-8");
1758
+ if (config2.contentFeatures.pages) {
1759
+ content = uncommentPermissionBlock(content, "PAGES");
1760
+ }
1761
+ if (config2.contentFeatures.blog) {
1762
+ content = uncommentPermissionBlock(content, "POSTS");
1763
+ }
1764
+ await fs2.writeFile(permissionsConfigPath, content, "utf-8");
1765
+ }
1766
+ function uncommentPermissionBlock(content, markerName) {
1767
+ const startMarker = `// __${markerName}_PERMISSIONS_START__`;
1768
+ const endMarker = `// __${markerName}_PERMISSIONS_END__`;
1769
+ const startIndex = content.indexOf(startMarker);
1770
+ const endIndex = content.indexOf(endMarker);
1771
+ if (startIndex === -1 || endIndex === -1) {
1772
+ return content;
1773
+ }
1774
+ const beforeBlock = content.slice(0, startIndex);
1775
+ const block = content.slice(startIndex + startMarker.length, endIndex);
1776
+ const afterBlock = content.slice(endIndex + endMarker.length);
1777
+ const uncommentedBlock = block.split("\n").map((line) => {
1778
+ if (line.match(/^\s*\/\/\s+/)) {
1779
+ return line.replace(/^(\s*)\/\/\s*/, "$1");
1780
+ }
1781
+ return line;
1782
+ }).join("\n");
1783
+ return beforeBlock + uncommentedBlock + afterBlock;
1784
+ }
1785
+ async function updateDashboardConfig(config2) {
1599
1786
  const dashboardConfigPath = path2.join(
1600
1787
  getTargetThemesDir2(),
1601
- config.projectSlug,
1788
+ config2.projectSlug,
1602
1789
  "config",
1603
1790
  "dashboard.config.ts"
1604
1791
  );
@@ -1606,19 +1793,19 @@ async function updateDashboardConfig(config) {
1606
1793
  return;
1607
1794
  }
1608
1795
  let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1609
- if (!config.features.analytics) {
1796
+ if (!config2.features.analytics) {
1610
1797
  content = content.replace(
1611
1798
  /(id:\s*['"]analytics['"].*?enabled:\s*)true/gs,
1612
1799
  "$1false"
1613
1800
  );
1614
1801
  }
1615
- if (!config.features.billing) {
1802
+ if (!config2.features.billing) {
1616
1803
  content = content.replace(
1617
1804
  /(id:\s*['"]billing['"].*?enabled:\s*)true/gs,
1618
1805
  "$1false"
1619
1806
  );
1620
1807
  }
1621
- if (config.teamMode === "single-user") {
1808
+ if (config2.teamMode === "single-user") {
1622
1809
  content = content.replace(
1623
1810
  /(id:\s*['"]team['"].*?enabled:\s*)true/gs,
1624
1811
  "$1false"
@@ -1630,10 +1817,10 @@ async function updateDashboardConfig(config) {
1630
1817
  }
1631
1818
  await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1632
1819
  }
1633
- async function generateEnvExample(config) {
1820
+ async function generateEnvExample(config2) {
1634
1821
  const envExamplePath = path2.resolve(process.cwd(), ".env.example");
1635
1822
  let oauthSection = "";
1636
- if (config.auth.googleOAuth) {
1823
+ if (config2.auth.googleOAuth) {
1637
1824
  oauthSection = `# =============================================================================
1638
1825
  # OAUTH PROVIDERS
1639
1826
  # =============================================================================
@@ -1649,7 +1836,7 @@ GOOGLE_CLIENT_SECRET="your-google-client-secret"
1649
1836
  `;
1650
1837
  }
1651
1838
  const envContent = `# NextSpark Environment Configuration
1652
- # Generated for: ${config.projectName}
1839
+ # Generated for: ${config2.projectName}
1653
1840
 
1654
1841
  # =============================================================================
1655
1842
  # DATABASE
@@ -1665,7 +1852,7 @@ BETTER_AUTH_SECRET="your-secret-key-here"
1665
1852
  # =============================================================================
1666
1853
  # THEME
1667
1854
  # =============================================================================
1668
- NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
1855
+ NEXT_PUBLIC_ACTIVE_THEME="${config2.projectSlug}"
1669
1856
 
1670
1857
  # =============================================================================
1671
1858
  # APPLICATION
@@ -1673,7 +1860,7 @@ NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
1673
1860
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
1674
1861
  NODE_ENV="development"
1675
1862
 
1676
- ${config.features.billing ? `# =============================================================================
1863
+ ${config2.features.billing ? `# =============================================================================
1677
1864
  # STRIPE (Billing)
1678
1865
  # =============================================================================
1679
1866
  STRIPE_SECRET_KEY="sk_test_..."
@@ -1691,16 +1878,16 @@ ${oauthSection}`;
1691
1878
  await fs2.writeFile(envExamplePath, envContent, "utf-8");
1692
1879
  }
1693
1880
  }
1694
- async function updateReadme(config) {
1695
- const readmePath = path2.join(getTargetThemesDir2(), config.projectSlug, "README.md");
1881
+ async function updateReadme(config2) {
1882
+ const readmePath = path2.join(getTargetThemesDir2(), config2.projectSlug, "README.md");
1696
1883
  if (!await fs2.pathExists(readmePath)) {
1697
1884
  return;
1698
1885
  }
1699
1886
  let content = await fs2.readFile(readmePath, "utf-8");
1700
- content = content.replace(/# Starter Theme/g, `# ${config.projectName}`);
1887
+ content = content.replace(/# Starter Theme/g, `# ${config2.projectName}`);
1701
1888
  content = content.replace(
1702
1889
  /Minimal starter theme for NextSpark/g,
1703
- config.projectDescription
1890
+ config2.projectDescription
1704
1891
  );
1705
1892
  await fs2.writeFile(readmePath, content, "utf-8");
1706
1893
  }
@@ -1712,6 +1899,18 @@ async function copyEnvExampleToEnv() {
1712
1899
  await fs2.copy(envExamplePath, envPath);
1713
1900
  }
1714
1901
  }
1902
+ async function updateGlobalsCss(config2) {
1903
+ const globalsCssPath = path2.resolve(process.cwd(), "app", "globals.css");
1904
+ if (!await fs2.pathExists(globalsCssPath)) {
1905
+ return;
1906
+ }
1907
+ let content = await fs2.readFile(globalsCssPath, "utf-8");
1908
+ content = content.replace(
1909
+ /@import\s+["']\.\.\/contents\/themes\/[^/]+\/styles\/globals\.css["'];?/,
1910
+ `@import "../contents/themes/${config2.projectSlug}/styles/globals.css";`
1911
+ );
1912
+ await fs2.writeFile(globalsCssPath, content, "utf-8");
1913
+ }
1715
1914
 
1716
1915
  // src/wizard/generators/messages-generator.ts
1717
1916
  import fs3 from "fs-extra";
@@ -1719,8 +1918,8 @@ import path3 from "path";
1719
1918
  function getTargetThemesDir3() {
1720
1919
  return path3.resolve(process.cwd(), "contents", "themes");
1721
1920
  }
1722
- async function removeUnusedLanguages(config) {
1723
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1921
+ async function removeUnusedLanguages(config2) {
1922
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1724
1923
  if (!await fs3.pathExists(messagesDir)) {
1725
1924
  return;
1726
1925
  }
@@ -1730,13 +1929,13 @@ async function removeUnusedLanguages(config) {
1730
1929
  return fs3.statSync(folderPath).isDirectory() && Object.keys(AVAILABLE_LOCALES).includes(f);
1731
1930
  });
1732
1931
  for (const folder of languageFolders) {
1733
- if (!config.supportedLocales.includes(folder)) {
1932
+ if (!config2.supportedLocales.includes(folder)) {
1734
1933
  await fs3.remove(path3.join(messagesDir, folder));
1735
1934
  }
1736
1935
  }
1737
1936
  }
1738
- async function removeUnusedEntityMessages(config) {
1739
- const entitiesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "entities");
1937
+ async function removeUnusedEntityMessages(config2) {
1938
+ const entitiesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "entities");
1740
1939
  if (!await fs3.pathExists(entitiesDir)) {
1741
1940
  return;
1742
1941
  }
@@ -1749,45 +1948,45 @@ async function removeUnusedEntityMessages(config) {
1749
1948
  const files = await fs3.readdir(messagesDir);
1750
1949
  for (const file of files) {
1751
1950
  const locale = path3.basename(file, ".json");
1752
- if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config.supportedLocales.includes(locale)) {
1951
+ if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config2.supportedLocales.includes(locale)) {
1753
1952
  await fs3.remove(path3.join(messagesDir, file));
1754
1953
  }
1755
1954
  }
1756
1955
  }
1757
1956
  }
1758
- async function ensureLanguageFolders(config) {
1759
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1957
+ async function ensureLanguageFolders(config2) {
1958
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1760
1959
  if (!await fs3.pathExists(messagesDir)) {
1761
1960
  return;
1762
1961
  }
1763
- const defaultLocaleDir = path3.join(messagesDir, config.defaultLocale);
1962
+ const defaultLocaleDir = path3.join(messagesDir, config2.defaultLocale);
1764
1963
  if (!await fs3.pathExists(defaultLocaleDir)) {
1765
1964
  const enDir = path3.join(messagesDir, "en");
1766
1965
  if (await fs3.pathExists(enDir)) {
1767
1966
  await fs3.copy(enDir, defaultLocaleDir);
1768
1967
  }
1769
1968
  }
1770
- for (const locale of config.supportedLocales) {
1969
+ for (const locale of config2.supportedLocales) {
1771
1970
  const localeDir = path3.join(messagesDir, locale);
1772
1971
  if (!await fs3.pathExists(localeDir) && await fs3.pathExists(defaultLocaleDir)) {
1773
1972
  await fs3.copy(defaultLocaleDir, localeDir);
1774
1973
  }
1775
1974
  }
1776
1975
  }
1777
- async function updateMessageFiles(config) {
1778
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1976
+ async function updateMessageFiles(config2) {
1977
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1779
1978
  if (!await fs3.pathExists(messagesDir)) {
1780
1979
  return;
1781
1980
  }
1782
- for (const locale of config.supportedLocales) {
1981
+ for (const locale of config2.supportedLocales) {
1783
1982
  const commonPath = path3.join(messagesDir, locale, "common.json");
1784
1983
  if (await fs3.pathExists(commonPath)) {
1785
1984
  try {
1786
1985
  const content = await fs3.readJson(commonPath);
1787
1986
  if (content.app) {
1788
- content.app.name = config.projectName;
1987
+ content.app.name = config2.projectName;
1789
1988
  if (content.app.description) {
1790
- content.app.description = config.projectDescription;
1989
+ content.app.description = config2.projectDescription;
1791
1990
  }
1792
1991
  }
1793
1992
  await fs3.writeJson(commonPath, content, { spaces: 2 });
@@ -1796,11 +1995,11 @@ async function updateMessageFiles(config) {
1796
1995
  }
1797
1996
  }
1798
1997
  }
1799
- async function processI18n(config) {
1800
- await removeUnusedLanguages(config);
1801
- await removeUnusedEntityMessages(config);
1802
- await ensureLanguageFolders(config);
1803
- await updateMessageFiles(config);
1998
+ async function processI18n(config2) {
1999
+ await removeUnusedLanguages(config2);
2000
+ await removeUnusedEntityMessages(config2);
2001
+ await ensureLanguageFolders(config2);
2002
+ await updateMessageFiles(config2);
1804
2003
  }
1805
2004
 
1806
2005
  // src/wizard/generators/content-features-generator.ts
@@ -1810,22 +2009,22 @@ import { fileURLToPath as fileURLToPath4 } from "url";
1810
2009
  var __filename4 = fileURLToPath4(import.meta.url);
1811
2010
  var __dirname4 = path4.dirname(__filename4);
1812
2011
  function getTemplatesDir2() {
1813
- try {
1814
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1815
- return path4.join(path4.dirname(corePkgPath), "templates");
1816
- } catch {
1817
- const possiblePaths = [
1818
- path4.resolve(__dirname4, "../../../../../core/templates"),
1819
- path4.resolve(__dirname4, "../../../../core/templates"),
1820
- path4.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1821
- ];
1822
- for (const p of possiblePaths) {
1823
- if (fs4.existsSync(p)) {
1824
- return p;
1825
- }
2012
+ const rootDir = process.cwd();
2013
+ const possiblePaths = [
2014
+ // From project root node_modules (most common for installed packages)
2015
+ path4.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
2016
+ // From CLI dist folder for development
2017
+ path4.resolve(__dirname4, "../../core/templates"),
2018
+ // Legacy paths for different build structures
2019
+ path4.resolve(__dirname4, "../../../../../core/templates"),
2020
+ path4.resolve(__dirname4, "../../../../core/templates")
2021
+ ];
2022
+ for (const p of possiblePaths) {
2023
+ if (fs4.existsSync(p)) {
2024
+ return p;
1826
2025
  }
1827
- throw new Error("Could not find @nextsparkjs/core templates directory");
1828
2026
  }
2027
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
1829
2028
  }
1830
2029
  function getFeaturesDir() {
1831
2030
  return path4.join(getTemplatesDir2(), "features");
@@ -1833,9 +2032,9 @@ function getFeaturesDir() {
1833
2032
  function getTargetThemeDir(projectSlug) {
1834
2033
  return path4.resolve(process.cwd(), "contents", "themes", projectSlug);
1835
2034
  }
1836
- async function copyPagesFeature(config) {
2035
+ async function copyPagesFeature(config2) {
1837
2036
  const featuresDir = getFeaturesDir();
1838
- const targetThemeDir = getTargetThemeDir(config.projectSlug);
2037
+ const targetThemeDir = getTargetThemeDir(config2.projectSlug);
1839
2038
  const sourcePagesEntity = path4.join(featuresDir, "pages", "entities", "pages");
1840
2039
  const targetEntitiesDir = path4.join(targetThemeDir, "entities", "pages");
1841
2040
  if (await fs4.pathExists(sourcePagesEntity)) {
@@ -1851,9 +2050,9 @@ async function copyPagesFeature(config) {
1851
2050
  console.warn(`Warning: Hero block not found at: ${sourceHeroBlock}`);
1852
2051
  }
1853
2052
  }
1854
- async function copyBlogFeature(config) {
2053
+ async function copyBlogFeature(config2) {
1855
2054
  const featuresDir = getFeaturesDir();
1856
- const targetThemeDir = getTargetThemeDir(config.projectSlug);
2055
+ const targetThemeDir = getTargetThemeDir(config2.projectSlug);
1857
2056
  const sourcePostsEntity = path4.join(featuresDir, "blog", "entities", "posts");
1858
2057
  const targetPostsEntity = path4.join(targetThemeDir, "entities", "posts");
1859
2058
  if (await fs4.pathExists(sourcePostsEntity)) {
@@ -1869,21 +2068,21 @@ async function copyBlogFeature(config) {
1869
2068
  console.warn(`Warning: Post-content block not found at: ${sourcePostContentBlock}`);
1870
2069
  }
1871
2070
  }
1872
- async function copyContentFeatures(config) {
1873
- if (!config.contentFeatures.pages && !config.contentFeatures.blog) {
2071
+ async function copyContentFeatures(config2) {
2072
+ if (!config2.contentFeatures.pages && !config2.contentFeatures.blog) {
1874
2073
  return;
1875
2074
  }
1876
- if (config.contentFeatures.pages) {
1877
- await copyPagesFeature(config);
2075
+ if (config2.contentFeatures.pages) {
2076
+ await copyPagesFeature(config2);
1878
2077
  }
1879
- if (config.contentFeatures.blog) {
1880
- await copyBlogFeature(config);
2078
+ if (config2.contentFeatures.blog) {
2079
+ await copyBlogFeature(config2);
1881
2080
  }
1882
2081
  }
1883
2082
 
1884
2083
  // src/wizard/generators/theme-plugins-installer.ts
1885
2084
  import { existsSync as existsSync6, cpSync, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
1886
- import { join as join6, resolve as resolve2 } from "path";
2085
+ import { join as join7, resolve as resolve2 } from "path";
1887
2086
  import chalk9 from "chalk";
1888
2087
  import ora6 from "ora";
1889
2088
 
@@ -1891,12 +2090,12 @@ import ora6 from "ora";
1891
2090
  import chalk8 from "chalk";
1892
2091
  import ora5 from "ora";
1893
2092
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1894
- import { join as join5 } from "path";
2093
+ import { join as join6 } from "path";
1895
2094
  async function addTheme(packageSpec, options = {}) {
1896
2095
  const spinner = ora5(`Adding theme ${packageSpec}`).start();
1897
2096
  let cleanup = null;
1898
2097
  try {
1899
- const contentsDir = join5(process.cwd(), "contents");
2098
+ const contentsDir = join6(process.cwd(), "contents");
1900
2099
  if (!existsSync5(contentsDir)) {
1901
2100
  spinner.fail('contents/ directory not found. Run "nextspark init" first.');
1902
2101
  return;
@@ -1959,14 +2158,14 @@ async function addTheme(packageSpec, options = {}) {
1959
2158
  }
1960
2159
  function checkPluginExists(pluginName) {
1961
2160
  const name = pluginName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
1962
- return existsSync5(join5(process.cwd(), "contents", "plugins", name));
2161
+ return existsSync5(join6(process.cwd(), "contents", "plugins", name));
1963
2162
  }
1964
2163
  function getCoreVersion() {
1965
- const pkgPath = join5(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
2164
+ const pkgPath = join6(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
1966
2165
  if (existsSync5(pkgPath)) {
1967
2166
  try {
1968
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1969
- return pkg.version || "0.0.0";
2167
+ const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
2168
+ return pkg2.version || "0.0.0";
1970
2169
  } catch {
1971
2170
  return "0.0.0";
1972
2171
  }
@@ -2003,20 +2202,20 @@ var THEME_REQUIRED_PLUGINS2 = {
2003
2202
  };
2004
2203
  function isMonorepoMode2() {
2005
2204
  const possiblePaths = [
2006
- join6(process.cwd(), "pnpm-workspace.yaml"),
2007
- join6(process.cwd(), "..", "pnpm-workspace.yaml"),
2008
- join6(process.cwd(), "..", "..", "pnpm-workspace.yaml")
2205
+ join7(process.cwd(), "pnpm-workspace.yaml"),
2206
+ join7(process.cwd(), "..", "pnpm-workspace.yaml"),
2207
+ join7(process.cwd(), "..", "..", "pnpm-workspace.yaml")
2009
2208
  ];
2010
2209
  return possiblePaths.some((p) => existsSync6(p));
2011
2210
  }
2012
2211
  function getMonorepoRoot() {
2013
2212
  const possibleRoots = [
2014
2213
  process.cwd(),
2015
- join6(process.cwd(), ".."),
2016
- join6(process.cwd(), "..", "..")
2214
+ join7(process.cwd(), ".."),
2215
+ join7(process.cwd(), "..", "..")
2017
2216
  ];
2018
2217
  for (const root of possibleRoots) {
2019
- if (existsSync6(join6(root, "pnpm-workspace.yaml"))) {
2218
+ if (existsSync6(join7(root, "pnpm-workspace.yaml"))) {
2020
2219
  return resolve2(root);
2021
2220
  }
2022
2221
  }
@@ -2026,15 +2225,15 @@ function getLocalPackageDir(type, name) {
2026
2225
  const monorepoRoot = getMonorepoRoot();
2027
2226
  if (!monorepoRoot) return null;
2028
2227
  const baseDir = type === "theme" ? "themes" : "plugins";
2029
- const packageDir = join6(monorepoRoot, baseDir, name);
2030
- if (existsSync6(packageDir) && existsSync6(join6(packageDir, "package.json"))) {
2228
+ const packageDir = join7(monorepoRoot, baseDir, name);
2229
+ if (existsSync6(packageDir) && existsSync6(join7(packageDir, "package.json"))) {
2031
2230
  return packageDir;
2032
2231
  }
2033
2232
  return null;
2034
2233
  }
2035
2234
  async function copyLocalTheme(name, sourceDir) {
2036
- const targetDir = join6(process.cwd(), "contents", "themes", name);
2037
- const themesDir = join6(process.cwd(), "contents", "themes");
2235
+ const targetDir = join7(process.cwd(), "contents", "themes", name);
2236
+ const themesDir = join7(process.cwd(), "contents", "themes");
2038
2237
  if (!existsSync6(themesDir)) {
2039
2238
  mkdirSync(themesDir, { recursive: true });
2040
2239
  }
@@ -2047,8 +2246,8 @@ async function copyLocalTheme(name, sourceDir) {
2047
2246
  return true;
2048
2247
  }
2049
2248
  async function copyLocalPlugin(name, sourceDir) {
2050
- const targetDir = join6(process.cwd(), "contents", "plugins", name);
2051
- const pluginsDir = join6(process.cwd(), "contents", "plugins");
2249
+ const targetDir = join7(process.cwd(), "contents", "plugins", name);
2250
+ const pluginsDir = join7(process.cwd(), "contents", "plugins");
2052
2251
  if (!existsSync6(pluginsDir)) {
2053
2252
  mkdirSync(pluginsDir, { recursive: true });
2054
2253
  }
@@ -2061,7 +2260,7 @@ async function copyLocalPlugin(name, sourceDir) {
2061
2260
  return true;
2062
2261
  }
2063
2262
  async function updateTsConfigPaths(name, type) {
2064
- const tsconfigPath = join6(process.cwd(), "tsconfig.json");
2263
+ const tsconfigPath = join7(process.cwd(), "tsconfig.json");
2065
2264
  if (!existsSync6(tsconfigPath)) {
2066
2265
  return;
2067
2266
  }
@@ -2106,7 +2305,7 @@ async function installTheme2(theme) {
2106
2305
  prefixText: " "
2107
2306
  }).start();
2108
2307
  try {
2109
- const targetDir = join6(process.cwd(), "contents", "themes", theme);
2308
+ const targetDir = join7(process.cwd(), "contents", "themes", theme);
2110
2309
  if (existsSync6(targetDir)) {
2111
2310
  spinner.info(chalk9.gray(`Reference theme ${theme} already exists`));
2112
2311
  return true;
@@ -2159,7 +2358,7 @@ async function installPlugins(plugins) {
2159
2358
  prefixText: " "
2160
2359
  }).start();
2161
2360
  try {
2162
- const pluginDir = join6(process.cwd(), "contents", "plugins", plugin);
2361
+ const pluginDir = join7(process.cwd(), "contents", "plugins", plugin);
2163
2362
  if (existsSync6(pluginDir)) {
2164
2363
  spinner.info(chalk9.gray(`Plugin ${plugin} already installed`));
2165
2364
  continue;
@@ -2219,62 +2418,608 @@ async function installThemeAndPlugins(theme, plugins) {
2219
2418
 
2220
2419
  // src/wizard/generators/env-setup.ts
2221
2420
  import fs5 from "fs-extra";
2421
+ import path5 from "path";
2422
+ import { fileURLToPath as fileURLToPath5 } from "url";
2423
+ var __filename5 = fileURLToPath5(import.meta.url);
2424
+ var __dirname5 = path5.dirname(__filename5);
2425
+ var ENV_TEMPLATE_PATH = path5.resolve(__dirname5, "../../../templates/env.template");
2222
2426
 
2223
2427
  // src/wizard/generators/git-init.ts
2224
2428
  import fs6 from "fs-extra";
2225
2429
 
2430
+ // src/wizard/generators/monorepo-generator.ts
2431
+ import fs7 from "fs-extra";
2432
+ import path6 from "path";
2433
+ import { fileURLToPath as fileURLToPath6 } from "url";
2434
+ var __filename6 = fileURLToPath6(import.meta.url);
2435
+ var __dirname6 = path6.dirname(__filename6);
2436
+ var DIRS = {
2437
+ WEB: "web",
2438
+ MOBILE: "mobile",
2439
+ ASSETS: "assets"
2440
+ };
2441
+ var FILES = {
2442
+ PNPM_WORKSPACE: "pnpm-workspace.yaml",
2443
+ NPMRC: ".npmrc",
2444
+ GITIGNORE: ".gitignore",
2445
+ TSCONFIG: "tsconfig.json",
2446
+ PACKAGE_JSON: "package.json",
2447
+ README: "README.md",
2448
+ APP_CONFIG: "app.config.ts"
2449
+ };
2450
+ var REQUIRED_MOBILE_TEMPLATE_FILES = [
2451
+ "app",
2452
+ "src",
2453
+ "babel.config.js",
2454
+ "metro.config.js"
2455
+ ];
2456
+ var MOBILE_TEMPLATE_FILES = [
2457
+ "app",
2458
+ "src",
2459
+ "assets",
2460
+ "babel.config.js",
2461
+ "metro.config.js",
2462
+ "tailwind.config.js",
2463
+ "tsconfig.json",
2464
+ "jest.config.js",
2465
+ "eas.json"
2466
+ ];
2467
+ var VERSIONS = {
2468
+ // NextSpark packages - use 'latest' for consistency with web packages
2469
+ NEXTSPARK_MOBILE: "latest",
2470
+ NEXTSPARK_UI: "latest",
2471
+ // Core dependencies
2472
+ TANSTACK_QUERY: "^5.62.0",
2473
+ EXPO: "^54.0.0",
2474
+ REACT: "19.1.0",
2475
+ REACT_NATIVE: "0.81.5",
2476
+ TYPESCRIPT: "^5.3.0",
2477
+ // Expo modules (use ~ for patch compatibility)
2478
+ EXPO_CONSTANTS: "~18.0.13",
2479
+ EXPO_LINKING: "~8.0.11",
2480
+ EXPO_ROUTER: "~6.0.22",
2481
+ EXPO_SECURE_STORE: "~15.0.8",
2482
+ EXPO_STATUS_BAR: "~3.0.9",
2483
+ // React Native modules
2484
+ RN_GESTURE_HANDLER: "~2.28.0",
2485
+ RN_REANIMATED: "~4.1.1",
2486
+ RN_SAFE_AREA: "~5.6.0",
2487
+ RN_SCREENS: "~4.16.0",
2488
+ RN_SVG: "15.12.1",
2489
+ RN_WEB: "^0.21.0",
2490
+ // Styling
2491
+ NATIVEWIND: "^4.2.1",
2492
+ TAILWINDCSS: "^3",
2493
+ TAILWIND_MERGE: "^3.4.0",
2494
+ LUCIDE_RN: "^0.563.0",
2495
+ // Dev dependencies
2496
+ BABEL_CORE: "^7.25.0",
2497
+ JEST: "^29.7.0",
2498
+ JEST_EXPO: "^54.0.16",
2499
+ TESTING_LIBRARY_JEST_NATIVE: "^5.4.3",
2500
+ TESTING_LIBRARY_RN: "^13.3.3"
2501
+ };
2502
+ function getMobileTemplatesDir() {
2503
+ const possiblePaths = [
2504
+ // From CWD node_modules (installed package)
2505
+ path6.resolve(process.cwd(), "node_modules/@nextsparkjs/mobile/templates"),
2506
+ // From CLI dist folder: ../../mobile/templates (development)
2507
+ path6.resolve(__dirname6, "../../mobile/templates"),
2508
+ // Legacy paths for different build structures
2509
+ path6.resolve(__dirname6, "../../../../../mobile/templates"),
2510
+ path6.resolve(__dirname6, "../../../../mobile/templates")
2511
+ ];
2512
+ for (const p of possiblePaths) {
2513
+ if (fs7.existsSync(p)) {
2514
+ return p;
2515
+ }
2516
+ }
2517
+ const searchedPaths = possiblePaths.map((p) => ` - ${p}`).join("\n");
2518
+ throw new Error(
2519
+ `Could not find @nextsparkjs/mobile templates directory.
2520
+
2521
+ Searched paths:
2522
+ ${searchedPaths}
2523
+
2524
+ To fix this, ensure @nextsparkjs/mobile is installed:
2525
+ pnpm add @nextsparkjs/mobile
2526
+
2527
+ If you're developing locally, make sure the mobile package is built:
2528
+ cd packages/mobile && pnpm build`
2529
+ );
2530
+ }
2531
+ async function validateMobileTemplate(templateDir) {
2532
+ const missing = [];
2533
+ for (const file of REQUIRED_MOBILE_TEMPLATE_FILES) {
2534
+ const filePath = path6.join(templateDir, file);
2535
+ if (!await fs7.pathExists(filePath)) {
2536
+ missing.push(file);
2537
+ }
2538
+ }
2539
+ if (missing.length > 0) {
2540
+ throw new Error(
2541
+ `Mobile template is incomplete. Missing required files:
2542
+ ` + missing.map((f) => ` - ${f}`).join("\n") + `
2543
+
2544
+ Template location: ${templateDir}
2545
+ Please ensure @nextsparkjs/mobile is properly installed and built.`
2546
+ );
2547
+ }
2548
+ }
2549
+ function slugToBundleId(slug) {
2550
+ return slug.toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "").replace(/\.{2,}/g, ".");
2551
+ }
2552
+ async function createRootPackageJson(targetDir, config2) {
2553
+ const rootPkg = {
2554
+ name: config2.projectSlug,
2555
+ version: "0.1.0",
2556
+ private: true,
2557
+ scripts: {
2558
+ // Web commands
2559
+ "dev": `pnpm --filter ${DIRS.WEB} dev`,
2560
+ "build": `pnpm --filter ${DIRS.WEB} build`,
2561
+ "start": `pnpm --filter ${DIRS.WEB} start`,
2562
+ "lint": "pnpm -r lint",
2563
+ // Mobile commands
2564
+ "dev:mobile": `pnpm --filter ${DIRS.MOBILE} start`,
2565
+ "ios": `pnpm --filter ${DIRS.MOBILE} ios`,
2566
+ "android": `pnpm --filter ${DIRS.MOBILE} android`,
2567
+ // Shared commands
2568
+ "typecheck": "pnpm -r typecheck",
2569
+ "test": "pnpm -r test",
2570
+ // Web-specific CLI commands (run from root)
2571
+ "db:migrate": `pnpm --filter ${DIRS.WEB} db:migrate`,
2572
+ "db:seed": `pnpm --filter ${DIRS.WEB} db:seed`,
2573
+ "build:registries": `pnpm --filter ${DIRS.WEB} build:registries`
2574
+ },
2575
+ devDependencies: {
2576
+ "typescript": VERSIONS.TYPESCRIPT
2577
+ }
2578
+ };
2579
+ await fs7.writeJson(path6.join(targetDir, FILES.PACKAGE_JSON), rootPkg, { spaces: 2 });
2580
+ }
2581
+ async function createPnpmWorkspace(targetDir) {
2582
+ const workspaceContent = `packages:
2583
+ - '${DIRS.WEB}'
2584
+ - '${DIRS.MOBILE}'
2585
+ `;
2586
+ await fs7.writeFile(path6.join(targetDir, FILES.PNPM_WORKSPACE), workspaceContent);
2587
+ }
2588
+ async function createNpmrc(targetDir) {
2589
+ const npmrcContent = `# Enable proper hoisting for monorepo
2590
+ public-hoist-pattern[]=*@nextsparkjs/*
2591
+ public-hoist-pattern[]=*expo*
2592
+ public-hoist-pattern[]=*react-native*
2593
+ shamefully-hoist=true
2594
+ `;
2595
+ await fs7.writeFile(path6.join(targetDir, FILES.NPMRC), npmrcContent);
2596
+ }
2597
+ async function createGitignore(targetDir) {
2598
+ const gitignoreContent = `# Dependencies
2599
+ node_modules/
2600
+
2601
+ # Build outputs
2602
+ .next/
2603
+ dist/
2604
+ out/
2605
+ build/
2606
+ .expo/
2607
+
2608
+ # NextSpark
2609
+ .nextspark/
2610
+
2611
+ # Environment
2612
+ .env
2613
+ .env.local
2614
+ .env.*.local
2615
+
2616
+ # IDE
2617
+ .idea/
2618
+ .vscode/
2619
+ *.swp
2620
+ *.swo
2621
+
2622
+ # OS
2623
+ .DS_Store
2624
+ Thumbs.db
2625
+
2626
+ # Testing
2627
+ coverage/
2628
+ .nyc_output/
2629
+
2630
+ # Cypress (theme-based in web/)
2631
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/videos
2632
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/screenshots
2633
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/allure-results
2634
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/allure-report
2635
+
2636
+ # Jest (theme-based in web/)
2637
+ ${DIRS.WEB}/contents/themes/*/tests/jest/coverage
2638
+
2639
+ # Mobile specific
2640
+ ${DIRS.MOBILE}/.expo/
2641
+ *.jks
2642
+ *.p8
2643
+ *.p12
2644
+ *.key
2645
+ *.mobileprovision
2646
+ *.orig.*
2647
+ web-build/
2648
+ `;
2649
+ await fs7.writeFile(path6.join(targetDir, FILES.GITIGNORE), gitignoreContent);
2650
+ }
2651
+ async function createRootTsConfig(targetDir) {
2652
+ const tsConfig = {
2653
+ compilerOptions: {
2654
+ target: "ES2022",
2655
+ module: "ESNext",
2656
+ moduleResolution: "bundler",
2657
+ strict: true,
2658
+ skipLibCheck: true,
2659
+ esModuleInterop: true
2660
+ },
2661
+ references: [
2662
+ { path: `./${DIRS.WEB}` },
2663
+ { path: `./${DIRS.MOBILE}` }
2664
+ ]
2665
+ };
2666
+ await fs7.writeJson(path6.join(targetDir, FILES.TSCONFIG), tsConfig, { spaces: 2 });
2667
+ }
2668
+ async function copyMobileTemplate(targetDir, config2) {
2669
+ const mobileTemplatesDir = getMobileTemplatesDir();
2670
+ await validateMobileTemplate(mobileTemplatesDir);
2671
+ const mobileDir = path6.join(targetDir, DIRS.MOBILE);
2672
+ await fs7.ensureDir(mobileDir);
2673
+ for (const item of MOBILE_TEMPLATE_FILES) {
2674
+ const srcPath = path6.join(mobileTemplatesDir, item);
2675
+ const destPath = path6.join(mobileDir, item);
2676
+ if (await fs7.pathExists(srcPath)) {
2677
+ await fs7.copy(srcPath, destPath);
2678
+ }
2679
+ }
2680
+ await createMobilePackageJson(mobileDir, config2);
2681
+ await createMobileAppConfig(mobileDir, config2);
2682
+ await createMobileAssets(mobileDir, config2);
2683
+ }
2684
+ async function createMobilePackageJson(mobileDir, config2) {
2685
+ const mobileSlug = `${config2.projectSlug}-mobile`;
2686
+ const packageJson = {
2687
+ name: mobileSlug,
2688
+ version: "0.1.0",
2689
+ private: true,
2690
+ main: "expo-router/entry",
2691
+ scripts: {
2692
+ start: "expo start",
2693
+ android: "expo start --android",
2694
+ ios: "expo start --ios",
2695
+ web: "expo start --web",
2696
+ lint: "eslint .",
2697
+ typecheck: "tsc --noEmit",
2698
+ test: "jest",
2699
+ "test:watch": "jest --watch",
2700
+ "test:coverage": "jest --coverage"
2701
+ },
2702
+ dependencies: {
2703
+ "@nextsparkjs/mobile": VERSIONS.NEXTSPARK_MOBILE,
2704
+ "@nextsparkjs/ui": VERSIONS.NEXTSPARK_UI,
2705
+ "@tanstack/react-query": VERSIONS.TANSTACK_QUERY,
2706
+ "expo": VERSIONS.EXPO,
2707
+ "expo-constants": VERSIONS.EXPO_CONSTANTS,
2708
+ "expo-linking": VERSIONS.EXPO_LINKING,
2709
+ "expo-router": VERSIONS.EXPO_ROUTER,
2710
+ "expo-secure-store": VERSIONS.EXPO_SECURE_STORE,
2711
+ "expo-status-bar": VERSIONS.EXPO_STATUS_BAR,
2712
+ "lucide-react-native": VERSIONS.LUCIDE_RN,
2713
+ "nativewind": VERSIONS.NATIVEWIND,
2714
+ "react": VERSIONS.REACT,
2715
+ "react-dom": VERSIONS.REACT,
2716
+ "react-native": VERSIONS.REACT_NATIVE,
2717
+ "react-native-web": VERSIONS.RN_WEB,
2718
+ "react-native-gesture-handler": VERSIONS.RN_GESTURE_HANDLER,
2719
+ "react-native-reanimated": VERSIONS.RN_REANIMATED,
2720
+ "react-native-safe-area-context": VERSIONS.RN_SAFE_AREA,
2721
+ "react-native-screens": VERSIONS.RN_SCREENS,
2722
+ "react-native-svg": VERSIONS.RN_SVG,
2723
+ "tailwind-merge": VERSIONS.TAILWIND_MERGE,
2724
+ "tailwindcss": VERSIONS.TAILWINDCSS
2725
+ },
2726
+ devDependencies: {
2727
+ "@babel/core": VERSIONS.BABEL_CORE,
2728
+ "@testing-library/jest-native": VERSIONS.TESTING_LIBRARY_JEST_NATIVE,
2729
+ "@testing-library/react-native": VERSIONS.TESTING_LIBRARY_RN,
2730
+ "@types/jest": "^29.5.0",
2731
+ "@types/react": "^19",
2732
+ "jest": VERSIONS.JEST,
2733
+ "jest-expo": VERSIONS.JEST_EXPO,
2734
+ "react-test-renderer": VERSIONS.REACT,
2735
+ "typescript": VERSIONS.TYPESCRIPT
2736
+ }
2737
+ };
2738
+ await fs7.writeJson(path6.join(mobileDir, FILES.PACKAGE_JSON), packageJson, { spaces: 2 });
2739
+ }
2740
+ async function createMobileAppConfig(mobileDir, config2) {
2741
+ const bundleId = slugToBundleId(config2.projectSlug);
2742
+ const appConfigContent = `import { ExpoConfig, ConfigContext } from 'expo/config'
2743
+
2744
+ export default ({ config }: ConfigContext): ExpoConfig => ({
2745
+ ...config,
2746
+ name: '${config2.projectName}',
2747
+ slug: '${config2.projectSlug}',
2748
+ version: '1.0.0',
2749
+ orientation: 'portrait',
2750
+ icon: './${DIRS.ASSETS}/icon.png',
2751
+ userInterfaceStyle: 'automatic',
2752
+ splash: {
2753
+ image: './${DIRS.ASSETS}/splash.png',
2754
+ resizeMode: 'contain',
2755
+ backgroundColor: '#ffffff',
2756
+ },
2757
+ assetBundlePatterns: ['**/*'],
2758
+ ios: {
2759
+ supportsTablet: true,
2760
+ bundleIdentifier: 'com.${bundleId}.app',
2761
+ },
2762
+ android: {
2763
+ adaptiveIcon: {
2764
+ foregroundImage: './${DIRS.ASSETS}/adaptive-icon.png',
2765
+ backgroundColor: '#ffffff',
2766
+ },
2767
+ package: 'com.${bundleId}.app',
2768
+ },
2769
+ web: {
2770
+ favicon: './${DIRS.ASSETS}/favicon.png',
2771
+ },
2772
+ extra: {
2773
+ // API URL - Configure for your environment
2774
+ // Development: point to your local web app (e.g., http://localhost:3000)
2775
+ // Production: set via EAS environment variables
2776
+ apiUrl: process.env.EXPO_PUBLIC_API_URL,
2777
+ },
2778
+ plugins: ['expo-router', 'expo-secure-store'],
2779
+ scheme: '${config2.projectSlug}',
2780
+ })
2781
+ `;
2782
+ await fs7.writeFile(path6.join(mobileDir, FILES.APP_CONFIG), appConfigContent);
2783
+ }
2784
+ async function createMobileAssets(mobileDir, config2) {
2785
+ const assetsDir = path6.join(mobileDir, DIRS.ASSETS);
2786
+ await fs7.ensureDir(assetsDir);
2787
+ await fs7.writeFile(
2788
+ path6.join(assetsDir, ".gitkeep"),
2789
+ "# Placeholder - replace with your app icons and splash screens\n"
2790
+ );
2791
+ const assetsReadme = `# Mobile App Assets for ${config2.projectName}
2792
+
2793
+ This directory contains your mobile app icons and splash screens.
2794
+
2795
+ ## Required Files
2796
+
2797
+ | File | Size | Description |
2798
+ |------|------|-------------|
2799
+ | \`icon.png\` | 1024x1024px | Main app icon (iOS & Android) |
2800
+ | \`splash.png\` | 1284x2778px | Splash screen image |
2801
+ | \`adaptive-icon.png\` | 1024x1024px | Android adaptive icon (foreground) |
2802
+ | \`favicon.png\` | 48x48px | Web favicon |
2803
+
2804
+ ## How to Generate
2805
+
2806
+ ### Option 1: Expo Asset Generator (Recommended)
2807
+
2808
+ 1. Create a 1024x1024px icon image
2809
+ 2. Use Expo's icon generator:
2810
+ \`\`\`bash
2811
+ npx expo-optimize
2812
+ \`\`\`
2813
+
2814
+ ### Option 2: Online Tools
2815
+
2816
+ - [Expo Icon Generator](https://docs.expo.dev/develop/user-interface/app-icons/)
2817
+ - [App Icon Generator](https://appicon.co/)
2818
+ - [Figma App Icon Template](https://www.figma.com/community/file/824894885635013116)
2819
+
2820
+ ### Option 3: Manual Creation
2821
+
2822
+ Create each file at the specified sizes above. Use PNG format with transparency for icons.
2823
+
2824
+ ## Tips
2825
+
2826
+ - Use a simple, recognizable design that works at small sizes
2827
+ - Test your icon on both light and dark backgrounds
2828
+ - Avoid text in the icon (it becomes illegible at small sizes)
2829
+ - Keep important content within the "safe zone" (center 80%)
2830
+
2831
+ ## Resources
2832
+
2833
+ - [Expo App Icons Documentation](https://docs.expo.dev/develop/user-interface/app-icons/)
2834
+ - [iOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/app-icons)
2835
+ - [Android Adaptive Icons](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive)
2836
+ `;
2837
+ await fs7.writeFile(path6.join(assetsDir, "README.md"), assetsReadme);
2838
+ }
2839
+ async function createMonorepoReadme(targetDir, config2) {
2840
+ const readmeContent = `# ${config2.projectName}
2841
+
2842
+ ${config2.projectDescription}
2843
+
2844
+ ## Project Structure
2845
+
2846
+ This is a monorepo containing both web and mobile applications:
2847
+
2848
+ \`\`\`
2849
+ ${config2.projectSlug}/
2850
+ \u251C\u2500\u2500 ${DIRS.WEB}/ # Next.js web application
2851
+ \u2502 \u251C\u2500\u2500 app/ # Next.js App Router
2852
+ \u2502 \u251C\u2500\u2500 contents/ # Themes and plugins
2853
+ \u2502 \u2514\u2500\u2500 package.json
2854
+ \u251C\u2500\u2500 ${DIRS.MOBILE}/ # Expo mobile application
2855
+ \u2502 \u251C\u2500\u2500 app/ # Expo Router screens
2856
+ \u2502 \u251C\u2500\u2500 src/ # Mobile-specific code
2857
+ \u2502 \u2514\u2500\u2500 package.json
2858
+ \u251C\u2500\u2500 package.json # Root monorepo
2859
+ \u2514\u2500\u2500 ${FILES.PNPM_WORKSPACE}
2860
+ \`\`\`
2861
+
2862
+ ## Getting Started
2863
+
2864
+ ### Prerequisites
2865
+
2866
+ - Node.js 20+
2867
+ - pnpm 9+
2868
+ - For mobile: Expo CLI (\`npm install -g expo-cli\`)
2869
+
2870
+ ### Installation
2871
+
2872
+ \`\`\`bash
2873
+ # Install all dependencies
2874
+ pnpm install
2875
+
2876
+ # Set up environment variables
2877
+ cp ${DIRS.WEB}/.env.example ${DIRS.WEB}/.env
2878
+ # Edit ${DIRS.WEB}/.env with your configuration
2879
+ \`\`\`
2880
+
2881
+ ### Development
2882
+
2883
+ **Web Application:**
2884
+ \`\`\`bash
2885
+ # From root directory
2886
+ pnpm dev
2887
+
2888
+ # Or from web directory
2889
+ cd ${DIRS.WEB} && pnpm dev
2890
+ \`\`\`
2891
+
2892
+ **Mobile Application:**
2893
+ \`\`\`bash
2894
+ # From root directory
2895
+ pnpm dev:mobile
2896
+
2897
+ # Or from mobile directory
2898
+ cd ${DIRS.MOBILE} && pnpm start
2899
+ \`\`\`
2900
+
2901
+ ### Running Tests
2902
+
2903
+ \`\`\`bash
2904
+ # Run all tests
2905
+ pnpm test
2906
+
2907
+ # Run web tests only
2908
+ pnpm --filter ${DIRS.WEB} test
2909
+
2910
+ # Run mobile tests only
2911
+ pnpm --filter ${DIRS.MOBILE} test
2912
+ \`\`\`
2913
+
2914
+ ## Mobile App Configuration
2915
+
2916
+ The mobile app connects to your web API. Configure the API URL:
2917
+
2918
+ - **Development:** The mobile app will auto-detect your local server
2919
+ - **Production:** Set \`EXPO_PUBLIC_API_URL\` in your EAS environment
2920
+
2921
+ ## Building for Production
2922
+
2923
+ **Web:**
2924
+ \`\`\`bash
2925
+ pnpm build
2926
+ \`\`\`
2927
+
2928
+ **Mobile:**
2929
+ \`\`\`bash
2930
+ cd ${DIRS.MOBILE}
2931
+ eas build --platform ios
2932
+ eas build --platform android
2933
+ \`\`\`
2934
+
2935
+ ## Learn More
2936
+
2937
+ - [NextSpark Documentation](https://nextspark.dev/docs)
2938
+ - [Expo Documentation](https://docs.expo.dev)
2939
+ - [Next.js Documentation](https://nextjs.org/docs)
2940
+ `;
2941
+ await fs7.writeFile(path6.join(targetDir, FILES.README), readmeContent);
2942
+ }
2943
+ async function generateMonorepoStructure(targetDir, config2) {
2944
+ await createRootPackageJson(targetDir, config2);
2945
+ await createPnpmWorkspace(targetDir);
2946
+ await createNpmrc(targetDir);
2947
+ await createGitignore(targetDir);
2948
+ await createRootTsConfig(targetDir);
2949
+ await createMonorepoReadme(targetDir, config2);
2950
+ const webDir = path6.join(targetDir, DIRS.WEB);
2951
+ await fs7.ensureDir(webDir);
2952
+ await copyMobileTemplate(targetDir, config2);
2953
+ }
2954
+ function isMonorepoProject(config2) {
2955
+ return config2.projectType === "web-mobile";
2956
+ }
2957
+ function getWebDir(targetDir, config2) {
2958
+ return config2.projectType === "web-mobile" ? path6.join(targetDir, DIRS.WEB) : targetDir;
2959
+ }
2960
+
2226
2961
  // src/wizard/generators/index.ts
2227
- var __filename5 = fileURLToPath5(import.meta.url);
2228
- var __dirname5 = path5.dirname(__filename5);
2229
- function getTemplatesDir3() {
2230
- try {
2231
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
2232
- return path5.join(path5.dirname(corePkgPath), "templates");
2233
- } catch {
2234
- const possiblePaths = [
2235
- path5.resolve(__dirname5, "../../../../../core/templates"),
2236
- path5.resolve(__dirname5, "../../../../core/templates"),
2237
- path5.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
2238
- ];
2239
- for (const p of possiblePaths) {
2240
- if (fs7.existsSync(p)) {
2241
- return p;
2242
- }
2962
+ var __filename7 = fileURLToPath7(import.meta.url);
2963
+ var __dirname7 = path7.dirname(__filename7);
2964
+ function getTemplatesDir3(projectRoot) {
2965
+ const rootDir = projectRoot || process.cwd();
2966
+ const possiblePaths = [
2967
+ // From project root node_modules (most common for installed packages)
2968
+ path7.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
2969
+ // From CLI dist folder for development
2970
+ path7.resolve(__dirname7, "../../core/templates"),
2971
+ // Legacy paths for different build structures
2972
+ path7.resolve(__dirname7, "../../../../../core/templates"),
2973
+ path7.resolve(__dirname7, "../../../../core/templates")
2974
+ ];
2975
+ for (const p of possiblePaths) {
2976
+ if (fs8.existsSync(p)) {
2977
+ return p;
2243
2978
  }
2244
- throw new Error("Could not find @nextsparkjs/core templates directory");
2245
2979
  }
2980
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
2246
2981
  }
2982
+ var cachedTemplatesDir = null;
2247
2983
  async function copyProjectFiles() {
2248
- const templatesDir = getTemplatesDir3();
2984
+ if (!cachedTemplatesDir) {
2985
+ throw new Error("Templates directory not cached. Call cacheTemplatesDir() first.");
2986
+ }
2987
+ const templatesDir = cachedTemplatesDir;
2249
2988
  const projectDir = process.cwd();
2250
2989
  const itemsToCopy = [
2251
2990
  { src: "app", dest: "app", force: true },
2252
2991
  { src: "public", dest: "public", force: true },
2992
+ { src: "proxy.ts", dest: "proxy.ts", force: true },
2993
+ // Next.js 16+ proxy (formerly middleware.ts)
2253
2994
  { src: "next.config.mjs", dest: "next.config.mjs", force: true },
2254
2995
  { src: "tsconfig.json", dest: "tsconfig.json", force: true },
2255
2996
  { src: "postcss.config.mjs", dest: "postcss.config.mjs", force: true },
2256
2997
  { src: "i18n.ts", dest: "i18n.ts", force: true },
2257
- { src: "npmrc", dest: ".npmrc", force: false },
2998
+ { src: "pnpm-workspace.yaml", dest: "pnpm-workspace.yaml", force: true },
2999
+ // Enable workspace for themes/plugins - REQUIRED
3000
+ // Note: .npmrc is NOT copied for web-only projects (not needed, pnpm default hoisting works fine)
3001
+ // For monorepo projects, monorepo-generator.ts creates a specific .npmrc with expo/react-native patterns
2258
3002
  { src: "tsconfig.cypress.json", dest: "tsconfig.cypress.json", force: false },
2259
3003
  { src: "cypress.d.ts", dest: "cypress.d.ts", force: false },
2260
- { src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false }
3004
+ { src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false },
3005
+ { src: "scripts/cy-run-prod.cjs", dest: "scripts/cy-run-prod.cjs", force: false }
2261
3006
  ];
2262
3007
  for (const item of itemsToCopy) {
2263
- const srcPath = path5.join(templatesDir, item.src);
2264
- const destPath = path5.join(projectDir, item.dest);
2265
- if (await fs7.pathExists(srcPath)) {
2266
- if (item.force || !await fs7.pathExists(destPath)) {
2267
- await fs7.copy(srcPath, destPath);
3008
+ const srcPath = path7.join(templatesDir, item.src);
3009
+ const destPath = path7.join(projectDir, item.dest);
3010
+ if (await fs8.pathExists(srcPath)) {
3011
+ if (item.force || !await fs8.pathExists(destPath)) {
3012
+ await fs8.copy(srcPath, destPath);
2268
3013
  }
2269
3014
  }
2270
3015
  }
2271
3016
  }
2272
- async function updatePackageJson(config) {
2273
- const packageJsonPath = path5.resolve(process.cwd(), "package.json");
3017
+ async function updatePackageJson(config2) {
3018
+ const packageJsonPath = path7.resolve(process.cwd(), "package.json");
2274
3019
  let packageJson;
2275
- if (!await fs7.pathExists(packageJsonPath)) {
3020
+ if (!await fs8.pathExists(packageJsonPath)) {
2276
3021
  packageJson = {
2277
- name: config.projectSlug,
3022
+ name: isMonorepoProject(config2) ? "web" : config2.projectSlug,
2278
3023
  version: "0.1.0",
2279
3024
  private: true,
2280
3025
  scripts: {},
@@ -2282,7 +3027,7 @@ async function updatePackageJson(config) {
2282
3027
  devDependencies: {}
2283
3028
  };
2284
3029
  } else {
2285
- packageJson = await fs7.readJson(packageJsonPath);
3030
+ packageJson = await fs8.readJson(packageJsonPath);
2286
3031
  }
2287
3032
  packageJson.scripts = packageJson.scripts || {};
2288
3033
  const scriptsToAdd = {
@@ -2293,11 +3038,13 @@ async function updatePackageJson(config) {
2293
3038
  "build:registries": "nextspark registry:build",
2294
3039
  "db:migrate": "nextspark db:migrate",
2295
3040
  "db:seed": "nextspark db:seed",
2296
- "test:theme": `jest --config contents/themes/${config.projectSlug}/tests/jest/jest.config.ts`,
2297
- "cy:open": `cypress open --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2298
- "cy:run": `cypress run --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2299
- "allure:generate": `allure generate contents/themes/${config.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config.projectSlug}/tests/cypress/allure-report`,
2300
- "allure:open": `allure open contents/themes/${config.projectSlug}/tests/cypress/allure-report`
3041
+ "test": "node node_modules/@nextsparkjs/core/scripts/test/jest-theme.mjs",
3042
+ "cy:open": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs open",
3043
+ "cy:run": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs run",
3044
+ "cy:tags": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs tags",
3045
+ "cy:run:prod": "node scripts/cy-run-prod.cjs",
3046
+ "allure:generate": `allure generate contents/themes/${config2.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config2.projectSlug}/tests/cypress/allure-report`,
3047
+ "allure:open": `allure open contents/themes/${config2.projectSlug}/tests/cypress/allure-report`
2301
3048
  };
2302
3049
  for (const [name, command] of Object.entries(scriptsToAdd)) {
2303
3050
  if (!packageJson.scripts[name]) {
@@ -2307,8 +3054,8 @@ async function updatePackageJson(config) {
2307
3054
  packageJson.dependencies = packageJson.dependencies || {};
2308
3055
  const depsToAdd = {
2309
3056
  // NextSpark
2310
- "@nextsparkjs/core": "^0.1.0-beta.4",
2311
- "@nextsparkjs/cli": "^0.1.0-beta.4",
3057
+ "@nextsparkjs/core": "latest",
3058
+ "@nextsparkjs/cli": "latest",
2312
3059
  // Next.js + React
2313
3060
  "next": "^15.1.0",
2314
3061
  "react": "^19.0.0",
@@ -2339,7 +3086,9 @@ async function updatePackageJson(config) {
2339
3086
  "slugify": "^1.6.6"
2340
3087
  };
2341
3088
  for (const [name, version] of Object.entries(depsToAdd)) {
2342
- if (!packageJson.dependencies[name]) {
3089
+ if (name.startsWith("@nextsparkjs/")) {
3090
+ packageJson.dependencies[name] = version;
3091
+ } else if (!packageJson.dependencies[name]) {
2343
3092
  packageJson.dependencies[name] = version;
2344
3093
  }
2345
3094
  }
@@ -2367,24 +3116,28 @@ async function updatePackageJson(config) {
2367
3116
  "@testing-library/react": "^16.3.0",
2368
3117
  "jest-environment-jsdom": "^29.7.0",
2369
3118
  // Cypress
2370
- "cypress": "^14.0.0",
3119
+ "cypress": "^15.8.2",
2371
3120
  "@testing-library/cypress": "^10.0.2",
2372
3121
  "@cypress/webpack-preprocessor": "^6.0.2",
2373
- "@cypress/grep": "^4.1.0",
3122
+ "@cypress/grep": "^5.0.1",
2374
3123
  "ts-loader": "^9.5.1",
2375
3124
  "webpack": "^5.97.0",
2376
3125
  "allure-cypress": "^3.0.0",
2377
- "allure-commandline": "^2.27.0"
3126
+ "allure-commandline": "^2.27.0",
3127
+ // NextSpark Testing
3128
+ "@nextsparkjs/testing": "latest"
2378
3129
  };
2379
3130
  for (const [name, version] of Object.entries(devDepsToAdd)) {
2380
- if (!packageJson.devDependencies[name]) {
3131
+ if (name.startsWith("@nextsparkjs/")) {
3132
+ packageJson.devDependencies[name] = version;
3133
+ } else if (!packageJson.devDependencies[name]) {
2381
3134
  packageJson.devDependencies[name] = version;
2382
3135
  }
2383
3136
  }
2384
- await fs7.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3137
+ await fs8.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2385
3138
  }
2386
- async function updateGitignore(config) {
2387
- const gitignorePath = path5.resolve(process.cwd(), ".gitignore");
3139
+ async function updateGitignore(config2) {
3140
+ const gitignorePath = path7.resolve(process.cwd(), ".gitignore");
2388
3141
  const entriesToAdd = `
2389
3142
  # NextSpark
2390
3143
  .nextspark/
@@ -2402,40 +3155,66 @@ contents/themes/*/tests/jest/coverage
2402
3155
  .env
2403
3156
  .env.local
2404
3157
  `;
2405
- if (await fs7.pathExists(gitignorePath)) {
2406
- const currentContent = await fs7.readFile(gitignorePath, "utf-8");
3158
+ if (await fs8.pathExists(gitignorePath)) {
3159
+ const currentContent = await fs8.readFile(gitignorePath, "utf-8");
2407
3160
  if (!currentContent.includes(".nextspark/")) {
2408
- await fs7.appendFile(gitignorePath, entriesToAdd);
3161
+ await fs8.appendFile(gitignorePath, entriesToAdd);
2409
3162
  }
2410
3163
  } else {
2411
- await fs7.writeFile(gitignorePath, entriesToAdd.trim());
2412
- }
2413
- }
2414
- async function generateProject(config) {
2415
- await copyProjectFiles();
2416
- await copyStarterTheme(config);
2417
- await copyContentFeatures(config);
2418
- await updateThemeConfig(config);
2419
- await updateDevConfig(config);
2420
- await updateAppConfig(config);
2421
- await updateBillingConfig(config);
2422
- await updateRolesConfig(config);
2423
- await updateMigrations(config);
2424
- await updatePermissionsConfig(config);
2425
- await updateDashboardConfig(config);
2426
- await updateAuthConfig(config);
2427
- await updateDashboardUIConfig(config);
2428
- await updateDevToolsConfig(config);
2429
- await processI18n(config);
2430
- await updatePackageJson(config);
2431
- await updateGitignore(config);
2432
- await generateEnvExample(config);
2433
- await updateReadme(config);
2434
- await copyEnvExampleToEnv();
3164
+ await fs8.writeFile(gitignorePath, entriesToAdd.trim());
3165
+ }
3166
+ }
3167
+ async function generateProject(config2) {
3168
+ const projectDir = process.cwd();
3169
+ cachedTemplatesDir = getTemplatesDir3(projectDir);
3170
+ const webDir = getWebDir(projectDir, config2);
3171
+ if (isMonorepoProject(config2)) {
3172
+ await generateMonorepoStructure(projectDir, config2);
3173
+ }
3174
+ const originalCwd = process.cwd();
3175
+ if (isMonorepoProject(config2)) {
3176
+ process.chdir(webDir);
3177
+ }
3178
+ try {
3179
+ await copyProjectFiles();
3180
+ await updateGlobalsCss(config2);
3181
+ await copyStarterTheme(config2);
3182
+ await fs8.ensureDir(path7.join(process.cwd(), "contents", "plugins"));
3183
+ await copyContentFeatures(config2);
3184
+ await updateThemeConfig(config2);
3185
+ await updateDevConfig(config2);
3186
+ await updateAppConfig(config2);
3187
+ await updateBillingConfig(config2);
3188
+ await updateRolesConfig(config2);
3189
+ await updateMigrations(config2);
3190
+ await updateTestFiles(config2);
3191
+ await updatePermissionsConfig(config2);
3192
+ await updateEntityPermissions(config2);
3193
+ await updateDashboardConfig(config2);
3194
+ await updateAuthConfig(config2);
3195
+ await updateDashboardUIConfig(config2);
3196
+ await updateDevToolsConfig(config2);
3197
+ await processI18n(config2);
3198
+ await updatePackageJson(config2);
3199
+ if (!isMonorepoProject(config2)) {
3200
+ await updateGitignore(config2);
3201
+ }
3202
+ await generateEnvExample(config2);
3203
+ if (!isMonorepoProject(config2)) {
3204
+ await updateReadme(config2);
3205
+ }
3206
+ await copyEnvExampleToEnv();
3207
+ } finally {
3208
+ if (isMonorepoProject(config2)) {
3209
+ process.chdir(originalCwd);
3210
+ }
3211
+ cachedTemplatesDir = null;
3212
+ }
2435
3213
  }
2436
3214
 
2437
3215
  // src/wizard/presets.ts
2438
3216
  var SAAS_PRESET = {
3217
+ projectType: "web",
2439
3218
  teamMode: "multi-tenant",
2440
3219
  teamRoles: ["owner", "admin", "member", "viewer"],
2441
3220
  defaultLocale: "en",
@@ -2454,6 +3233,7 @@ var SAAS_PRESET = {
2454
3233
  blog: false
2455
3234
  },
2456
3235
  auth: {
3236
+ registrationMode: "open",
2457
3237
  emailPassword: true,
2458
3238
  googleOAuth: true,
2459
3239
  emailVerification: true
@@ -2474,6 +3254,7 @@ var SAAS_PRESET = {
2474
3254
  }
2475
3255
  };
2476
3256
  var BLOG_PRESET = {
3257
+ projectType: "web",
2477
3258
  teamMode: "single-user",
2478
3259
  teamRoles: ["owner"],
2479
3260
  defaultLocale: "en",
@@ -2492,6 +3273,7 @@ var BLOG_PRESET = {
2492
3273
  blog: true
2493
3274
  },
2494
3275
  auth: {
3276
+ registrationMode: "invitation-only",
2495
3277
  emailPassword: true,
2496
3278
  googleOAuth: false,
2497
3279
  emailVerification: false
@@ -2512,6 +3294,7 @@ var BLOG_PRESET = {
2512
3294
  }
2513
3295
  };
2514
3296
  var CRM_PRESET = {
3297
+ projectType: "web",
2515
3298
  teamMode: "single-tenant",
2516
3299
  teamRoles: ["owner", "admin", "member"],
2517
3300
  defaultLocale: "en",
@@ -2530,6 +3313,7 @@ var CRM_PRESET = {
2530
3313
  blog: false
2531
3314
  },
2532
3315
  auth: {
3316
+ registrationMode: "invitation-only",
2533
3317
  emailPassword: true,
2534
3318
  googleOAuth: true,
2535
3319
  emailVerification: true
@@ -2566,11 +3350,13 @@ function getPreset(name) {
2566
3350
  }
2567
3351
  return preset;
2568
3352
  }
2569
- function applyPreset(projectInfo, presetName) {
3353
+ function applyPreset(projectInfo, presetName, typeOverride) {
2570
3354
  const preset = getPreset(presetName);
2571
3355
  return {
2572
3356
  ...projectInfo,
2573
- ...preset
3357
+ ...preset,
3358
+ // Apply type override if provided
3359
+ ...typeOverride && { projectType: typeOverride }
2574
3360
  };
2575
3361
  }
2576
3362
 
@@ -2594,9 +3380,9 @@ var BOX = {
2594
3380
  bottomRight: "\u2518"
2595
3381
  // bottom-right corner
2596
3382
  };
2597
- function getFileTree(config) {
3383
+ function getFileTree(config2) {
2598
3384
  const files = [];
2599
- const themeDir = `contents/themes/${config.projectSlug}`;
3385
+ const themeDir = `contents/themes/${config2.projectSlug}`;
2600
3386
  files.push(`${themeDir}/config/app.config.ts`);
2601
3387
  files.push(`${themeDir}/config/billing.config.ts`);
2602
3388
  files.push(`${themeDir}/config/dashboard.config.ts`);
@@ -2612,7 +3398,7 @@ function getFileTree(config) {
2612
3398
  files.push(`${themeDir}/blocks/hero/block.tsx`);
2613
3399
  files.push(`${themeDir}/blocks/hero/schema.ts`);
2614
3400
  files.push(`${themeDir}/blocks/hero/styles.ts`);
2615
- for (const locale of config.supportedLocales) {
3401
+ for (const locale of config2.supportedLocales) {
2616
3402
  files.push(`${themeDir}/messages/${locale}/common.json`);
2617
3403
  files.push(`${themeDir}/messages/${locale}/auth.json`);
2618
3404
  files.push(`${themeDir}/messages/${locale}/dashboard.json`);
@@ -2625,7 +3411,7 @@ function getFileTree(config) {
2625
3411
  files.push(`${themeDir}/styles/theme.css`);
2626
3412
  files.push(`${themeDir}/styles/components.css`);
2627
3413
  files.push(`${themeDir}/tests/cypress.config.ts`);
2628
- files.push(`${themeDir}/tests/jest/jest.config.ts`);
3414
+ files.push(`${themeDir}/tests/jest/jest.config.cjs`);
2629
3415
  files.push(`${themeDir}/tests/cypress/e2e/auth.cy.ts`);
2630
3416
  files.push(`${themeDir}/tests/cypress/e2e/dashboard.cy.ts`);
2631
3417
  files.push(`${themeDir}/tests/jest/components/hero.test.tsx`);
@@ -2663,10 +3449,10 @@ function groupFilesByCategory(files) {
2663
3449
  function formatFilePath(file, themeDir) {
2664
3450
  return file.replace(`${themeDir}/`, "");
2665
3451
  }
2666
- function showConfigPreview(config) {
2667
- const files = getFileTree(config);
3452
+ function showConfigPreview(config2) {
3453
+ const files = getFileTree(config2);
2668
3454
  const groups = groupFilesByCategory(files);
2669
- const themeDir = `contents/themes/${config.projectSlug}`;
3455
+ const themeDir = `contents/themes/${config2.projectSlug}`;
2670
3456
  console.log("");
2671
3457
  console.log(chalk10.cyan.bold(" Theme Preview"));
2672
3458
  console.log(chalk10.gray(" " + "=".repeat(50)));
@@ -2728,8 +3514,8 @@ function showConfigPreview(config) {
2728
3514
  }
2729
3515
  console.log("");
2730
3516
  console.log(chalk10.gray(" Locales configured:"));
2731
- for (const locale of config.supportedLocales) {
2732
- const isDefault = locale === config.defaultLocale;
3517
+ for (const locale of config2.supportedLocales) {
3518
+ const isDefault = locale === config2.defaultLocale;
2733
3519
  const suffix = isDefault ? chalk10.cyan(" (default)") : "";
2734
3520
  console.log(chalk10.gray(` - `) + chalk10.white(locale) + suffix);
2735
3521
  }
@@ -2753,43 +3539,41 @@ async function runWizard(options = { mode: "interactive" }) {
2753
3539
  try {
2754
3540
  let selectedTheme = null;
2755
3541
  let selectedPlugins = [];
2756
- if (!options.preset) {
2757
- if (options.theme !== void 0) {
2758
- selectedTheme = options.theme === "none" ? null : options.theme;
2759
- showInfo(`Reference theme: ${selectedTheme || "None"}`);
2760
- } else if (options.mode !== "quick") {
2761
- selectedTheme = await promptThemeSelection();
2762
- }
2763
- if (options.plugins !== void 0) {
2764
- selectedPlugins = options.plugins;
2765
- if (selectedPlugins.length > 0) {
2766
- showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
2767
- }
2768
- } else if (options.mode !== "quick" && !options.yes) {
2769
- selectedPlugins = await promptPluginsSelection(selectedTheme);
2770
- } else if (selectedTheme) {
2771
- selectedPlugins = getRequiredPlugins(selectedTheme);
2772
- }
2773
- }
2774
- let config;
3542
+ let config2;
2775
3543
  if (options.preset) {
2776
- config = await runPresetMode(options.preset, options);
3544
+ config2 = await runPresetMode(options.preset, options);
2777
3545
  } else {
2778
3546
  switch (options.mode) {
2779
3547
  case "quick":
2780
- config = await runQuickPrompts();
3548
+ config2 = await runQuickPrompts();
2781
3549
  break;
2782
3550
  case "expert":
2783
- config = await runExpertPrompts();
3551
+ config2 = await runExpertPrompts();
2784
3552
  break;
2785
3553
  case "interactive":
2786
3554
  default:
2787
- config = await runAllPrompts();
3555
+ config2 = await runAllPrompts();
2788
3556
  break;
2789
3557
  }
2790
3558
  }
2791
- showConfigSummary(config);
2792
- showConfigPreview(config);
3559
+ if (options.theme !== void 0) {
3560
+ selectedTheme = options.theme === "none" ? null : options.theme;
3561
+ showInfo(`Reference theme: ${selectedTheme || "None"}`);
3562
+ } else if (!options.preset && options.mode !== "quick") {
3563
+ selectedTheme = await promptThemeSelection();
3564
+ }
3565
+ if (options.plugins !== void 0) {
3566
+ selectedPlugins = options.plugins;
3567
+ if (selectedPlugins.length > 0) {
3568
+ showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
3569
+ }
3570
+ } else if (!options.preset && options.mode !== "quick" && !options.yes) {
3571
+ selectedPlugins = await promptPluginsSelection(selectedTheme);
3572
+ } else if (selectedTheme) {
3573
+ selectedPlugins = getRequiredPlugins(selectedTheme);
3574
+ }
3575
+ showConfigSummary(config2);
3576
+ showConfigPreview(config2);
2793
3577
  if (!options.yes) {
2794
3578
  console.log("");
2795
3579
  const proceed = await confirm5({
@@ -2802,20 +3586,27 @@ async function runWizard(options = { mode: "interactive" }) {
2802
3586
  process.exit(0);
2803
3587
  }
2804
3588
  }
2805
- await copyNpmrc();
2806
3589
  console.log("");
2807
3590
  const coreInstalled = await installCore();
2808
3591
  if (!coreInstalled) {
2809
3592
  showError("Failed to install @nextsparkjs/core. Cannot generate project.");
2810
3593
  process.exit(1);
2811
3594
  }
3595
+ if (config2.projectType === "web-mobile") {
3596
+ console.log("");
3597
+ const mobileInstalled = await installMobile();
3598
+ if (!mobileInstalled) {
3599
+ showError("Failed to install @nextsparkjs/mobile. Cannot generate monorepo project.");
3600
+ process.exit(1);
3601
+ }
3602
+ }
2812
3603
  console.log("");
2813
3604
  const spinner = ora7({
2814
3605
  text: "Generating your NextSpark project...",
2815
3606
  prefixText: " "
2816
3607
  }).start();
2817
3608
  try {
2818
- await generateProject(config);
3609
+ await generateProject(config2);
2819
3610
  spinner.succeed("Project generated successfully!");
2820
3611
  } catch (error) {
2821
3612
  spinner.fail("Failed to generate project");
@@ -2824,14 +3615,19 @@ async function runWizard(options = { mode: "interactive" }) {
2824
3615
  if (selectedTheme || selectedPlugins.length > 0) {
2825
3616
  await installThemeAndPlugins(selectedTheme, selectedPlugins);
2826
3617
  }
3618
+ const projectRoot = process.cwd();
3619
+ const webDir = getWebDir(projectRoot, config2);
3620
+ const isMonorepo = isMonorepoProject(config2);
2827
3621
  const installSpinner = ora7({
2828
- text: "Installing dependencies...",
3622
+ text: isMonorepo ? "Installing dependencies (monorepo)..." : "Installing dependencies...",
2829
3623
  prefixText: " "
2830
3624
  }).start();
2831
3625
  try {
3626
+ installSpinner.stop();
2832
3627
  execSync("pnpm install --force", {
2833
- cwd: process.cwd(),
2834
- stdio: "pipe"
3628
+ cwd: projectRoot,
3629
+ // Always install from root (works for both flat and monorepo)
3630
+ stdio: "inherit"
2835
3631
  });
2836
3632
  installSpinner.succeed("Dependencies installed!");
2837
3633
  } catch (error) {
@@ -2843,22 +3639,25 @@ async function runWizard(options = { mode: "interactive" }) {
2843
3639
  prefixText: " "
2844
3640
  }).start();
2845
3641
  try {
2846
- const projectRoot = process.cwd();
2847
- const registryScript = join7(projectRoot, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
3642
+ const registryScript = join8(webDir, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
3643
+ registrySpinner.stop();
2848
3644
  execSync(`node "${registryScript}" --build`, {
2849
- cwd: projectRoot,
2850
- stdio: "pipe",
3645
+ cwd: webDir,
3646
+ // Run from web directory
3647
+ stdio: "inherit",
2851
3648
  env: {
2852
3649
  ...process.env,
2853
- NEXTSPARK_PROJECT_ROOT: projectRoot
3650
+ NEXTSPARK_PROJECT_ROOT: webDir
2854
3651
  }
2855
3652
  });
2856
3653
  registrySpinner.succeed("Registries built!");
2857
3654
  } catch (error) {
2858
3655
  registrySpinner.fail("Failed to build registries");
2859
- console.log(chalk11.yellow(' Registries will be built automatically when you run "pnpm dev"'));
3656
+ const devCmd = isMonorepo ? "pnpm dev" : "pnpm dev";
3657
+ console.log(chalk11.yellow(` Registries will be built automatically when you run "${devCmd}"`));
2860
3658
  }
2861
- showNextSteps(config, selectedTheme);
3659
+ const aiChoice = await promptAIWorkflowSetup(config2);
3660
+ showNextSteps(config2, selectedTheme, aiChoice);
2862
3661
  } catch (error) {
2863
3662
  if (error instanceof Error) {
2864
3663
  if (error.message.includes("User force closed")) {
@@ -2898,48 +3697,58 @@ async function runPresetMode(presetName, options) {
2898
3697
  console.log("");
2899
3698
  projectInfo = await promptProjectInfo();
2900
3699
  }
2901
- const config = applyPreset(projectInfo, presetName);
2902
- return config;
3700
+ const config2 = applyPreset(projectInfo, presetName, options.type);
3701
+ return config2;
2903
3702
  }
2904
- function showConfigSummary(config) {
3703
+ function showConfigSummary(config2) {
2905
3704
  console.log("");
2906
3705
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2907
3706
  console.log(chalk11.bold.white(" Configuration Summary"));
2908
3707
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2909
3708
  console.log("");
2910
3709
  console.log(chalk11.white(" Project:"));
2911
- console.log(chalk11.gray(` Name: ${chalk11.white(config.projectName)}`));
2912
- console.log(chalk11.gray(` Slug: ${chalk11.white(config.projectSlug)}`));
2913
- console.log(chalk11.gray(` Description: ${chalk11.white(config.projectDescription)}`));
3710
+ console.log(chalk11.gray(` Name: ${chalk11.white(config2.projectName)}`));
3711
+ console.log(chalk11.gray(` Slug: ${chalk11.white(config2.projectSlug)}`));
3712
+ console.log(chalk11.gray(` Description: ${chalk11.white(config2.projectDescription)}`));
3713
+ console.log(chalk11.gray(` Type: ${chalk11.white(config2.projectType === "web-mobile" ? "Web + Mobile (Monorepo)" : "Web only")}`));
2914
3714
  console.log("");
2915
3715
  console.log(chalk11.white(" Team Mode:"));
2916
- console.log(chalk11.gray(` Mode: ${chalk11.white(config.teamMode)}`));
2917
- console.log(chalk11.gray(` Roles: ${chalk11.white(config.teamRoles.join(", "))}`));
3716
+ console.log(chalk11.gray(` Mode: ${chalk11.white(config2.teamMode)}`));
3717
+ console.log(chalk11.gray(` Roles: ${chalk11.white(config2.teamRoles.join(", "))}`));
2918
3718
  console.log("");
2919
3719
  console.log(chalk11.white(" Internationalization:"));
2920
- console.log(chalk11.gray(` Default: ${chalk11.white(config.defaultLocale)}`));
2921
- console.log(chalk11.gray(` Languages: ${chalk11.white(config.supportedLocales.join(", "))}`));
3720
+ console.log(chalk11.gray(` Default: ${chalk11.white(config2.defaultLocale)}`));
3721
+ console.log(chalk11.gray(` Languages: ${chalk11.white(config2.supportedLocales.join(", "))}`));
2922
3722
  console.log("");
2923
3723
  console.log(chalk11.white(" Billing:"));
2924
- console.log(chalk11.gray(` Model: ${chalk11.white(config.billingModel)}`));
2925
- console.log(chalk11.gray(` Currency: ${chalk11.white(config.currency.toUpperCase())}`));
3724
+ console.log(chalk11.gray(` Model: ${chalk11.white(config2.billingModel)}`));
3725
+ console.log(chalk11.gray(` Currency: ${chalk11.white(config2.currency.toUpperCase())}`));
2926
3726
  console.log("");
2927
3727
  console.log(chalk11.white(" Features:"));
2928
- const enabledFeatures = Object.entries(config.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
3728
+ const enabledFeatures = Object.entries(config2.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
2929
3729
  console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledFeatures.join(", ") || "None")}`));
2930
3730
  console.log("");
2931
3731
  console.log(chalk11.white(" Authentication:"));
2932
- const enabledAuth = Object.entries(config.auth).filter(([_, enabled]) => enabled).map(([method]) => formatAuthMethod(method));
3732
+ console.log(chalk11.gray(` Registration: ${chalk11.white(formatRegistrationMode(config2.auth.registrationMode))}`));
3733
+ const enabledAuth = Object.entries(config2.auth).filter(([key, enabled]) => key !== "registrationMode" && enabled).map(([method]) => formatAuthMethod(method));
2933
3734
  console.log(chalk11.gray(` Methods: ${chalk11.white(enabledAuth.join(", ") || "None")}`));
2934
3735
  console.log("");
2935
3736
  console.log(chalk11.white(" Dashboard:"));
2936
- const enabledDashboard = Object.entries(config.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
3737
+ const enabledDashboard = Object.entries(config2.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
2937
3738
  console.log(chalk11.gray(` Features: ${chalk11.white(enabledDashboard.join(", ") || "None")}`));
2938
3739
  console.log("");
2939
3740
  console.log(chalk11.white(" Dev Tools:"));
2940
- const enabledDevTools = Object.entries(config.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
3741
+ const enabledDevTools = Object.entries(config2.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
2941
3742
  console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledDevTools.join(", ") || "None")}`));
2942
3743
  }
3744
+ function formatRegistrationMode(mode) {
3745
+ const mapping = {
3746
+ "open": "Open (anyone can register)",
3747
+ "domain-restricted": "Domain-Restricted (Google OAuth only)",
3748
+ "invitation-only": "Invitation-Only"
3749
+ };
3750
+ return mapping[mode] || mode;
3751
+ }
2943
3752
  function formatAuthMethod(method) {
2944
3753
  const mapping = {
2945
3754
  emailPassword: "Email/Password",
@@ -2964,7 +3773,9 @@ function formatDevTool(tool) {
2964
3773
  };
2965
3774
  return mapping[tool] || tool;
2966
3775
  }
2967
- function showNextSteps(config, referenceTheme = null) {
3776
+ function showNextSteps(config2, referenceTheme = null, aiChoice = "skip") {
3777
+ const isMonorepo = config2.projectType === "web-mobile";
3778
+ const aiSetupDone = aiChoice === "claude";
2968
3779
  console.log("");
2969
3780
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2970
3781
  console.log(chalk11.bold.green(" \u2728 NextSpark project ready!"));
@@ -2972,12 +3783,13 @@ function showNextSteps(config, referenceTheme = null) {
2972
3783
  console.log("");
2973
3784
  console.log(chalk11.bold.white(" Next steps:"));
2974
3785
  console.log("");
2975
- console.log(chalk11.white(" 1. Configure your .env file:"));
2976
- console.log(chalk11.gray(" Edit these values in .env:"));
3786
+ const envPath = isMonorepo ? "web/.env" : ".env";
3787
+ console.log(chalk11.white(" 1. Configure your environment:"));
3788
+ console.log(chalk11.gray(` Edit ${envPath} with your credentials:`));
2977
3789
  console.log("");
2978
3790
  console.log(chalk11.yellow(" DATABASE_URL"));
2979
3791
  console.log(chalk11.gray(" PostgreSQL connection string"));
2980
- console.log(chalk11.dim(" Example: postgresql://user:pass@localhost:5432/mydb"));
3792
+ console.log(chalk11.gray(` Recommended: ${chalk11.cyan("https://supabase.com")} | ${chalk11.cyan("https://neon.com")}`));
2981
3793
  console.log("");
2982
3794
  console.log(chalk11.yellow(" BETTER_AUTH_SECRET"));
2983
3795
  console.log(chalk11.gray(" Generate with:"));
@@ -2989,15 +3801,85 @@ function showNextSteps(config, referenceTheme = null) {
2989
3801
  console.log(chalk11.white(" 3. Start the development server:"));
2990
3802
  console.log(chalk11.cyan(" pnpm dev"));
2991
3803
  console.log("");
3804
+ let nextStep = 4;
3805
+ if (isMonorepo) {
3806
+ console.log(chalk11.white(` ${nextStep}. (Optional) Start the mobile app:`));
3807
+ console.log(chalk11.cyan(" pnpm dev:mobile"));
3808
+ console.log(chalk11.gray(" Or: cd mobile && pnpm start"));
3809
+ console.log("");
3810
+ nextStep++;
3811
+ }
3812
+ if (aiSetupDone) {
3813
+ console.log(chalk11.white(` ${nextStep}. Start building with AI:`));
3814
+ console.log(chalk11.gray(" Open Claude Code in your project and run:"));
3815
+ console.log(chalk11.cyan(" /how-to:start"));
3816
+ console.log("");
3817
+ } else {
3818
+ console.log(chalk11.white(` ${nextStep}. (Optional) Setup AI workflows:`));
3819
+ console.log(chalk11.cyan(" nextspark setup:ai"));
3820
+ console.log("");
3821
+ }
2992
3822
  console.log(chalk11.gray(" " + "-".repeat(60)));
2993
- console.log(chalk11.gray(` Theme: ${chalk11.white(`contents/themes/${config.projectSlug}/`)}`));
2994
- console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config.projectSlug}`)}`));
3823
+ if (isMonorepo) {
3824
+ console.log(chalk11.gray(` Structure: ${chalk11.white("Monorepo (web/ + mobile/)")}`));
3825
+ console.log(chalk11.gray(` Web theme: ${chalk11.white(`web/contents/themes/${config2.projectSlug}/`)}`));
3826
+ console.log(chalk11.gray(` Mobile app: ${chalk11.white("mobile/")}`));
3827
+ console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
3828
+ } else {
3829
+ console.log(chalk11.gray(` Theme: ${chalk11.white(`contents/themes/${config2.projectSlug}/`)}`));
3830
+ console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
3831
+ }
2995
3832
  if (referenceTheme) {
2996
- console.log(chalk11.gray(` Reference: ${chalk11.white(`contents/themes/${referenceTheme}/`)}`));
3833
+ const refPath = isMonorepo ? `web/contents/themes/${referenceTheme}/` : `contents/themes/${referenceTheme}/`;
3834
+ console.log(chalk11.gray(` Reference: ${chalk11.white(refPath)}`));
2997
3835
  }
2998
- console.log(chalk11.gray(" Docs: https://nextspark.dev/docs"));
3836
+ console.log(chalk11.gray(` Docs: ${chalk11.cyan("https://nextspark.dev/docs")}`));
2999
3837
  console.log("");
3000
3838
  }
3839
+ async function promptAIWorkflowSetup(config2) {
3840
+ console.log("");
3841
+ const choice = await select7({
3842
+ message: "Setup AI-assisted development workflows?",
3843
+ choices: [
3844
+ { name: "Claude Code (Recommended)", value: "claude" },
3845
+ { name: "Cursor (Coming soon)", value: "cursor" },
3846
+ { name: "Antigravity (Coming soon)", value: "antigravity" },
3847
+ { name: "Skip for now", value: "skip" }
3848
+ ]
3849
+ });
3850
+ if (choice === "skip") {
3851
+ return "skip";
3852
+ }
3853
+ if (choice === "cursor" || choice === "antigravity") {
3854
+ showInfo(`${choice} support is coming soon. For now, use Claude Code.`);
3855
+ return "skip";
3856
+ }
3857
+ const projectRoot = process.cwd();
3858
+ const isMonorepo = isMonorepoProject(config2);
3859
+ try {
3860
+ execSync("pnpm add -D -w @nextsparkjs/ai-workflow", {
3861
+ cwd: projectRoot,
3862
+ stdio: "inherit"
3863
+ });
3864
+ let setupScript = join8(projectRoot, "node_modules", "@nextsparkjs", "ai-workflow", "scripts", "setup.mjs");
3865
+ if (!existsSync7(setupScript) && isMonorepo) {
3866
+ setupScript = join8(projectRoot, "web", "node_modules", "@nextsparkjs", "ai-workflow", "scripts", "setup.mjs");
3867
+ }
3868
+ if (existsSync7(setupScript)) {
3869
+ execSync(`node "${setupScript}" ${choice}`, {
3870
+ cwd: projectRoot,
3871
+ stdio: "inherit"
3872
+ });
3873
+ showSuccess("AI workflow setup complete!");
3874
+ } else {
3875
+ showWarning('AI workflow package installed but setup script not found. Run "nextspark setup:ai" manually.');
3876
+ }
3877
+ } catch (error) {
3878
+ showError('Failed to install AI workflow package. Run "nextspark setup:ai" later.');
3879
+ return "skip";
3880
+ }
3881
+ return choice;
3882
+ }
3001
3883
  function findLocalCoreTarball() {
3002
3884
  const cwd = process.cwd();
3003
3885
  try {
@@ -3006,14 +3888,14 @@ function findLocalCoreTarball() {
3006
3888
  (f) => f.includes("nextsparkjs-core") && f.endsWith(".tgz")
3007
3889
  );
3008
3890
  if (coreTarball) {
3009
- return join7(cwd, coreTarball);
3891
+ return join8(cwd, coreTarball);
3010
3892
  }
3011
3893
  } catch {
3012
3894
  }
3013
3895
  return null;
3014
3896
  }
3015
3897
  function isCoreInstalled() {
3016
- const corePath = join7(process.cwd(), "node_modules", "@nextsparkjs", "core");
3898
+ const corePath = join8(process.cwd(), "node_modules", "@nextsparkjs", "core");
3017
3899
  return existsSync7(corePath);
3018
3900
  }
3019
3901
  async function installCore() {
@@ -3031,8 +3913,8 @@ async function installCore() {
3031
3913
  packageSpec = localTarball;
3032
3914
  spinner.text = "Installing @nextsparkjs/core from local tarball...";
3033
3915
  }
3034
- const useYarn = existsSync7(join7(process.cwd(), "yarn.lock"));
3035
- const usePnpm = existsSync7(join7(process.cwd(), "pnpm-lock.yaml"));
3916
+ const useYarn = existsSync7(join8(process.cwd(), "yarn.lock"));
3917
+ const usePnpm = existsSync7(join8(process.cwd(), "pnpm-lock.yaml"));
3036
3918
  let installCmd;
3037
3919
  if (usePnpm) {
3038
3920
  installCmd = `pnpm add ${packageSpec}`;
@@ -3041,8 +3923,9 @@ async function installCore() {
3041
3923
  } else {
3042
3924
  installCmd = `npm install ${packageSpec}`;
3043
3925
  }
3926
+ spinner.stop();
3044
3927
  execSync(installCmd, {
3045
- stdio: "pipe",
3928
+ stdio: "inherit",
3046
3929
  cwd: process.cwd()
3047
3930
  });
3048
3931
  spinner.succeed(chalk11.green("@nextsparkjs/core installed successfully!"));
@@ -3056,17 +3939,64 @@ async function installCore() {
3056
3939
  return false;
3057
3940
  }
3058
3941
  }
3059
- async function copyNpmrc() {
3060
- const npmrcPath = join7(process.cwd(), ".npmrc");
3061
- if (existsSync7(npmrcPath)) {
3062
- return;
3942
+ function isMobileInstalled() {
3943
+ const mobilePath = join8(process.cwd(), "node_modules", "@nextsparkjs", "mobile");
3944
+ return existsSync7(mobilePath);
3945
+ }
3946
+ function findLocalMobileTarball() {
3947
+ const cwd = process.cwd();
3948
+ try {
3949
+ const files = readdirSync(cwd);
3950
+ const mobileTarball = files.find(
3951
+ (f) => f.includes("nextsparkjs-mobile") && f.endsWith(".tgz")
3952
+ );
3953
+ if (mobileTarball) {
3954
+ return join8(cwd, mobileTarball);
3955
+ }
3956
+ } catch {
3957
+ }
3958
+ return null;
3959
+ }
3960
+ async function installMobile() {
3961
+ if (isMobileInstalled()) {
3962
+ return true;
3963
+ }
3964
+ const spinner = ora7({
3965
+ text: "Installing @nextsparkjs/mobile...",
3966
+ prefixText: " "
3967
+ }).start();
3968
+ try {
3969
+ const localTarball = findLocalMobileTarball();
3970
+ let packageSpec = "@nextsparkjs/mobile";
3971
+ if (localTarball) {
3972
+ packageSpec = localTarball;
3973
+ spinner.text = "Installing @nextsparkjs/mobile from local tarball...";
3974
+ }
3975
+ const useYarn = existsSync7(join8(process.cwd(), "yarn.lock"));
3976
+ const usePnpm = existsSync7(join8(process.cwd(), "pnpm-lock.yaml"));
3977
+ let installCmd;
3978
+ if (usePnpm) {
3979
+ installCmd = `pnpm add ${packageSpec}`;
3980
+ } else if (useYarn) {
3981
+ installCmd = `yarn add ${packageSpec}`;
3982
+ } else {
3983
+ installCmd = `npm install ${packageSpec}`;
3984
+ }
3985
+ spinner.stop();
3986
+ execSync(installCmd, {
3987
+ stdio: "inherit",
3988
+ cwd: process.cwd()
3989
+ });
3990
+ spinner.succeed(chalk11.green("@nextsparkjs/mobile installed successfully!"));
3991
+ return true;
3992
+ } catch (error) {
3993
+ spinner.fail(chalk11.red("Failed to install @nextsparkjs/mobile"));
3994
+ if (error instanceof Error) {
3995
+ console.log(chalk11.red(` Error: ${error.message}`));
3996
+ }
3997
+ console.log(chalk11.gray(" Hint: Make sure the package is available (npm registry or local tarball)"));
3998
+ return false;
3063
3999
  }
3064
- const npmrcContent = `# Hoist @nextsparkjs/core dependencies so they're accessible from the project
3065
- # This is required for pnpm to make peer dependencies available
3066
- public-hoist-pattern[]=*
3067
- `;
3068
- const { writeFileSync: writeFileSync3 } = await import("fs");
3069
- writeFileSync3(npmrcPath, npmrcContent);
3070
4000
  }
3071
4001
 
3072
4002
  // src/commands/init.ts
@@ -3081,10 +4011,10 @@ function parsePlugins(pluginsStr) {
3081
4011
  }
3082
4012
  function hasExistingProject() {
3083
4013
  const projectRoot = process.cwd();
3084
- return existsSync8(join8(projectRoot, "contents")) || existsSync8(join8(projectRoot, ".nextspark"));
4014
+ return existsSync8(join9(projectRoot, "contents")) || existsSync8(join9(projectRoot, ".nextspark"));
3085
4015
  }
3086
4016
  function generateInitialRegistries(registriesDir) {
3087
- writeFileSync2(join8(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
4017
+ writeFileSync2(join9(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
3088
4018
  import type { ComponentType } from 'react'
3089
4019
 
3090
4020
  export const BLOCK_REGISTRY: Record<string, {
@@ -3097,26 +4027,26 @@ export const BLOCK_REGISTRY: Record<string, {
3097
4027
 
3098
4028
  export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
3099
4029
  `);
3100
- writeFileSync2(join8(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
4030
+ writeFileSync2(join9(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
3101
4031
  export const THEME_REGISTRY: Record<string, unknown> = {}
3102
4032
  `);
3103
- writeFileSync2(join8(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
4033
+ writeFileSync2(join9(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
3104
4034
  export const ENTITY_REGISTRY: Record<string, unknown> = {}
3105
4035
  `);
3106
- writeFileSync2(join8(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
4036
+ writeFileSync2(join9(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
3107
4037
  export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
3108
4038
  export function parseChildEntity(path: string) { return null }
3109
4039
  export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
3110
4040
  export function clientMetaSystemAdapter() { return {} }
3111
4041
  export type ClientEntityConfig = Record<string, unknown>
3112
4042
  `);
3113
- writeFileSync2(join8(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
4043
+ writeFileSync2(join9(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
3114
4044
  export const BILLING_REGISTRY = { plans: [], features: [] }
3115
4045
  `);
3116
- writeFileSync2(join8(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
4046
+ writeFileSync2(join9(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
3117
4047
  export const PLUGIN_REGISTRY: Record<string, unknown> = {}
3118
4048
  `);
3119
- writeFileSync2(join8(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
4049
+ writeFileSync2(join9(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
3120
4050
  export const FLOW_REGISTRY: Record<string, unknown> = {}
3121
4051
  export const FEATURE_REGISTRY: Record<string, unknown> = {}
3122
4052
  export const TAGS_REGISTRY: Record<string, unknown> = {}
@@ -3124,11 +4054,11 @@ export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
3124
4054
  export type FlowEntry = unknown
3125
4055
  export type FeatureEntry = unknown
3126
4056
  `);
3127
- writeFileSync2(join8(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
4057
+ writeFileSync2(join9(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
3128
4058
  export const DOCS_REGISTRY = { sections: [], pages: [] }
3129
4059
  export type DocSectionMeta = { title: string; slug: string }
3130
4060
  `);
3131
- writeFileSync2(join8(registriesDir, "index.ts"), `// Auto-generated by nextspark init
4061
+ writeFileSync2(join9(registriesDir, "index.ts"), `// Auto-generated by nextspark init
3132
4062
  export * from './block-registry'
3133
4063
  export * from './theme-registry'
3134
4064
  export * from './entity-registry'
@@ -3143,15 +4073,15 @@ async function simpleInit(options) {
3143
4073
  const spinner = ora8("Initializing NextSpark project...").start();
3144
4074
  const projectRoot = process.cwd();
3145
4075
  try {
3146
- const nextspark = join8(projectRoot, ".nextspark");
3147
- const registriesDir = join8(nextspark, "registries");
4076
+ const nextspark = join9(projectRoot, ".nextspark");
4077
+ const registriesDir = join9(nextspark, "registries");
3148
4078
  if (!existsSync8(registriesDir) || options.force) {
3149
4079
  mkdirSync2(registriesDir, { recursive: true });
3150
4080
  spinner.text = "Creating .nextspark/registries/";
3151
4081
  generateInitialRegistries(registriesDir);
3152
4082
  spinner.text = "Generated initial registries";
3153
4083
  }
3154
- const tsconfigPath = join8(projectRoot, "tsconfig.json");
4084
+ const tsconfigPath = join9(projectRoot, "tsconfig.json");
3155
4085
  if (existsSync8(tsconfigPath)) {
3156
4086
  spinner.text = "Updating tsconfig.json paths...";
3157
4087
  const tsconfig = JSON.parse(readFileSync7(tsconfigPath, "utf-8"));
@@ -3163,7 +4093,7 @@ async function simpleInit(options) {
3163
4093
  };
3164
4094
  writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2));
3165
4095
  }
3166
- const envExample = join8(projectRoot, ".env.example");
4096
+ const envExample = join9(projectRoot, ".env.example");
3167
4097
  if (!existsSync8(envExample)) {
3168
4098
  const envContent = `# NextSpark Configuration
3169
4099
  DATABASE_URL="postgresql://user:password@localhost:5432/db"
@@ -3206,25 +4136,133 @@ async function initCommand(options) {
3206
4136
  yes: options.yes,
3207
4137
  name: options.name,
3208
4138
  slug: options.slug,
3209
- description: options.description
4139
+ description: options.description,
4140
+ type: options.type
3210
4141
  };
3211
4142
  await runWizard(wizardOptions);
3212
4143
  }
3213
4144
 
4145
+ // src/commands/add-mobile.ts
4146
+ import { existsSync as existsSync9, cpSync as cpSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3, renameSync, mkdirSync as mkdirSync3 } from "fs";
4147
+ import { join as join10 } from "path";
4148
+ import chalk13 from "chalk";
4149
+ import ora9 from "ora";
4150
+ import { execSync as execSync2 } from "child_process";
4151
+ function findMobileCoreDir() {
4152
+ const projectRoot = process.cwd();
4153
+ const npmPath = join10(projectRoot, "node_modules", "@nextsparkjs", "mobile");
4154
+ if (existsSync9(npmPath)) {
4155
+ return npmPath;
4156
+ }
4157
+ const monoPath = join10(projectRoot, "packages", "mobile");
4158
+ if (existsSync9(monoPath)) {
4159
+ return monoPath;
4160
+ }
4161
+ const parentNpmPath = join10(projectRoot, "..", "node_modules", "@nextsparkjs", "mobile");
4162
+ if (existsSync9(parentNpmPath)) {
4163
+ return parentNpmPath;
4164
+ }
4165
+ throw new Error(
4166
+ "Could not find @nextsparkjs/mobile package.\nRun: npm install @nextsparkjs/mobile"
4167
+ );
4168
+ }
4169
+ async function addMobileCommand(options = {}) {
4170
+ const projectRoot = process.cwd();
4171
+ const mobileDir = join10(projectRoot, "mobile");
4172
+ console.log();
4173
+ console.log(chalk13.bold("Adding NextSpark Mobile App"));
4174
+ console.log();
4175
+ if (existsSync9(mobileDir) && !options.force) {
4176
+ console.log(chalk13.red("Error: Mobile app already exists at mobile/"));
4177
+ console.log(chalk13.gray("Use --force to overwrite"));
4178
+ process.exit(1);
4179
+ }
4180
+ let mobileCoreDir;
4181
+ try {
4182
+ mobileCoreDir = findMobileCoreDir();
4183
+ } catch (error) {
4184
+ console.log(chalk13.red(error.message));
4185
+ process.exit(1);
4186
+ }
4187
+ const templatesDir = join10(mobileCoreDir, "templates");
4188
+ if (!existsSync9(templatesDir)) {
4189
+ console.log(chalk13.red("Error: Could not find mobile templates"));
4190
+ console.log(chalk13.gray(`Expected at: ${templatesDir}`));
4191
+ process.exit(1);
4192
+ }
4193
+ const copySpinner = ora9("Copying mobile app template...").start();
4194
+ try {
4195
+ mkdirSync3(mobileDir, { recursive: true });
4196
+ cpSync2(templatesDir, mobileDir, { recursive: true });
4197
+ const pkgTemplatePath = join10(mobileDir, "package.json.template");
4198
+ const pkgPath = join10(mobileDir, "package.json");
4199
+ if (existsSync9(pkgTemplatePath)) {
4200
+ renameSync(pkgTemplatePath, pkgPath);
4201
+ const pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
4202
+ const rootPkgPath = join10(projectRoot, "package.json");
4203
+ if (existsSync9(rootPkgPath)) {
4204
+ const rootPkg = JSON.parse(readFileSync8(rootPkgPath, "utf-8"));
4205
+ const rawName = rootPkg.name || "my-project";
4206
+ if (rawName.startsWith("@")) {
4207
+ const scopeMatch = rawName.match(/^@[\w-]+/);
4208
+ const scope = scopeMatch ? scopeMatch[0] : "";
4209
+ const projectName = rawName.replace(/^@[\w-]+\//, "");
4210
+ pkg2.name = `${scope}/${projectName}-mobile`;
4211
+ } else {
4212
+ pkg2.name = `${rawName}-mobile`;
4213
+ }
4214
+ }
4215
+ writeFileSync3(pkgPath, JSON.stringify(pkg2, null, 2));
4216
+ }
4217
+ copySpinner.succeed("Mobile app template copied");
4218
+ } catch (error) {
4219
+ copySpinner.fail("Failed to copy templates");
4220
+ console.log(chalk13.red(error.message));
4221
+ process.exit(1);
4222
+ }
4223
+ if (!options.skipInstall) {
4224
+ const installSpinner = ora9("Installing dependencies...").start();
4225
+ try {
4226
+ execSync2("npm install", {
4227
+ cwd: mobileDir,
4228
+ stdio: "pipe",
4229
+ timeout: 3e5
4230
+ // 5 minutes
4231
+ });
4232
+ installSpinner.succeed("Dependencies installed");
4233
+ } catch (error) {
4234
+ installSpinner.fail("Failed to install dependencies");
4235
+ console.log(chalk13.yellow(" Run `npm install` in mobile/ manually"));
4236
+ }
4237
+ }
4238
+ console.log();
4239
+ console.log(chalk13.green.bold(" Mobile app created successfully!"));
4240
+ console.log();
4241
+ console.log(chalk13.bold(" Next steps:"));
4242
+ console.log();
4243
+ console.log(` ${chalk13.cyan("1.")} cd mobile`);
4244
+ console.log(` ${chalk13.cyan("2.")} Update ${chalk13.bold("app.config.ts")} with your app name and bundle ID`);
4245
+ console.log(` ${chalk13.cyan("3.")} Add your entities in ${chalk13.bold("src/entities/")}`);
4246
+ console.log(` ${chalk13.cyan("4.")} npm start`);
4247
+ console.log();
4248
+ console.log(chalk13.gray(" Documentation: https://nextspark.dev/docs/mobile"));
4249
+ console.log();
4250
+ }
4251
+
3214
4252
  // src/commands/doctor.ts
3215
- import chalk14 from "chalk";
4253
+ import chalk15 from "chalk";
3216
4254
 
3217
4255
  // src/doctor/index.ts
3218
- import chalk13 from "chalk";
4256
+ import chalk14 from "chalk";
3219
4257
 
3220
4258
  // src/doctor/checks/dependencies.ts
3221
- import fs8 from "fs-extra";
3222
- import path6 from "path";
4259
+ import fs9 from "fs-extra";
4260
+ import path8 from "path";
3223
4261
  async function checkDependencies() {
3224
4262
  const cwd = process.cwd();
3225
- const nodeModulesPath = path6.join(cwd, "node_modules");
3226
- const packageJsonPath = path6.join(cwd, "package.json");
3227
- if (!await fs8.pathExists(packageJsonPath)) {
4263
+ const nodeModulesPath = path8.join(cwd, "node_modules");
4264
+ const packageJsonPath = path8.join(cwd, "package.json");
4265
+ if (!await fs9.pathExists(packageJsonPath)) {
3228
4266
  return {
3229
4267
  name: "Dependencies",
3230
4268
  status: "fail",
@@ -3232,7 +4270,7 @@ async function checkDependencies() {
3232
4270
  fix: "Ensure you are in a NextSpark project directory"
3233
4271
  };
3234
4272
  }
3235
- if (!await fs8.pathExists(nodeModulesPath)) {
4273
+ if (!await fs9.pathExists(nodeModulesPath)) {
3236
4274
  return {
3237
4275
  name: "Dependencies",
3238
4276
  status: "fail",
@@ -3242,7 +4280,7 @@ async function checkDependencies() {
3242
4280
  }
3243
4281
  let packageJson;
3244
4282
  try {
3245
- packageJson = await fs8.readJson(packageJsonPath);
4283
+ packageJson = await fs9.readJson(packageJsonPath);
3246
4284
  } catch {
3247
4285
  return {
3248
4286
  name: "Dependencies",
@@ -3259,14 +4297,14 @@ async function checkDependencies() {
3259
4297
  const criticalDeps = ["next", "react", "react-dom"];
3260
4298
  for (const dep of criticalDeps) {
3261
4299
  if (allDependencies[dep]) {
3262
- const depPath = path6.join(nodeModulesPath, dep);
3263
- if (!await fs8.pathExists(depPath)) {
4300
+ const depPath = path8.join(nodeModulesPath, dep);
4301
+ if (!await fs9.pathExists(depPath)) {
3264
4302
  missingDeps.push(dep);
3265
4303
  }
3266
4304
  }
3267
4305
  }
3268
- const nextsparksCorePath = path6.join(nodeModulesPath, "@nextsparkjs", "core");
3269
- const hasNextSparkCore = await fs8.pathExists(nextsparksCorePath);
4306
+ const nextsparksCorePath = path8.join(nodeModulesPath, "@nextsparkjs", "core");
4307
+ const hasNextSparkCore = await fs9.pathExists(nextsparksCorePath);
3270
4308
  if (missingDeps.length > 0) {
3271
4309
  return {
3272
4310
  name: "Dependencies",
@@ -3291,8 +4329,8 @@ async function checkDependencies() {
3291
4329
  }
3292
4330
 
3293
4331
  // src/doctor/checks/config.ts
3294
- import fs9 from "fs-extra";
3295
- import path7 from "path";
4332
+ import fs10 from "fs-extra";
4333
+ import path9 from "path";
3296
4334
  var REQUIRED_CONFIG_FILES = [
3297
4335
  "next.config.ts",
3298
4336
  "tailwind.config.ts",
@@ -3303,13 +4341,13 @@ async function checkConfigs() {
3303
4341
  const missingFiles = [];
3304
4342
  const invalidFiles = [];
3305
4343
  for (const file of REQUIRED_CONFIG_FILES) {
3306
- const filePath = path7.join(cwd, file);
4344
+ const filePath = path9.join(cwd, file);
3307
4345
  if (file.endsWith(".ts")) {
3308
- const jsPath = path7.join(cwd, file.replace(".ts", ".js"));
3309
- const mjsPath = path7.join(cwd, file.replace(".ts", ".mjs"));
3310
- const tsExists = await fs9.pathExists(filePath);
3311
- const jsExists = await fs9.pathExists(jsPath);
3312
- const mjsExists = await fs9.pathExists(mjsPath);
4346
+ const jsPath = path9.join(cwd, file.replace(".ts", ".js"));
4347
+ const mjsPath = path9.join(cwd, file.replace(".ts", ".mjs"));
4348
+ const tsExists = await fs10.pathExists(filePath);
4349
+ const jsExists = await fs10.pathExists(jsPath);
4350
+ const mjsExists = await fs10.pathExists(mjsPath);
3313
4351
  if (!tsExists && !jsExists && !mjsExists) {
3314
4352
  if (!file.includes("next.config") && !file.includes("tailwind.config")) {
3315
4353
  missingFiles.push(file);
@@ -3317,14 +4355,14 @@ async function checkConfigs() {
3317
4355
  }
3318
4356
  continue;
3319
4357
  }
3320
- if (!await fs9.pathExists(filePath)) {
4358
+ if (!await fs10.pathExists(filePath)) {
3321
4359
  missingFiles.push(file);
3322
4360
  }
3323
4361
  }
3324
- const tsconfigPath = path7.join(cwd, "tsconfig.json");
3325
- if (await fs9.pathExists(tsconfigPath)) {
4362
+ const tsconfigPath = path9.join(cwd, "tsconfig.json");
4363
+ if (await fs10.pathExists(tsconfigPath)) {
3326
4364
  try {
3327
- const content = await fs9.readFile(tsconfigPath, "utf-8");
4365
+ const content = await fs10.readFile(tsconfigPath, "utf-8");
3328
4366
  JSON.parse(content);
3329
4367
  } catch {
3330
4368
  invalidFiles.push("tsconfig.json");
@@ -3332,14 +4370,14 @@ async function checkConfigs() {
3332
4370
  } else {
3333
4371
  missingFiles.push("tsconfig.json");
3334
4372
  }
3335
- const configDir = path7.join(cwd, "config");
3336
- if (await fs9.pathExists(configDir)) {
3337
- const configFiles = await fs9.readdir(configDir);
4373
+ const configDir = path9.join(cwd, "config");
4374
+ if (await fs10.pathExists(configDir)) {
4375
+ const configFiles = await fs10.readdir(configDir);
3338
4376
  const tsConfigFiles = configFiles.filter((f) => f.endsWith(".ts"));
3339
4377
  for (const file of tsConfigFiles) {
3340
- const filePath = path7.join(configDir, file);
4378
+ const filePath = path9.join(configDir, file);
3341
4379
  try {
3342
- const content = await fs9.readFile(filePath, "utf-8");
4380
+ const content = await fs10.readFile(filePath, "utf-8");
3343
4381
  if (content.trim().length === 0) {
3344
4382
  invalidFiles.push(`config/${file}`);
3345
4383
  }
@@ -3372,8 +4410,8 @@ async function checkConfigs() {
3372
4410
  }
3373
4411
 
3374
4412
  // src/doctor/checks/database.ts
3375
- import fs10 from "fs-extra";
3376
- import path8 from "path";
4413
+ import fs11 from "fs-extra";
4414
+ import path10 from "path";
3377
4415
  function parseEnvFile(content) {
3378
4416
  const result = {};
3379
4417
  const lines = content.split("\n");
@@ -3396,25 +4434,25 @@ function parseEnvFile(content) {
3396
4434
  }
3397
4435
  async function checkDatabase() {
3398
4436
  const cwd = process.cwd();
3399
- const envPath = path8.join(cwd, ".env");
3400
- const envLocalPath = path8.join(cwd, ".env.local");
4437
+ const envPath = path10.join(cwd, ".env");
4438
+ const envLocalPath = path10.join(cwd, ".env.local");
3401
4439
  let envVars = {};
3402
- if (await fs10.pathExists(envLocalPath)) {
4440
+ if (await fs11.pathExists(envLocalPath)) {
3403
4441
  try {
3404
- const content = await fs10.readFile(envLocalPath, "utf-8");
4442
+ const content = await fs11.readFile(envLocalPath, "utf-8");
3405
4443
  envVars = { ...envVars, ...parseEnvFile(content) };
3406
4444
  } catch {
3407
4445
  }
3408
4446
  }
3409
- if (await fs10.pathExists(envPath)) {
4447
+ if (await fs11.pathExists(envPath)) {
3410
4448
  try {
3411
- const content = await fs10.readFile(envPath, "utf-8");
4449
+ const content = await fs11.readFile(envPath, "utf-8");
3412
4450
  envVars = { ...envVars, ...parseEnvFile(content) };
3413
4451
  } catch {
3414
4452
  }
3415
4453
  }
3416
4454
  const databaseUrl = envVars["DATABASE_URL"] || process.env.DATABASE_URL;
3417
- if (!await fs10.pathExists(envPath) && !await fs10.pathExists(envLocalPath)) {
4455
+ if (!await fs11.pathExists(envPath) && !await fs11.pathExists(envLocalPath)) {
3418
4456
  return {
3419
4457
  name: "Database",
3420
4458
  status: "warn",
@@ -3455,22 +4493,22 @@ async function checkDatabase() {
3455
4493
  }
3456
4494
 
3457
4495
  // src/doctor/checks/imports.ts
3458
- import fs11 from "fs-extra";
3459
- import path9 from "path";
4496
+ import fs12 from "fs-extra";
4497
+ import path11 from "path";
3460
4498
  async function checkCorePackage(cwd) {
3461
- const nodeModulesPath = path9.join(cwd, "node_modules", "@nextsparkjs", "core");
3462
- if (!await fs11.pathExists(nodeModulesPath)) {
4499
+ const nodeModulesPath = path11.join(cwd, "node_modules", "@nextsparkjs", "core");
4500
+ if (!await fs12.pathExists(nodeModulesPath)) {
3463
4501
  return { accessible: false, reason: "@nextsparkjs/core not found in node_modules" };
3464
4502
  }
3465
- const packageJsonPath = path9.join(nodeModulesPath, "package.json");
3466
- if (!await fs11.pathExists(packageJsonPath)) {
4503
+ const packageJsonPath = path11.join(nodeModulesPath, "package.json");
4504
+ if (!await fs12.pathExists(packageJsonPath)) {
3467
4505
  return { accessible: false, reason: "@nextsparkjs/core package.json not found" };
3468
4506
  }
3469
4507
  try {
3470
- const packageJson = await fs11.readJson(packageJsonPath);
4508
+ const packageJson = await fs12.readJson(packageJsonPath);
3471
4509
  const mainEntry = packageJson.main || packageJson.module || "./dist/index.js";
3472
- const mainPath = path9.join(nodeModulesPath, mainEntry);
3473
- if (!await fs11.pathExists(mainPath)) {
4510
+ const mainPath = path11.join(nodeModulesPath, mainEntry);
4511
+ if (!await fs12.pathExists(mainPath)) {
3474
4512
  return { accessible: false, reason: "@nextsparkjs/core entry point not found" };
3475
4513
  }
3476
4514
  } catch {
@@ -3483,8 +4521,8 @@ async function scanForBrokenImports(cwd) {
3483
4521
  const srcDirs = ["src", "app", "pages", "components", "lib"];
3484
4522
  const existingDirs = [];
3485
4523
  for (const dir of srcDirs) {
3486
- const dirPath = path9.join(cwd, dir);
3487
- if (await fs11.pathExists(dirPath)) {
4524
+ const dirPath = path11.join(cwd, dir);
4525
+ if (await fs12.pathExists(dirPath)) {
3488
4526
  existingDirs.push(dirPath);
3489
4527
  }
3490
4528
  }
@@ -3493,32 +4531,32 @@ async function scanForBrokenImports(cwd) {
3493
4531
  for (const dir of existingDirs) {
3494
4532
  if (filesScanned >= maxFilesToScan) break;
3495
4533
  try {
3496
- const files = await fs11.readdir(dir, { withFileTypes: true });
4534
+ const files = await fs12.readdir(dir, { withFileTypes: true });
3497
4535
  for (const file of files) {
3498
4536
  if (filesScanned >= maxFilesToScan) break;
3499
4537
  if (file.isFile() && (file.name.endsWith(".ts") || file.name.endsWith(".tsx"))) {
3500
- const filePath = path9.join(dir, file.name);
4538
+ const filePath = path11.join(dir, file.name);
3501
4539
  try {
3502
- const content = await fs11.readFile(filePath, "utf-8");
4540
+ const content = await fs12.readFile(filePath, "utf-8");
3503
4541
  const importMatches = content.match(/from ['"](@?[^'"]+)['"]/g);
3504
4542
  if (importMatches) {
3505
4543
  for (const match of importMatches) {
3506
4544
  const importPath = match.replace(/from ['"]/g, "").replace(/['"]/g, "");
3507
4545
  if (importPath.startsWith(".")) {
3508
- const absoluteImportPath = path9.resolve(path9.dirname(filePath), importPath);
4546
+ const absoluteImportPath = path11.resolve(path11.dirname(filePath), importPath);
3509
4547
  const possiblePaths = [
3510
4548
  absoluteImportPath,
3511
4549
  `${absoluteImportPath}.ts`,
3512
4550
  `${absoluteImportPath}.tsx`,
3513
4551
  `${absoluteImportPath}.js`,
3514
4552
  `${absoluteImportPath}.jsx`,
3515
- path9.join(absoluteImportPath, "index.ts"),
3516
- path9.join(absoluteImportPath, "index.tsx"),
3517
- path9.join(absoluteImportPath, "index.js")
4553
+ path11.join(absoluteImportPath, "index.ts"),
4554
+ path11.join(absoluteImportPath, "index.tsx"),
4555
+ path11.join(absoluteImportPath, "index.js")
3518
4556
  ];
3519
4557
  const exists = await Promise.any(
3520
4558
  possiblePaths.map(async (p) => {
3521
- if (await fs11.pathExists(p)) return true;
4559
+ if (await fs12.pathExists(p)) return true;
3522
4560
  throw new Error("Not found");
3523
4561
  })
3524
4562
  ).catch(() => false);
@@ -3542,11 +4580,11 @@ async function checkImports() {
3542
4580
  const cwd = process.cwd();
3543
4581
  const coreCheck = await checkCorePackage(cwd);
3544
4582
  if (!coreCheck.accessible) {
3545
- const packageJsonPath = path9.join(cwd, "package.json");
4583
+ const packageJsonPath = path11.join(cwd, "package.json");
3546
4584
  let hasCoreDep = false;
3547
- if (await fs11.pathExists(packageJsonPath)) {
4585
+ if (await fs12.pathExists(packageJsonPath)) {
3548
4586
  try {
3549
- const packageJson = await fs11.readJson(packageJsonPath);
4587
+ const packageJson = await fs12.readJson(packageJsonPath);
3550
4588
  hasCoreDep = !!(packageJson.dependencies?.["@nextsparkjs/core"] || packageJson.devDependencies?.["@nextsparkjs/core"]);
3551
4589
  } catch {
3552
4590
  }
@@ -3583,25 +4621,25 @@ async function checkImports() {
3583
4621
 
3584
4622
  // src/doctor/index.ts
3585
4623
  var STATUS_ICONS = {
3586
- pass: chalk13.green("\u2713"),
3587
- warn: chalk13.yellow("\u26A0"),
3588
- fail: chalk13.red("\u2717")
4624
+ pass: chalk14.green("\u2713"),
4625
+ warn: chalk14.yellow("\u26A0"),
4626
+ fail: chalk14.red("\u2717")
3589
4627
  };
3590
4628
  function formatResult(result) {
3591
4629
  const icon = STATUS_ICONS[result.status];
3592
- const nameColor = result.status === "fail" ? chalk13.red : result.status === "warn" ? chalk13.yellow : chalk13.white;
4630
+ const nameColor = result.status === "fail" ? chalk14.red : result.status === "warn" ? chalk14.yellow : chalk14.white;
3593
4631
  const name = nameColor(result.name.padEnd(18));
3594
- const message = chalk13.gray(result.message);
4632
+ const message = chalk14.gray(result.message);
3595
4633
  let output = `${icon} ${name} ${message}`;
3596
4634
  if (result.fix && result.status !== "pass") {
3597
4635
  output += `
3598
- ${" ".repeat(22)}${chalk13.cyan("\u2192")} ${chalk13.cyan(result.fix)}`;
4636
+ ${" ".repeat(22)}${chalk14.cyan("\u2192")} ${chalk14.cyan(result.fix)}`;
3599
4637
  }
3600
4638
  return output;
3601
4639
  }
3602
4640
  function showHeader() {
3603
4641
  console.log("");
3604
- console.log(chalk13.cyan("\u{1FA7A} NextSpark Health Check"));
4642
+ console.log(chalk14.cyan("\u{1FA7A} NextSpark Health Check"));
3605
4643
  console.log("");
3606
4644
  }
3607
4645
  function showSummary(results) {
@@ -3609,11 +4647,11 @@ function showSummary(results) {
3609
4647
  const warnings = results.filter((r) => r.status === "warn").length;
3610
4648
  const failed = results.filter((r) => r.status === "fail").length;
3611
4649
  console.log("");
3612
- console.log(chalk13.gray("-".repeat(50)));
4650
+ console.log(chalk14.gray("-".repeat(50)));
3613
4651
  const summary = [
3614
- chalk13.green(`${passed} passed`),
3615
- warnings > 0 ? chalk13.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
3616
- failed > 0 ? chalk13.red(`${failed} failed`) : null
4652
+ chalk14.green(`${passed} passed`),
4653
+ warnings > 0 ? chalk14.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
4654
+ failed > 0 ? chalk14.red(`${failed} failed`) : null
3617
4655
  ].filter(Boolean).join(", ");
3618
4656
  console.log(`Summary: ${summary}`);
3619
4657
  console.log("");
@@ -3655,7 +4693,7 @@ async function runDoctorCommand() {
3655
4693
  var isDirectExecution = process.argv[1]?.includes("doctor") || process.argv.includes("doctor");
3656
4694
  if (isDirectExecution && typeof __require !== "undefined") {
3657
4695
  runDoctorCommand().catch((error) => {
3658
- console.error(chalk13.red("An unexpected error occurred:"), error.message);
4696
+ console.error(chalk14.red("An unexpected error occurred:"), error.message);
3659
4697
  process.exit(1);
3660
4698
  });
3661
4699
  }
@@ -3666,16 +4704,458 @@ async function doctorCommand() {
3666
4704
  await runDoctorCommand();
3667
4705
  } catch (error) {
3668
4706
  if (error instanceof Error) {
3669
- console.error(chalk14.red(`Error: ${error.message}`));
4707
+ console.error(chalk15.red(`Error: ${error.message}`));
4708
+ }
4709
+ process.exit(1);
4710
+ }
4711
+ }
4712
+
4713
+ // src/commands/db.ts
4714
+ import { spawn as spawn5 } from "child_process";
4715
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
4716
+ import { join as join11 } from "path";
4717
+ import chalk16 from "chalk";
4718
+ import ora10 from "ora";
4719
+ function loadProjectEnv4(projectRoot) {
4720
+ const envPath = join11(projectRoot, ".env");
4721
+ const envVars = {};
4722
+ if (existsSync10(envPath)) {
4723
+ const content = readFileSync9(envPath, "utf-8");
4724
+ for (const line of content.split("\n")) {
4725
+ const trimmed = line.trim();
4726
+ if (trimmed && !trimmed.startsWith("#")) {
4727
+ const [key, ...valueParts] = trimmed.split("=");
4728
+ if (key && valueParts.length > 0) {
4729
+ let value = valueParts.join("=");
4730
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
4731
+ value = value.slice(1, -1);
4732
+ }
4733
+ envVars[key] = value;
4734
+ }
4735
+ }
4736
+ }
4737
+ }
4738
+ return envVars;
4739
+ }
4740
+ async function dbMigrateCommand() {
4741
+ const spinner = ora10("Preparing to run migrations...").start();
4742
+ try {
4743
+ const coreDir = getCoreDir();
4744
+ const projectRoot = getProjectRoot();
4745
+ const migrationsScript = join11(coreDir, "scripts", "db", "run-migrations.mjs");
4746
+ if (!existsSync10(migrationsScript)) {
4747
+ spinner.fail("Migrations script not found");
4748
+ console.error(chalk16.red(`Expected script at: ${migrationsScript}`));
4749
+ process.exit(1);
4750
+ }
4751
+ spinner.succeed("Core package found");
4752
+ const projectEnv = loadProjectEnv4(projectRoot);
4753
+ if (!projectEnv.DATABASE_URL) {
4754
+ spinner.fail("DATABASE_URL not found in .env file");
4755
+ console.error(chalk16.red("Please configure DATABASE_URL in your .env file"));
4756
+ process.exit(1);
4757
+ }
4758
+ if (!projectEnv.NEXT_PUBLIC_ACTIVE_THEME) {
4759
+ spinner.fail("NEXT_PUBLIC_ACTIVE_THEME not found in .env file");
4760
+ console.error(chalk16.red("Please configure NEXT_PUBLIC_ACTIVE_THEME in your .env file"));
4761
+ process.exit(1);
4762
+ }
4763
+ spinner.start("Running database migrations...");
4764
+ const migrateProcess = spawn5("node", [migrationsScript], {
4765
+ cwd: projectRoot,
4766
+ stdio: "inherit",
4767
+ env: {
4768
+ ...projectEnv,
4769
+ ...process.env,
4770
+ NEXTSPARK_PROJECT_ROOT: projectRoot,
4771
+ NEXTSPARK_CORE_DIR: coreDir
4772
+ }
4773
+ });
4774
+ migrateProcess.on("error", (err) => {
4775
+ spinner.fail("Migration failed");
4776
+ console.error(chalk16.red(err.message));
4777
+ process.exit(1);
4778
+ });
4779
+ migrateProcess.on("close", (code) => {
4780
+ if (code === 0) {
4781
+ console.log(chalk16.green("\n\u2705 Migrations completed successfully!"));
4782
+ process.exit(0);
4783
+ } else {
4784
+ console.error(chalk16.red(`
4785
+ \u274C Migrations failed with exit code ${code}`));
4786
+ process.exit(code ?? 1);
4787
+ }
4788
+ });
4789
+ } catch (error) {
4790
+ spinner.fail("Migration preparation failed");
4791
+ if (error instanceof Error) {
4792
+ console.error(chalk16.red(error.message));
4793
+ }
4794
+ process.exit(1);
4795
+ }
4796
+ }
4797
+ async function dbSeedCommand() {
4798
+ console.log(chalk16.cyan("\u2139\uFE0F Sample data is included as part of the migration process."));
4799
+ console.log(chalk16.cyan(" Running db:migrate to apply all migrations including sample data...\n"));
4800
+ await dbMigrateCommand();
4801
+ }
4802
+
4803
+ // src/commands/sync-app.ts
4804
+ import { existsSync as existsSync11, readdirSync as readdirSync2, mkdirSync as mkdirSync4, copyFileSync, readFileSync as readFileSync10 } from "fs";
4805
+ import { join as join12, dirname as dirname4, relative } from "path";
4806
+ import chalk17 from "chalk";
4807
+ import ora11 from "ora";
4808
+ var EXCLUDED_TEMPLATE_PATTERNS = ["(templates)"];
4809
+ var ROOT_TEMPLATE_FILES = [
4810
+ "proxy.ts",
4811
+ // Next.js 16+ proxy (formerly middleware.ts) - required for auth/permission validation
4812
+ "next.config.mjs",
4813
+ // Required for webpack aliases, transpilePackages, security headers
4814
+ "tsconfig.json",
4815
+ // Required for proper path aliases and test file exclusions
4816
+ "i18n.ts"
4817
+ // Required for next-intl configuration
4818
+ ];
4819
+ var MAX_VERBOSE_FILES = 10;
4820
+ var MAX_SUMMARY_FILES = 5;
4821
+ function getAllFiles(dir, baseDir = dir) {
4822
+ const files = [];
4823
+ if (!existsSync11(dir)) {
4824
+ return files;
4825
+ }
4826
+ const entries = readdirSync2(dir, { withFileTypes: true });
4827
+ for (const entry of entries) {
4828
+ const fullPath = join12(dir, entry.name);
4829
+ const relativePath = relative(baseDir, fullPath);
4830
+ if (entry.isDirectory()) {
4831
+ files.push(...getAllFiles(fullPath, baseDir));
4832
+ } else if (entry.isFile()) {
4833
+ if (entry.name !== ".DS_Store" && entry.name !== "README.md") {
4834
+ files.push(relativePath);
4835
+ }
4836
+ }
4837
+ }
4838
+ return files;
4839
+ }
4840
+ function copyFile(source, target) {
4841
+ const targetDir = dirname4(target);
4842
+ if (!existsSync11(targetDir)) {
4843
+ mkdirSync4(targetDir, { recursive: true });
4844
+ }
4845
+ copyFileSync(source, target);
4846
+ }
4847
+ function backupDirectory(source, target) {
4848
+ if (!existsSync11(source)) {
4849
+ return;
4850
+ }
4851
+ const files = getAllFiles(source);
4852
+ for (const file of files) {
4853
+ const sourcePath = join12(source, file);
4854
+ const targetPath = join12(target, file);
4855
+ copyFile(sourcePath, targetPath);
4856
+ }
4857
+ }
4858
+ function getCoreVersion2(coreDir) {
4859
+ try {
4860
+ const pkgPath = join12(coreDir, "package.json");
4861
+ const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4862
+ return pkg2.version || "unknown";
4863
+ } catch {
4864
+ return "unknown";
4865
+ }
4866
+ }
4867
+ async function syncAppCommand(options) {
4868
+ const spinner = ora11({ text: "Preparing sync...", isSilent: options.dryRun }).start();
4869
+ try {
4870
+ const coreDir = getCoreDir();
4871
+ const projectRoot = getProjectRoot();
4872
+ const coreVersion = getCoreVersion2(coreDir);
4873
+ const templatesDir = join12(coreDir, "templates", "app");
4874
+ const appDir = join12(projectRoot, "app");
4875
+ if (!existsSync11(templatesDir)) {
4876
+ spinner.fail("Templates directory not found in @nextsparkjs/core");
4877
+ console.error(chalk17.red(`
4878
+ Expected path: ${templatesDir}`));
4879
+ process.exit(1);
4880
+ }
4881
+ if (!existsSync11(appDir)) {
4882
+ spinner.fail("No /app directory found");
4883
+ console.error(chalk17.red("\n This project does not have an /app folder."));
4884
+ console.error(chalk17.yellow(' Run "nextspark init" first to initialize your project.\n'));
4885
+ process.exit(1);
4886
+ }
4887
+ spinner.text = "Scanning template files...";
4888
+ const templateFiles = getAllFiles(templatesDir).filter((f) => !EXCLUDED_TEMPLATE_PATTERNS.some((pattern) => f.startsWith(pattern)));
4889
+ const existingAppFiles = getAllFiles(appDir);
4890
+ const customFiles = existingAppFiles.filter((f) => !templateFiles.includes(f));
4891
+ const filesToUpdate = templateFiles;
4892
+ spinner.succeed("Scan complete");
4893
+ console.log(chalk17.cyan(`
4894
+ Syncing /app with @nextsparkjs/core@${coreVersion}...
4895
+ `));
4896
+ if (options.dryRun) {
4897
+ console.log(chalk17.yellow(" [DRY RUN] No changes will be made\n"));
4898
+ }
4899
+ if (options.verbose) {
4900
+ console.log(chalk17.gray(" Files to sync:"));
4901
+ for (const file of filesToUpdate) {
4902
+ const targetPath = join12(appDir, file);
4903
+ const status = existsSync11(targetPath) ? chalk17.yellow("update") : chalk17.green("create");
4904
+ console.log(chalk17.gray(` ${status} ${file}`));
4905
+ }
4906
+ console.log();
4907
+ }
4908
+ console.log(chalk17.white(` Template files: ${filesToUpdate.length}`));
4909
+ console.log(chalk17.white(` Custom files preserved: ${customFiles.length}`));
4910
+ if (customFiles.length > 0 && options.verbose) {
4911
+ console.log(chalk17.gray("\n Custom files (will be preserved):"));
4912
+ for (const file of customFiles.slice(0, MAX_VERBOSE_FILES)) {
4913
+ console.log(chalk17.gray(` - ${file}`));
4914
+ }
4915
+ if (customFiles.length > MAX_VERBOSE_FILES) {
4916
+ console.log(chalk17.gray(` ... and ${customFiles.length - MAX_VERBOSE_FILES} more`));
4917
+ }
4918
+ }
4919
+ if (!options.force && !options.dryRun) {
4920
+ console.log(chalk17.yellow(`
4921
+ This will overwrite ${filesToUpdate.length} core template files.`));
4922
+ console.log(chalk17.gray(" Run with --dry-run to preview changes, or --force to skip this prompt.\n"));
4923
+ try {
4924
+ const { confirm: confirm6 } = await import("@inquirer/prompts");
4925
+ const confirmed = await confirm6({
4926
+ message: "Proceed with sync?",
4927
+ default: true
4928
+ });
4929
+ if (!confirmed) {
4930
+ console.log(chalk17.yellow("\n Sync cancelled.\n"));
4931
+ process.exit(0);
4932
+ }
4933
+ } catch (promptError) {
4934
+ console.error(chalk17.red("\n Failed to load confirmation prompt. Use --force to skip.\n"));
4935
+ process.exit(1);
4936
+ }
4937
+ }
4938
+ if (options.backup && !options.dryRun) {
4939
+ const backupDir = join12(projectRoot, `app.backup.v${coreVersion}.${Date.now()}`);
4940
+ spinner.start("Creating backup...");
4941
+ backupDirectory(appDir, backupDir);
4942
+ spinner.succeed(`Backup created: ${relative(projectRoot, backupDir)}`);
4943
+ }
4944
+ if (!options.dryRun) {
4945
+ spinner.start("Syncing files...");
4946
+ let updated = 0;
4947
+ let created = 0;
4948
+ for (const file of filesToUpdate) {
4949
+ const sourcePath = join12(templatesDir, file);
4950
+ const targetPath = join12(appDir, file);
4951
+ const isNew = !existsSync11(targetPath);
4952
+ copyFile(sourcePath, targetPath);
4953
+ if (isNew) {
4954
+ created++;
4955
+ } else {
4956
+ updated++;
4957
+ }
4958
+ if (options.verbose) {
4959
+ spinner.text = `Syncing: ${file}`;
4960
+ }
4961
+ }
4962
+ spinner.succeed(`Synced ${filesToUpdate.length} files (${updated} updated, ${created} created)`);
4963
+ const rootTemplatesDir = join12(coreDir, "templates");
4964
+ let rootUpdated = 0;
4965
+ let rootCreated = 0;
4966
+ for (const file of ROOT_TEMPLATE_FILES) {
4967
+ const sourcePath = join12(rootTemplatesDir, file);
4968
+ const targetPath = join12(projectRoot, file);
4969
+ if (existsSync11(sourcePath)) {
4970
+ const isNew = !existsSync11(targetPath);
4971
+ copyFile(sourcePath, targetPath);
4972
+ if (isNew) {
4973
+ rootCreated++;
4974
+ if (options.verbose) {
4975
+ console.log(chalk17.green(` + Created: ${file}`));
4976
+ }
4977
+ } else {
4978
+ rootUpdated++;
4979
+ if (options.verbose) {
4980
+ console.log(chalk17.yellow(` ~ Updated: ${file}`));
4981
+ }
4982
+ }
4983
+ }
4984
+ }
4985
+ if (rootUpdated + rootCreated > 0) {
4986
+ console.log(chalk17.gray(` Root files: ${rootUpdated} updated, ${rootCreated} created`));
4987
+ }
4988
+ }
4989
+ console.log(chalk17.green("\n \u2705 Sync complete!\n"));
4990
+ if (customFiles.length > 0) {
4991
+ console.log(chalk17.gray(` Preserved ${customFiles.length} custom file(s):`));
4992
+ for (const file of customFiles.slice(0, MAX_SUMMARY_FILES)) {
4993
+ console.log(chalk17.gray(` - app/${file}`));
4994
+ }
4995
+ if (customFiles.length > MAX_SUMMARY_FILES) {
4996
+ console.log(chalk17.gray(` ... and ${customFiles.length - MAX_SUMMARY_FILES} more
4997
+ `));
4998
+ } else {
4999
+ console.log();
5000
+ }
5001
+ }
5002
+ } catch (error) {
5003
+ spinner.fail("Sync failed");
5004
+ if (error instanceof Error) {
5005
+ console.error(chalk17.red(`
5006
+ Error: ${error.message}
5007
+ `));
5008
+ if (options.verbose && error.stack) {
5009
+ console.error(chalk17.gray(` Stack trace:
5010
+ ${error.stack}
5011
+ `));
5012
+ }
5013
+ }
5014
+ process.exit(1);
5015
+ }
5016
+ }
5017
+
5018
+ // src/commands/setup-ai.ts
5019
+ import { existsSync as existsSync12 } from "fs";
5020
+ import { join as join13 } from "path";
5021
+ import { execSync as execSync3 } from "child_process";
5022
+ import chalk18 from "chalk";
5023
+ import ora12 from "ora";
5024
+ var VALID_EDITORS = ["claude", "cursor", "antigravity", "all"];
5025
+ async function setupAICommand(options) {
5026
+ const editor = options.editor || "claude";
5027
+ if (!VALID_EDITORS.includes(editor)) {
5028
+ console.log(chalk18.red(` Unknown editor: ${editor}`));
5029
+ console.log(chalk18.gray(` Available: ${VALID_EDITORS.join(", ")}`));
5030
+ process.exit(1);
5031
+ }
5032
+ console.log("");
5033
+ console.log(chalk18.cyan(" AI Workflow Setup"));
5034
+ console.log(chalk18.gray(" " + "-".repeat(40)));
5035
+ console.log("");
5036
+ const pkgPath = getAIWorkflowDir();
5037
+ if (!pkgPath) {
5038
+ console.log(chalk18.red(" @nextsparkjs/ai-workflow package not found."));
5039
+ console.log("");
5040
+ console.log(chalk18.gray(" Install it first:"));
5041
+ console.log(chalk18.cyan(" pnpm add -D -w @nextsparkjs/ai-workflow"));
5042
+ console.log("");
5043
+ process.exit(1);
5044
+ }
5045
+ const setupScript = join13(pkgPath, "scripts", "setup.mjs");
5046
+ if (!existsSync12(setupScript)) {
5047
+ console.log(chalk18.red(" Setup script not found in ai-workflow package."));
5048
+ console.log(chalk18.gray(` Expected: ${setupScript}`));
5049
+ process.exit(1);
5050
+ }
5051
+ const spinner = ora12({
5052
+ text: `Setting up AI workflow for ${editor}...`,
5053
+ prefixText: " "
5054
+ }).start();
5055
+ try {
5056
+ spinner.stop();
5057
+ execSync3(`node "${setupScript}" ${editor}`, {
5058
+ cwd: process.cwd(),
5059
+ stdio: "inherit"
5060
+ });
5061
+ } catch (error) {
5062
+ console.log("");
5063
+ console.log(chalk18.red(" AI workflow setup failed."));
5064
+ if (error instanceof Error) {
5065
+ console.log(chalk18.gray(` ${error.message}`));
5066
+ }
5067
+ process.exit(1);
5068
+ }
5069
+ }
5070
+
5071
+ // src/commands/sync-ai.ts
5072
+ import { existsSync as existsSync13 } from "fs";
5073
+ import { join as join14 } from "path";
5074
+ import { execSync as execSync4 } from "child_process";
5075
+ import chalk19 from "chalk";
5076
+ import ora13 from "ora";
5077
+ var VALID_EDITORS2 = ["claude", "cursor", "antigravity", "all"];
5078
+ async function syncAICommand(options) {
5079
+ const editor = options.editor || "claude";
5080
+ if (!VALID_EDITORS2.includes(editor)) {
5081
+ console.log(chalk19.red(` Unknown editor: ${editor}`));
5082
+ console.log(chalk19.gray(` Available: ${VALID_EDITORS2.join(", ")}`));
5083
+ process.exit(1);
5084
+ }
5085
+ console.log("");
5086
+ console.log(chalk19.cyan(" AI Workflow Sync"));
5087
+ console.log(chalk19.gray(" " + "-".repeat(40)));
5088
+ console.log("");
5089
+ const editorDir = editor === "cursor" ? ".cursor" : ".claude";
5090
+ const editorDirPath = join14(process.cwd(), editorDir);
5091
+ if (!existsSync13(editorDirPath)) {
5092
+ console.log(chalk19.red(` No ${editorDir}/ directory found.`));
5093
+ console.log("");
5094
+ console.log(chalk19.gray(" AI workflow must be set up first. Run:"));
5095
+ console.log(chalk19.cyan(" nextspark setup:ai --editor " + editor));
5096
+ console.log("");
5097
+ process.exit(1);
5098
+ }
5099
+ const pkgPath = getAIWorkflowDir();
5100
+ if (!pkgPath) {
5101
+ console.log(chalk19.red(" @nextsparkjs/ai-workflow package not found."));
5102
+ console.log("");
5103
+ console.log(chalk19.gray(" Install it first:"));
5104
+ console.log(chalk19.cyan(" pnpm add -D -w @nextsparkjs/ai-workflow"));
5105
+ console.log("");
5106
+ process.exit(1);
5107
+ }
5108
+ const setupScript = join14(pkgPath, "scripts", "setup.mjs");
5109
+ if (!existsSync13(setupScript)) {
5110
+ console.log(chalk19.red(" Setup script not found in ai-workflow package."));
5111
+ console.log(chalk19.gray(` Expected: ${setupScript}`));
5112
+ process.exit(1);
5113
+ }
5114
+ if (!options.force) {
5115
+ console.log(chalk19.yellow(" This will sync AI workflow files from @nextsparkjs/ai-workflow."));
5116
+ console.log(chalk19.gray(" Framework files will be overwritten. Custom files will be preserved."));
5117
+ console.log(chalk19.gray(" Config JSON files will never be overwritten.\n"));
5118
+ try {
5119
+ const { confirm: confirm6 } = await import("@inquirer/prompts");
5120
+ const confirmed = await confirm6({
5121
+ message: "Proceed with sync?",
5122
+ default: true
5123
+ });
5124
+ if (!confirmed) {
5125
+ console.log(chalk19.yellow("\n Sync cancelled.\n"));
5126
+ process.exit(0);
5127
+ }
5128
+ } catch {
5129
+ console.error(chalk19.red("\n Failed to load confirmation prompt. Use --force to skip.\n"));
5130
+ process.exit(1);
5131
+ }
5132
+ }
5133
+ const spinner = ora13({
5134
+ text: `Syncing AI workflow for ${editor}...`,
5135
+ prefixText: " "
5136
+ }).start();
5137
+ try {
5138
+ spinner.stop();
5139
+ execSync4(`node "${setupScript}" ${editor}`, {
5140
+ cwd: process.cwd(),
5141
+ stdio: "inherit"
5142
+ });
5143
+ } catch (error) {
5144
+ console.log("");
5145
+ console.log(chalk19.red(" AI workflow sync failed."));
5146
+ if (error instanceof Error) {
5147
+ console.log(chalk19.gray(` ${error.message}`));
3670
5148
  }
3671
5149
  process.exit(1);
3672
5150
  }
3673
5151
  }
3674
5152
 
3675
5153
  // src/cli.ts
5154
+ config();
5155
+ var pkg = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
3676
5156
  var program = new Command();
3677
- program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version("0.1.0-beta.4");
3678
- program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
5157
+ program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version(pkg.version);
5158
+ program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", process.env.PORT || "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
3679
5159
  program.command("build").description("Build for production").option("--no-registry", "Skip registry generation before build").action(buildCommand);
3680
5160
  program.command("generate").description("Generate all registries").option("-w, --watch", "Watch for changes").action(generateCommand);
3681
5161
  var registry = program.command("registry").description("Registry management commands");
@@ -3683,12 +5163,21 @@ registry.command("build").description("Build all registries").action(registryBui
3683
5163
  registry.command("watch").description("Watch and rebuild registries on changes").action(registryWatchCommand);
3684
5164
  program.command("registry:build").description("Build all registries (alias)").action(registryBuildCommand);
3685
5165
  program.command("registry:watch").description("Watch and rebuild registries (alias)").action(registryWatchCommand);
3686
- program.command("init").description("Initialize NextSpark project").option("-f, --force", "Overwrite existing configuration").option("--wizard", "Run full project wizard").option("--quick", "Quick wizard mode (essential steps only)").option("--expert", "Expert wizard mode (all options)").option("--preset <name>", "Use preset configuration (saas, blog, crm)").option("--theme <name>", "Pre-select theme (default, blog, crm, productivity, none)").option("--plugins <list>", "Pre-select plugins (comma-separated)").option("-y, --yes", "Skip confirmations").option("--registries-only", "Only create registries (no wizard)").option("--name <name>", "Project name (non-interactive mode)").option("--slug <slug>", "Project slug (non-interactive mode)").option("--description <desc>", "Project description (non-interactive mode)").action(initCommand);
5166
+ 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)").option("--type <type>", "Project type: web or web-mobile (non-interactive mode)").action(initCommand);
3687
5167
  program.command("add:plugin <package>").description("Add a plugin to your project").option("-v, --version <version>", "Specific version to install").option("-f, --force", "Overwrite if already exists").option("--skip-postinstall", "Skip postinstall hooks").option("--no-deps", "Skip installing dependencies").option("--dry-run", "Show what would be done without making changes").action(addPluginCommand);
3688
5168
  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);
5169
+ program.command("add:mobile").description("Add mobile app to your project").option("-f, --force", "Overwrite if already exists").option("--skip-install", "Skip npm install").action(addMobileCommand);
3689
5170
  program.command("doctor").description("Run health check on NextSpark project").action(doctorCommand);
5171
+ var db = program.command("db").description("Database management commands");
5172
+ db.command("migrate").description("Run database migrations").action(dbMigrateCommand);
5173
+ db.command("seed").description("Seed database with sample data").action(dbSeedCommand);
5174
+ program.command("db:migrate").description("Run database migrations (alias)").action(dbMigrateCommand);
5175
+ program.command("db:seed").description("Seed database with sample data (alias)").action(dbSeedCommand);
5176
+ program.command("sync:app").description("Sync /app folder with @nextsparkjs/core templates").option("--dry-run", "Preview changes without applying").option("-f, --force", "Skip confirmation prompt").option("--backup", "Backup existing files before overwriting").option("-v, --verbose", "Show detailed file operations").action(syncAppCommand);
5177
+ program.command("setup:ai").description("Setup AI workflow for your editor (Claude Code, Cursor, Antigravity)").option("-e, --editor <editor>", "Editor to setup (claude, cursor, antigravity, all)", "claude").action(setupAICommand);
5178
+ program.command("sync:ai").description("Sync AI workflow files from @nextsparkjs/ai-workflow").option("-e, --editor <editor>", "Editor to sync (claude, cursor, antigravity, all)", "claude").option("-f, --force", "Skip confirmation prompt").action(syncAICommand);
3690
5179
  program.showHelpAfterError();
3691
5180
  program.configureOutput({
3692
- writeErr: (str) => process.stderr.write(chalk15.red(str))
5181
+ writeErr: (str) => process.stderr.write(chalk20.red(str))
3693
5182
  });
3694
5183
  program.parse();