@nextsparkjs/cli 0.1.0-beta.8 → 0.1.0-beta.80

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 chalk18 from "chalk";
16
+ import { readFileSync as readFileSync11 } from "fs";
15
17
 
16
18
  // src/commands/dev.ts
17
19
  import { spawn } from "child_process";
@@ -80,6 +82,7 @@ async function devCommand(options) {
80
82
  const devProcess = spawn("npx", ["next", "dev", "-p", options.port], {
81
83
  cwd: projectRoot,
82
84
  stdio: "inherit",
85
+ shell: true,
83
86
  env: {
84
87
  ...process.env,
85
88
  NEXTSPARK_CORE_DIR: coreDir
@@ -179,6 +182,7 @@ async function buildCommand(options) {
179
182
  const buildProcess = spawn2("npx", ["next", "build"], {
180
183
  cwd: projectRoot,
181
184
  stdio: "inherit",
185
+ shell: true,
182
186
  env: {
183
187
  ...projectEnv,
184
188
  ...process.env,
@@ -422,8 +426,8 @@ Watcher exited with code ${code}`));
422
426
  }
423
427
 
424
428
  // src/commands/init.ts
425
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync6 } from "fs";
426
- import { join as join7 } from "path";
429
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync7 } from "fs";
430
+ import { join as join8 } from "path";
427
431
  import chalk12 from "chalk";
428
432
  import ora8 from "ora";
429
433
 
@@ -433,10 +437,35 @@ import ora7 from "ora";
433
437
  import { confirm as confirm5 } from "@inquirer/prompts";
434
438
  import { execSync } from "child_process";
435
439
  import { existsSync as existsSync7, readdirSync } from "fs";
436
- import { join as join6 } from "path";
440
+ import { join as join7 } from "path";
437
441
 
438
442
  // src/wizard/banner.ts
439
443
  import chalk5 from "chalk";
444
+ import { readFileSync as readFileSync4 } from "fs";
445
+ import { fileURLToPath as fileURLToPath2 } from "url";
446
+ import { dirname as dirname2, join as join4 } from "path";
447
+ var __filename2 = fileURLToPath2(import.meta.url);
448
+ var __dirname2 = dirname2(__filename2);
449
+ function getCliVersion() {
450
+ const possiblePaths = [
451
+ join4(__dirname2, "../package.json"),
452
+ // from dist/
453
+ join4(__dirname2, "../../package.json"),
454
+ // from dist/wizard/ or src/wizard/
455
+ join4(__dirname2, "../../../package.json")
456
+ // fallback
457
+ ];
458
+ for (const packageJsonPath of possiblePaths) {
459
+ try {
460
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
461
+ if (packageJson.name === "@nextsparkjs/cli" && packageJson.version) {
462
+ return packageJson.version;
463
+ }
464
+ } catch {
465
+ }
466
+ }
467
+ return "unknown";
468
+ }
440
469
  var BANNER = `
441
470
  _ __ __ _____ __
442
471
  / | / /__ _ __/ /_/ ___/____ ____ ______/ /__
@@ -446,9 +475,11 @@ var BANNER = `
446
475
  /_/
447
476
  `;
448
477
  function showBanner() {
478
+ const version = getCliVersion();
449
479
  console.log(chalk5.cyan(BANNER));
450
480
  console.log(chalk5.bold.white(" Welcome to NextSpark - The Complete SaaS Framework for Next.js"));
451
- console.log(chalk5.gray(" Create production-ready SaaS applications in minutes\n"));
481
+ console.log(chalk5.gray(` Version ${version} - Create production-ready SaaS applications in minutes
482
+ `));
452
483
  console.log(chalk5.gray(" " + "=".repeat(60) + "\n"));
453
484
  }
454
485
  function showSection(title, step, totalSteps) {
@@ -500,7 +531,7 @@ function validateSlug(slug) {
500
531
  return true;
501
532
  }
502
533
  async function promptProjectInfo() {
503
- showSection("Project Information", 1, 5);
534
+ showSection("Project Information", 2, 10);
504
535
  const projectName = await input({
505
536
  message: "What is your project name?",
506
537
  default: "My SaaS App",
@@ -523,8 +554,43 @@ async function promptProjectInfo() {
523
554
  };
524
555
  }
525
556
 
557
+ // src/wizard/prompts/project-type.ts
558
+ import { select } from "@inquirer/prompts";
559
+ var PROJECT_TYPE_OPTIONS = [
560
+ {
561
+ name: "Web only",
562
+ value: "web",
563
+ description: "Next.js web application with NextSpark. Standard flat project structure."
564
+ },
565
+ {
566
+ name: "Web + Mobile",
567
+ value: "web-mobile",
568
+ description: "Monorepo with Next.js web app and Expo mobile app sharing the same backend."
569
+ }
570
+ ];
571
+ async function promptProjectType() {
572
+ showSection("Project Type", 1, 10);
573
+ const projectType = await select({
574
+ message: "What type of project do you want to create?",
575
+ choices: PROJECT_TYPE_OPTIONS,
576
+ default: "web"
577
+ });
578
+ const selectedOption = PROJECT_TYPE_OPTIONS.find((o) => o.value === projectType);
579
+ if (selectedOption) {
580
+ showInfo(selectedOption.description);
581
+ }
582
+ if (projectType === "web-mobile") {
583
+ console.log("");
584
+ showInfo("Your project will be a pnpm monorepo with web/ and mobile/ directories.");
585
+ showInfo("The mobile app will use Expo and share the same backend API.");
586
+ }
587
+ return {
588
+ projectType
589
+ };
590
+ }
591
+
526
592
  // src/wizard/prompts/team-config.ts
527
- import { select, checkbox } from "@inquirer/prompts";
593
+ import { select as select2, checkbox } from "@inquirer/prompts";
528
594
 
529
595
  // src/wizard/types.ts
530
596
  var AVAILABLE_LOCALES = {
@@ -572,8 +638,8 @@ var ROLE_OPTIONS = [
572
638
  { name: "Viewer (Read-only access)", value: "viewer", checked: true }
573
639
  ];
574
640
  async function promptTeamConfig() {
575
- showSection("Team Configuration", 2, 5);
576
- const teamMode = await select({
641
+ showSection("Team Configuration", 3, 10);
642
+ const teamMode = await select2({
577
643
  message: "What team mode do you need?",
578
644
  choices: TEAM_MODE_OPTIONS,
579
645
  default: "multi-tenant"
@@ -604,7 +670,7 @@ async function promptTeamConfig() {
604
670
  }
605
671
 
606
672
  // src/wizard/prompts/i18n-config.ts
607
- import { select as select2, checkbox as checkbox2 } from "@inquirer/prompts";
673
+ import { select as select3, checkbox as checkbox2 } from "@inquirer/prompts";
608
674
  var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
609
675
  name: `${name} (${value})`,
610
676
  value,
@@ -612,7 +678,7 @@ var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
612
678
  // English selected by default
613
679
  }));
614
680
  async function promptI18nConfig() {
615
- showSection("Internationalization", 3, 5);
681
+ showSection("Internationalization", 4, 10);
616
682
  const supportedLocales = await checkbox2({
617
683
  message: "Which languages do you want to support?",
618
684
  choices: LOCALE_OPTIONS,
@@ -628,7 +694,7 @@ async function promptI18nConfig() {
628
694
  showInfo(`Default language set to ${AVAILABLE_LOCALES[defaultLocale]}`);
629
695
  } else {
630
696
  console.log("");
631
- defaultLocale = await select2({
697
+ defaultLocale = await select3({
632
698
  message: "Which should be the default language?",
633
699
  choices: supportedLocales.map((locale) => ({
634
700
  name: `${AVAILABLE_LOCALES[locale]} (${locale})`,
@@ -644,7 +710,7 @@ async function promptI18nConfig() {
644
710
  }
645
711
 
646
712
  // src/wizard/prompts/billing-config.ts
647
- import { select as select3 } from "@inquirer/prompts";
713
+ import { select as select4 } from "@inquirer/prompts";
648
714
  var BILLING_MODEL_OPTIONS = [
649
715
  {
650
716
  name: "Free (No payments)",
@@ -663,8 +729,8 @@ var BILLING_MODEL_OPTIONS = [
663
729
  }
664
730
  ];
665
731
  async function promptBillingConfig() {
666
- showSection("Billing Configuration", 4, 5);
667
- const billingModel = await select3({
732
+ showSection("Billing Configuration", 5, 10);
733
+ const billingModel = await select4({
668
734
  message: "What billing model do you want to use?",
669
735
  choices: BILLING_MODEL_OPTIONS,
670
736
  default: "freemium"
@@ -676,7 +742,7 @@ async function promptBillingConfig() {
676
742
  let currency = "usd";
677
743
  if (billingModel !== "free") {
678
744
  console.log("");
679
- currency = await select3({
745
+ currency = await select4({
680
746
  message: "What currency will you use?",
681
747
  choices: CURRENCIES.map((c) => ({
682
748
  name: c.label,
@@ -728,7 +794,7 @@ var FEATURE_OPTIONS = [
728
794
  }
729
795
  ];
730
796
  async function promptFeaturesConfig() {
731
- showSection("Features", 5, 5);
797
+ showSection("Features", 6, 10);
732
798
  showInfo("Select the features you want to include in your project.");
733
799
  showInfo("You can add or remove features later by editing your config files.");
734
800
  console.log("");
@@ -765,7 +831,7 @@ var CONTENT_FEATURE_OPTIONS = [
765
831
  }
766
832
  ];
767
833
  async function promptContentFeaturesConfig(mode = "interactive", totalSteps = 9) {
768
- showSection("Content Features", 6, totalSteps);
834
+ showSection("Content Features", 7, totalSteps);
769
835
  showInfo("Enable optional content features for your project.");
770
836
  showInfo("These features add entities and blocks for building pages and blog posts.");
771
837
  console.log("");
@@ -816,7 +882,7 @@ function getDefaultAuthConfig() {
816
882
  };
817
883
  }
818
884
  async function promptAuthConfig(mode = "interactive", totalSteps = 8) {
819
- showSection("Authentication", 6, totalSteps);
885
+ showSection("Authentication", 8, totalSteps);
820
886
  showInfo("Configure how users will authenticate to your application.");
821
887
  console.log("");
822
888
  const selectedMethods = await checkbox5({
@@ -900,7 +966,7 @@ function getDefaultDashboardConfig() {
900
966
  };
901
967
  }
902
968
  async function promptDashboardConfig(mode = "interactive", totalSteps = 8) {
903
- showSection("Dashboard", 7, totalSteps);
969
+ showSection("Dashboard", 9, totalSteps);
904
970
  showInfo("Configure your dashboard user interface.");
905
971
  showInfo("These settings can be changed later in your theme config.");
906
972
  console.log("");
@@ -952,7 +1018,7 @@ function getDefaultDevConfig() {
952
1018
  };
953
1019
  }
954
1020
  async function promptDevConfig(mode = "interactive", totalSteps = 8) {
955
- showSection("Development Tools", 8, totalSteps);
1021
+ showSection("Development Tools", 10, totalSteps);
956
1022
  showInfo("Configure development and debugging tools.");
957
1023
  showWarning("These tools are disabled in production builds.");
958
1024
  console.log("");
@@ -979,7 +1045,7 @@ async function promptDevConfig(mode = "interactive", totalSteps = 8) {
979
1045
  }
980
1046
 
981
1047
  // src/wizard/prompts/theme-selection.ts
982
- import { select as select4 } from "@inquirer/prompts";
1048
+ import { select as select5 } from "@inquirer/prompts";
983
1049
  import chalk6 from "chalk";
984
1050
  async function promptThemeSelection() {
985
1051
  console.log("");
@@ -989,7 +1055,7 @@ async function promptThemeSelection() {
989
1055
  console.log(chalk6.gray(" A reference theme provides a complete example to learn from."));
990
1056
  console.log(chalk6.gray(" Your custom theme (based on starter) will be your active theme."));
991
1057
  console.log("");
992
- const theme = await select4({
1058
+ const theme = await select5({
993
1059
  message: "Which reference theme would you like to install?",
994
1060
  choices: [
995
1061
  {
@@ -1082,17 +1148,19 @@ import { confirm as confirm4, input as input3 } from "@inquirer/prompts";
1082
1148
 
1083
1149
  // src/wizard/prompts/index.ts
1084
1150
  async function runAllPrompts() {
1151
+ const projectTypeConfig = await promptProjectType();
1085
1152
  const projectInfo = await promptProjectInfo();
1086
1153
  const teamConfig = await promptTeamConfig();
1087
1154
  const i18nConfig = await promptI18nConfig();
1088
1155
  const billingConfig = await promptBillingConfig();
1089
1156
  const featuresConfig = await promptFeaturesConfig();
1090
- const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 9);
1091
- const authConfig = await promptAuthConfig("interactive", 9);
1092
- const dashboardConfig = await promptDashboardConfig("interactive", 9);
1093
- const devConfig = await promptDevConfig("interactive", 9);
1157
+ const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 10);
1158
+ const authConfig = await promptAuthConfig("interactive", 10);
1159
+ const dashboardConfig = await promptDashboardConfig("interactive", 10);
1160
+ const devConfig = await promptDevConfig("interactive", 10);
1094
1161
  return {
1095
1162
  ...projectInfo,
1163
+ ...projectTypeConfig,
1096
1164
  ...teamConfig,
1097
1165
  ...i18nConfig,
1098
1166
  ...billingConfig,
@@ -1104,17 +1172,19 @@ async function runAllPrompts() {
1104
1172
  };
1105
1173
  }
1106
1174
  async function runQuickPrompts() {
1175
+ const projectTypeConfig = await promptProjectType();
1107
1176
  const projectInfo = await promptProjectInfo();
1108
1177
  const teamConfig = await promptTeamConfig();
1109
1178
  const i18nConfig = await promptI18nConfig();
1110
1179
  const billingConfig = await promptBillingConfig();
1111
1180
  const featuresConfig = await promptFeaturesConfig();
1112
- const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 9);
1181
+ const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 10);
1113
1182
  const authConfig = { auth: getDefaultAuthConfig() };
1114
1183
  const dashboardConfig = { dashboard: getDefaultDashboardConfig() };
1115
1184
  const devConfig = { dev: getDefaultDevConfig() };
1116
1185
  return {
1117
1186
  ...projectInfo,
1187
+ ...projectTypeConfig,
1118
1188
  ...teamConfig,
1119
1189
  ...i18nConfig,
1120
1190
  ...billingConfig,
@@ -1126,17 +1196,19 @@ async function runQuickPrompts() {
1126
1196
  };
1127
1197
  }
1128
1198
  async function runExpertPrompts() {
1199
+ const projectTypeConfig = await promptProjectType();
1129
1200
  const projectInfo = await promptProjectInfo();
1130
1201
  const teamConfig = await promptTeamConfig();
1131
1202
  const i18nConfig = await promptI18nConfig();
1132
1203
  const billingConfig = await promptBillingConfig();
1133
1204
  const featuresConfig = await promptFeaturesConfig();
1134
- const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 9);
1135
- const authConfig = await promptAuthConfig("expert", 9);
1136
- const dashboardConfig = await promptDashboardConfig("expert", 9);
1137
- const devConfig = await promptDevConfig("expert", 9);
1205
+ const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 10);
1206
+ const authConfig = await promptAuthConfig("expert", 10);
1207
+ const dashboardConfig = await promptDashboardConfig("expert", 10);
1208
+ const devConfig = await promptDevConfig("expert", 10);
1138
1209
  return {
1139
1210
  ...projectInfo,
1211
+ ...projectTypeConfig,
1140
1212
  ...teamConfig,
1141
1213
  ...i18nConfig,
1142
1214
  ...billingConfig,
@@ -1149,42 +1221,42 @@ async function runExpertPrompts() {
1149
1221
  }
1150
1222
 
1151
1223
  // src/wizard/generators/index.ts
1152
- import fs7 from "fs-extra";
1153
- import path5 from "path";
1154
- import { fileURLToPath as fileURLToPath4 } from "url";
1224
+ import fs8 from "fs-extra";
1225
+ import path7 from "path";
1226
+ import { fileURLToPath as fileURLToPath7 } from "url";
1155
1227
 
1156
1228
  // src/wizard/generators/theme-renamer.ts
1157
1229
  import fs from "fs-extra";
1158
1230
  import path from "path";
1159
- import { fileURLToPath as fileURLToPath2 } from "url";
1160
- var __filename2 = fileURLToPath2(import.meta.url);
1161
- var __dirname2 = path.dirname(__filename2);
1231
+ import { fileURLToPath as fileURLToPath3 } from "url";
1232
+ var __filename3 = fileURLToPath3(import.meta.url);
1233
+ var __dirname3 = path.dirname(__filename3);
1162
1234
  function getTemplatesDir() {
1163
- try {
1164
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1165
- return path.join(path.dirname(corePkgPath), "templates");
1166
- } catch {
1167
- const possiblePaths = [
1168
- path.resolve(__dirname2, "../../../../../core/templates"),
1169
- path.resolve(__dirname2, "../../../../core/templates"),
1170
- path.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1171
- ];
1172
- for (const p of possiblePaths) {
1173
- if (fs.existsSync(p)) {
1174
- return p;
1175
- }
1235
+ const rootDir = process.cwd();
1236
+ const possiblePaths = [
1237
+ // From project root node_modules (most common for installed packages)
1238
+ path.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
1239
+ // From CLI dist folder for development
1240
+ path.resolve(__dirname3, "../../core/templates"),
1241
+ // Legacy paths for different build structures
1242
+ path.resolve(__dirname3, "../../../../../core/templates"),
1243
+ path.resolve(__dirname3, "../../../../core/templates")
1244
+ ];
1245
+ for (const p of possiblePaths) {
1246
+ if (fs.existsSync(p)) {
1247
+ return p;
1176
1248
  }
1177
- throw new Error("Could not find @nextsparkjs/core templates directory");
1178
1249
  }
1250
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
1179
1251
  }
1180
1252
  function getTargetThemesDir() {
1181
1253
  return path.resolve(process.cwd(), "contents", "themes");
1182
1254
  }
1183
- async function copyStarterTheme(config) {
1255
+ async function copyStarterTheme(config2) {
1184
1256
  const templatesDir = getTemplatesDir();
1185
1257
  const starterThemePath = path.join(templatesDir, "contents", "themes", "starter");
1186
1258
  const targetThemesDir = getTargetThemesDir();
1187
- const newThemePath = path.join(targetThemesDir, config.projectSlug);
1259
+ const newThemePath = path.join(targetThemesDir, config2.projectSlug);
1188
1260
  if (!await fs.pathExists(starterThemePath)) {
1189
1261
  throw new Error(`Starter theme not found at: ${starterThemePath}`);
1190
1262
  }
@@ -1194,99 +1266,97 @@ async function copyStarterTheme(config) {
1194
1266
  await fs.ensureDir(targetThemesDir);
1195
1267
  await fs.copy(starterThemePath, newThemePath);
1196
1268
  }
1197
- async function updateThemeConfig(config) {
1198
- const themeConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "theme.config.ts");
1269
+ async function updateThemeConfig(config2) {
1270
+ const themeConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "theme.config.ts");
1199
1271
  if (!await fs.pathExists(themeConfigPath)) {
1200
1272
  throw new Error(`theme.config.ts not found at: ${themeConfigPath}`);
1201
1273
  }
1202
1274
  let content = await fs.readFile(themeConfigPath, "utf-8");
1203
1275
  content = content.replace(
1204
1276
  /name:\s*['"]starter['"]/g,
1205
- `name: '${config.projectSlug}'`
1277
+ `name: '${config2.projectSlug}'`
1206
1278
  );
1207
1279
  content = content.replace(
1208
1280
  /displayName:\s*['"]Starter['"]/g,
1209
- `displayName: '${config.projectName}'`
1281
+ `displayName: '${config2.projectName}'`
1210
1282
  );
1211
1283
  content = content.replace(
1212
1284
  /description:\s*['"]Minimal starter theme for NextSpark['"]/g,
1213
- `description: '${config.projectDescription}'`
1285
+ `description: '${config2.projectDescription}'`
1214
1286
  );
1215
1287
  content = content.replace(
1216
1288
  /export const starterThemeConfig/g,
1217
- `export const ${toCamelCase(config.projectSlug)}ThemeConfig`
1289
+ `export const ${toCamelCase(config2.projectSlug)}ThemeConfig`
1218
1290
  );
1219
1291
  content = content.replace(
1220
1292
  /export default starterThemeConfig/g,
1221
- `export default ${toCamelCase(config.projectSlug)}ThemeConfig`
1293
+ `export default ${toCamelCase(config2.projectSlug)}ThemeConfig`
1222
1294
  );
1223
1295
  await fs.writeFile(themeConfigPath, content, "utf-8");
1224
1296
  }
1225
- async function updateDevConfig(config) {
1226
- const devConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "dev.config.ts");
1297
+ async function updateDevConfig(config2) {
1298
+ const devConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "dev.config.ts");
1227
1299
  if (!await fs.pathExists(devConfigPath)) {
1228
1300
  return;
1229
1301
  }
1230
1302
  let content = await fs.readFile(devConfigPath, "utf-8");
1231
- content = content.replace(/@starter\.dev/g, `@${config.projectSlug}.dev`);
1232
- content = content.replace(/STARTER THEME/g, `${config.projectName.toUpperCase()}`);
1233
- content = content.replace(/Starter Theme/g, config.projectName);
1234
- content = content.replace(/Starter Team/g, `${config.projectName} Team`);
1303
+ content = content.replace(/STARTER THEME/g, `${config2.projectName.toUpperCase()}`);
1304
+ content = content.replace(/Starter Theme/g, config2.projectName);
1235
1305
  await fs.writeFile(devConfigPath, content, "utf-8");
1236
1306
  }
1237
- async function updateAppConfig(config) {
1238
- const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1307
+ async function updateAppConfig(config2) {
1308
+ const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
1239
1309
  if (!await fs.pathExists(appConfigPath)) {
1240
1310
  throw new Error(`app.config.ts not found at: ${appConfigPath}`);
1241
1311
  }
1242
1312
  let content = await fs.readFile(appConfigPath, "utf-8");
1243
1313
  content = content.replace(
1244
1314
  /name:\s*['"]Starter['"]/g,
1245
- `name: '${config.projectName}'`
1315
+ `name: '${config2.projectName}'`
1246
1316
  );
1247
1317
  content = content.replace(
1248
1318
  /mode:\s*['"]multi-tenant['"]\s*as\s*const/g,
1249
- `mode: '${config.teamMode}' as const`
1319
+ `mode: '${config2.teamMode}' as const`
1250
1320
  );
1251
- const localesArray = config.supportedLocales.map((l) => `'${l}'`).join(", ");
1321
+ const localesArray = config2.supportedLocales.map((l) => `'${l}'`).join(", ");
1252
1322
  content = content.replace(
1253
1323
  /supportedLocales:\s*\[.*?\]/g,
1254
1324
  `supportedLocales: [${localesArray}]`
1255
1325
  );
1256
1326
  content = content.replace(
1257
1327
  /defaultLocale:\s*['"]en['"]\s*as\s*const/g,
1258
- `defaultLocale: '${config.defaultLocale}' as const`
1328
+ `defaultLocale: '${config2.defaultLocale}' as const`
1259
1329
  );
1260
1330
  content = content.replace(
1261
1331
  /label:\s*['"]Starter['"]/g,
1262
- `label: '${config.projectName}'`
1332
+ `label: '${config2.projectName}'`
1263
1333
  );
1264
1334
  await fs.writeFile(appConfigPath, content, "utf-8");
1265
1335
  }
1266
- async function updateRolesConfig(config) {
1267
- const appConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "app.config.ts");
1336
+ async function updateRolesConfig(config2) {
1337
+ const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
1268
1338
  if (!await fs.pathExists(appConfigPath)) {
1269
1339
  return;
1270
1340
  }
1271
1341
  let content = await fs.readFile(appConfigPath, "utf-8");
1272
- const rolesArray = config.teamRoles.map((r) => `'${r}'`).join(", ");
1342
+ const rolesArray = config2.teamRoles.map((r) => `'${r}'`).join(", ");
1273
1343
  content = content.replace(
1274
1344
  /availableTeamRoles:\s*\[.*?\]/g,
1275
1345
  `availableTeamRoles: [${rolesArray}]`
1276
1346
  );
1277
1347
  await fs.writeFile(appConfigPath, content, "utf-8");
1278
1348
  }
1279
- async function updateBillingConfig(config) {
1280
- const billingConfigPath = path.join(getTargetThemesDir(), config.projectSlug, "config", "billing.config.ts");
1349
+ async function updateBillingConfig(config2) {
1350
+ const billingConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "billing.config.ts");
1281
1351
  if (!await fs.pathExists(billingConfigPath)) {
1282
1352
  return;
1283
1353
  }
1284
1354
  let content = await fs.readFile(billingConfigPath, "utf-8");
1285
1355
  content = content.replace(
1286
1356
  /currency:\s*['"]usd['"]/g,
1287
- `currency: '${config.currency}'`
1357
+ `currency: '${config2.currency}'`
1288
1358
  );
1289
- const plansContent = generateBillingPlans(config.billingModel, config.currency);
1359
+ const plansContent = generateBillingPlans(config2.billingModel, config2.currency);
1290
1360
  content = content.replace(
1291
1361
  /plans:\s*\[[\s\S]*?\],\s*\n\s*\/\/ ===+\s*\n\s*\/\/ ACTION MAPPINGS/,
1292
1362
  `plans: ${plansContent},
@@ -1436,8 +1506,8 @@ function generateBillingPlans(billingModel, currency) {
1436
1506
  },
1437
1507
  ]`;
1438
1508
  }
1439
- async function updateMigrations(config) {
1440
- const migrationsDir = path.join(getTargetThemesDir(), config.projectSlug, "migrations");
1509
+ async function updateMigrations(config2) {
1510
+ const migrationsDir = path.join(getTargetThemesDir(), config2.projectSlug, "migrations");
1441
1511
  if (!await fs.pathExists(migrationsDir)) {
1442
1512
  return;
1443
1513
  }
@@ -1446,12 +1516,39 @@ async function updateMigrations(config) {
1446
1516
  for (const file of sqlFiles) {
1447
1517
  const filePath = path.join(migrationsDir, file);
1448
1518
  let content = await fs.readFile(filePath, "utf-8");
1449
- content = content.replace(/@starter\.dev/g, `@${config.projectSlug}.dev`);
1450
- content = content.replace(/Starter Theme/g, config.projectName);
1451
- content = content.replace(/starter theme/g, config.projectSlug);
1519
+ content = content.replace(/@starter\.dev/g, `@${config2.projectSlug}.dev`);
1520
+ content = content.replace(/Starter Theme/g, config2.projectName);
1521
+ content = content.replace(/starter theme/g, config2.projectSlug);
1452
1522
  await fs.writeFile(filePath, content, "utf-8");
1453
1523
  }
1454
1524
  }
1525
+ async function updateTestFiles(config2) {
1526
+ const testsDir = path.join(getTargetThemesDir(), config2.projectSlug, "tests");
1527
+ if (!await fs.pathExists(testsDir)) {
1528
+ return;
1529
+ }
1530
+ const processDir = async (dir) => {
1531
+ const items = await fs.readdir(dir);
1532
+ for (const item of items) {
1533
+ const itemPath = path.join(dir, item);
1534
+ const stat = await fs.stat(itemPath);
1535
+ if (stat.isDirectory()) {
1536
+ await processDir(itemPath);
1537
+ } else if (item.endsWith(".ts") || item.endsWith(".tsx")) {
1538
+ let content = await fs.readFile(itemPath, "utf-8");
1539
+ const hasChanges = content.includes("@/contents/themes/starter/");
1540
+ if (hasChanges) {
1541
+ content = content.replace(
1542
+ /@\/contents\/themes\/starter\//g,
1543
+ `@/contents/themes/${config2.projectSlug}/`
1544
+ );
1545
+ await fs.writeFile(itemPath, content, "utf-8");
1546
+ }
1547
+ }
1548
+ }
1549
+ };
1550
+ await processDir(testsDir);
1551
+ }
1455
1552
  function toCamelCase(str) {
1456
1553
  return str.split("-").map((word, index) => {
1457
1554
  if (index === 0) {
@@ -1467,10 +1564,10 @@ import path2 from "path";
1467
1564
  function getTargetThemesDir2() {
1468
1565
  return path2.resolve(process.cwd(), "contents", "themes");
1469
1566
  }
1470
- async function updateAuthConfig(config) {
1567
+ async function updateAuthConfig(config2) {
1471
1568
  const authConfigPath = path2.join(
1472
1569
  getTargetThemesDir2(),
1473
- config.projectSlug,
1570
+ config2.projectSlug,
1474
1571
  "config",
1475
1572
  "auth.config.ts"
1476
1573
  );
@@ -1480,22 +1577,22 @@ async function updateAuthConfig(config) {
1480
1577
  let content = await fs2.readFile(authConfigPath, "utf-8");
1481
1578
  content = content.replace(
1482
1579
  /(emailPassword:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1483
- `$1${config.auth.emailPassword}`
1580
+ `$1${config2.auth.emailPassword}`
1484
1581
  );
1485
1582
  content = content.replace(
1486
1583
  /(google:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1487
- `$1${config.auth.googleOAuth}`
1584
+ `$1${config2.auth.googleOAuth}`
1488
1585
  );
1489
1586
  content = content.replace(
1490
1587
  /(emailVerification:\s*)(?:true|false)/g,
1491
- `$1${config.auth.emailVerification}`
1588
+ `$1${config2.auth.emailVerification}`
1492
1589
  );
1493
1590
  await fs2.writeFile(authConfigPath, content, "utf-8");
1494
1591
  }
1495
- async function updateDashboardUIConfig(config) {
1592
+ async function updateDashboardUIConfig(config2) {
1496
1593
  const dashboardConfigPath = path2.join(
1497
1594
  getTargetThemesDir2(),
1498
- config.projectSlug,
1595
+ config2.projectSlug,
1499
1596
  "config",
1500
1597
  "dashboard.config.ts"
1501
1598
  );
@@ -1505,42 +1602,42 @@ async function updateDashboardUIConfig(config) {
1505
1602
  let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1506
1603
  content = content.replace(
1507
1604
  /(search:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1508
- `$1${config.dashboard.search}`
1605
+ `$1${config2.dashboard.search}`
1509
1606
  );
1510
1607
  content = content.replace(
1511
1608
  /(notifications:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1512
- `$1${config.dashboard.notifications}`
1609
+ `$1${config2.dashboard.notifications}`
1513
1610
  );
1514
1611
  content = content.replace(
1515
1612
  /(themeToggle:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1516
- `$1${config.dashboard.themeToggle}`
1613
+ `$1${config2.dashboard.themeToggle}`
1517
1614
  );
1518
1615
  content = content.replace(
1519
1616
  /(support:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1520
- `$1${config.dashboard.support}`
1617
+ `$1${config2.dashboard.support}`
1521
1618
  );
1522
1619
  content = content.replace(
1523
1620
  /(quickCreate:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1524
- `$1${config.dashboard.quickCreate}`
1621
+ `$1${config2.dashboard.quickCreate}`
1525
1622
  );
1526
1623
  content = content.replace(
1527
1624
  /(adminAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1528
- `$1${config.dashboard.superadminAccess}`
1625
+ `$1${config2.dashboard.superadminAccess}`
1529
1626
  );
1530
1627
  content = content.replace(
1531
1628
  /(devtoolsAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1532
- `$1${config.dashboard.devtoolsAccess}`
1629
+ `$1${config2.dashboard.devtoolsAccess}`
1533
1630
  );
1534
1631
  content = content.replace(
1535
1632
  /(defaultCollapsed:\s*)(?:true|false)/g,
1536
- `$1${config.dashboard.sidebarCollapsed}`
1633
+ `$1${config2.dashboard.sidebarCollapsed}`
1537
1634
  );
1538
1635
  await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1539
1636
  }
1540
- async function updateDevToolsConfig(config) {
1637
+ async function updateDevToolsConfig(config2) {
1541
1638
  const devConfigPath = path2.join(
1542
1639
  getTargetThemesDir2(),
1543
- config.projectSlug,
1640
+ config2.projectSlug,
1544
1641
  "config",
1545
1642
  "dev.config.ts"
1546
1643
  );
@@ -1550,18 +1647,18 @@ async function updateDevToolsConfig(config) {
1550
1647
  let content = await fs2.readFile(devConfigPath, "utf-8");
1551
1648
  content = content.replace(
1552
1649
  /(devKeyring:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
1553
- `$1${config.dev.devKeyring}`
1650
+ `$1${config2.dev.devKeyring}`
1554
1651
  );
1555
1652
  content = content.replace(
1556
1653
  /(debugMode:\s*)(?:true|false)/g,
1557
- `$1${config.dev.debugMode}`
1654
+ `$1${config2.dev.debugMode}`
1558
1655
  );
1559
1656
  await fs2.writeFile(devConfigPath, content, "utf-8");
1560
1657
  }
1561
- async function updatePermissionsConfig(config) {
1658
+ async function updatePermissionsConfig(config2) {
1562
1659
  const permissionsConfigPath = path2.join(
1563
1660
  getTargetThemesDir2(),
1564
- config.projectSlug,
1661
+ config2.projectSlug,
1565
1662
  "config",
1566
1663
  "permissions.config.ts"
1567
1664
  );
@@ -1569,7 +1666,7 @@ async function updatePermissionsConfig(config) {
1569
1666
  return;
1570
1667
  }
1571
1668
  let content = await fs2.readFile(permissionsConfigPath, "utf-8");
1572
- const availableRoles = config.teamRoles;
1669
+ const availableRoles = config2.teamRoles;
1573
1670
  const roleArrayPattern = /roles:\s*\[(.*?)\]/g;
1574
1671
  content = content.replace(roleArrayPattern, (match, rolesStr) => {
1575
1672
  const currentRoles = rolesStr.split(",").map((r) => r.trim().replace(/['"]/g, "")).filter((r) => r.length > 0);
@@ -1581,10 +1678,48 @@ async function updatePermissionsConfig(config) {
1581
1678
  });
1582
1679
  await fs2.writeFile(permissionsConfigPath, content, "utf-8");
1583
1680
  }
1584
- async function updateDashboardConfig(config) {
1681
+ async function updateEntityPermissions(config2) {
1682
+ const permissionsConfigPath = path2.join(
1683
+ getTargetThemesDir2(),
1684
+ config2.projectSlug,
1685
+ "config",
1686
+ "permissions.config.ts"
1687
+ );
1688
+ if (!await fs2.pathExists(permissionsConfigPath)) {
1689
+ return;
1690
+ }
1691
+ let content = await fs2.readFile(permissionsConfigPath, "utf-8");
1692
+ if (config2.contentFeatures.pages) {
1693
+ content = uncommentPermissionBlock(content, "PAGES");
1694
+ }
1695
+ if (config2.contentFeatures.blog) {
1696
+ content = uncommentPermissionBlock(content, "POSTS");
1697
+ }
1698
+ await fs2.writeFile(permissionsConfigPath, content, "utf-8");
1699
+ }
1700
+ function uncommentPermissionBlock(content, markerName) {
1701
+ const startMarker = `// __${markerName}_PERMISSIONS_START__`;
1702
+ const endMarker = `// __${markerName}_PERMISSIONS_END__`;
1703
+ const startIndex = content.indexOf(startMarker);
1704
+ const endIndex = content.indexOf(endMarker);
1705
+ if (startIndex === -1 || endIndex === -1) {
1706
+ return content;
1707
+ }
1708
+ const beforeBlock = content.slice(0, startIndex);
1709
+ const block = content.slice(startIndex + startMarker.length, endIndex);
1710
+ const afterBlock = content.slice(endIndex + endMarker.length);
1711
+ const uncommentedBlock = block.split("\n").map((line) => {
1712
+ if (line.match(/^\s*\/\/\s+/)) {
1713
+ return line.replace(/^(\s*)\/\/\s*/, "$1");
1714
+ }
1715
+ return line;
1716
+ }).join("\n");
1717
+ return beforeBlock + uncommentedBlock + afterBlock;
1718
+ }
1719
+ async function updateDashboardConfig(config2) {
1585
1720
  const dashboardConfigPath = path2.join(
1586
1721
  getTargetThemesDir2(),
1587
- config.projectSlug,
1722
+ config2.projectSlug,
1588
1723
  "config",
1589
1724
  "dashboard.config.ts"
1590
1725
  );
@@ -1592,19 +1727,19 @@ async function updateDashboardConfig(config) {
1592
1727
  return;
1593
1728
  }
1594
1729
  let content = await fs2.readFile(dashboardConfigPath, "utf-8");
1595
- if (!config.features.analytics) {
1730
+ if (!config2.features.analytics) {
1596
1731
  content = content.replace(
1597
1732
  /(id:\s*['"]analytics['"].*?enabled:\s*)true/gs,
1598
1733
  "$1false"
1599
1734
  );
1600
1735
  }
1601
- if (!config.features.billing) {
1736
+ if (!config2.features.billing) {
1602
1737
  content = content.replace(
1603
1738
  /(id:\s*['"]billing['"].*?enabled:\s*)true/gs,
1604
1739
  "$1false"
1605
1740
  );
1606
1741
  }
1607
- if (config.teamMode === "single-user") {
1742
+ if (config2.teamMode === "single-user") {
1608
1743
  content = content.replace(
1609
1744
  /(id:\s*['"]team['"].*?enabled:\s*)true/gs,
1610
1745
  "$1false"
@@ -1616,10 +1751,10 @@ async function updateDashboardConfig(config) {
1616
1751
  }
1617
1752
  await fs2.writeFile(dashboardConfigPath, content, "utf-8");
1618
1753
  }
1619
- async function generateEnvExample(config) {
1754
+ async function generateEnvExample(config2) {
1620
1755
  const envExamplePath = path2.resolve(process.cwd(), ".env.example");
1621
1756
  let oauthSection = "";
1622
- if (config.auth.googleOAuth) {
1757
+ if (config2.auth.googleOAuth) {
1623
1758
  oauthSection = `# =============================================================================
1624
1759
  # OAUTH PROVIDERS
1625
1760
  # =============================================================================
@@ -1635,7 +1770,7 @@ GOOGLE_CLIENT_SECRET="your-google-client-secret"
1635
1770
  `;
1636
1771
  }
1637
1772
  const envContent = `# NextSpark Environment Configuration
1638
- # Generated for: ${config.projectName}
1773
+ # Generated for: ${config2.projectName}
1639
1774
 
1640
1775
  # =============================================================================
1641
1776
  # DATABASE
@@ -1651,7 +1786,7 @@ BETTER_AUTH_SECRET="your-secret-key-here"
1651
1786
  # =============================================================================
1652
1787
  # THEME
1653
1788
  # =============================================================================
1654
- NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
1789
+ NEXT_PUBLIC_ACTIVE_THEME="${config2.projectSlug}"
1655
1790
 
1656
1791
  # =============================================================================
1657
1792
  # APPLICATION
@@ -1659,7 +1794,7 @@ NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
1659
1794
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
1660
1795
  NODE_ENV="development"
1661
1796
 
1662
- ${config.features.billing ? `# =============================================================================
1797
+ ${config2.features.billing ? `# =============================================================================
1663
1798
  # STRIPE (Billing)
1664
1799
  # =============================================================================
1665
1800
  STRIPE_SECRET_KEY="sk_test_..."
@@ -1677,16 +1812,16 @@ ${oauthSection}`;
1677
1812
  await fs2.writeFile(envExamplePath, envContent, "utf-8");
1678
1813
  }
1679
1814
  }
1680
- async function updateReadme(config) {
1681
- const readmePath = path2.join(getTargetThemesDir2(), config.projectSlug, "README.md");
1815
+ async function updateReadme(config2) {
1816
+ const readmePath = path2.join(getTargetThemesDir2(), config2.projectSlug, "README.md");
1682
1817
  if (!await fs2.pathExists(readmePath)) {
1683
1818
  return;
1684
1819
  }
1685
1820
  let content = await fs2.readFile(readmePath, "utf-8");
1686
- content = content.replace(/# Starter Theme/g, `# ${config.projectName}`);
1821
+ content = content.replace(/# Starter Theme/g, `# ${config2.projectName}`);
1687
1822
  content = content.replace(
1688
1823
  /Minimal starter theme for NextSpark/g,
1689
- config.projectDescription
1824
+ config2.projectDescription
1690
1825
  );
1691
1826
  await fs2.writeFile(readmePath, content, "utf-8");
1692
1827
  }
@@ -1698,6 +1833,18 @@ async function copyEnvExampleToEnv() {
1698
1833
  await fs2.copy(envExamplePath, envPath);
1699
1834
  }
1700
1835
  }
1836
+ async function updateGlobalsCss(config2) {
1837
+ const globalsCssPath = path2.resolve(process.cwd(), "app", "globals.css");
1838
+ if (!await fs2.pathExists(globalsCssPath)) {
1839
+ return;
1840
+ }
1841
+ let content = await fs2.readFile(globalsCssPath, "utf-8");
1842
+ content = content.replace(
1843
+ /@import\s+["']\.\.\/contents\/themes\/[^/]+\/styles\/globals\.css["'];?/,
1844
+ `@import "../contents/themes/${config2.projectSlug}/styles/globals.css";`
1845
+ );
1846
+ await fs2.writeFile(globalsCssPath, content, "utf-8");
1847
+ }
1701
1848
 
1702
1849
  // src/wizard/generators/messages-generator.ts
1703
1850
  import fs3 from "fs-extra";
@@ -1705,8 +1852,8 @@ import path3 from "path";
1705
1852
  function getTargetThemesDir3() {
1706
1853
  return path3.resolve(process.cwd(), "contents", "themes");
1707
1854
  }
1708
- async function removeUnusedLanguages(config) {
1709
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1855
+ async function removeUnusedLanguages(config2) {
1856
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1710
1857
  if (!await fs3.pathExists(messagesDir)) {
1711
1858
  return;
1712
1859
  }
@@ -1716,13 +1863,13 @@ async function removeUnusedLanguages(config) {
1716
1863
  return fs3.statSync(folderPath).isDirectory() && Object.keys(AVAILABLE_LOCALES).includes(f);
1717
1864
  });
1718
1865
  for (const folder of languageFolders) {
1719
- if (!config.supportedLocales.includes(folder)) {
1866
+ if (!config2.supportedLocales.includes(folder)) {
1720
1867
  await fs3.remove(path3.join(messagesDir, folder));
1721
1868
  }
1722
1869
  }
1723
1870
  }
1724
- async function removeUnusedEntityMessages(config) {
1725
- const entitiesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "entities");
1871
+ async function removeUnusedEntityMessages(config2) {
1872
+ const entitiesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "entities");
1726
1873
  if (!await fs3.pathExists(entitiesDir)) {
1727
1874
  return;
1728
1875
  }
@@ -1735,45 +1882,45 @@ async function removeUnusedEntityMessages(config) {
1735
1882
  const files = await fs3.readdir(messagesDir);
1736
1883
  for (const file of files) {
1737
1884
  const locale = path3.basename(file, ".json");
1738
- if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config.supportedLocales.includes(locale)) {
1885
+ if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config2.supportedLocales.includes(locale)) {
1739
1886
  await fs3.remove(path3.join(messagesDir, file));
1740
1887
  }
1741
1888
  }
1742
1889
  }
1743
1890
  }
1744
- async function ensureLanguageFolders(config) {
1745
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1891
+ async function ensureLanguageFolders(config2) {
1892
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1746
1893
  if (!await fs3.pathExists(messagesDir)) {
1747
1894
  return;
1748
1895
  }
1749
- const defaultLocaleDir = path3.join(messagesDir, config.defaultLocale);
1896
+ const defaultLocaleDir = path3.join(messagesDir, config2.defaultLocale);
1750
1897
  if (!await fs3.pathExists(defaultLocaleDir)) {
1751
1898
  const enDir = path3.join(messagesDir, "en");
1752
1899
  if (await fs3.pathExists(enDir)) {
1753
1900
  await fs3.copy(enDir, defaultLocaleDir);
1754
1901
  }
1755
1902
  }
1756
- for (const locale of config.supportedLocales) {
1903
+ for (const locale of config2.supportedLocales) {
1757
1904
  const localeDir = path3.join(messagesDir, locale);
1758
1905
  if (!await fs3.pathExists(localeDir) && await fs3.pathExists(defaultLocaleDir)) {
1759
1906
  await fs3.copy(defaultLocaleDir, localeDir);
1760
1907
  }
1761
1908
  }
1762
1909
  }
1763
- async function updateMessageFiles(config) {
1764
- const messagesDir = path3.join(getTargetThemesDir3(), config.projectSlug, "messages");
1910
+ async function updateMessageFiles(config2) {
1911
+ const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
1765
1912
  if (!await fs3.pathExists(messagesDir)) {
1766
1913
  return;
1767
1914
  }
1768
- for (const locale of config.supportedLocales) {
1915
+ for (const locale of config2.supportedLocales) {
1769
1916
  const commonPath = path3.join(messagesDir, locale, "common.json");
1770
1917
  if (await fs3.pathExists(commonPath)) {
1771
1918
  try {
1772
1919
  const content = await fs3.readJson(commonPath);
1773
1920
  if (content.app) {
1774
- content.app.name = config.projectName;
1921
+ content.app.name = config2.projectName;
1775
1922
  if (content.app.description) {
1776
- content.app.description = config.projectDescription;
1923
+ content.app.description = config2.projectDescription;
1777
1924
  }
1778
1925
  }
1779
1926
  await fs3.writeJson(commonPath, content, { spaces: 2 });
@@ -1782,36 +1929,36 @@ async function updateMessageFiles(config) {
1782
1929
  }
1783
1930
  }
1784
1931
  }
1785
- async function processI18n(config) {
1786
- await removeUnusedLanguages(config);
1787
- await removeUnusedEntityMessages(config);
1788
- await ensureLanguageFolders(config);
1789
- await updateMessageFiles(config);
1932
+ async function processI18n(config2) {
1933
+ await removeUnusedLanguages(config2);
1934
+ await removeUnusedEntityMessages(config2);
1935
+ await ensureLanguageFolders(config2);
1936
+ await updateMessageFiles(config2);
1790
1937
  }
1791
1938
 
1792
1939
  // src/wizard/generators/content-features-generator.ts
1793
1940
  import fs4 from "fs-extra";
1794
1941
  import path4 from "path";
1795
- import { fileURLToPath as fileURLToPath3 } from "url";
1796
- var __filename3 = fileURLToPath3(import.meta.url);
1797
- var __dirname3 = path4.dirname(__filename3);
1942
+ import { fileURLToPath as fileURLToPath4 } from "url";
1943
+ var __filename4 = fileURLToPath4(import.meta.url);
1944
+ var __dirname4 = path4.dirname(__filename4);
1798
1945
  function getTemplatesDir2() {
1799
- try {
1800
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
1801
- return path4.join(path4.dirname(corePkgPath), "templates");
1802
- } catch {
1803
- const possiblePaths = [
1804
- path4.resolve(__dirname3, "../../../../../core/templates"),
1805
- path4.resolve(__dirname3, "../../../../core/templates"),
1806
- path4.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
1807
- ];
1808
- for (const p of possiblePaths) {
1809
- if (fs4.existsSync(p)) {
1810
- return p;
1811
- }
1946
+ const rootDir = process.cwd();
1947
+ const possiblePaths = [
1948
+ // From project root node_modules (most common for installed packages)
1949
+ path4.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
1950
+ // From CLI dist folder for development
1951
+ path4.resolve(__dirname4, "../../core/templates"),
1952
+ // Legacy paths for different build structures
1953
+ path4.resolve(__dirname4, "../../../../../core/templates"),
1954
+ path4.resolve(__dirname4, "../../../../core/templates")
1955
+ ];
1956
+ for (const p of possiblePaths) {
1957
+ if (fs4.existsSync(p)) {
1958
+ return p;
1812
1959
  }
1813
- throw new Error("Could not find @nextsparkjs/core templates directory");
1814
1960
  }
1961
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
1815
1962
  }
1816
1963
  function getFeaturesDir() {
1817
1964
  return path4.join(getTemplatesDir2(), "features");
@@ -1819,20 +1966,27 @@ function getFeaturesDir() {
1819
1966
  function getTargetThemeDir(projectSlug) {
1820
1967
  return path4.resolve(process.cwd(), "contents", "themes", projectSlug);
1821
1968
  }
1822
- async function copyPagesFeature(config) {
1969
+ async function copyPagesFeature(config2) {
1823
1970
  const featuresDir = getFeaturesDir();
1824
- const targetThemeDir = getTargetThemeDir(config.projectSlug);
1971
+ const targetThemeDir = getTargetThemeDir(config2.projectSlug);
1825
1972
  const sourcePagesEntity = path4.join(featuresDir, "pages", "entities", "pages");
1826
1973
  const targetEntitiesDir = path4.join(targetThemeDir, "entities", "pages");
1827
- if (!await fs4.pathExists(sourcePagesEntity)) {
1974
+ if (await fs4.pathExists(sourcePagesEntity)) {
1975
+ await fs4.copy(sourcePagesEntity, targetEntitiesDir);
1976
+ } else {
1828
1977
  console.warn(`Warning: Pages entity not found at: ${sourcePagesEntity}`);
1829
- return;
1830
1978
  }
1831
- await fs4.copy(sourcePagesEntity, targetEntitiesDir);
1979
+ const sourceHeroBlock = path4.join(featuresDir, "pages", "blocks", "hero");
1980
+ const targetHeroBlock = path4.join(targetThemeDir, "blocks", "hero");
1981
+ if (await fs4.pathExists(sourceHeroBlock)) {
1982
+ await fs4.copy(sourceHeroBlock, targetHeroBlock);
1983
+ } else {
1984
+ console.warn(`Warning: Hero block not found at: ${sourceHeroBlock}`);
1985
+ }
1832
1986
  }
1833
- async function copyBlogFeature(config) {
1987
+ async function copyBlogFeature(config2) {
1834
1988
  const featuresDir = getFeaturesDir();
1835
- const targetThemeDir = getTargetThemeDir(config.projectSlug);
1989
+ const targetThemeDir = getTargetThemeDir(config2.projectSlug);
1836
1990
  const sourcePostsEntity = path4.join(featuresDir, "blog", "entities", "posts");
1837
1991
  const targetPostsEntity = path4.join(targetThemeDir, "entities", "posts");
1838
1992
  if (await fs4.pathExists(sourcePostsEntity)) {
@@ -1848,34 +2002,34 @@ async function copyBlogFeature(config) {
1848
2002
  console.warn(`Warning: Post-content block not found at: ${sourcePostContentBlock}`);
1849
2003
  }
1850
2004
  }
1851
- async function copyContentFeatures(config) {
1852
- if (!config.contentFeatures.pages && !config.contentFeatures.blog) {
2005
+ async function copyContentFeatures(config2) {
2006
+ if (!config2.contentFeatures.pages && !config2.contentFeatures.blog) {
1853
2007
  return;
1854
2008
  }
1855
- if (config.contentFeatures.pages) {
1856
- await copyPagesFeature(config);
2009
+ if (config2.contentFeatures.pages) {
2010
+ await copyPagesFeature(config2);
1857
2011
  }
1858
- if (config.contentFeatures.blog) {
1859
- await copyBlogFeature(config);
2012
+ if (config2.contentFeatures.blog) {
2013
+ await copyBlogFeature(config2);
1860
2014
  }
1861
2015
  }
1862
2016
 
1863
2017
  // src/wizard/generators/theme-plugins-installer.ts
1864
- import { existsSync as existsSync6, cpSync, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
1865
- import { join as join5, resolve as resolve2 } from "path";
2018
+ import { existsSync as existsSync6, cpSync, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
2019
+ import { join as join6, resolve as resolve2 } from "path";
1866
2020
  import chalk9 from "chalk";
1867
2021
  import ora6 from "ora";
1868
2022
 
1869
2023
  // src/commands/add-theme.ts
1870
2024
  import chalk8 from "chalk";
1871
2025
  import ora5 from "ora";
1872
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1873
- import { join as join4 } from "path";
2026
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2027
+ import { join as join5 } from "path";
1874
2028
  async function addTheme(packageSpec, options = {}) {
1875
2029
  const spinner = ora5(`Adding theme ${packageSpec}`).start();
1876
2030
  let cleanup = null;
1877
2031
  try {
1878
- const contentsDir = join4(process.cwd(), "contents");
2032
+ const contentsDir = join5(process.cwd(), "contents");
1879
2033
  if (!existsSync5(contentsDir)) {
1880
2034
  spinner.fail('contents/ directory not found. Run "nextspark init" first.');
1881
2035
  return;
@@ -1938,14 +2092,14 @@ async function addTheme(packageSpec, options = {}) {
1938
2092
  }
1939
2093
  function checkPluginExists(pluginName) {
1940
2094
  const name = pluginName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
1941
- return existsSync5(join4(process.cwd(), "contents", "plugins", name));
2095
+ return existsSync5(join5(process.cwd(), "contents", "plugins", name));
1942
2096
  }
1943
2097
  function getCoreVersion() {
1944
- const pkgPath = join4(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
2098
+ const pkgPath = join5(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
1945
2099
  if (existsSync5(pkgPath)) {
1946
2100
  try {
1947
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1948
- return pkg.version || "0.0.0";
2101
+ const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
2102
+ return pkg2.version || "0.0.0";
1949
2103
  } catch {
1950
2104
  return "0.0.0";
1951
2105
  }
@@ -1982,20 +2136,20 @@ var THEME_REQUIRED_PLUGINS2 = {
1982
2136
  };
1983
2137
  function isMonorepoMode2() {
1984
2138
  const possiblePaths = [
1985
- join5(process.cwd(), "pnpm-workspace.yaml"),
1986
- join5(process.cwd(), "..", "pnpm-workspace.yaml"),
1987
- join5(process.cwd(), "..", "..", "pnpm-workspace.yaml")
2139
+ join6(process.cwd(), "pnpm-workspace.yaml"),
2140
+ join6(process.cwd(), "..", "pnpm-workspace.yaml"),
2141
+ join6(process.cwd(), "..", "..", "pnpm-workspace.yaml")
1988
2142
  ];
1989
2143
  return possiblePaths.some((p) => existsSync6(p));
1990
2144
  }
1991
2145
  function getMonorepoRoot() {
1992
2146
  const possibleRoots = [
1993
2147
  process.cwd(),
1994
- join5(process.cwd(), ".."),
1995
- join5(process.cwd(), "..", "..")
2148
+ join6(process.cwd(), ".."),
2149
+ join6(process.cwd(), "..", "..")
1996
2150
  ];
1997
2151
  for (const root of possibleRoots) {
1998
- if (existsSync6(join5(root, "pnpm-workspace.yaml"))) {
2152
+ if (existsSync6(join6(root, "pnpm-workspace.yaml"))) {
1999
2153
  return resolve2(root);
2000
2154
  }
2001
2155
  }
@@ -2005,15 +2159,15 @@ function getLocalPackageDir(type, name) {
2005
2159
  const monorepoRoot = getMonorepoRoot();
2006
2160
  if (!monorepoRoot) return null;
2007
2161
  const baseDir = type === "theme" ? "themes" : "plugins";
2008
- const packageDir = join5(monorepoRoot, baseDir, name);
2009
- if (existsSync6(packageDir) && existsSync6(join5(packageDir, "package.json"))) {
2162
+ const packageDir = join6(monorepoRoot, baseDir, name);
2163
+ if (existsSync6(packageDir) && existsSync6(join6(packageDir, "package.json"))) {
2010
2164
  return packageDir;
2011
2165
  }
2012
2166
  return null;
2013
2167
  }
2014
2168
  async function copyLocalTheme(name, sourceDir) {
2015
- const targetDir = join5(process.cwd(), "contents", "themes", name);
2016
- const themesDir = join5(process.cwd(), "contents", "themes");
2169
+ const targetDir = join6(process.cwd(), "contents", "themes", name);
2170
+ const themesDir = join6(process.cwd(), "contents", "themes");
2017
2171
  if (!existsSync6(themesDir)) {
2018
2172
  mkdirSync(themesDir, { recursive: true });
2019
2173
  }
@@ -2026,8 +2180,8 @@ async function copyLocalTheme(name, sourceDir) {
2026
2180
  return true;
2027
2181
  }
2028
2182
  async function copyLocalPlugin(name, sourceDir) {
2029
- const targetDir = join5(process.cwd(), "contents", "plugins", name);
2030
- const pluginsDir = join5(process.cwd(), "contents", "plugins");
2183
+ const targetDir = join6(process.cwd(), "contents", "plugins", name);
2184
+ const pluginsDir = join6(process.cwd(), "contents", "plugins");
2031
2185
  if (!existsSync6(pluginsDir)) {
2032
2186
  mkdirSync(pluginsDir, { recursive: true });
2033
2187
  }
@@ -2040,12 +2194,12 @@ async function copyLocalPlugin(name, sourceDir) {
2040
2194
  return true;
2041
2195
  }
2042
2196
  async function updateTsConfigPaths(name, type) {
2043
- const tsconfigPath = join5(process.cwd(), "tsconfig.json");
2197
+ const tsconfigPath = join6(process.cwd(), "tsconfig.json");
2044
2198
  if (!existsSync6(tsconfigPath)) {
2045
2199
  return;
2046
2200
  }
2047
2201
  try {
2048
- const content = readFileSync5(tsconfigPath, "utf-8");
2202
+ const content = readFileSync6(tsconfigPath, "utf-8");
2049
2203
  const tsconfig = JSON.parse(content);
2050
2204
  if (!tsconfig.compilerOptions) {
2051
2205
  tsconfig.compilerOptions = {};
@@ -2085,7 +2239,7 @@ async function installTheme2(theme) {
2085
2239
  prefixText: " "
2086
2240
  }).start();
2087
2241
  try {
2088
- const targetDir = join5(process.cwd(), "contents", "themes", theme);
2242
+ const targetDir = join6(process.cwd(), "contents", "themes", theme);
2089
2243
  if (existsSync6(targetDir)) {
2090
2244
  spinner.info(chalk9.gray(`Reference theme ${theme} already exists`));
2091
2245
  return true;
@@ -2138,7 +2292,7 @@ async function installPlugins(plugins) {
2138
2292
  prefixText: " "
2139
2293
  }).start();
2140
2294
  try {
2141
- const pluginDir = join5(process.cwd(), "contents", "plugins", plugin);
2295
+ const pluginDir = join6(process.cwd(), "contents", "plugins", plugin);
2142
2296
  if (existsSync6(pluginDir)) {
2143
2297
  spinner.info(chalk9.gray(`Plugin ${plugin} already installed`));
2144
2298
  continue;
@@ -2198,62 +2352,607 @@ async function installThemeAndPlugins(theme, plugins) {
2198
2352
 
2199
2353
  // src/wizard/generators/env-setup.ts
2200
2354
  import fs5 from "fs-extra";
2355
+ import path5 from "path";
2356
+ import { fileURLToPath as fileURLToPath5 } from "url";
2357
+ var __filename5 = fileURLToPath5(import.meta.url);
2358
+ var __dirname5 = path5.dirname(__filename5);
2359
+ var ENV_TEMPLATE_PATH = path5.resolve(__dirname5, "../../../templates/env.template");
2201
2360
 
2202
2361
  // src/wizard/generators/git-init.ts
2203
2362
  import fs6 from "fs-extra";
2204
2363
 
2364
+ // src/wizard/generators/monorepo-generator.ts
2365
+ import fs7 from "fs-extra";
2366
+ import path6 from "path";
2367
+ import { fileURLToPath as fileURLToPath6 } from "url";
2368
+ var __filename6 = fileURLToPath6(import.meta.url);
2369
+ var __dirname6 = path6.dirname(__filename6);
2370
+ var DIRS = {
2371
+ WEB: "web",
2372
+ MOBILE: "mobile",
2373
+ ASSETS: "assets"
2374
+ };
2375
+ var FILES = {
2376
+ PNPM_WORKSPACE: "pnpm-workspace.yaml",
2377
+ NPMRC: ".npmrc",
2378
+ GITIGNORE: ".gitignore",
2379
+ TSCONFIG: "tsconfig.json",
2380
+ PACKAGE_JSON: "package.json",
2381
+ README: "README.md",
2382
+ APP_CONFIG: "app.config.ts"
2383
+ };
2384
+ var REQUIRED_MOBILE_TEMPLATE_FILES = [
2385
+ "app",
2386
+ "src",
2387
+ "babel.config.js",
2388
+ "metro.config.js"
2389
+ ];
2390
+ var MOBILE_TEMPLATE_FILES = [
2391
+ "app",
2392
+ "src",
2393
+ "babel.config.js",
2394
+ "metro.config.js",
2395
+ "tailwind.config.js",
2396
+ "tsconfig.json",
2397
+ "jest.config.js",
2398
+ "eas.json"
2399
+ ];
2400
+ var VERSIONS = {
2401
+ // NextSpark packages
2402
+ NEXTSPARK_MOBILE: "^0.1.0-beta.1",
2403
+ NEXTSPARK_UI: "^0.1.0-beta.1",
2404
+ // Core dependencies
2405
+ TANSTACK_QUERY: "^5.62.0",
2406
+ EXPO: "^54.0.0",
2407
+ REACT: "19.1.0",
2408
+ REACT_NATIVE: "0.81.5",
2409
+ TYPESCRIPT: "^5.3.0",
2410
+ // Expo modules (use ~ for patch compatibility)
2411
+ EXPO_CONSTANTS: "~18.0.13",
2412
+ EXPO_LINKING: "~8.0.11",
2413
+ EXPO_ROUTER: "~6.0.22",
2414
+ EXPO_SECURE_STORE: "~15.0.8",
2415
+ EXPO_STATUS_BAR: "~3.0.9",
2416
+ // React Native modules
2417
+ RN_GESTURE_HANDLER: "~2.28.0",
2418
+ RN_REANIMATED: "~4.1.1",
2419
+ RN_SAFE_AREA: "~5.6.0",
2420
+ RN_SCREENS: "~4.16.0",
2421
+ RN_SVG: "15.12.1",
2422
+ RN_WEB: "^0.21.0",
2423
+ // Styling
2424
+ NATIVEWIND: "^4.2.1",
2425
+ TAILWINDCSS: "^3",
2426
+ TAILWIND_MERGE: "^3.4.0",
2427
+ LUCIDE_RN: "^0.563.0",
2428
+ // Dev dependencies
2429
+ BABEL_CORE: "^7.25.0",
2430
+ JEST: "^29.7.0",
2431
+ JEST_EXPO: "^54.0.16",
2432
+ TESTING_LIBRARY_JEST_NATIVE: "^5.4.3",
2433
+ TESTING_LIBRARY_RN: "^13.3.3"
2434
+ };
2435
+ function getMobileTemplatesDir() {
2436
+ const possiblePaths = [
2437
+ // From CWD node_modules (installed package)
2438
+ path6.resolve(process.cwd(), "node_modules/@nextsparkjs/mobile/templates"),
2439
+ // From CLI dist folder: ../../mobile/templates (development)
2440
+ path6.resolve(__dirname6, "../../mobile/templates"),
2441
+ // Legacy paths for different build structures
2442
+ path6.resolve(__dirname6, "../../../../../mobile/templates"),
2443
+ path6.resolve(__dirname6, "../../../../mobile/templates")
2444
+ ];
2445
+ for (const p of possiblePaths) {
2446
+ if (fs7.existsSync(p)) {
2447
+ return p;
2448
+ }
2449
+ }
2450
+ const searchedPaths = possiblePaths.map((p) => ` - ${p}`).join("\n");
2451
+ throw new Error(
2452
+ `Could not find @nextsparkjs/mobile templates directory.
2453
+
2454
+ Searched paths:
2455
+ ${searchedPaths}
2456
+
2457
+ To fix this, ensure @nextsparkjs/mobile is installed:
2458
+ pnpm add @nextsparkjs/mobile
2459
+
2460
+ If you're developing locally, make sure the mobile package is built:
2461
+ cd packages/mobile && pnpm build`
2462
+ );
2463
+ }
2464
+ async function validateMobileTemplate(templateDir) {
2465
+ const missing = [];
2466
+ for (const file of REQUIRED_MOBILE_TEMPLATE_FILES) {
2467
+ const filePath = path6.join(templateDir, file);
2468
+ if (!await fs7.pathExists(filePath)) {
2469
+ missing.push(file);
2470
+ }
2471
+ }
2472
+ if (missing.length > 0) {
2473
+ throw new Error(
2474
+ `Mobile template is incomplete. Missing required files:
2475
+ ` + missing.map((f) => ` - ${f}`).join("\n") + `
2476
+
2477
+ Template location: ${templateDir}
2478
+ Please ensure @nextsparkjs/mobile is properly installed and built.`
2479
+ );
2480
+ }
2481
+ }
2482
+ function slugToBundleId(slug) {
2483
+ return slug.toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "").replace(/\.{2,}/g, ".");
2484
+ }
2485
+ async function createRootPackageJson(targetDir, config2) {
2486
+ const rootPkg = {
2487
+ name: config2.projectSlug,
2488
+ version: "0.1.0",
2489
+ private: true,
2490
+ scripts: {
2491
+ // Web commands
2492
+ "dev": `pnpm --filter ${DIRS.WEB} dev`,
2493
+ "build": `pnpm --filter ${DIRS.WEB} build`,
2494
+ "start": `pnpm --filter ${DIRS.WEB} start`,
2495
+ "lint": "pnpm -r lint",
2496
+ // Mobile commands
2497
+ "dev:mobile": `pnpm --filter ${DIRS.MOBILE} start`,
2498
+ "ios": `pnpm --filter ${DIRS.MOBILE} ios`,
2499
+ "android": `pnpm --filter ${DIRS.MOBILE} android`,
2500
+ // Shared commands
2501
+ "typecheck": "pnpm -r typecheck",
2502
+ "test": "pnpm -r test",
2503
+ // Web-specific CLI commands (run from root)
2504
+ "db:migrate": `pnpm --filter ${DIRS.WEB} db:migrate`,
2505
+ "db:seed": `pnpm --filter ${DIRS.WEB} db:seed`,
2506
+ "build:registries": `pnpm --filter ${DIRS.WEB} build:registries`
2507
+ },
2508
+ devDependencies: {
2509
+ "typescript": VERSIONS.TYPESCRIPT
2510
+ }
2511
+ };
2512
+ await fs7.writeJson(path6.join(targetDir, FILES.PACKAGE_JSON), rootPkg, { spaces: 2 });
2513
+ }
2514
+ async function createPnpmWorkspace(targetDir) {
2515
+ const workspaceContent = `packages:
2516
+ - '${DIRS.WEB}'
2517
+ - '${DIRS.MOBILE}'
2518
+ `;
2519
+ await fs7.writeFile(path6.join(targetDir, FILES.PNPM_WORKSPACE), workspaceContent);
2520
+ }
2521
+ async function createNpmrc(targetDir) {
2522
+ const npmrcContent = `# Enable proper hoisting for monorepo
2523
+ public-hoist-pattern[]=*@nextsparkjs/*
2524
+ public-hoist-pattern[]=*expo*
2525
+ public-hoist-pattern[]=*react-native*
2526
+ shamefully-hoist=true
2527
+ `;
2528
+ await fs7.writeFile(path6.join(targetDir, FILES.NPMRC), npmrcContent);
2529
+ }
2530
+ async function createGitignore(targetDir) {
2531
+ const gitignoreContent = `# Dependencies
2532
+ node_modules/
2533
+
2534
+ # Build outputs
2535
+ .next/
2536
+ dist/
2537
+ out/
2538
+ build/
2539
+ .expo/
2540
+
2541
+ # NextSpark
2542
+ .nextspark/
2543
+
2544
+ # Environment
2545
+ .env
2546
+ .env.local
2547
+ .env.*.local
2548
+
2549
+ # IDE
2550
+ .idea/
2551
+ .vscode/
2552
+ *.swp
2553
+ *.swo
2554
+
2555
+ # OS
2556
+ .DS_Store
2557
+ Thumbs.db
2558
+
2559
+ # Testing
2560
+ coverage/
2561
+ .nyc_output/
2562
+
2563
+ # Cypress (theme-based in web/)
2564
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/videos
2565
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/screenshots
2566
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/allure-results
2567
+ ${DIRS.WEB}/contents/themes/*/tests/cypress/allure-report
2568
+
2569
+ # Jest (theme-based in web/)
2570
+ ${DIRS.WEB}/contents/themes/*/tests/jest/coverage
2571
+
2572
+ # Mobile specific
2573
+ ${DIRS.MOBILE}/.expo/
2574
+ *.jks
2575
+ *.p8
2576
+ *.p12
2577
+ *.key
2578
+ *.mobileprovision
2579
+ *.orig.*
2580
+ web-build/
2581
+ `;
2582
+ await fs7.writeFile(path6.join(targetDir, FILES.GITIGNORE), gitignoreContent);
2583
+ }
2584
+ async function createRootTsConfig(targetDir) {
2585
+ const tsConfig = {
2586
+ compilerOptions: {
2587
+ target: "ES2022",
2588
+ module: "ESNext",
2589
+ moduleResolution: "bundler",
2590
+ strict: true,
2591
+ skipLibCheck: true,
2592
+ esModuleInterop: true
2593
+ },
2594
+ references: [
2595
+ { path: `./${DIRS.WEB}` },
2596
+ { path: `./${DIRS.MOBILE}` }
2597
+ ]
2598
+ };
2599
+ await fs7.writeJson(path6.join(targetDir, FILES.TSCONFIG), tsConfig, { spaces: 2 });
2600
+ }
2601
+ async function copyMobileTemplate(targetDir, config2) {
2602
+ const mobileTemplatesDir = getMobileTemplatesDir();
2603
+ await validateMobileTemplate(mobileTemplatesDir);
2604
+ const mobileDir = path6.join(targetDir, DIRS.MOBILE);
2605
+ await fs7.ensureDir(mobileDir);
2606
+ for (const item of MOBILE_TEMPLATE_FILES) {
2607
+ const srcPath = path6.join(mobileTemplatesDir, item);
2608
+ const destPath = path6.join(mobileDir, item);
2609
+ if (await fs7.pathExists(srcPath)) {
2610
+ await fs7.copy(srcPath, destPath);
2611
+ }
2612
+ }
2613
+ await createMobilePackageJson(mobileDir, config2);
2614
+ await createMobileAppConfig(mobileDir, config2);
2615
+ await createMobileAssets(mobileDir, config2);
2616
+ }
2617
+ async function createMobilePackageJson(mobileDir, config2) {
2618
+ const mobileSlug = `${config2.projectSlug}-mobile`;
2619
+ const packageJson = {
2620
+ name: mobileSlug,
2621
+ version: "0.1.0",
2622
+ private: true,
2623
+ main: "expo-router/entry",
2624
+ scripts: {
2625
+ start: "expo start",
2626
+ android: "expo start --android",
2627
+ ios: "expo start --ios",
2628
+ web: "expo start --web",
2629
+ lint: "eslint .",
2630
+ typecheck: "tsc --noEmit",
2631
+ test: "jest",
2632
+ "test:watch": "jest --watch",
2633
+ "test:coverage": "jest --coverage"
2634
+ },
2635
+ dependencies: {
2636
+ "@nextsparkjs/mobile": VERSIONS.NEXTSPARK_MOBILE,
2637
+ "@nextsparkjs/ui": VERSIONS.NEXTSPARK_UI,
2638
+ "@tanstack/react-query": VERSIONS.TANSTACK_QUERY,
2639
+ "expo": VERSIONS.EXPO,
2640
+ "expo-constants": VERSIONS.EXPO_CONSTANTS,
2641
+ "expo-linking": VERSIONS.EXPO_LINKING,
2642
+ "expo-router": VERSIONS.EXPO_ROUTER,
2643
+ "expo-secure-store": VERSIONS.EXPO_SECURE_STORE,
2644
+ "expo-status-bar": VERSIONS.EXPO_STATUS_BAR,
2645
+ "lucide-react-native": VERSIONS.LUCIDE_RN,
2646
+ "nativewind": VERSIONS.NATIVEWIND,
2647
+ "react": VERSIONS.REACT,
2648
+ "react-dom": VERSIONS.REACT,
2649
+ "react-native": VERSIONS.REACT_NATIVE,
2650
+ "react-native-web": VERSIONS.RN_WEB,
2651
+ "react-native-gesture-handler": VERSIONS.RN_GESTURE_HANDLER,
2652
+ "react-native-reanimated": VERSIONS.RN_REANIMATED,
2653
+ "react-native-safe-area-context": VERSIONS.RN_SAFE_AREA,
2654
+ "react-native-screens": VERSIONS.RN_SCREENS,
2655
+ "react-native-svg": VERSIONS.RN_SVG,
2656
+ "tailwind-merge": VERSIONS.TAILWIND_MERGE,
2657
+ "tailwindcss": VERSIONS.TAILWINDCSS
2658
+ },
2659
+ devDependencies: {
2660
+ "@babel/core": VERSIONS.BABEL_CORE,
2661
+ "@testing-library/jest-native": VERSIONS.TESTING_LIBRARY_JEST_NATIVE,
2662
+ "@testing-library/react-native": VERSIONS.TESTING_LIBRARY_RN,
2663
+ "@types/jest": "^29.5.0",
2664
+ "@types/react": "^19",
2665
+ "jest": VERSIONS.JEST,
2666
+ "jest-expo": VERSIONS.JEST_EXPO,
2667
+ "react-test-renderer": VERSIONS.REACT,
2668
+ "typescript": VERSIONS.TYPESCRIPT
2669
+ }
2670
+ };
2671
+ await fs7.writeJson(path6.join(mobileDir, FILES.PACKAGE_JSON), packageJson, { spaces: 2 });
2672
+ }
2673
+ async function createMobileAppConfig(mobileDir, config2) {
2674
+ const bundleId = slugToBundleId(config2.projectSlug);
2675
+ const appConfigContent = `import { ExpoConfig, ConfigContext } from 'expo/config'
2676
+
2677
+ export default ({ config }: ConfigContext): ExpoConfig => ({
2678
+ ...config,
2679
+ name: '${config2.projectName}',
2680
+ slug: '${config2.projectSlug}',
2681
+ version: '1.0.0',
2682
+ orientation: 'portrait',
2683
+ icon: './${DIRS.ASSETS}/icon.png',
2684
+ userInterfaceStyle: 'automatic',
2685
+ splash: {
2686
+ image: './${DIRS.ASSETS}/splash.png',
2687
+ resizeMode: 'contain',
2688
+ backgroundColor: '#ffffff',
2689
+ },
2690
+ assetBundlePatterns: ['**/*'],
2691
+ ios: {
2692
+ supportsTablet: true,
2693
+ bundleIdentifier: 'com.${bundleId}.app',
2694
+ },
2695
+ android: {
2696
+ adaptiveIcon: {
2697
+ foregroundImage: './${DIRS.ASSETS}/adaptive-icon.png',
2698
+ backgroundColor: '#ffffff',
2699
+ },
2700
+ package: 'com.${bundleId}.app',
2701
+ },
2702
+ web: {
2703
+ favicon: './${DIRS.ASSETS}/favicon.png',
2704
+ },
2705
+ extra: {
2706
+ // API URL - Configure for your environment
2707
+ // Development: point to your local web app (e.g., http://localhost:3000)
2708
+ // Production: set via EAS environment variables
2709
+ apiUrl: process.env.EXPO_PUBLIC_API_URL,
2710
+ },
2711
+ plugins: ['expo-router', 'expo-secure-store'],
2712
+ scheme: '${config2.projectSlug}',
2713
+ })
2714
+ `;
2715
+ await fs7.writeFile(path6.join(mobileDir, FILES.APP_CONFIG), appConfigContent);
2716
+ }
2717
+ async function createMobileAssets(mobileDir, config2) {
2718
+ const assetsDir = path6.join(mobileDir, DIRS.ASSETS);
2719
+ await fs7.ensureDir(assetsDir);
2720
+ await fs7.writeFile(
2721
+ path6.join(assetsDir, ".gitkeep"),
2722
+ "# Placeholder - replace with your app icons and splash screens\n"
2723
+ );
2724
+ const assetsReadme = `# Mobile App Assets for ${config2.projectName}
2725
+
2726
+ This directory contains your mobile app icons and splash screens.
2727
+
2728
+ ## Required Files
2729
+
2730
+ | File | Size | Description |
2731
+ |------|------|-------------|
2732
+ | \`icon.png\` | 1024x1024px | Main app icon (iOS & Android) |
2733
+ | \`splash.png\` | 1284x2778px | Splash screen image |
2734
+ | \`adaptive-icon.png\` | 1024x1024px | Android adaptive icon (foreground) |
2735
+ | \`favicon.png\` | 48x48px | Web favicon |
2736
+
2737
+ ## How to Generate
2738
+
2739
+ ### Option 1: Expo Asset Generator (Recommended)
2740
+
2741
+ 1. Create a 1024x1024px icon image
2742
+ 2. Use Expo's icon generator:
2743
+ \`\`\`bash
2744
+ npx expo-optimize
2745
+ \`\`\`
2746
+
2747
+ ### Option 2: Online Tools
2748
+
2749
+ - [Expo Icon Generator](https://docs.expo.dev/develop/user-interface/app-icons/)
2750
+ - [App Icon Generator](https://appicon.co/)
2751
+ - [Figma App Icon Template](https://www.figma.com/community/file/824894885635013116)
2752
+
2753
+ ### Option 3: Manual Creation
2754
+
2755
+ Create each file at the specified sizes above. Use PNG format with transparency for icons.
2756
+
2757
+ ## Tips
2758
+
2759
+ - Use a simple, recognizable design that works at small sizes
2760
+ - Test your icon on both light and dark backgrounds
2761
+ - Avoid text in the icon (it becomes illegible at small sizes)
2762
+ - Keep important content within the "safe zone" (center 80%)
2763
+
2764
+ ## Resources
2765
+
2766
+ - [Expo App Icons Documentation](https://docs.expo.dev/develop/user-interface/app-icons/)
2767
+ - [iOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/app-icons)
2768
+ - [Android Adaptive Icons](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive)
2769
+ `;
2770
+ await fs7.writeFile(path6.join(assetsDir, "README.md"), assetsReadme);
2771
+ }
2772
+ async function createMonorepoReadme(targetDir, config2) {
2773
+ const readmeContent = `# ${config2.projectName}
2774
+
2775
+ ${config2.projectDescription}
2776
+
2777
+ ## Project Structure
2778
+
2779
+ This is a monorepo containing both web and mobile applications:
2780
+
2781
+ \`\`\`
2782
+ ${config2.projectSlug}/
2783
+ \u251C\u2500\u2500 ${DIRS.WEB}/ # Next.js web application
2784
+ \u2502 \u251C\u2500\u2500 app/ # Next.js App Router
2785
+ \u2502 \u251C\u2500\u2500 contents/ # Themes and plugins
2786
+ \u2502 \u2514\u2500\u2500 package.json
2787
+ \u251C\u2500\u2500 ${DIRS.MOBILE}/ # Expo mobile application
2788
+ \u2502 \u251C\u2500\u2500 app/ # Expo Router screens
2789
+ \u2502 \u251C\u2500\u2500 src/ # Mobile-specific code
2790
+ \u2502 \u2514\u2500\u2500 package.json
2791
+ \u251C\u2500\u2500 package.json # Root monorepo
2792
+ \u2514\u2500\u2500 ${FILES.PNPM_WORKSPACE}
2793
+ \`\`\`
2794
+
2795
+ ## Getting Started
2796
+
2797
+ ### Prerequisites
2798
+
2799
+ - Node.js 20+
2800
+ - pnpm 9+
2801
+ - For mobile: Expo CLI (\`npm install -g expo-cli\`)
2802
+
2803
+ ### Installation
2804
+
2805
+ \`\`\`bash
2806
+ # Install all dependencies
2807
+ pnpm install
2808
+
2809
+ # Set up environment variables
2810
+ cp ${DIRS.WEB}/.env.example ${DIRS.WEB}/.env
2811
+ # Edit ${DIRS.WEB}/.env with your configuration
2812
+ \`\`\`
2813
+
2814
+ ### Development
2815
+
2816
+ **Web Application:**
2817
+ \`\`\`bash
2818
+ # From root directory
2819
+ pnpm dev
2820
+
2821
+ # Or from web directory
2822
+ cd ${DIRS.WEB} && pnpm dev
2823
+ \`\`\`
2824
+
2825
+ **Mobile Application:**
2826
+ \`\`\`bash
2827
+ # From root directory
2828
+ pnpm dev:mobile
2829
+
2830
+ # Or from mobile directory
2831
+ cd ${DIRS.MOBILE} && pnpm start
2832
+ \`\`\`
2833
+
2834
+ ### Running Tests
2835
+
2836
+ \`\`\`bash
2837
+ # Run all tests
2838
+ pnpm test
2839
+
2840
+ # Run web tests only
2841
+ pnpm --filter ${DIRS.WEB} test
2842
+
2843
+ # Run mobile tests only
2844
+ pnpm --filter ${DIRS.MOBILE} test
2845
+ \`\`\`
2846
+
2847
+ ## Mobile App Configuration
2848
+
2849
+ The mobile app connects to your web API. Configure the API URL:
2850
+
2851
+ - **Development:** The mobile app will auto-detect your local server
2852
+ - **Production:** Set \`EXPO_PUBLIC_API_URL\` in your EAS environment
2853
+
2854
+ ## Building for Production
2855
+
2856
+ **Web:**
2857
+ \`\`\`bash
2858
+ pnpm build
2859
+ \`\`\`
2860
+
2861
+ **Mobile:**
2862
+ \`\`\`bash
2863
+ cd ${DIRS.MOBILE}
2864
+ eas build --platform ios
2865
+ eas build --platform android
2866
+ \`\`\`
2867
+
2868
+ ## Learn More
2869
+
2870
+ - [NextSpark Documentation](https://nextspark.dev/docs)
2871
+ - [Expo Documentation](https://docs.expo.dev)
2872
+ - [Next.js Documentation](https://nextjs.org/docs)
2873
+ `;
2874
+ await fs7.writeFile(path6.join(targetDir, FILES.README), readmeContent);
2875
+ }
2876
+ async function generateMonorepoStructure(targetDir, config2) {
2877
+ await createRootPackageJson(targetDir, config2);
2878
+ await createPnpmWorkspace(targetDir);
2879
+ await createNpmrc(targetDir);
2880
+ await createGitignore(targetDir);
2881
+ await createRootTsConfig(targetDir);
2882
+ await createMonorepoReadme(targetDir, config2);
2883
+ const webDir = path6.join(targetDir, DIRS.WEB);
2884
+ await fs7.ensureDir(webDir);
2885
+ await copyMobileTemplate(targetDir, config2);
2886
+ }
2887
+ function isMonorepoProject(config2) {
2888
+ return config2.projectType === "web-mobile";
2889
+ }
2890
+ function getWebDir(targetDir, config2) {
2891
+ return config2.projectType === "web-mobile" ? path6.join(targetDir, DIRS.WEB) : targetDir;
2892
+ }
2893
+
2205
2894
  // src/wizard/generators/index.ts
2206
- var __filename4 = fileURLToPath4(import.meta.url);
2207
- var __dirname4 = path5.dirname(__filename4);
2208
- function getTemplatesDir3() {
2209
- try {
2210
- const corePkgPath = __require.resolve("@nextsparkjs/core/package.json");
2211
- return path5.join(path5.dirname(corePkgPath), "templates");
2212
- } catch {
2213
- const possiblePaths = [
2214
- path5.resolve(__dirname4, "../../../../../core/templates"),
2215
- path5.resolve(__dirname4, "../../../../core/templates"),
2216
- path5.resolve(process.cwd(), "node_modules/@nextsparkjs/core/templates")
2217
- ];
2218
- for (const p of possiblePaths) {
2219
- if (fs7.existsSync(p)) {
2220
- return p;
2221
- }
2895
+ var __filename7 = fileURLToPath7(import.meta.url);
2896
+ var __dirname7 = path7.dirname(__filename7);
2897
+ function getTemplatesDir3(projectRoot) {
2898
+ const rootDir = projectRoot || process.cwd();
2899
+ const possiblePaths = [
2900
+ // From project root node_modules (most common for installed packages)
2901
+ path7.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
2902
+ // From CLI dist folder for development
2903
+ path7.resolve(__dirname7, "../../core/templates"),
2904
+ // Legacy paths for different build structures
2905
+ path7.resolve(__dirname7, "../../../../../core/templates"),
2906
+ path7.resolve(__dirname7, "../../../../core/templates")
2907
+ ];
2908
+ for (const p of possiblePaths) {
2909
+ if (fs8.existsSync(p)) {
2910
+ return p;
2222
2911
  }
2223
- throw new Error("Could not find @nextsparkjs/core templates directory");
2224
2912
  }
2913
+ throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
2225
2914
  }
2915
+ var cachedTemplatesDir = null;
2226
2916
  async function copyProjectFiles() {
2227
- const templatesDir = getTemplatesDir3();
2917
+ if (!cachedTemplatesDir) {
2918
+ throw new Error("Templates directory not cached. Call cacheTemplatesDir() first.");
2919
+ }
2920
+ const templatesDir = cachedTemplatesDir;
2228
2921
  const projectDir = process.cwd();
2229
2922
  const itemsToCopy = [
2230
2923
  { src: "app", dest: "app", force: true },
2231
2924
  { src: "public", dest: "public", force: true },
2925
+ { src: "proxy.ts", dest: "proxy.ts", force: true },
2926
+ // Next.js 16+ proxy (formerly middleware.ts)
2232
2927
  { src: "next.config.mjs", dest: "next.config.mjs", force: true },
2233
2928
  { src: "tsconfig.json", dest: "tsconfig.json", force: true },
2234
2929
  { src: "postcss.config.mjs", dest: "postcss.config.mjs", force: true },
2235
2930
  { src: "i18n.ts", dest: "i18n.ts", force: true },
2236
- { src: "npmrc", dest: ".npmrc", force: false },
2931
+ { src: "pnpm-workspace.yaml", dest: "pnpm-workspace.yaml", force: true },
2932
+ // Enable workspace for themes/plugins - REQUIRED
2933
+ // Note: .npmrc is NOT copied for web-only projects (not needed, pnpm default hoisting works fine)
2934
+ // For monorepo projects, monorepo-generator.ts creates a specific .npmrc with expo/react-native patterns
2237
2935
  { src: "tsconfig.cypress.json", dest: "tsconfig.cypress.json", force: false },
2238
2936
  { src: "cypress.d.ts", dest: "cypress.d.ts", force: false },
2239
- { src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false }
2937
+ { src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false },
2938
+ { src: "scripts/cy-run-prod.cjs", dest: "scripts/cy-run-prod.cjs", force: false }
2240
2939
  ];
2241
2940
  for (const item of itemsToCopy) {
2242
- const srcPath = path5.join(templatesDir, item.src);
2243
- const destPath = path5.join(projectDir, item.dest);
2244
- if (await fs7.pathExists(srcPath)) {
2245
- if (item.force || !await fs7.pathExists(destPath)) {
2246
- await fs7.copy(srcPath, destPath);
2941
+ const srcPath = path7.join(templatesDir, item.src);
2942
+ const destPath = path7.join(projectDir, item.dest);
2943
+ if (await fs8.pathExists(srcPath)) {
2944
+ if (item.force || !await fs8.pathExists(destPath)) {
2945
+ await fs8.copy(srcPath, destPath);
2247
2946
  }
2248
2947
  }
2249
2948
  }
2250
2949
  }
2251
- async function updatePackageJson(config) {
2252
- const packageJsonPath = path5.resolve(process.cwd(), "package.json");
2950
+ async function updatePackageJson(config2) {
2951
+ const packageJsonPath = path7.resolve(process.cwd(), "package.json");
2253
2952
  let packageJson;
2254
- if (!await fs7.pathExists(packageJsonPath)) {
2953
+ if (!await fs8.pathExists(packageJsonPath)) {
2255
2954
  packageJson = {
2256
- name: config.projectSlug,
2955
+ name: config2.projectSlug,
2257
2956
  version: "0.1.0",
2258
2957
  private: true,
2259
2958
  scripts: {},
@@ -2261,7 +2960,7 @@ async function updatePackageJson(config) {
2261
2960
  devDependencies: {}
2262
2961
  };
2263
2962
  } else {
2264
- packageJson = await fs7.readJson(packageJsonPath);
2963
+ packageJson = await fs8.readJson(packageJsonPath);
2265
2964
  }
2266
2965
  packageJson.scripts = packageJson.scripts || {};
2267
2966
  const scriptsToAdd = {
@@ -2272,11 +2971,13 @@ async function updatePackageJson(config) {
2272
2971
  "build:registries": "nextspark registry:build",
2273
2972
  "db:migrate": "nextspark db:migrate",
2274
2973
  "db:seed": "nextspark db:seed",
2275
- "test:theme": `jest --config contents/themes/${config.projectSlug}/tests/jest/jest.config.ts`,
2276
- "cy:open": `cypress open --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2277
- "cy:run": `cypress run --config-file contents/themes/${config.projectSlug}/tests/cypress.config.ts`,
2278
- "allure:generate": `allure generate contents/themes/${config.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config.projectSlug}/tests/cypress/allure-report`,
2279
- "allure:open": `allure open contents/themes/${config.projectSlug}/tests/cypress/allure-report`
2974
+ "test": "node node_modules/@nextsparkjs/core/scripts/test/jest-theme.mjs",
2975
+ "cy:open": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs open",
2976
+ "cy:run": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs run",
2977
+ "cy:tags": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs tags",
2978
+ "cy:run:prod": "node scripts/cy-run-prod.cjs",
2979
+ "allure:generate": `allure generate contents/themes/${config2.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config2.projectSlug}/tests/cypress/allure-report`,
2980
+ "allure:open": `allure open contents/themes/${config2.projectSlug}/tests/cypress/allure-report`
2280
2981
  };
2281
2982
  for (const [name, command] of Object.entries(scriptsToAdd)) {
2282
2983
  if (!packageJson.scripts[name]) {
@@ -2286,8 +2987,8 @@ async function updatePackageJson(config) {
2286
2987
  packageJson.dependencies = packageJson.dependencies || {};
2287
2988
  const depsToAdd = {
2288
2989
  // NextSpark
2289
- "@nextsparkjs/core": "^0.1.0-beta.4",
2290
- "@nextsparkjs/cli": "^0.1.0-beta.4",
2990
+ "@nextsparkjs/core": "latest",
2991
+ "@nextsparkjs/cli": "latest",
2291
2992
  // Next.js + React
2292
2993
  "next": "^15.1.0",
2293
2994
  "react": "^19.0.0",
@@ -2346,24 +3047,26 @@ async function updatePackageJson(config) {
2346
3047
  "@testing-library/react": "^16.3.0",
2347
3048
  "jest-environment-jsdom": "^29.7.0",
2348
3049
  // Cypress
2349
- "cypress": "^14.0.0",
3050
+ "cypress": "^15.8.2",
2350
3051
  "@testing-library/cypress": "^10.0.2",
2351
3052
  "@cypress/webpack-preprocessor": "^6.0.2",
2352
- "@cypress/grep": "^4.1.0",
3053
+ "@cypress/grep": "^5.0.1",
2353
3054
  "ts-loader": "^9.5.1",
2354
3055
  "webpack": "^5.97.0",
2355
3056
  "allure-cypress": "^3.0.0",
2356
- "allure-commandline": "^2.27.0"
3057
+ "allure-commandline": "^2.27.0",
3058
+ // NextSpark Testing
3059
+ "@nextsparkjs/testing": "latest"
2357
3060
  };
2358
3061
  for (const [name, version] of Object.entries(devDepsToAdd)) {
2359
3062
  if (!packageJson.devDependencies[name]) {
2360
3063
  packageJson.devDependencies[name] = version;
2361
3064
  }
2362
3065
  }
2363
- await fs7.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3066
+ await fs8.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2364
3067
  }
2365
- async function updateGitignore(config) {
2366
- const gitignorePath = path5.resolve(process.cwd(), ".gitignore");
3068
+ async function updateGitignore(config2) {
3069
+ const gitignorePath = path7.resolve(process.cwd(), ".gitignore");
2367
3070
  const entriesToAdd = `
2368
3071
  # NextSpark
2369
3072
  .nextspark/
@@ -2381,40 +3084,65 @@ contents/themes/*/tests/jest/coverage
2381
3084
  .env
2382
3085
  .env.local
2383
3086
  `;
2384
- if (await fs7.pathExists(gitignorePath)) {
2385
- const currentContent = await fs7.readFile(gitignorePath, "utf-8");
3087
+ if (await fs8.pathExists(gitignorePath)) {
3088
+ const currentContent = await fs8.readFile(gitignorePath, "utf-8");
2386
3089
  if (!currentContent.includes(".nextspark/")) {
2387
- await fs7.appendFile(gitignorePath, entriesToAdd);
3090
+ await fs8.appendFile(gitignorePath, entriesToAdd);
2388
3091
  }
2389
3092
  } else {
2390
- await fs7.writeFile(gitignorePath, entriesToAdd.trim());
2391
- }
2392
- }
2393
- async function generateProject(config) {
2394
- await copyProjectFiles();
2395
- await copyStarterTheme(config);
2396
- await copyContentFeatures(config);
2397
- await updateThemeConfig(config);
2398
- await updateDevConfig(config);
2399
- await updateAppConfig(config);
2400
- await updateBillingConfig(config);
2401
- await updateRolesConfig(config);
2402
- await updateMigrations(config);
2403
- await updatePermissionsConfig(config);
2404
- await updateDashboardConfig(config);
2405
- await updateAuthConfig(config);
2406
- await updateDashboardUIConfig(config);
2407
- await updateDevToolsConfig(config);
2408
- await processI18n(config);
2409
- await updatePackageJson(config);
2410
- await updateGitignore(config);
2411
- await generateEnvExample(config);
2412
- await updateReadme(config);
2413
- await copyEnvExampleToEnv();
3093
+ await fs8.writeFile(gitignorePath, entriesToAdd.trim());
3094
+ }
3095
+ }
3096
+ async function generateProject(config2) {
3097
+ const projectDir = process.cwd();
3098
+ cachedTemplatesDir = getTemplatesDir3(projectDir);
3099
+ const webDir = getWebDir(projectDir, config2);
3100
+ if (isMonorepoProject(config2)) {
3101
+ await generateMonorepoStructure(projectDir, config2);
3102
+ }
3103
+ const originalCwd = process.cwd();
3104
+ if (isMonorepoProject(config2)) {
3105
+ process.chdir(webDir);
3106
+ }
3107
+ try {
3108
+ await copyProjectFiles();
3109
+ await updateGlobalsCss(config2);
3110
+ await copyStarterTheme(config2);
3111
+ await copyContentFeatures(config2);
3112
+ await updateThemeConfig(config2);
3113
+ await updateDevConfig(config2);
3114
+ await updateAppConfig(config2);
3115
+ await updateBillingConfig(config2);
3116
+ await updateRolesConfig(config2);
3117
+ await updateMigrations(config2);
3118
+ await updateTestFiles(config2);
3119
+ await updatePermissionsConfig(config2);
3120
+ await updateEntityPermissions(config2);
3121
+ await updateDashboardConfig(config2);
3122
+ await updateAuthConfig(config2);
3123
+ await updateDashboardUIConfig(config2);
3124
+ await updateDevToolsConfig(config2);
3125
+ await processI18n(config2);
3126
+ await updatePackageJson(config2);
3127
+ if (!isMonorepoProject(config2)) {
3128
+ await updateGitignore(config2);
3129
+ }
3130
+ await generateEnvExample(config2);
3131
+ if (!isMonorepoProject(config2)) {
3132
+ await updateReadme(config2);
3133
+ }
3134
+ await copyEnvExampleToEnv();
3135
+ } finally {
3136
+ if (isMonorepoProject(config2)) {
3137
+ process.chdir(originalCwd);
3138
+ }
3139
+ cachedTemplatesDir = null;
3140
+ }
2414
3141
  }
2415
3142
 
2416
3143
  // src/wizard/presets.ts
2417
3144
  var SAAS_PRESET = {
3145
+ projectType: "web",
2418
3146
  teamMode: "multi-tenant",
2419
3147
  teamRoles: ["owner", "admin", "member", "viewer"],
2420
3148
  defaultLocale: "en",
@@ -2453,6 +3181,7 @@ var SAAS_PRESET = {
2453
3181
  }
2454
3182
  };
2455
3183
  var BLOG_PRESET = {
3184
+ projectType: "web",
2456
3185
  teamMode: "single-user",
2457
3186
  teamRoles: ["owner"],
2458
3187
  defaultLocale: "en",
@@ -2491,6 +3220,7 @@ var BLOG_PRESET = {
2491
3220
  }
2492
3221
  };
2493
3222
  var CRM_PRESET = {
3223
+ projectType: "web",
2494
3224
  teamMode: "single-tenant",
2495
3225
  teamRoles: ["owner", "admin", "member"],
2496
3226
  defaultLocale: "en",
@@ -2545,11 +3275,13 @@ function getPreset(name) {
2545
3275
  }
2546
3276
  return preset;
2547
3277
  }
2548
- function applyPreset(projectInfo, presetName) {
3278
+ function applyPreset(projectInfo, presetName, typeOverride) {
2549
3279
  const preset = getPreset(presetName);
2550
3280
  return {
2551
3281
  ...projectInfo,
2552
- ...preset
3282
+ ...preset,
3283
+ // Apply type override if provided
3284
+ ...typeOverride && { projectType: typeOverride }
2553
3285
  };
2554
3286
  }
2555
3287
 
@@ -2573,9 +3305,9 @@ var BOX = {
2573
3305
  bottomRight: "\u2518"
2574
3306
  // bottom-right corner
2575
3307
  };
2576
- function getFileTree(config) {
3308
+ function getFileTree(config2) {
2577
3309
  const files = [];
2578
- const themeDir = `contents/themes/${config.projectSlug}`;
3310
+ const themeDir = `contents/themes/${config2.projectSlug}`;
2579
3311
  files.push(`${themeDir}/config/app.config.ts`);
2580
3312
  files.push(`${themeDir}/config/billing.config.ts`);
2581
3313
  files.push(`${themeDir}/config/dashboard.config.ts`);
@@ -2591,7 +3323,7 @@ function getFileTree(config) {
2591
3323
  files.push(`${themeDir}/blocks/hero/block.tsx`);
2592
3324
  files.push(`${themeDir}/blocks/hero/schema.ts`);
2593
3325
  files.push(`${themeDir}/blocks/hero/styles.ts`);
2594
- for (const locale of config.supportedLocales) {
3326
+ for (const locale of config2.supportedLocales) {
2595
3327
  files.push(`${themeDir}/messages/${locale}/common.json`);
2596
3328
  files.push(`${themeDir}/messages/${locale}/auth.json`);
2597
3329
  files.push(`${themeDir}/messages/${locale}/dashboard.json`);
@@ -2604,7 +3336,7 @@ function getFileTree(config) {
2604
3336
  files.push(`${themeDir}/styles/theme.css`);
2605
3337
  files.push(`${themeDir}/styles/components.css`);
2606
3338
  files.push(`${themeDir}/tests/cypress.config.ts`);
2607
- files.push(`${themeDir}/tests/jest/jest.config.ts`);
3339
+ files.push(`${themeDir}/tests/jest/jest.config.cjs`);
2608
3340
  files.push(`${themeDir}/tests/cypress/e2e/auth.cy.ts`);
2609
3341
  files.push(`${themeDir}/tests/cypress/e2e/dashboard.cy.ts`);
2610
3342
  files.push(`${themeDir}/tests/jest/components/hero.test.tsx`);
@@ -2642,10 +3374,10 @@ function groupFilesByCategory(files) {
2642
3374
  function formatFilePath(file, themeDir) {
2643
3375
  return file.replace(`${themeDir}/`, "");
2644
3376
  }
2645
- function showConfigPreview(config) {
2646
- const files = getFileTree(config);
3377
+ function showConfigPreview(config2) {
3378
+ const files = getFileTree(config2);
2647
3379
  const groups = groupFilesByCategory(files);
2648
- const themeDir = `contents/themes/${config.projectSlug}`;
3380
+ const themeDir = `contents/themes/${config2.projectSlug}`;
2649
3381
  console.log("");
2650
3382
  console.log(chalk10.cyan.bold(" Theme Preview"));
2651
3383
  console.log(chalk10.gray(" " + "=".repeat(50)));
@@ -2707,8 +3439,8 @@ function showConfigPreview(config) {
2707
3439
  }
2708
3440
  console.log("");
2709
3441
  console.log(chalk10.gray(" Locales configured:"));
2710
- for (const locale of config.supportedLocales) {
2711
- const isDefault = locale === config.defaultLocale;
3442
+ for (const locale of config2.supportedLocales) {
3443
+ const isDefault = locale === config2.defaultLocale;
2712
3444
  const suffix = isDefault ? chalk10.cyan(" (default)") : "";
2713
3445
  console.log(chalk10.gray(` - `) + chalk10.white(locale) + suffix);
2714
3446
  }
@@ -2732,43 +3464,41 @@ async function runWizard(options = { mode: "interactive" }) {
2732
3464
  try {
2733
3465
  let selectedTheme = null;
2734
3466
  let selectedPlugins = [];
2735
- if (!options.preset) {
2736
- if (options.theme !== void 0) {
2737
- selectedTheme = options.theme === "none" ? null : options.theme;
2738
- showInfo(`Reference theme: ${selectedTheme || "None"}`);
2739
- } else if (options.mode !== "quick") {
2740
- selectedTheme = await promptThemeSelection();
2741
- }
2742
- if (options.plugins !== void 0) {
2743
- selectedPlugins = options.plugins;
2744
- if (selectedPlugins.length > 0) {
2745
- showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
2746
- }
2747
- } else if (options.mode !== "quick" && !options.yes) {
2748
- selectedPlugins = await promptPluginsSelection(selectedTheme);
2749
- } else if (selectedTheme) {
2750
- selectedPlugins = getRequiredPlugins(selectedTheme);
2751
- }
2752
- }
2753
- let config;
3467
+ let config2;
2754
3468
  if (options.preset) {
2755
- config = await runPresetMode(options.preset, options);
3469
+ config2 = await runPresetMode(options.preset, options);
2756
3470
  } else {
2757
3471
  switch (options.mode) {
2758
3472
  case "quick":
2759
- config = await runQuickPrompts();
3473
+ config2 = await runQuickPrompts();
2760
3474
  break;
2761
3475
  case "expert":
2762
- config = await runExpertPrompts();
3476
+ config2 = await runExpertPrompts();
2763
3477
  break;
2764
3478
  case "interactive":
2765
3479
  default:
2766
- config = await runAllPrompts();
3480
+ config2 = await runAllPrompts();
2767
3481
  break;
2768
3482
  }
2769
3483
  }
2770
- showConfigSummary(config);
2771
- showConfigPreview(config);
3484
+ if (options.theme !== void 0) {
3485
+ selectedTheme = options.theme === "none" ? null : options.theme;
3486
+ showInfo(`Reference theme: ${selectedTheme || "None"}`);
3487
+ } else if (!options.preset && options.mode !== "quick") {
3488
+ selectedTheme = await promptThemeSelection();
3489
+ }
3490
+ if (options.plugins !== void 0) {
3491
+ selectedPlugins = options.plugins;
3492
+ if (selectedPlugins.length > 0) {
3493
+ showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
3494
+ }
3495
+ } else if (!options.preset && options.mode !== "quick" && !options.yes) {
3496
+ selectedPlugins = await promptPluginsSelection(selectedTheme);
3497
+ } else if (selectedTheme) {
3498
+ selectedPlugins = getRequiredPlugins(selectedTheme);
3499
+ }
3500
+ showConfigSummary(config2);
3501
+ showConfigPreview(config2);
2772
3502
  if (!options.yes) {
2773
3503
  console.log("");
2774
3504
  const proceed = await confirm5({
@@ -2781,20 +3511,27 @@ async function runWizard(options = { mode: "interactive" }) {
2781
3511
  process.exit(0);
2782
3512
  }
2783
3513
  }
2784
- await copyNpmrc();
2785
3514
  console.log("");
2786
3515
  const coreInstalled = await installCore();
2787
3516
  if (!coreInstalled) {
2788
3517
  showError("Failed to install @nextsparkjs/core. Cannot generate project.");
2789
3518
  process.exit(1);
2790
3519
  }
3520
+ if (config2.projectType === "web-mobile") {
3521
+ console.log("");
3522
+ const mobileInstalled = await installMobile();
3523
+ if (!mobileInstalled) {
3524
+ showError("Failed to install @nextsparkjs/mobile. Cannot generate monorepo project.");
3525
+ process.exit(1);
3526
+ }
3527
+ }
2791
3528
  console.log("");
2792
3529
  const spinner = ora7({
2793
3530
  text: "Generating your NextSpark project...",
2794
3531
  prefixText: " "
2795
3532
  }).start();
2796
3533
  try {
2797
- await generateProject(config);
3534
+ await generateProject(config2);
2798
3535
  spinner.succeed("Project generated successfully!");
2799
3536
  } catch (error) {
2800
3537
  spinner.fail("Failed to generate project");
@@ -2803,14 +3540,19 @@ async function runWizard(options = { mode: "interactive" }) {
2803
3540
  if (selectedTheme || selectedPlugins.length > 0) {
2804
3541
  await installThemeAndPlugins(selectedTheme, selectedPlugins);
2805
3542
  }
3543
+ const projectRoot = process.cwd();
3544
+ const webDir = getWebDir(projectRoot, config2);
3545
+ const isMonorepo = isMonorepoProject(config2);
2806
3546
  const installSpinner = ora7({
2807
- text: "Installing dependencies...",
3547
+ text: isMonorepo ? "Installing dependencies (monorepo)..." : "Installing dependencies...",
2808
3548
  prefixText: " "
2809
3549
  }).start();
2810
3550
  try {
3551
+ installSpinner.stop();
2811
3552
  execSync("pnpm install --force", {
2812
- cwd: process.cwd(),
2813
- stdio: "pipe"
3553
+ cwd: projectRoot,
3554
+ // Always install from root (works for both flat and monorepo)
3555
+ stdio: "inherit"
2814
3556
  });
2815
3557
  installSpinner.succeed("Dependencies installed!");
2816
3558
  } catch (error) {
@@ -2822,22 +3564,24 @@ async function runWizard(options = { mode: "interactive" }) {
2822
3564
  prefixText: " "
2823
3565
  }).start();
2824
3566
  try {
2825
- const projectRoot = process.cwd();
2826
- const registryScript = join6(projectRoot, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
3567
+ const registryScript = join7(webDir, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
3568
+ registrySpinner.stop();
2827
3569
  execSync(`node "${registryScript}" --build`, {
2828
- cwd: projectRoot,
2829
- stdio: "pipe",
3570
+ cwd: webDir,
3571
+ // Run from web directory
3572
+ stdio: "inherit",
2830
3573
  env: {
2831
3574
  ...process.env,
2832
- NEXTSPARK_PROJECT_ROOT: projectRoot
3575
+ NEXTSPARK_PROJECT_ROOT: webDir
2833
3576
  }
2834
3577
  });
2835
3578
  registrySpinner.succeed("Registries built!");
2836
3579
  } catch (error) {
2837
3580
  registrySpinner.fail("Failed to build registries");
2838
- console.log(chalk11.yellow(' Registries will be built automatically when you run "pnpm dev"'));
3581
+ const devCmd = isMonorepo ? "pnpm dev" : "pnpm dev";
3582
+ console.log(chalk11.yellow(` Registries will be built automatically when you run "${devCmd}"`));
2839
3583
  }
2840
- showNextSteps(config, selectedTheme);
3584
+ showNextSteps(config2, selectedTheme);
2841
3585
  } catch (error) {
2842
3586
  if (error instanceof Error) {
2843
3587
  if (error.message.includes("User force closed")) {
@@ -2877,46 +3621,47 @@ async function runPresetMode(presetName, options) {
2877
3621
  console.log("");
2878
3622
  projectInfo = await promptProjectInfo();
2879
3623
  }
2880
- const config = applyPreset(projectInfo, presetName);
2881
- return config;
3624
+ const config2 = applyPreset(projectInfo, presetName, options.type);
3625
+ return config2;
2882
3626
  }
2883
- function showConfigSummary(config) {
3627
+ function showConfigSummary(config2) {
2884
3628
  console.log("");
2885
3629
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2886
3630
  console.log(chalk11.bold.white(" Configuration Summary"));
2887
3631
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2888
3632
  console.log("");
2889
3633
  console.log(chalk11.white(" Project:"));
2890
- console.log(chalk11.gray(` Name: ${chalk11.white(config.projectName)}`));
2891
- console.log(chalk11.gray(` Slug: ${chalk11.white(config.projectSlug)}`));
2892
- console.log(chalk11.gray(` Description: ${chalk11.white(config.projectDescription)}`));
3634
+ console.log(chalk11.gray(` Name: ${chalk11.white(config2.projectName)}`));
3635
+ console.log(chalk11.gray(` Slug: ${chalk11.white(config2.projectSlug)}`));
3636
+ console.log(chalk11.gray(` Description: ${chalk11.white(config2.projectDescription)}`));
3637
+ console.log(chalk11.gray(` Type: ${chalk11.white(config2.projectType === "web-mobile" ? "Web + Mobile (Monorepo)" : "Web only")}`));
2893
3638
  console.log("");
2894
3639
  console.log(chalk11.white(" Team Mode:"));
2895
- console.log(chalk11.gray(` Mode: ${chalk11.white(config.teamMode)}`));
2896
- console.log(chalk11.gray(` Roles: ${chalk11.white(config.teamRoles.join(", "))}`));
3640
+ console.log(chalk11.gray(` Mode: ${chalk11.white(config2.teamMode)}`));
3641
+ console.log(chalk11.gray(` Roles: ${chalk11.white(config2.teamRoles.join(", "))}`));
2897
3642
  console.log("");
2898
3643
  console.log(chalk11.white(" Internationalization:"));
2899
- console.log(chalk11.gray(` Default: ${chalk11.white(config.defaultLocale)}`));
2900
- console.log(chalk11.gray(` Languages: ${chalk11.white(config.supportedLocales.join(", "))}`));
3644
+ console.log(chalk11.gray(` Default: ${chalk11.white(config2.defaultLocale)}`));
3645
+ console.log(chalk11.gray(` Languages: ${chalk11.white(config2.supportedLocales.join(", "))}`));
2901
3646
  console.log("");
2902
3647
  console.log(chalk11.white(" Billing:"));
2903
- console.log(chalk11.gray(` Model: ${chalk11.white(config.billingModel)}`));
2904
- console.log(chalk11.gray(` Currency: ${chalk11.white(config.currency.toUpperCase())}`));
3648
+ console.log(chalk11.gray(` Model: ${chalk11.white(config2.billingModel)}`));
3649
+ console.log(chalk11.gray(` Currency: ${chalk11.white(config2.currency.toUpperCase())}`));
2905
3650
  console.log("");
2906
3651
  console.log(chalk11.white(" Features:"));
2907
- const enabledFeatures = Object.entries(config.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
3652
+ const enabledFeatures = Object.entries(config2.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
2908
3653
  console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledFeatures.join(", ") || "None")}`));
2909
3654
  console.log("");
2910
3655
  console.log(chalk11.white(" Authentication:"));
2911
- const enabledAuth = Object.entries(config.auth).filter(([_, enabled]) => enabled).map(([method]) => formatAuthMethod(method));
3656
+ const enabledAuth = Object.entries(config2.auth).filter(([_, enabled]) => enabled).map(([method]) => formatAuthMethod(method));
2912
3657
  console.log(chalk11.gray(` Methods: ${chalk11.white(enabledAuth.join(", ") || "None")}`));
2913
3658
  console.log("");
2914
3659
  console.log(chalk11.white(" Dashboard:"));
2915
- const enabledDashboard = Object.entries(config.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
3660
+ const enabledDashboard = Object.entries(config2.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
2916
3661
  console.log(chalk11.gray(` Features: ${chalk11.white(enabledDashboard.join(", ") || "None")}`));
2917
3662
  console.log("");
2918
3663
  console.log(chalk11.white(" Dev Tools:"));
2919
- const enabledDevTools = Object.entries(config.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
3664
+ const enabledDevTools = Object.entries(config2.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
2920
3665
  console.log(chalk11.gray(` Enabled: ${chalk11.white(enabledDevTools.join(", ") || "None")}`));
2921
3666
  }
2922
3667
  function formatAuthMethod(method) {
@@ -2943,7 +3688,8 @@ function formatDevTool(tool) {
2943
3688
  };
2944
3689
  return mapping[tool] || tool;
2945
3690
  }
2946
- function showNextSteps(config, referenceTheme = null) {
3691
+ function showNextSteps(config2, referenceTheme = null) {
3692
+ const isMonorepo = config2.projectType === "web-mobile";
2947
3693
  console.log("");
2948
3694
  console.log(chalk11.cyan(" " + "=".repeat(60)));
2949
3695
  console.log(chalk11.bold.green(" \u2728 NextSpark project ready!"));
@@ -2951,8 +3697,9 @@ function showNextSteps(config, referenceTheme = null) {
2951
3697
  console.log("");
2952
3698
  console.log(chalk11.bold.white(" Next steps:"));
2953
3699
  console.log("");
3700
+ const envPath = isMonorepo ? "web/.env" : ".env";
2954
3701
  console.log(chalk11.white(" 1. Configure your .env file:"));
2955
- console.log(chalk11.gray(" Edit these values in .env:"));
3702
+ console.log(chalk11.gray(` Edit these values in ${envPath}:`));
2956
3703
  console.log("");
2957
3704
  console.log(chalk11.yellow(" DATABASE_URL"));
2958
3705
  console.log(chalk11.gray(" PostgreSQL connection string"));
@@ -2968,11 +3715,25 @@ function showNextSteps(config, referenceTheme = null) {
2968
3715
  console.log(chalk11.white(" 3. Start the development server:"));
2969
3716
  console.log(chalk11.cyan(" pnpm dev"));
2970
3717
  console.log("");
3718
+ if (isMonorepo) {
3719
+ console.log(chalk11.white(" 4. (Optional) Start the mobile app:"));
3720
+ console.log(chalk11.cyan(" pnpm dev:mobile"));
3721
+ console.log(chalk11.gray(" Or: cd mobile && pnpm start"));
3722
+ console.log("");
3723
+ }
2971
3724
  console.log(chalk11.gray(" " + "-".repeat(60)));
2972
- console.log(chalk11.gray(` Theme: ${chalk11.white(`contents/themes/${config.projectSlug}/`)}`));
2973
- console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config.projectSlug}`)}`));
3725
+ if (isMonorepo) {
3726
+ console.log(chalk11.gray(` Structure: ${chalk11.white("Monorepo (web/ + mobile/)")}`));
3727
+ console.log(chalk11.gray(` Web theme: ${chalk11.white(`web/contents/themes/${config2.projectSlug}/`)}`));
3728
+ console.log(chalk11.gray(` Mobile app: ${chalk11.white("mobile/")}`));
3729
+ console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
3730
+ } else {
3731
+ console.log(chalk11.gray(` Theme: ${chalk11.white(`contents/themes/${config2.projectSlug}/`)}`));
3732
+ console.log(chalk11.gray(` Active theme: ${chalk11.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
3733
+ }
2974
3734
  if (referenceTheme) {
2975
- console.log(chalk11.gray(` Reference: ${chalk11.white(`contents/themes/${referenceTheme}/`)}`));
3735
+ const refPath = isMonorepo ? `web/contents/themes/${referenceTheme}/` : `contents/themes/${referenceTheme}/`;
3736
+ console.log(chalk11.gray(` Reference: ${chalk11.white(refPath)}`));
2976
3737
  }
2977
3738
  console.log(chalk11.gray(" Docs: https://nextspark.dev/docs"));
2978
3739
  console.log("");
@@ -2985,14 +3746,14 @@ function findLocalCoreTarball() {
2985
3746
  (f) => f.includes("nextsparkjs-core") && f.endsWith(".tgz")
2986
3747
  );
2987
3748
  if (coreTarball) {
2988
- return join6(cwd, coreTarball);
3749
+ return join7(cwd, coreTarball);
2989
3750
  }
2990
3751
  } catch {
2991
3752
  }
2992
3753
  return null;
2993
3754
  }
2994
3755
  function isCoreInstalled() {
2995
- const corePath = join6(process.cwd(), "node_modules", "@nextsparkjs", "core");
3756
+ const corePath = join7(process.cwd(), "node_modules", "@nextsparkjs", "core");
2996
3757
  return existsSync7(corePath);
2997
3758
  }
2998
3759
  async function installCore() {
@@ -3010,8 +3771,8 @@ async function installCore() {
3010
3771
  packageSpec = localTarball;
3011
3772
  spinner.text = "Installing @nextsparkjs/core from local tarball...";
3012
3773
  }
3013
- const useYarn = existsSync7(join6(process.cwd(), "yarn.lock"));
3014
- const usePnpm = existsSync7(join6(process.cwd(), "pnpm-lock.yaml"));
3774
+ const useYarn = existsSync7(join7(process.cwd(), "yarn.lock"));
3775
+ const usePnpm = existsSync7(join7(process.cwd(), "pnpm-lock.yaml"));
3015
3776
  let installCmd;
3016
3777
  if (usePnpm) {
3017
3778
  installCmd = `pnpm add ${packageSpec}`;
@@ -3020,8 +3781,9 @@ async function installCore() {
3020
3781
  } else {
3021
3782
  installCmd = `npm install ${packageSpec}`;
3022
3783
  }
3784
+ spinner.stop();
3023
3785
  execSync(installCmd, {
3024
- stdio: "pipe",
3786
+ stdio: "inherit",
3025
3787
  cwd: process.cwd()
3026
3788
  });
3027
3789
  spinner.succeed(chalk11.green("@nextsparkjs/core installed successfully!"));
@@ -3035,17 +3797,64 @@ async function installCore() {
3035
3797
  return false;
3036
3798
  }
3037
3799
  }
3038
- async function copyNpmrc() {
3039
- const npmrcPath = join6(process.cwd(), ".npmrc");
3040
- if (existsSync7(npmrcPath)) {
3041
- return;
3800
+ function isMobileInstalled() {
3801
+ const mobilePath = join7(process.cwd(), "node_modules", "@nextsparkjs", "mobile");
3802
+ return existsSync7(mobilePath);
3803
+ }
3804
+ function findLocalMobileTarball() {
3805
+ const cwd = process.cwd();
3806
+ try {
3807
+ const files = readdirSync(cwd);
3808
+ const mobileTarball = files.find(
3809
+ (f) => f.includes("nextsparkjs-mobile") && f.endsWith(".tgz")
3810
+ );
3811
+ if (mobileTarball) {
3812
+ return join7(cwd, mobileTarball);
3813
+ }
3814
+ } catch {
3815
+ }
3816
+ return null;
3817
+ }
3818
+ async function installMobile() {
3819
+ if (isMobileInstalled()) {
3820
+ return true;
3821
+ }
3822
+ const spinner = ora7({
3823
+ text: "Installing @nextsparkjs/mobile...",
3824
+ prefixText: " "
3825
+ }).start();
3826
+ try {
3827
+ const localTarball = findLocalMobileTarball();
3828
+ let packageSpec = "@nextsparkjs/mobile";
3829
+ if (localTarball) {
3830
+ packageSpec = localTarball;
3831
+ spinner.text = "Installing @nextsparkjs/mobile from local tarball...";
3832
+ }
3833
+ const useYarn = existsSync7(join7(process.cwd(), "yarn.lock"));
3834
+ const usePnpm = existsSync7(join7(process.cwd(), "pnpm-lock.yaml"));
3835
+ let installCmd;
3836
+ if (usePnpm) {
3837
+ installCmd = `pnpm add ${packageSpec}`;
3838
+ } else if (useYarn) {
3839
+ installCmd = `yarn add ${packageSpec}`;
3840
+ } else {
3841
+ installCmd = `npm install ${packageSpec}`;
3842
+ }
3843
+ spinner.stop();
3844
+ execSync(installCmd, {
3845
+ stdio: "inherit",
3846
+ cwd: process.cwd()
3847
+ });
3848
+ spinner.succeed(chalk11.green("@nextsparkjs/mobile installed successfully!"));
3849
+ return true;
3850
+ } catch (error) {
3851
+ spinner.fail(chalk11.red("Failed to install @nextsparkjs/mobile"));
3852
+ if (error instanceof Error) {
3853
+ console.log(chalk11.red(` Error: ${error.message}`));
3854
+ }
3855
+ console.log(chalk11.gray(" Hint: Make sure the package is available (npm registry or local tarball)"));
3856
+ return false;
3042
3857
  }
3043
- const npmrcContent = `# Hoist @nextsparkjs/core dependencies so they're accessible from the project
3044
- # This is required for pnpm to make peer dependencies available
3045
- public-hoist-pattern[]=*
3046
- `;
3047
- const { writeFileSync: writeFileSync3 } = await import("fs");
3048
- writeFileSync3(npmrcPath, npmrcContent);
3049
3858
  }
3050
3859
 
3051
3860
  // src/commands/init.ts
@@ -3060,10 +3869,10 @@ function parsePlugins(pluginsStr) {
3060
3869
  }
3061
3870
  function hasExistingProject() {
3062
3871
  const projectRoot = process.cwd();
3063
- return existsSync8(join7(projectRoot, "contents")) || existsSync8(join7(projectRoot, ".nextspark"));
3872
+ return existsSync8(join8(projectRoot, "contents")) || existsSync8(join8(projectRoot, ".nextspark"));
3064
3873
  }
3065
3874
  function generateInitialRegistries(registriesDir) {
3066
- writeFileSync2(join7(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
3875
+ writeFileSync2(join8(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
3067
3876
  import type { ComponentType } from 'react'
3068
3877
 
3069
3878
  export const BLOCK_REGISTRY: Record<string, {
@@ -3076,26 +3885,26 @@ export const BLOCK_REGISTRY: Record<string, {
3076
3885
 
3077
3886
  export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
3078
3887
  `);
3079
- writeFileSync2(join7(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
3888
+ writeFileSync2(join8(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
3080
3889
  export const THEME_REGISTRY: Record<string, unknown> = {}
3081
3890
  `);
3082
- writeFileSync2(join7(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
3891
+ writeFileSync2(join8(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
3083
3892
  export const ENTITY_REGISTRY: Record<string, unknown> = {}
3084
3893
  `);
3085
- writeFileSync2(join7(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
3894
+ writeFileSync2(join8(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
3086
3895
  export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
3087
3896
  export function parseChildEntity(path: string) { return null }
3088
3897
  export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
3089
3898
  export function clientMetaSystemAdapter() { return {} }
3090
3899
  export type ClientEntityConfig = Record<string, unknown>
3091
3900
  `);
3092
- writeFileSync2(join7(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
3901
+ writeFileSync2(join8(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
3093
3902
  export const BILLING_REGISTRY = { plans: [], features: [] }
3094
3903
  `);
3095
- writeFileSync2(join7(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
3904
+ writeFileSync2(join8(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
3096
3905
  export const PLUGIN_REGISTRY: Record<string, unknown> = {}
3097
3906
  `);
3098
- writeFileSync2(join7(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
3907
+ writeFileSync2(join8(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
3099
3908
  export const FLOW_REGISTRY: Record<string, unknown> = {}
3100
3909
  export const FEATURE_REGISTRY: Record<string, unknown> = {}
3101
3910
  export const TAGS_REGISTRY: Record<string, unknown> = {}
@@ -3103,11 +3912,11 @@ export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
3103
3912
  export type FlowEntry = unknown
3104
3913
  export type FeatureEntry = unknown
3105
3914
  `);
3106
- writeFileSync2(join7(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
3915
+ writeFileSync2(join8(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
3107
3916
  export const DOCS_REGISTRY = { sections: [], pages: [] }
3108
3917
  export type DocSectionMeta = { title: string; slug: string }
3109
3918
  `);
3110
- writeFileSync2(join7(registriesDir, "index.ts"), `// Auto-generated by nextspark init
3919
+ writeFileSync2(join8(registriesDir, "index.ts"), `// Auto-generated by nextspark init
3111
3920
  export * from './block-registry'
3112
3921
  export * from './theme-registry'
3113
3922
  export * from './entity-registry'
@@ -3122,18 +3931,18 @@ async function simpleInit(options) {
3122
3931
  const spinner = ora8("Initializing NextSpark project...").start();
3123
3932
  const projectRoot = process.cwd();
3124
3933
  try {
3125
- const nextspark = join7(projectRoot, ".nextspark");
3126
- const registriesDir = join7(nextspark, "registries");
3934
+ const nextspark = join8(projectRoot, ".nextspark");
3935
+ const registriesDir = join8(nextspark, "registries");
3127
3936
  if (!existsSync8(registriesDir) || options.force) {
3128
3937
  mkdirSync2(registriesDir, { recursive: true });
3129
3938
  spinner.text = "Creating .nextspark/registries/";
3130
3939
  generateInitialRegistries(registriesDir);
3131
3940
  spinner.text = "Generated initial registries";
3132
3941
  }
3133
- const tsconfigPath = join7(projectRoot, "tsconfig.json");
3942
+ const tsconfigPath = join8(projectRoot, "tsconfig.json");
3134
3943
  if (existsSync8(tsconfigPath)) {
3135
3944
  spinner.text = "Updating tsconfig.json paths...";
3136
- const tsconfig = JSON.parse(readFileSync6(tsconfigPath, "utf-8"));
3945
+ const tsconfig = JSON.parse(readFileSync7(tsconfigPath, "utf-8"));
3137
3946
  tsconfig.compilerOptions = tsconfig.compilerOptions || {};
3138
3947
  tsconfig.compilerOptions.paths = {
3139
3948
  ...tsconfig.compilerOptions.paths,
@@ -3142,7 +3951,7 @@ async function simpleInit(options) {
3142
3951
  };
3143
3952
  writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2));
3144
3953
  }
3145
- const envExample = join7(projectRoot, ".env.example");
3954
+ const envExample = join8(projectRoot, ".env.example");
3146
3955
  if (!existsSync8(envExample)) {
3147
3956
  const envContent = `# NextSpark Configuration
3148
3957
  DATABASE_URL="postgresql://user:password@localhost:5432/db"
@@ -3185,25 +3994,133 @@ async function initCommand(options) {
3185
3994
  yes: options.yes,
3186
3995
  name: options.name,
3187
3996
  slug: options.slug,
3188
- description: options.description
3997
+ description: options.description,
3998
+ type: options.type
3189
3999
  };
3190
4000
  await runWizard(wizardOptions);
3191
4001
  }
3192
4002
 
4003
+ // src/commands/add-mobile.ts
4004
+ import { existsSync as existsSync9, cpSync as cpSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3, renameSync, mkdirSync as mkdirSync3 } from "fs";
4005
+ import { join as join9 } from "path";
4006
+ import chalk13 from "chalk";
4007
+ import ora9 from "ora";
4008
+ import { execSync as execSync2 } from "child_process";
4009
+ function findMobileCoreDir() {
4010
+ const projectRoot = process.cwd();
4011
+ const npmPath = join9(projectRoot, "node_modules", "@nextsparkjs", "mobile");
4012
+ if (existsSync9(npmPath)) {
4013
+ return npmPath;
4014
+ }
4015
+ const monoPath = join9(projectRoot, "packages", "mobile");
4016
+ if (existsSync9(monoPath)) {
4017
+ return monoPath;
4018
+ }
4019
+ const parentNpmPath = join9(projectRoot, "..", "node_modules", "@nextsparkjs", "mobile");
4020
+ if (existsSync9(parentNpmPath)) {
4021
+ return parentNpmPath;
4022
+ }
4023
+ throw new Error(
4024
+ "Could not find @nextsparkjs/mobile package.\nRun: npm install @nextsparkjs/mobile"
4025
+ );
4026
+ }
4027
+ async function addMobileCommand(options = {}) {
4028
+ const projectRoot = process.cwd();
4029
+ const mobileDir = join9(projectRoot, "mobile");
4030
+ console.log();
4031
+ console.log(chalk13.bold("Adding NextSpark Mobile App"));
4032
+ console.log();
4033
+ if (existsSync9(mobileDir) && !options.force) {
4034
+ console.log(chalk13.red("Error: Mobile app already exists at mobile/"));
4035
+ console.log(chalk13.gray("Use --force to overwrite"));
4036
+ process.exit(1);
4037
+ }
4038
+ let mobileCoreDir;
4039
+ try {
4040
+ mobileCoreDir = findMobileCoreDir();
4041
+ } catch (error) {
4042
+ console.log(chalk13.red(error.message));
4043
+ process.exit(1);
4044
+ }
4045
+ const templatesDir = join9(mobileCoreDir, "templates");
4046
+ if (!existsSync9(templatesDir)) {
4047
+ console.log(chalk13.red("Error: Could not find mobile templates"));
4048
+ console.log(chalk13.gray(`Expected at: ${templatesDir}`));
4049
+ process.exit(1);
4050
+ }
4051
+ const copySpinner = ora9("Copying mobile app template...").start();
4052
+ try {
4053
+ mkdirSync3(mobileDir, { recursive: true });
4054
+ cpSync2(templatesDir, mobileDir, { recursive: true });
4055
+ const pkgTemplatePath = join9(mobileDir, "package.json.template");
4056
+ const pkgPath = join9(mobileDir, "package.json");
4057
+ if (existsSync9(pkgTemplatePath)) {
4058
+ renameSync(pkgTemplatePath, pkgPath);
4059
+ const pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
4060
+ const rootPkgPath = join9(projectRoot, "package.json");
4061
+ if (existsSync9(rootPkgPath)) {
4062
+ const rootPkg = JSON.parse(readFileSync8(rootPkgPath, "utf-8"));
4063
+ const rawName = rootPkg.name || "my-project";
4064
+ if (rawName.startsWith("@")) {
4065
+ const scopeMatch = rawName.match(/^@[\w-]+/);
4066
+ const scope = scopeMatch ? scopeMatch[0] : "";
4067
+ const projectName = rawName.replace(/^@[\w-]+\//, "");
4068
+ pkg2.name = `${scope}/${projectName}-mobile`;
4069
+ } else {
4070
+ pkg2.name = `${rawName}-mobile`;
4071
+ }
4072
+ }
4073
+ writeFileSync3(pkgPath, JSON.stringify(pkg2, null, 2));
4074
+ }
4075
+ copySpinner.succeed("Mobile app template copied");
4076
+ } catch (error) {
4077
+ copySpinner.fail("Failed to copy templates");
4078
+ console.log(chalk13.red(error.message));
4079
+ process.exit(1);
4080
+ }
4081
+ if (!options.skipInstall) {
4082
+ const installSpinner = ora9("Installing dependencies...").start();
4083
+ try {
4084
+ execSync2("npm install", {
4085
+ cwd: mobileDir,
4086
+ stdio: "pipe",
4087
+ timeout: 3e5
4088
+ // 5 minutes
4089
+ });
4090
+ installSpinner.succeed("Dependencies installed");
4091
+ } catch (error) {
4092
+ installSpinner.fail("Failed to install dependencies");
4093
+ console.log(chalk13.yellow(" Run `npm install` in mobile/ manually"));
4094
+ }
4095
+ }
4096
+ console.log();
4097
+ console.log(chalk13.green.bold(" Mobile app created successfully!"));
4098
+ console.log();
4099
+ console.log(chalk13.bold(" Next steps:"));
4100
+ console.log();
4101
+ console.log(` ${chalk13.cyan("1.")} cd mobile`);
4102
+ console.log(` ${chalk13.cyan("2.")} Update ${chalk13.bold("app.config.ts")} with your app name and bundle ID`);
4103
+ console.log(` ${chalk13.cyan("3.")} Add your entities in ${chalk13.bold("src/entities/")}`);
4104
+ console.log(` ${chalk13.cyan("4.")} npm start`);
4105
+ console.log();
4106
+ console.log(chalk13.gray(" Documentation: https://nextspark.dev/docs/mobile"));
4107
+ console.log();
4108
+ }
4109
+
3193
4110
  // src/commands/doctor.ts
3194
- import chalk14 from "chalk";
4111
+ import chalk15 from "chalk";
3195
4112
 
3196
4113
  // src/doctor/index.ts
3197
- import chalk13 from "chalk";
4114
+ import chalk14 from "chalk";
3198
4115
 
3199
4116
  // src/doctor/checks/dependencies.ts
3200
- import fs8 from "fs-extra";
3201
- import path6 from "path";
4117
+ import fs9 from "fs-extra";
4118
+ import path8 from "path";
3202
4119
  async function checkDependencies() {
3203
4120
  const cwd = process.cwd();
3204
- const nodeModulesPath = path6.join(cwd, "node_modules");
3205
- const packageJsonPath = path6.join(cwd, "package.json");
3206
- if (!await fs8.pathExists(packageJsonPath)) {
4121
+ const nodeModulesPath = path8.join(cwd, "node_modules");
4122
+ const packageJsonPath = path8.join(cwd, "package.json");
4123
+ if (!await fs9.pathExists(packageJsonPath)) {
3207
4124
  return {
3208
4125
  name: "Dependencies",
3209
4126
  status: "fail",
@@ -3211,7 +4128,7 @@ async function checkDependencies() {
3211
4128
  fix: "Ensure you are in a NextSpark project directory"
3212
4129
  };
3213
4130
  }
3214
- if (!await fs8.pathExists(nodeModulesPath)) {
4131
+ if (!await fs9.pathExists(nodeModulesPath)) {
3215
4132
  return {
3216
4133
  name: "Dependencies",
3217
4134
  status: "fail",
@@ -3221,7 +4138,7 @@ async function checkDependencies() {
3221
4138
  }
3222
4139
  let packageJson;
3223
4140
  try {
3224
- packageJson = await fs8.readJson(packageJsonPath);
4141
+ packageJson = await fs9.readJson(packageJsonPath);
3225
4142
  } catch {
3226
4143
  return {
3227
4144
  name: "Dependencies",
@@ -3238,14 +4155,14 @@ async function checkDependencies() {
3238
4155
  const criticalDeps = ["next", "react", "react-dom"];
3239
4156
  for (const dep of criticalDeps) {
3240
4157
  if (allDependencies[dep]) {
3241
- const depPath = path6.join(nodeModulesPath, dep);
3242
- if (!await fs8.pathExists(depPath)) {
4158
+ const depPath = path8.join(nodeModulesPath, dep);
4159
+ if (!await fs9.pathExists(depPath)) {
3243
4160
  missingDeps.push(dep);
3244
4161
  }
3245
4162
  }
3246
4163
  }
3247
- const nextsparksCorePath = path6.join(nodeModulesPath, "@nextsparkjs", "core");
3248
- const hasNextSparkCore = await fs8.pathExists(nextsparksCorePath);
4164
+ const nextsparksCorePath = path8.join(nodeModulesPath, "@nextsparkjs", "core");
4165
+ const hasNextSparkCore = await fs9.pathExists(nextsparksCorePath);
3249
4166
  if (missingDeps.length > 0) {
3250
4167
  return {
3251
4168
  name: "Dependencies",
@@ -3270,8 +4187,8 @@ async function checkDependencies() {
3270
4187
  }
3271
4188
 
3272
4189
  // src/doctor/checks/config.ts
3273
- import fs9 from "fs-extra";
3274
- import path7 from "path";
4190
+ import fs10 from "fs-extra";
4191
+ import path9 from "path";
3275
4192
  var REQUIRED_CONFIG_FILES = [
3276
4193
  "next.config.ts",
3277
4194
  "tailwind.config.ts",
@@ -3282,13 +4199,13 @@ async function checkConfigs() {
3282
4199
  const missingFiles = [];
3283
4200
  const invalidFiles = [];
3284
4201
  for (const file of REQUIRED_CONFIG_FILES) {
3285
- const filePath = path7.join(cwd, file);
4202
+ const filePath = path9.join(cwd, file);
3286
4203
  if (file.endsWith(".ts")) {
3287
- const jsPath = path7.join(cwd, file.replace(".ts", ".js"));
3288
- const mjsPath = path7.join(cwd, file.replace(".ts", ".mjs"));
3289
- const tsExists = await fs9.pathExists(filePath);
3290
- const jsExists = await fs9.pathExists(jsPath);
3291
- const mjsExists = await fs9.pathExists(mjsPath);
4204
+ const jsPath = path9.join(cwd, file.replace(".ts", ".js"));
4205
+ const mjsPath = path9.join(cwd, file.replace(".ts", ".mjs"));
4206
+ const tsExists = await fs10.pathExists(filePath);
4207
+ const jsExists = await fs10.pathExists(jsPath);
4208
+ const mjsExists = await fs10.pathExists(mjsPath);
3292
4209
  if (!tsExists && !jsExists && !mjsExists) {
3293
4210
  if (!file.includes("next.config") && !file.includes("tailwind.config")) {
3294
4211
  missingFiles.push(file);
@@ -3296,14 +4213,14 @@ async function checkConfigs() {
3296
4213
  }
3297
4214
  continue;
3298
4215
  }
3299
- if (!await fs9.pathExists(filePath)) {
4216
+ if (!await fs10.pathExists(filePath)) {
3300
4217
  missingFiles.push(file);
3301
4218
  }
3302
4219
  }
3303
- const tsconfigPath = path7.join(cwd, "tsconfig.json");
3304
- if (await fs9.pathExists(tsconfigPath)) {
4220
+ const tsconfigPath = path9.join(cwd, "tsconfig.json");
4221
+ if (await fs10.pathExists(tsconfigPath)) {
3305
4222
  try {
3306
- const content = await fs9.readFile(tsconfigPath, "utf-8");
4223
+ const content = await fs10.readFile(tsconfigPath, "utf-8");
3307
4224
  JSON.parse(content);
3308
4225
  } catch {
3309
4226
  invalidFiles.push("tsconfig.json");
@@ -3311,14 +4228,14 @@ async function checkConfigs() {
3311
4228
  } else {
3312
4229
  missingFiles.push("tsconfig.json");
3313
4230
  }
3314
- const configDir = path7.join(cwd, "config");
3315
- if (await fs9.pathExists(configDir)) {
3316
- const configFiles = await fs9.readdir(configDir);
4231
+ const configDir = path9.join(cwd, "config");
4232
+ if (await fs10.pathExists(configDir)) {
4233
+ const configFiles = await fs10.readdir(configDir);
3317
4234
  const tsConfigFiles = configFiles.filter((f) => f.endsWith(".ts"));
3318
4235
  for (const file of tsConfigFiles) {
3319
- const filePath = path7.join(configDir, file);
4236
+ const filePath = path9.join(configDir, file);
3320
4237
  try {
3321
- const content = await fs9.readFile(filePath, "utf-8");
4238
+ const content = await fs10.readFile(filePath, "utf-8");
3322
4239
  if (content.trim().length === 0) {
3323
4240
  invalidFiles.push(`config/${file}`);
3324
4241
  }
@@ -3351,8 +4268,8 @@ async function checkConfigs() {
3351
4268
  }
3352
4269
 
3353
4270
  // src/doctor/checks/database.ts
3354
- import fs10 from "fs-extra";
3355
- import path8 from "path";
4271
+ import fs11 from "fs-extra";
4272
+ import path10 from "path";
3356
4273
  function parseEnvFile(content) {
3357
4274
  const result = {};
3358
4275
  const lines = content.split("\n");
@@ -3375,25 +4292,25 @@ function parseEnvFile(content) {
3375
4292
  }
3376
4293
  async function checkDatabase() {
3377
4294
  const cwd = process.cwd();
3378
- const envPath = path8.join(cwd, ".env");
3379
- const envLocalPath = path8.join(cwd, ".env.local");
4295
+ const envPath = path10.join(cwd, ".env");
4296
+ const envLocalPath = path10.join(cwd, ".env.local");
3380
4297
  let envVars = {};
3381
- if (await fs10.pathExists(envLocalPath)) {
4298
+ if (await fs11.pathExists(envLocalPath)) {
3382
4299
  try {
3383
- const content = await fs10.readFile(envLocalPath, "utf-8");
4300
+ const content = await fs11.readFile(envLocalPath, "utf-8");
3384
4301
  envVars = { ...envVars, ...parseEnvFile(content) };
3385
4302
  } catch {
3386
4303
  }
3387
4304
  }
3388
- if (await fs10.pathExists(envPath)) {
4305
+ if (await fs11.pathExists(envPath)) {
3389
4306
  try {
3390
- const content = await fs10.readFile(envPath, "utf-8");
4307
+ const content = await fs11.readFile(envPath, "utf-8");
3391
4308
  envVars = { ...envVars, ...parseEnvFile(content) };
3392
4309
  } catch {
3393
4310
  }
3394
4311
  }
3395
4312
  const databaseUrl = envVars["DATABASE_URL"] || process.env.DATABASE_URL;
3396
- if (!await fs10.pathExists(envPath) && !await fs10.pathExists(envLocalPath)) {
4313
+ if (!await fs11.pathExists(envPath) && !await fs11.pathExists(envLocalPath)) {
3397
4314
  return {
3398
4315
  name: "Database",
3399
4316
  status: "warn",
@@ -3434,22 +4351,22 @@ async function checkDatabase() {
3434
4351
  }
3435
4352
 
3436
4353
  // src/doctor/checks/imports.ts
3437
- import fs11 from "fs-extra";
3438
- import path9 from "path";
4354
+ import fs12 from "fs-extra";
4355
+ import path11 from "path";
3439
4356
  async function checkCorePackage(cwd) {
3440
- const nodeModulesPath = path9.join(cwd, "node_modules", "@nextsparkjs", "core");
3441
- if (!await fs11.pathExists(nodeModulesPath)) {
4357
+ const nodeModulesPath = path11.join(cwd, "node_modules", "@nextsparkjs", "core");
4358
+ if (!await fs12.pathExists(nodeModulesPath)) {
3442
4359
  return { accessible: false, reason: "@nextsparkjs/core not found in node_modules" };
3443
4360
  }
3444
- const packageJsonPath = path9.join(nodeModulesPath, "package.json");
3445
- if (!await fs11.pathExists(packageJsonPath)) {
4361
+ const packageJsonPath = path11.join(nodeModulesPath, "package.json");
4362
+ if (!await fs12.pathExists(packageJsonPath)) {
3446
4363
  return { accessible: false, reason: "@nextsparkjs/core package.json not found" };
3447
4364
  }
3448
4365
  try {
3449
- const packageJson = await fs11.readJson(packageJsonPath);
4366
+ const packageJson = await fs12.readJson(packageJsonPath);
3450
4367
  const mainEntry = packageJson.main || packageJson.module || "./dist/index.js";
3451
- const mainPath = path9.join(nodeModulesPath, mainEntry);
3452
- if (!await fs11.pathExists(mainPath)) {
4368
+ const mainPath = path11.join(nodeModulesPath, mainEntry);
4369
+ if (!await fs12.pathExists(mainPath)) {
3453
4370
  return { accessible: false, reason: "@nextsparkjs/core entry point not found" };
3454
4371
  }
3455
4372
  } catch {
@@ -3462,8 +4379,8 @@ async function scanForBrokenImports(cwd) {
3462
4379
  const srcDirs = ["src", "app", "pages", "components", "lib"];
3463
4380
  const existingDirs = [];
3464
4381
  for (const dir of srcDirs) {
3465
- const dirPath = path9.join(cwd, dir);
3466
- if (await fs11.pathExists(dirPath)) {
4382
+ const dirPath = path11.join(cwd, dir);
4383
+ if (await fs12.pathExists(dirPath)) {
3467
4384
  existingDirs.push(dirPath);
3468
4385
  }
3469
4386
  }
@@ -3472,32 +4389,32 @@ async function scanForBrokenImports(cwd) {
3472
4389
  for (const dir of existingDirs) {
3473
4390
  if (filesScanned >= maxFilesToScan) break;
3474
4391
  try {
3475
- const files = await fs11.readdir(dir, { withFileTypes: true });
4392
+ const files = await fs12.readdir(dir, { withFileTypes: true });
3476
4393
  for (const file of files) {
3477
4394
  if (filesScanned >= maxFilesToScan) break;
3478
4395
  if (file.isFile() && (file.name.endsWith(".ts") || file.name.endsWith(".tsx"))) {
3479
- const filePath = path9.join(dir, file.name);
4396
+ const filePath = path11.join(dir, file.name);
3480
4397
  try {
3481
- const content = await fs11.readFile(filePath, "utf-8");
4398
+ const content = await fs12.readFile(filePath, "utf-8");
3482
4399
  const importMatches = content.match(/from ['"](@?[^'"]+)['"]/g);
3483
4400
  if (importMatches) {
3484
4401
  for (const match of importMatches) {
3485
4402
  const importPath = match.replace(/from ['"]/g, "").replace(/['"]/g, "");
3486
4403
  if (importPath.startsWith(".")) {
3487
- const absoluteImportPath = path9.resolve(path9.dirname(filePath), importPath);
4404
+ const absoluteImportPath = path11.resolve(path11.dirname(filePath), importPath);
3488
4405
  const possiblePaths = [
3489
4406
  absoluteImportPath,
3490
4407
  `${absoluteImportPath}.ts`,
3491
4408
  `${absoluteImportPath}.tsx`,
3492
4409
  `${absoluteImportPath}.js`,
3493
4410
  `${absoluteImportPath}.jsx`,
3494
- path9.join(absoluteImportPath, "index.ts"),
3495
- path9.join(absoluteImportPath, "index.tsx"),
3496
- path9.join(absoluteImportPath, "index.js")
4411
+ path11.join(absoluteImportPath, "index.ts"),
4412
+ path11.join(absoluteImportPath, "index.tsx"),
4413
+ path11.join(absoluteImportPath, "index.js")
3497
4414
  ];
3498
4415
  const exists = await Promise.any(
3499
4416
  possiblePaths.map(async (p) => {
3500
- if (await fs11.pathExists(p)) return true;
4417
+ if (await fs12.pathExists(p)) return true;
3501
4418
  throw new Error("Not found");
3502
4419
  })
3503
4420
  ).catch(() => false);
@@ -3521,11 +4438,11 @@ async function checkImports() {
3521
4438
  const cwd = process.cwd();
3522
4439
  const coreCheck = await checkCorePackage(cwd);
3523
4440
  if (!coreCheck.accessible) {
3524
- const packageJsonPath = path9.join(cwd, "package.json");
4441
+ const packageJsonPath = path11.join(cwd, "package.json");
3525
4442
  let hasCoreDep = false;
3526
- if (await fs11.pathExists(packageJsonPath)) {
4443
+ if (await fs12.pathExists(packageJsonPath)) {
3527
4444
  try {
3528
- const packageJson = await fs11.readJson(packageJsonPath);
4445
+ const packageJson = await fs12.readJson(packageJsonPath);
3529
4446
  hasCoreDep = !!(packageJson.dependencies?.["@nextsparkjs/core"] || packageJson.devDependencies?.["@nextsparkjs/core"]);
3530
4447
  } catch {
3531
4448
  }
@@ -3562,25 +4479,25 @@ async function checkImports() {
3562
4479
 
3563
4480
  // src/doctor/index.ts
3564
4481
  var STATUS_ICONS = {
3565
- pass: chalk13.green("\u2713"),
3566
- warn: chalk13.yellow("\u26A0"),
3567
- fail: chalk13.red("\u2717")
4482
+ pass: chalk14.green("\u2713"),
4483
+ warn: chalk14.yellow("\u26A0"),
4484
+ fail: chalk14.red("\u2717")
3568
4485
  };
3569
4486
  function formatResult(result) {
3570
4487
  const icon = STATUS_ICONS[result.status];
3571
- const nameColor = result.status === "fail" ? chalk13.red : result.status === "warn" ? chalk13.yellow : chalk13.white;
4488
+ const nameColor = result.status === "fail" ? chalk14.red : result.status === "warn" ? chalk14.yellow : chalk14.white;
3572
4489
  const name = nameColor(result.name.padEnd(18));
3573
- const message = chalk13.gray(result.message);
4490
+ const message = chalk14.gray(result.message);
3574
4491
  let output = `${icon} ${name} ${message}`;
3575
4492
  if (result.fix && result.status !== "pass") {
3576
4493
  output += `
3577
- ${" ".repeat(22)}${chalk13.cyan("\u2192")} ${chalk13.cyan(result.fix)}`;
4494
+ ${" ".repeat(22)}${chalk14.cyan("\u2192")} ${chalk14.cyan(result.fix)}`;
3578
4495
  }
3579
4496
  return output;
3580
4497
  }
3581
4498
  function showHeader() {
3582
4499
  console.log("");
3583
- console.log(chalk13.cyan("\u{1FA7A} NextSpark Health Check"));
4500
+ console.log(chalk14.cyan("\u{1FA7A} NextSpark Health Check"));
3584
4501
  console.log("");
3585
4502
  }
3586
4503
  function showSummary(results) {
@@ -3588,11 +4505,11 @@ function showSummary(results) {
3588
4505
  const warnings = results.filter((r) => r.status === "warn").length;
3589
4506
  const failed = results.filter((r) => r.status === "fail").length;
3590
4507
  console.log("");
3591
- console.log(chalk13.gray("-".repeat(50)));
4508
+ console.log(chalk14.gray("-".repeat(50)));
3592
4509
  const summary = [
3593
- chalk13.green(`${passed} passed`),
3594
- warnings > 0 ? chalk13.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
3595
- failed > 0 ? chalk13.red(`${failed} failed`) : null
4510
+ chalk14.green(`${passed} passed`),
4511
+ warnings > 0 ? chalk14.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
4512
+ failed > 0 ? chalk14.red(`${failed} failed`) : null
3596
4513
  ].filter(Boolean).join(", ");
3597
4514
  console.log(`Summary: ${summary}`);
3598
4515
  console.log("");
@@ -3634,7 +4551,7 @@ async function runDoctorCommand() {
3634
4551
  var isDirectExecution = process.argv[1]?.includes("doctor") || process.argv.includes("doctor");
3635
4552
  if (isDirectExecution && typeof __require !== "undefined") {
3636
4553
  runDoctorCommand().catch((error) => {
3637
- console.error(chalk13.red("An unexpected error occurred:"), error.message);
4554
+ console.error(chalk14.red("An unexpected error occurred:"), error.message);
3638
4555
  process.exit(1);
3639
4556
  });
3640
4557
  }
@@ -3645,16 +4562,323 @@ async function doctorCommand() {
3645
4562
  await runDoctorCommand();
3646
4563
  } catch (error) {
3647
4564
  if (error instanceof Error) {
3648
- console.error(chalk14.red(`Error: ${error.message}`));
4565
+ console.error(chalk15.red(`Error: ${error.message}`));
4566
+ }
4567
+ process.exit(1);
4568
+ }
4569
+ }
4570
+
4571
+ // src/commands/db.ts
4572
+ import { spawn as spawn5 } from "child_process";
4573
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
4574
+ import { join as join10 } from "path";
4575
+ import chalk16 from "chalk";
4576
+ import ora10 from "ora";
4577
+ function loadProjectEnv4(projectRoot) {
4578
+ const envPath = join10(projectRoot, ".env");
4579
+ const envVars = {};
4580
+ if (existsSync10(envPath)) {
4581
+ const content = readFileSync9(envPath, "utf-8");
4582
+ for (const line of content.split("\n")) {
4583
+ const trimmed = line.trim();
4584
+ if (trimmed && !trimmed.startsWith("#")) {
4585
+ const [key, ...valueParts] = trimmed.split("=");
4586
+ if (key && valueParts.length > 0) {
4587
+ let value = valueParts.join("=");
4588
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
4589
+ value = value.slice(1, -1);
4590
+ }
4591
+ envVars[key] = value;
4592
+ }
4593
+ }
4594
+ }
4595
+ }
4596
+ return envVars;
4597
+ }
4598
+ async function dbMigrateCommand() {
4599
+ const spinner = ora10("Preparing to run migrations...").start();
4600
+ try {
4601
+ const coreDir = getCoreDir();
4602
+ const projectRoot = getProjectRoot();
4603
+ const migrationsScript = join10(coreDir, "scripts", "db", "run-migrations.mjs");
4604
+ if (!existsSync10(migrationsScript)) {
4605
+ spinner.fail("Migrations script not found");
4606
+ console.error(chalk16.red(`Expected script at: ${migrationsScript}`));
4607
+ process.exit(1);
4608
+ }
4609
+ spinner.succeed("Core package found");
4610
+ const projectEnv = loadProjectEnv4(projectRoot);
4611
+ if (!projectEnv.DATABASE_URL) {
4612
+ spinner.fail("DATABASE_URL not found in .env file");
4613
+ console.error(chalk16.red("Please configure DATABASE_URL in your .env file"));
4614
+ process.exit(1);
4615
+ }
4616
+ if (!projectEnv.NEXT_PUBLIC_ACTIVE_THEME) {
4617
+ spinner.fail("NEXT_PUBLIC_ACTIVE_THEME not found in .env file");
4618
+ console.error(chalk16.red("Please configure NEXT_PUBLIC_ACTIVE_THEME in your .env file"));
4619
+ process.exit(1);
4620
+ }
4621
+ spinner.start("Running database migrations...");
4622
+ const migrateProcess = spawn5("node", [migrationsScript], {
4623
+ cwd: projectRoot,
4624
+ stdio: "inherit",
4625
+ env: {
4626
+ ...projectEnv,
4627
+ ...process.env,
4628
+ NEXTSPARK_PROJECT_ROOT: projectRoot,
4629
+ NEXTSPARK_CORE_DIR: coreDir
4630
+ }
4631
+ });
4632
+ migrateProcess.on("error", (err) => {
4633
+ spinner.fail("Migration failed");
4634
+ console.error(chalk16.red(err.message));
4635
+ process.exit(1);
4636
+ });
4637
+ migrateProcess.on("close", (code) => {
4638
+ if (code === 0) {
4639
+ console.log(chalk16.green("\n\u2705 Migrations completed successfully!"));
4640
+ process.exit(0);
4641
+ } else {
4642
+ console.error(chalk16.red(`
4643
+ \u274C Migrations failed with exit code ${code}`));
4644
+ process.exit(code ?? 1);
4645
+ }
4646
+ });
4647
+ } catch (error) {
4648
+ spinner.fail("Migration preparation failed");
4649
+ if (error instanceof Error) {
4650
+ console.error(chalk16.red(error.message));
4651
+ }
4652
+ process.exit(1);
4653
+ }
4654
+ }
4655
+ async function dbSeedCommand() {
4656
+ console.log(chalk16.cyan("\u2139\uFE0F Sample data is included as part of the migration process."));
4657
+ console.log(chalk16.cyan(" Running db:migrate to apply all migrations including sample data...\n"));
4658
+ await dbMigrateCommand();
4659
+ }
4660
+
4661
+ // src/commands/sync-app.ts
4662
+ import { existsSync as existsSync11, readdirSync as readdirSync2, mkdirSync as mkdirSync4, copyFileSync, readFileSync as readFileSync10 } from "fs";
4663
+ import { join as join11, dirname as dirname4, relative } from "path";
4664
+ import chalk17 from "chalk";
4665
+ import ora11 from "ora";
4666
+ var EXCLUDED_TEMPLATE_PATTERNS = ["(templates)"];
4667
+ var ROOT_TEMPLATE_FILES = [
4668
+ "proxy.ts",
4669
+ // Next.js 16+ proxy (formerly middleware.ts) - required for auth/permission validation
4670
+ "next.config.mjs",
4671
+ // Required for webpack aliases, transpilePackages, security headers
4672
+ "tsconfig.json",
4673
+ // Required for proper path aliases and test file exclusions
4674
+ "i18n.ts"
4675
+ // Required for next-intl configuration
4676
+ ];
4677
+ var MAX_VERBOSE_FILES = 10;
4678
+ var MAX_SUMMARY_FILES = 5;
4679
+ function getAllFiles(dir, baseDir = dir) {
4680
+ const files = [];
4681
+ if (!existsSync11(dir)) {
4682
+ return files;
4683
+ }
4684
+ const entries = readdirSync2(dir, { withFileTypes: true });
4685
+ for (const entry of entries) {
4686
+ const fullPath = join11(dir, entry.name);
4687
+ const relativePath = relative(baseDir, fullPath);
4688
+ if (entry.isDirectory()) {
4689
+ files.push(...getAllFiles(fullPath, baseDir));
4690
+ } else if (entry.isFile()) {
4691
+ if (entry.name !== ".DS_Store" && entry.name !== "README.md") {
4692
+ files.push(relativePath);
4693
+ }
4694
+ }
4695
+ }
4696
+ return files;
4697
+ }
4698
+ function copyFile(source, target) {
4699
+ const targetDir = dirname4(target);
4700
+ if (!existsSync11(targetDir)) {
4701
+ mkdirSync4(targetDir, { recursive: true });
4702
+ }
4703
+ copyFileSync(source, target);
4704
+ }
4705
+ function backupDirectory(source, target) {
4706
+ if (!existsSync11(source)) {
4707
+ return;
4708
+ }
4709
+ const files = getAllFiles(source);
4710
+ for (const file of files) {
4711
+ const sourcePath = join11(source, file);
4712
+ const targetPath = join11(target, file);
4713
+ copyFile(sourcePath, targetPath);
4714
+ }
4715
+ }
4716
+ function getCoreVersion2(coreDir) {
4717
+ try {
4718
+ const pkgPath = join11(coreDir, "package.json");
4719
+ const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
4720
+ return pkg2.version || "unknown";
4721
+ } catch {
4722
+ return "unknown";
4723
+ }
4724
+ }
4725
+ async function syncAppCommand(options) {
4726
+ const spinner = ora11({ text: "Preparing sync...", isSilent: options.dryRun }).start();
4727
+ try {
4728
+ const coreDir = getCoreDir();
4729
+ const projectRoot = getProjectRoot();
4730
+ const coreVersion = getCoreVersion2(coreDir);
4731
+ const templatesDir = join11(coreDir, "templates", "app");
4732
+ const appDir = join11(projectRoot, "app");
4733
+ if (!existsSync11(templatesDir)) {
4734
+ spinner.fail("Templates directory not found in @nextsparkjs/core");
4735
+ console.error(chalk17.red(`
4736
+ Expected path: ${templatesDir}`));
4737
+ process.exit(1);
4738
+ }
4739
+ if (!existsSync11(appDir)) {
4740
+ spinner.fail("No /app directory found");
4741
+ console.error(chalk17.red("\n This project does not have an /app folder."));
4742
+ console.error(chalk17.yellow(' Run "nextspark init" first to initialize your project.\n'));
4743
+ process.exit(1);
4744
+ }
4745
+ spinner.text = "Scanning template files...";
4746
+ const templateFiles = getAllFiles(templatesDir).filter((f) => !EXCLUDED_TEMPLATE_PATTERNS.some((pattern) => f.startsWith(pattern)));
4747
+ const existingAppFiles = getAllFiles(appDir);
4748
+ const customFiles = existingAppFiles.filter((f) => !templateFiles.includes(f));
4749
+ const filesToUpdate = templateFiles;
4750
+ spinner.succeed("Scan complete");
4751
+ console.log(chalk17.cyan(`
4752
+ Syncing /app with @nextsparkjs/core@${coreVersion}...
4753
+ `));
4754
+ if (options.dryRun) {
4755
+ console.log(chalk17.yellow(" [DRY RUN] No changes will be made\n"));
4756
+ }
4757
+ if (options.verbose) {
4758
+ console.log(chalk17.gray(" Files to sync:"));
4759
+ for (const file of filesToUpdate) {
4760
+ const targetPath = join11(appDir, file);
4761
+ const status = existsSync11(targetPath) ? chalk17.yellow("update") : chalk17.green("create");
4762
+ console.log(chalk17.gray(` ${status} ${file}`));
4763
+ }
4764
+ console.log();
4765
+ }
4766
+ console.log(chalk17.white(` Template files: ${filesToUpdate.length}`));
4767
+ console.log(chalk17.white(` Custom files preserved: ${customFiles.length}`));
4768
+ if (customFiles.length > 0 && options.verbose) {
4769
+ console.log(chalk17.gray("\n Custom files (will be preserved):"));
4770
+ for (const file of customFiles.slice(0, MAX_VERBOSE_FILES)) {
4771
+ console.log(chalk17.gray(` - ${file}`));
4772
+ }
4773
+ if (customFiles.length > MAX_VERBOSE_FILES) {
4774
+ console.log(chalk17.gray(` ... and ${customFiles.length - MAX_VERBOSE_FILES} more`));
4775
+ }
4776
+ }
4777
+ if (!options.force && !options.dryRun) {
4778
+ console.log(chalk17.yellow(`
4779
+ This will overwrite ${filesToUpdate.length} core template files.`));
4780
+ console.log(chalk17.gray(" Run with --dry-run to preview changes, or --force to skip this prompt.\n"));
4781
+ try {
4782
+ const { confirm: confirm6 } = await import("@inquirer/prompts");
4783
+ const confirmed = await confirm6({
4784
+ message: "Proceed with sync?",
4785
+ default: true
4786
+ });
4787
+ if (!confirmed) {
4788
+ console.log(chalk17.yellow("\n Sync cancelled.\n"));
4789
+ process.exit(0);
4790
+ }
4791
+ } catch (promptError) {
4792
+ console.error(chalk17.red("\n Failed to load confirmation prompt. Use --force to skip.\n"));
4793
+ process.exit(1);
4794
+ }
4795
+ }
4796
+ if (options.backup && !options.dryRun) {
4797
+ const backupDir = join11(projectRoot, `app.backup.v${coreVersion}.${Date.now()}`);
4798
+ spinner.start("Creating backup...");
4799
+ backupDirectory(appDir, backupDir);
4800
+ spinner.succeed(`Backup created: ${relative(projectRoot, backupDir)}`);
4801
+ }
4802
+ if (!options.dryRun) {
4803
+ spinner.start("Syncing files...");
4804
+ let updated = 0;
4805
+ let created = 0;
4806
+ for (const file of filesToUpdate) {
4807
+ const sourcePath = join11(templatesDir, file);
4808
+ const targetPath = join11(appDir, file);
4809
+ const isNew = !existsSync11(targetPath);
4810
+ copyFile(sourcePath, targetPath);
4811
+ if (isNew) {
4812
+ created++;
4813
+ } else {
4814
+ updated++;
4815
+ }
4816
+ if (options.verbose) {
4817
+ spinner.text = `Syncing: ${file}`;
4818
+ }
4819
+ }
4820
+ spinner.succeed(`Synced ${filesToUpdate.length} files (${updated} updated, ${created} created)`);
4821
+ const rootTemplatesDir = join11(coreDir, "templates");
4822
+ let rootUpdated = 0;
4823
+ let rootCreated = 0;
4824
+ for (const file of ROOT_TEMPLATE_FILES) {
4825
+ const sourcePath = join11(rootTemplatesDir, file);
4826
+ const targetPath = join11(projectRoot, file);
4827
+ if (existsSync11(sourcePath)) {
4828
+ const isNew = !existsSync11(targetPath);
4829
+ copyFile(sourcePath, targetPath);
4830
+ if (isNew) {
4831
+ rootCreated++;
4832
+ if (options.verbose) {
4833
+ console.log(chalk17.green(` + Created: ${file}`));
4834
+ }
4835
+ } else {
4836
+ rootUpdated++;
4837
+ if (options.verbose) {
4838
+ console.log(chalk17.yellow(` ~ Updated: ${file}`));
4839
+ }
4840
+ }
4841
+ }
4842
+ }
4843
+ if (rootUpdated + rootCreated > 0) {
4844
+ console.log(chalk17.gray(` Root files: ${rootUpdated} updated, ${rootCreated} created`));
4845
+ }
4846
+ }
4847
+ console.log(chalk17.green("\n \u2705 Sync complete!\n"));
4848
+ if (customFiles.length > 0) {
4849
+ console.log(chalk17.gray(` Preserved ${customFiles.length} custom file(s):`));
4850
+ for (const file of customFiles.slice(0, MAX_SUMMARY_FILES)) {
4851
+ console.log(chalk17.gray(` - app/${file}`));
4852
+ }
4853
+ if (customFiles.length > MAX_SUMMARY_FILES) {
4854
+ console.log(chalk17.gray(` ... and ${customFiles.length - MAX_SUMMARY_FILES} more
4855
+ `));
4856
+ } else {
4857
+ console.log();
4858
+ }
4859
+ }
4860
+ } catch (error) {
4861
+ spinner.fail("Sync failed");
4862
+ if (error instanceof Error) {
4863
+ console.error(chalk17.red(`
4864
+ Error: ${error.message}
4865
+ `));
4866
+ if (options.verbose && error.stack) {
4867
+ console.error(chalk17.gray(` Stack trace:
4868
+ ${error.stack}
4869
+ `));
4870
+ }
3649
4871
  }
3650
4872
  process.exit(1);
3651
4873
  }
3652
4874
  }
3653
4875
 
3654
4876
  // src/cli.ts
4877
+ config();
4878
+ var pkg = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
3655
4879
  var program = new Command();
3656
- program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version("0.1.0-beta.4");
3657
- 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);
4880
+ program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version(pkg.version);
4881
+ 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);
3658
4882
  program.command("build").description("Build for production").option("--no-registry", "Skip registry generation before build").action(buildCommand);
3659
4883
  program.command("generate").description("Generate all registries").option("-w, --watch", "Watch for changes").action(generateCommand);
3660
4884
  var registry = program.command("registry").description("Registry management commands");
@@ -3662,12 +4886,19 @@ registry.command("build").description("Build all registries").action(registryBui
3662
4886
  registry.command("watch").description("Watch and rebuild registries on changes").action(registryWatchCommand);
3663
4887
  program.command("registry:build").description("Build all registries (alias)").action(registryBuildCommand);
3664
4888
  program.command("registry:watch").description("Watch and rebuild registries (alias)").action(registryWatchCommand);
3665
- 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);
4889
+ 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);
3666
4890
  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);
3667
4891
  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);
4892
+ 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);
3668
4893
  program.command("doctor").description("Run health check on NextSpark project").action(doctorCommand);
4894
+ var db = program.command("db").description("Database management commands");
4895
+ db.command("migrate").description("Run database migrations").action(dbMigrateCommand);
4896
+ db.command("seed").description("Seed database with sample data").action(dbSeedCommand);
4897
+ program.command("db:migrate").description("Run database migrations (alias)").action(dbMigrateCommand);
4898
+ program.command("db:seed").description("Seed database with sample data (alias)").action(dbSeedCommand);
4899
+ 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);
3669
4900
  program.showHelpAfterError();
3670
4901
  program.configureOutput({
3671
- writeErr: (str) => process.stderr.write(chalk15.red(str))
4902
+ writeErr: (str) => process.stderr.write(chalk18.red(str))
3672
4903
  });
3673
4904
  program.parse();