@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/bin/nextspark.js +9 -0
- package/dist/{add-plugin-GCPJ4FHE.js → add-plugin-KIJLCOEQ.js} +1 -1
- package/dist/{chunk-EE6EJYHE.js → chunk-DXL5CEZD.js} +113 -112
- package/dist/cli.js +1677 -446
- package/package.json +13 -11
- package/templates/env.template +165 -0
package/dist/cli.js
CHANGED
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
installTheme,
|
|
8
8
|
runPostinstall,
|
|
9
9
|
validateTheme
|
|
10
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
426
|
-
import { join as
|
|
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
|
|
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(
|
|
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",
|
|
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",
|
|
576
|
-
const teamMode = await
|
|
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
|
|
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",
|
|
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
|
|
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
|
|
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",
|
|
667
|
-
const billingModel = await
|
|
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
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|
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
|
|
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",
|
|
1091
|
-
const authConfig = await promptAuthConfig("interactive",
|
|
1092
|
-
const dashboardConfig = await promptDashboardConfig("interactive",
|
|
1093
|
-
const devConfig = await promptDevConfig("interactive",
|
|
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",
|
|
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",
|
|
1135
|
-
const authConfig = await promptAuthConfig("expert",
|
|
1136
|
-
const dashboardConfig = await promptDashboardConfig("expert",
|
|
1137
|
-
const devConfig = await promptDevConfig("expert",
|
|
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
|
|
1153
|
-
import
|
|
1154
|
-
import { fileURLToPath as
|
|
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
|
|
1160
|
-
var
|
|
1161
|
-
var
|
|
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
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
1198
|
-
const themeConfigPath = path.join(getTargetThemesDir(),
|
|
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: '${
|
|
1277
|
+
`name: '${config2.projectSlug}'`
|
|
1206
1278
|
);
|
|
1207
1279
|
content = content.replace(
|
|
1208
1280
|
/displayName:\s*['"]Starter['"]/g,
|
|
1209
|
-
`displayName: '${
|
|
1281
|
+
`displayName: '${config2.projectName}'`
|
|
1210
1282
|
);
|
|
1211
1283
|
content = content.replace(
|
|
1212
1284
|
/description:\s*['"]Minimal starter theme for NextSpark['"]/g,
|
|
1213
|
-
`description: '${
|
|
1285
|
+
`description: '${config2.projectDescription}'`
|
|
1214
1286
|
);
|
|
1215
1287
|
content = content.replace(
|
|
1216
1288
|
/export const starterThemeConfig/g,
|
|
1217
|
-
`export const ${toCamelCase(
|
|
1289
|
+
`export const ${toCamelCase(config2.projectSlug)}ThemeConfig`
|
|
1218
1290
|
);
|
|
1219
1291
|
content = content.replace(
|
|
1220
1292
|
/export default starterThemeConfig/g,
|
|
1221
|
-
`export default ${toCamelCase(
|
|
1293
|
+
`export default ${toCamelCase(config2.projectSlug)}ThemeConfig`
|
|
1222
1294
|
);
|
|
1223
1295
|
await fs.writeFile(themeConfigPath, content, "utf-8");
|
|
1224
1296
|
}
|
|
1225
|
-
async function updateDevConfig(
|
|
1226
|
-
const devConfigPath = path.join(getTargetThemesDir(),
|
|
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(
|
|
1232
|
-
content = content.replace(/
|
|
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(
|
|
1238
|
-
const appConfigPath = path.join(getTargetThemesDir(),
|
|
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: '${
|
|
1315
|
+
`name: '${config2.projectName}'`
|
|
1246
1316
|
);
|
|
1247
1317
|
content = content.replace(
|
|
1248
1318
|
/mode:\s*['"]multi-tenant['"]\s*as\s*const/g,
|
|
1249
|
-
`mode: '${
|
|
1319
|
+
`mode: '${config2.teamMode}' as const`
|
|
1250
1320
|
);
|
|
1251
|
-
const localesArray =
|
|
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: '${
|
|
1328
|
+
`defaultLocale: '${config2.defaultLocale}' as const`
|
|
1259
1329
|
);
|
|
1260
1330
|
content = content.replace(
|
|
1261
1331
|
/label:\s*['"]Starter['"]/g,
|
|
1262
|
-
`label: '${
|
|
1332
|
+
`label: '${config2.projectName}'`
|
|
1263
1333
|
);
|
|
1264
1334
|
await fs.writeFile(appConfigPath, content, "utf-8");
|
|
1265
1335
|
}
|
|
1266
|
-
async function updateRolesConfig(
|
|
1267
|
-
const appConfigPath = path.join(getTargetThemesDir(),
|
|
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 =
|
|
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(
|
|
1280
|
-
const billingConfigPath = path.join(getTargetThemesDir(),
|
|
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: '${
|
|
1357
|
+
`currency: '${config2.currency}'`
|
|
1288
1358
|
);
|
|
1289
|
-
const plansContent = generateBillingPlans(
|
|
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(
|
|
1440
|
-
const migrationsDir = path.join(getTargetThemesDir(),
|
|
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, `@${
|
|
1450
|
-
content = content.replace(/Starter Theme/g,
|
|
1451
|
-
content = content.replace(/starter theme/g,
|
|
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(
|
|
1567
|
+
async function updateAuthConfig(config2) {
|
|
1471
1568
|
const authConfigPath = path2.join(
|
|
1472
1569
|
getTargetThemesDir2(),
|
|
1473
|
-
|
|
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${
|
|
1580
|
+
`$1${config2.auth.emailPassword}`
|
|
1484
1581
|
);
|
|
1485
1582
|
content = content.replace(
|
|
1486
1583
|
/(google:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1487
|
-
`$1${
|
|
1584
|
+
`$1${config2.auth.googleOAuth}`
|
|
1488
1585
|
);
|
|
1489
1586
|
content = content.replace(
|
|
1490
1587
|
/(emailVerification:\s*)(?:true|false)/g,
|
|
1491
|
-
`$1${
|
|
1588
|
+
`$1${config2.auth.emailVerification}`
|
|
1492
1589
|
);
|
|
1493
1590
|
await fs2.writeFile(authConfigPath, content, "utf-8");
|
|
1494
1591
|
}
|
|
1495
|
-
async function updateDashboardUIConfig(
|
|
1592
|
+
async function updateDashboardUIConfig(config2) {
|
|
1496
1593
|
const dashboardConfigPath = path2.join(
|
|
1497
1594
|
getTargetThemesDir2(),
|
|
1498
|
-
|
|
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${
|
|
1605
|
+
`$1${config2.dashboard.search}`
|
|
1509
1606
|
);
|
|
1510
1607
|
content = content.replace(
|
|
1511
1608
|
/(notifications:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1512
|
-
`$1${
|
|
1609
|
+
`$1${config2.dashboard.notifications}`
|
|
1513
1610
|
);
|
|
1514
1611
|
content = content.replace(
|
|
1515
1612
|
/(themeToggle:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1516
|
-
`$1${
|
|
1613
|
+
`$1${config2.dashboard.themeToggle}`
|
|
1517
1614
|
);
|
|
1518
1615
|
content = content.replace(
|
|
1519
1616
|
/(support:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1520
|
-
`$1${
|
|
1617
|
+
`$1${config2.dashboard.support}`
|
|
1521
1618
|
);
|
|
1522
1619
|
content = content.replace(
|
|
1523
1620
|
/(quickCreate:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1524
|
-
`$1${
|
|
1621
|
+
`$1${config2.dashboard.quickCreate}`
|
|
1525
1622
|
);
|
|
1526
1623
|
content = content.replace(
|
|
1527
1624
|
/(adminAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1528
|
-
`$1${
|
|
1625
|
+
`$1${config2.dashboard.superadminAccess}`
|
|
1529
1626
|
);
|
|
1530
1627
|
content = content.replace(
|
|
1531
1628
|
/(devtoolsAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1532
|
-
`$1${
|
|
1629
|
+
`$1${config2.dashboard.devtoolsAccess}`
|
|
1533
1630
|
);
|
|
1534
1631
|
content = content.replace(
|
|
1535
1632
|
/(defaultCollapsed:\s*)(?:true|false)/g,
|
|
1536
|
-
`$1${
|
|
1633
|
+
`$1${config2.dashboard.sidebarCollapsed}`
|
|
1537
1634
|
);
|
|
1538
1635
|
await fs2.writeFile(dashboardConfigPath, content, "utf-8");
|
|
1539
1636
|
}
|
|
1540
|
-
async function updateDevToolsConfig(
|
|
1637
|
+
async function updateDevToolsConfig(config2) {
|
|
1541
1638
|
const devConfigPath = path2.join(
|
|
1542
1639
|
getTargetThemesDir2(),
|
|
1543
|
-
|
|
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${
|
|
1650
|
+
`$1${config2.dev.devKeyring}`
|
|
1554
1651
|
);
|
|
1555
1652
|
content = content.replace(
|
|
1556
1653
|
/(debugMode:\s*)(?:true|false)/g,
|
|
1557
|
-
`$1${
|
|
1654
|
+
`$1${config2.dev.debugMode}`
|
|
1558
1655
|
);
|
|
1559
1656
|
await fs2.writeFile(devConfigPath, content, "utf-8");
|
|
1560
1657
|
}
|
|
1561
|
-
async function updatePermissionsConfig(
|
|
1658
|
+
async function updatePermissionsConfig(config2) {
|
|
1562
1659
|
const permissionsConfigPath = path2.join(
|
|
1563
1660
|
getTargetThemesDir2(),
|
|
1564
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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(
|
|
1754
|
+
async function generateEnvExample(config2) {
|
|
1620
1755
|
const envExamplePath = path2.resolve(process.cwd(), ".env.example");
|
|
1621
1756
|
let oauthSection = "";
|
|
1622
|
-
if (
|
|
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: ${
|
|
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="${
|
|
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
|
-
${
|
|
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(
|
|
1681
|
-
const readmePath = path2.join(getTargetThemesDir2(),
|
|
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, `# ${
|
|
1821
|
+
content = content.replace(/# Starter Theme/g, `# ${config2.projectName}`);
|
|
1687
1822
|
content = content.replace(
|
|
1688
1823
|
/Minimal starter theme for NextSpark/g,
|
|
1689
|
-
|
|
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(
|
|
1709
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
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 (!
|
|
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(
|
|
1725
|
-
const entitiesDir = path3.join(getTargetThemesDir3(),
|
|
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) && !
|
|
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(
|
|
1745
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
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,
|
|
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
|
|
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(
|
|
1764
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
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
|
|
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 =
|
|
1921
|
+
content.app.name = config2.projectName;
|
|
1775
1922
|
if (content.app.description) {
|
|
1776
|
-
content.app.description =
|
|
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(
|
|
1786
|
-
await removeUnusedLanguages(
|
|
1787
|
-
await removeUnusedEntityMessages(
|
|
1788
|
-
await ensureLanguageFolders(
|
|
1789
|
-
await updateMessageFiles(
|
|
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
|
|
1796
|
-
var
|
|
1797
|
-
var
|
|
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
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
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(
|
|
1969
|
+
async function copyPagesFeature(config2) {
|
|
1823
1970
|
const featuresDir = getFeaturesDir();
|
|
1824
|
-
const targetThemeDir = getTargetThemeDir(
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
1987
|
+
async function copyBlogFeature(config2) {
|
|
1834
1988
|
const featuresDir = getFeaturesDir();
|
|
1835
|
-
const targetThemeDir = getTargetThemeDir(
|
|
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(
|
|
1852
|
-
if (!
|
|
2005
|
+
async function copyContentFeatures(config2) {
|
|
2006
|
+
if (!config2.contentFeatures.pages && !config2.contentFeatures.blog) {
|
|
1853
2007
|
return;
|
|
1854
2008
|
}
|
|
1855
|
-
if (
|
|
1856
|
-
await copyPagesFeature(
|
|
2009
|
+
if (config2.contentFeatures.pages) {
|
|
2010
|
+
await copyPagesFeature(config2);
|
|
1857
2011
|
}
|
|
1858
|
-
if (
|
|
1859
|
-
await copyBlogFeature(
|
|
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
|
|
1865
|
-
import { join as
|
|
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
|
|
1873
|
-
import { join as
|
|
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 =
|
|
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(
|
|
2095
|
+
return existsSync5(join5(process.cwd(), "contents", "plugins", name));
|
|
1942
2096
|
}
|
|
1943
2097
|
function getCoreVersion() {
|
|
1944
|
-
const pkgPath =
|
|
2098
|
+
const pkgPath = join5(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
|
|
1945
2099
|
if (existsSync5(pkgPath)) {
|
|
1946
2100
|
try {
|
|
1947
|
-
const
|
|
1948
|
-
return
|
|
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
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
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
|
-
|
|
1995
|
-
|
|
2148
|
+
join6(process.cwd(), ".."),
|
|
2149
|
+
join6(process.cwd(), "..", "..")
|
|
1996
2150
|
];
|
|
1997
2151
|
for (const root of possibleRoots) {
|
|
1998
|
-
if (existsSync6(
|
|
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 =
|
|
2009
|
-
if (existsSync6(packageDir) && existsSync6(
|
|
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 =
|
|
2016
|
-
const themesDir =
|
|
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 =
|
|
2030
|
-
const pluginsDir =
|
|
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 =
|
|
2197
|
+
const tsconfigPath = join6(process.cwd(), "tsconfig.json");
|
|
2044
2198
|
if (!existsSync6(tsconfigPath)) {
|
|
2045
2199
|
return;
|
|
2046
2200
|
}
|
|
2047
2201
|
try {
|
|
2048
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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
|
|
2207
|
-
var
|
|
2208
|
-
function getTemplatesDir3() {
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
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
|
-
|
|
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: "
|
|
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 =
|
|
2243
|
-
const destPath =
|
|
2244
|
-
if (await
|
|
2245
|
-
if (item.force || !await
|
|
2246
|
-
await
|
|
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(
|
|
2252
|
-
const packageJsonPath =
|
|
2950
|
+
async function updatePackageJson(config2) {
|
|
2951
|
+
const packageJsonPath = path7.resolve(process.cwd(), "package.json");
|
|
2253
2952
|
let packageJson;
|
|
2254
|
-
if (!await
|
|
2953
|
+
if (!await fs8.pathExists(packageJsonPath)) {
|
|
2255
2954
|
packageJson = {
|
|
2256
|
-
name:
|
|
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
|
|
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
|
|
2276
|
-
"cy:open":
|
|
2277
|
-
"cy:run":
|
|
2278
|
-
"
|
|
2279
|
-
"
|
|
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": "
|
|
2290
|
-
"@nextsparkjs/cli": "
|
|
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": "^
|
|
3050
|
+
"cypress": "^15.8.2",
|
|
2350
3051
|
"@testing-library/cypress": "^10.0.2",
|
|
2351
3052
|
"@cypress/webpack-preprocessor": "^6.0.2",
|
|
2352
|
-
"@cypress/grep": "^
|
|
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
|
|
3066
|
+
await fs8.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2364
3067
|
}
|
|
2365
|
-
async function updateGitignore(
|
|
2366
|
-
const gitignorePath =
|
|
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
|
|
2385
|
-
const currentContent = await
|
|
3087
|
+
if (await fs8.pathExists(gitignorePath)) {
|
|
3088
|
+
const currentContent = await fs8.readFile(gitignorePath, "utf-8");
|
|
2386
3089
|
if (!currentContent.includes(".nextspark/")) {
|
|
2387
|
-
await
|
|
3090
|
+
await fs8.appendFile(gitignorePath, entriesToAdd);
|
|
2388
3091
|
}
|
|
2389
3092
|
} else {
|
|
2390
|
-
await
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
async function generateProject(
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
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(
|
|
3308
|
+
function getFileTree(config2) {
|
|
2577
3309
|
const files = [];
|
|
2578
|
-
const themeDir = `contents/themes/${
|
|
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
|
|
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.
|
|
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(
|
|
2646
|
-
const files = getFileTree(
|
|
3377
|
+
function showConfigPreview(config2) {
|
|
3378
|
+
const files = getFileTree(config2);
|
|
2647
3379
|
const groups = groupFilesByCategory(files);
|
|
2648
|
-
const themeDir = `contents/themes/${
|
|
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
|
|
2711
|
-
const isDefault = locale ===
|
|
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
|
-
|
|
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
|
-
|
|
3469
|
+
config2 = await runPresetMode(options.preset, options);
|
|
2756
3470
|
} else {
|
|
2757
3471
|
switch (options.mode) {
|
|
2758
3472
|
case "quick":
|
|
2759
|
-
|
|
3473
|
+
config2 = await runQuickPrompts();
|
|
2760
3474
|
break;
|
|
2761
3475
|
case "expert":
|
|
2762
|
-
|
|
3476
|
+
config2 = await runExpertPrompts();
|
|
2763
3477
|
break;
|
|
2764
3478
|
case "interactive":
|
|
2765
3479
|
default:
|
|
2766
|
-
|
|
3480
|
+
config2 = await runAllPrompts();
|
|
2767
3481
|
break;
|
|
2768
3482
|
}
|
|
2769
3483
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
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(
|
|
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:
|
|
2813
|
-
|
|
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
|
|
2826
|
-
|
|
3567
|
+
const registryScript = join7(webDir, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
|
|
3568
|
+
registrySpinner.stop();
|
|
2827
3569
|
execSync(`node "${registryScript}" --build`, {
|
|
2828
|
-
cwd:
|
|
2829
|
-
|
|
3570
|
+
cwd: webDir,
|
|
3571
|
+
// Run from web directory
|
|
3572
|
+
stdio: "inherit",
|
|
2830
3573
|
env: {
|
|
2831
3574
|
...process.env,
|
|
2832
|
-
NEXTSPARK_PROJECT_ROOT:
|
|
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
|
-
|
|
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(
|
|
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
|
|
2881
|
-
return
|
|
3624
|
+
const config2 = applyPreset(projectInfo, presetName, options.type);
|
|
3625
|
+
return config2;
|
|
2882
3626
|
}
|
|
2883
|
-
function showConfigSummary(
|
|
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(
|
|
2891
|
-
console.log(chalk11.gray(` Slug: ${chalk11.white(
|
|
2892
|
-
console.log(chalk11.gray(` Description: ${chalk11.white(
|
|
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(
|
|
2896
|
-
console.log(chalk11.gray(` Roles: ${chalk11.white(
|
|
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(
|
|
2900
|
-
console.log(chalk11.gray(` Languages: ${chalk11.white(
|
|
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(
|
|
2904
|
-
console.log(chalk11.gray(` Currency: ${chalk11.white(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2973
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
3014
|
-
const usePnpm = existsSync7(
|
|
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: "
|
|
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
|
-
|
|
3039
|
-
const
|
|
3040
|
-
|
|
3041
|
-
|
|
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(
|
|
3872
|
+
return existsSync8(join8(projectRoot, "contents")) || existsSync8(join8(projectRoot, ".nextspark"));
|
|
3064
3873
|
}
|
|
3065
3874
|
function generateInitialRegistries(registriesDir) {
|
|
3066
|
-
writeFileSync2(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3901
|
+
writeFileSync2(join8(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
|
|
3093
3902
|
export const BILLING_REGISTRY = { plans: [], features: [] }
|
|
3094
3903
|
`);
|
|
3095
|
-
writeFileSync2(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
3126
|
-
const registriesDir =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
4111
|
+
import chalk15 from "chalk";
|
|
3195
4112
|
|
|
3196
4113
|
// src/doctor/index.ts
|
|
3197
|
-
import
|
|
4114
|
+
import chalk14 from "chalk";
|
|
3198
4115
|
|
|
3199
4116
|
// src/doctor/checks/dependencies.ts
|
|
3200
|
-
import
|
|
3201
|
-
import
|
|
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 =
|
|
3205
|
-
const packageJsonPath =
|
|
3206
|
-
if (!await
|
|
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
|
|
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
|
|
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 =
|
|
3242
|
-
if (!await
|
|
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 =
|
|
3248
|
-
const hasNextSparkCore = await
|
|
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
|
|
3274
|
-
import
|
|
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 =
|
|
4202
|
+
const filePath = path9.join(cwd, file);
|
|
3286
4203
|
if (file.endsWith(".ts")) {
|
|
3287
|
-
const jsPath =
|
|
3288
|
-
const mjsPath =
|
|
3289
|
-
const tsExists = await
|
|
3290
|
-
const jsExists = await
|
|
3291
|
-
const mjsExists = await
|
|
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
|
|
4216
|
+
if (!await fs10.pathExists(filePath)) {
|
|
3300
4217
|
missingFiles.push(file);
|
|
3301
4218
|
}
|
|
3302
4219
|
}
|
|
3303
|
-
const tsconfigPath =
|
|
3304
|
-
if (await
|
|
4220
|
+
const tsconfigPath = path9.join(cwd, "tsconfig.json");
|
|
4221
|
+
if (await fs10.pathExists(tsconfigPath)) {
|
|
3305
4222
|
try {
|
|
3306
|
-
const content = await
|
|
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 =
|
|
3315
|
-
if (await
|
|
3316
|
-
const configFiles = await
|
|
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 =
|
|
4236
|
+
const filePath = path9.join(configDir, file);
|
|
3320
4237
|
try {
|
|
3321
|
-
const content = await
|
|
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
|
|
3355
|
-
import
|
|
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 =
|
|
3379
|
-
const envLocalPath =
|
|
4295
|
+
const envPath = path10.join(cwd, ".env");
|
|
4296
|
+
const envLocalPath = path10.join(cwd, ".env.local");
|
|
3380
4297
|
let envVars = {};
|
|
3381
|
-
if (await
|
|
4298
|
+
if (await fs11.pathExists(envLocalPath)) {
|
|
3382
4299
|
try {
|
|
3383
|
-
const content = await
|
|
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
|
|
4305
|
+
if (await fs11.pathExists(envPath)) {
|
|
3389
4306
|
try {
|
|
3390
|
-
const content = await
|
|
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
|
|
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
|
|
3438
|
-
import
|
|
4354
|
+
import fs12 from "fs-extra";
|
|
4355
|
+
import path11 from "path";
|
|
3439
4356
|
async function checkCorePackage(cwd) {
|
|
3440
|
-
const nodeModulesPath =
|
|
3441
|
-
if (!await
|
|
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 =
|
|
3445
|
-
if (!await
|
|
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
|
|
4366
|
+
const packageJson = await fs12.readJson(packageJsonPath);
|
|
3450
4367
|
const mainEntry = packageJson.main || packageJson.module || "./dist/index.js";
|
|
3451
|
-
const mainPath =
|
|
3452
|
-
if (!await
|
|
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 =
|
|
3466
|
-
if (await
|
|
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
|
|
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 =
|
|
4396
|
+
const filePath = path11.join(dir, file.name);
|
|
3480
4397
|
try {
|
|
3481
|
-
const content = await
|
|
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 =
|
|
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
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
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
|
|
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 =
|
|
4441
|
+
const packageJsonPath = path11.join(cwd, "package.json");
|
|
3525
4442
|
let hasCoreDep = false;
|
|
3526
|
-
if (await
|
|
4443
|
+
if (await fs12.pathExists(packageJsonPath)) {
|
|
3527
4444
|
try {
|
|
3528
|
-
const packageJson = await
|
|
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:
|
|
3566
|
-
warn:
|
|
3567
|
-
fail:
|
|
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" ?
|
|
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 =
|
|
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)}${
|
|
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(
|
|
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(
|
|
4508
|
+
console.log(chalk14.gray("-".repeat(50)));
|
|
3592
4509
|
const summary = [
|
|
3593
|
-
|
|
3594
|
-
warnings > 0 ?
|
|
3595
|
-
failed > 0 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4902
|
+
writeErr: (str) => process.stderr.write(chalk18.red(str))
|
|
3672
4903
|
});
|
|
3673
4904
|
program.parse();
|