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