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