@nextsparkjs/cli 0.1.0-beta.10 → 0.1.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nextspark.js +9 -0
- package/dist/add-plugin-Q7DOR7XO.js +10 -0
- package/dist/add-plugin-Q7DOR7XO.js.map +1 -0
- package/dist/{chunk-EE6EJYHE.js → chunk-5GBCARGX.js} +106 -112
- package/dist/chunk-5GBCARGX.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-QXD4PBX6.js +435 -0
- package/dist/chunk-QXD4PBX6.js.map +1 -0
- package/dist/cli.js +2034 -1017
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +21 -11
- package/templates/env.template +165 -0
- package/dist/add-plugin-GCPJ4FHE.js +0 -8
- package/dist/add-plugin-I3UJFL7U.js +0 -8
- package/dist/chunk-ALB2C27N.js +0 -712
package/dist/cli.js
CHANGED
|
@@ -1,455 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
buildCommand,
|
|
4
|
+
devCommand,
|
|
5
|
+
generateCommand,
|
|
6
|
+
getAIWorkflowDir,
|
|
7
|
+
getCoreDir,
|
|
8
|
+
getProjectRoot,
|
|
9
|
+
registryBuildCommand,
|
|
10
|
+
registryWatchCommand
|
|
11
|
+
} from "./chunk-QXD4PBX6.js";
|
|
12
|
+
import {
|
|
4
13
|
addPlugin,
|
|
5
14
|
addPluginCommand,
|
|
6
15
|
fetchPackage,
|
|
7
16
|
installTheme,
|
|
8
17
|
runPostinstall,
|
|
9
18
|
validateTheme
|
|
10
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-5GBCARGX.js";
|
|
20
|
+
import {
|
|
21
|
+
__require
|
|
22
|
+
} from "./chunk-DGUM43GV.js";
|
|
11
23
|
|
|
12
24
|
// src/cli.ts
|
|
25
|
+
import { config } from "dotenv";
|
|
13
26
|
import { Command } from "commander";
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
// src/commands/dev.ts
|
|
17
|
-
import { spawn } from "child_process";
|
|
18
|
-
import chalk from "chalk";
|
|
19
|
-
import ora from "ora";
|
|
20
|
-
|
|
21
|
-
// src/utils/paths.ts
|
|
22
|
-
import { existsSync } from "fs";
|
|
23
|
-
import { resolve, dirname } from "path";
|
|
24
|
-
import { fileURLToPath } from "url";
|
|
25
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
26
|
-
var __dirname = dirname(__filename);
|
|
27
|
-
function getCoreDir() {
|
|
28
|
-
const cwd = process.cwd();
|
|
29
|
-
const npmCorePath = resolve(cwd, "node_modules", "@nextsparkjs", "core");
|
|
30
|
-
if (existsSync(npmCorePath)) {
|
|
31
|
-
return npmCorePath;
|
|
32
|
-
}
|
|
33
|
-
const monorepoCorePath = resolve(__dirname, "..", "..", "..", "..", "core");
|
|
34
|
-
if (existsSync(monorepoCorePath)) {
|
|
35
|
-
return monorepoCorePath;
|
|
36
|
-
}
|
|
37
|
-
const cwdMonorepoCorePath = resolve(cwd, "..", "..", "packages", "core");
|
|
38
|
-
if (existsSync(cwdMonorepoCorePath)) {
|
|
39
|
-
return cwdMonorepoCorePath;
|
|
40
|
-
}
|
|
41
|
-
throw new Error(
|
|
42
|
-
"Could not find @nextsparkjs/core. Make sure it is installed as a dependency or you are running from within the monorepo."
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
function getProjectRoot() {
|
|
46
|
-
return process.cwd();
|
|
47
|
-
}
|
|
48
|
-
function isMonorepoMode() {
|
|
49
|
-
const cwd = process.cwd();
|
|
50
|
-
const npmCorePath = resolve(cwd, "node_modules", "@nextsparkjs", "core");
|
|
51
|
-
return !existsSync(npmCorePath);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/commands/dev.ts
|
|
55
|
-
async function devCommand(options) {
|
|
56
|
-
const spinner = ora("Starting development environment...").start();
|
|
57
|
-
try {
|
|
58
|
-
const coreDir = getCoreDir();
|
|
59
|
-
const projectRoot = getProjectRoot();
|
|
60
|
-
const mode = isMonorepoMode() ? "monorepo" : "npm";
|
|
61
|
-
spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
|
|
62
|
-
const processes = [];
|
|
63
|
-
if (options.registry) {
|
|
64
|
-
console.log(chalk.blue("\n[Registry] Starting registry builder with watch mode..."));
|
|
65
|
-
const registryProcess = spawn("node", ["scripts/build/registry.mjs", "--watch"], {
|
|
66
|
-
cwd: coreDir,
|
|
67
|
-
stdio: "inherit",
|
|
68
|
-
env: {
|
|
69
|
-
...process.env,
|
|
70
|
-
NEXTSPARK_PROJECT_ROOT: projectRoot
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
processes.push(registryProcess);
|
|
74
|
-
registryProcess.on("error", (err) => {
|
|
75
|
-
console.error(chalk.red(`[Registry] Error: ${err.message}`));
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
console.log(chalk.green(`
|
|
79
|
-
[Dev] Starting Next.js dev server on port ${options.port}...`));
|
|
80
|
-
const devProcess = spawn("npx", ["next", "dev", "-p", options.port], {
|
|
81
|
-
cwd: projectRoot,
|
|
82
|
-
stdio: "inherit",
|
|
83
|
-
env: {
|
|
84
|
-
...process.env,
|
|
85
|
-
NEXTSPARK_CORE_DIR: coreDir
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
processes.push(devProcess);
|
|
89
|
-
devProcess.on("error", (err) => {
|
|
90
|
-
console.error(chalk.red(`[Dev] Error: ${err.message}`));
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
const cleanup = () => {
|
|
94
|
-
console.log(chalk.yellow("\nShutting down..."));
|
|
95
|
-
processes.forEach((p) => {
|
|
96
|
-
if (!p.killed) {
|
|
97
|
-
p.kill("SIGTERM");
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
process.exit(0);
|
|
101
|
-
};
|
|
102
|
-
process.on("SIGINT", cleanup);
|
|
103
|
-
process.on("SIGTERM", cleanup);
|
|
104
|
-
devProcess.on("exit", (code) => {
|
|
105
|
-
cleanup();
|
|
106
|
-
process.exit(code ?? 0);
|
|
107
|
-
});
|
|
108
|
-
} catch (error) {
|
|
109
|
-
spinner.fail("Failed to start development environment");
|
|
110
|
-
if (error instanceof Error) {
|
|
111
|
-
console.error(chalk.red(error.message));
|
|
112
|
-
}
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/commands/build.ts
|
|
118
|
-
import { spawn as spawn2 } from "child_process";
|
|
119
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
120
|
-
import { join } from "path";
|
|
121
|
-
import chalk2 from "chalk";
|
|
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
|
-
}
|
|
144
|
-
async function buildCommand(options) {
|
|
145
|
-
const spinner = ora2("Preparing production build...").start();
|
|
146
|
-
try {
|
|
147
|
-
const coreDir = getCoreDir();
|
|
148
|
-
const projectRoot = getProjectRoot();
|
|
149
|
-
spinner.succeed("Core package found");
|
|
150
|
-
const projectEnv = loadProjectEnv(projectRoot);
|
|
151
|
-
if (options.registry) {
|
|
152
|
-
spinner.start("Generating registries...");
|
|
153
|
-
await new Promise((resolve4, reject) => {
|
|
154
|
-
const registryProcess = spawn2("node", ["scripts/build/registry.mjs"], {
|
|
155
|
-
cwd: coreDir,
|
|
156
|
-
stdio: "pipe",
|
|
157
|
-
env: {
|
|
158
|
-
...projectEnv,
|
|
159
|
-
...process.env,
|
|
160
|
-
NEXTSPARK_PROJECT_ROOT: projectRoot
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
let stderr = "";
|
|
164
|
-
registryProcess.stderr?.on("data", (data) => {
|
|
165
|
-
stderr += data.toString();
|
|
166
|
-
});
|
|
167
|
-
registryProcess.on("close", (code) => {
|
|
168
|
-
if (code === 0) {
|
|
169
|
-
resolve4();
|
|
170
|
-
} else {
|
|
171
|
-
reject(new Error(`Registry generation failed: ${stderr}`));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
registryProcess.on("error", reject);
|
|
175
|
-
});
|
|
176
|
-
spinner.succeed("Registries generated");
|
|
177
|
-
}
|
|
178
|
-
spinner.start("Building for production...");
|
|
179
|
-
const buildProcess = spawn2("npx", ["next", "build"], {
|
|
180
|
-
cwd: projectRoot,
|
|
181
|
-
stdio: "inherit",
|
|
182
|
-
env: {
|
|
183
|
-
...projectEnv,
|
|
184
|
-
...process.env,
|
|
185
|
-
NEXTSPARK_CORE_DIR: coreDir,
|
|
186
|
-
NODE_ENV: "production"
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
buildProcess.on("error", (err) => {
|
|
190
|
-
spinner.fail("Build failed");
|
|
191
|
-
console.error(chalk2.red(err.message));
|
|
192
|
-
process.exit(1);
|
|
193
|
-
});
|
|
194
|
-
buildProcess.on("close", (code) => {
|
|
195
|
-
if (code === 0) {
|
|
196
|
-
console.log(chalk2.green("\nBuild completed successfully!"));
|
|
197
|
-
process.exit(0);
|
|
198
|
-
} else {
|
|
199
|
-
console.error(chalk2.red(`
|
|
200
|
-
Build failed with exit code ${code}`));
|
|
201
|
-
process.exit(code ?? 1);
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
} catch (error) {
|
|
205
|
-
spinner.fail("Build preparation failed");
|
|
206
|
-
if (error instanceof Error) {
|
|
207
|
-
console.error(chalk2.red(error.message));
|
|
208
|
-
}
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/commands/generate.ts
|
|
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";
|
|
217
|
-
import chalk3 from "chalk";
|
|
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
|
-
}
|
|
240
|
-
async function generateCommand(options) {
|
|
241
|
-
const spinner = ora3("Preparing registry generation...").start();
|
|
242
|
-
try {
|
|
243
|
-
const coreDir = getCoreDir();
|
|
244
|
-
const projectRoot = getProjectRoot();
|
|
245
|
-
const mode = isMonorepoMode() ? "monorepo" : "npm";
|
|
246
|
-
spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
|
|
247
|
-
const projectEnv = loadProjectEnv2(projectRoot);
|
|
248
|
-
const watchArg = options.watch ? ["--watch"] : [];
|
|
249
|
-
const action = options.watch ? "Watching" : "Generating";
|
|
250
|
-
console.log(chalk3.blue(`
|
|
251
|
-
${action} registries...`));
|
|
252
|
-
const generateProcess = spawn3("node", ["scripts/build/registry.mjs", ...watchArg], {
|
|
253
|
-
cwd: coreDir,
|
|
254
|
-
stdio: "inherit",
|
|
255
|
-
env: {
|
|
256
|
-
...projectEnv,
|
|
257
|
-
...process.env,
|
|
258
|
-
NEXTSPARK_PROJECT_ROOT: projectRoot
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
generateProcess.on("error", (err) => {
|
|
262
|
-
console.error(chalk3.red(`Error: ${err.message}`));
|
|
263
|
-
process.exit(1);
|
|
264
|
-
});
|
|
265
|
-
generateProcess.on("close", (code) => {
|
|
266
|
-
if (code === 0) {
|
|
267
|
-
if (!options.watch) {
|
|
268
|
-
console.log(chalk3.green("\nRegistry generation completed!"));
|
|
269
|
-
}
|
|
270
|
-
process.exit(0);
|
|
271
|
-
} else {
|
|
272
|
-
console.error(chalk3.red(`
|
|
273
|
-
Registry generation failed with exit code ${code}`));
|
|
274
|
-
process.exit(code ?? 1);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
if (options.watch) {
|
|
278
|
-
const cleanup = () => {
|
|
279
|
-
console.log(chalk3.yellow("\nStopping watcher..."));
|
|
280
|
-
if (!generateProcess.killed) {
|
|
281
|
-
generateProcess.kill("SIGTERM");
|
|
282
|
-
}
|
|
283
|
-
process.exit(0);
|
|
284
|
-
};
|
|
285
|
-
process.on("SIGINT", cleanup);
|
|
286
|
-
process.on("SIGTERM", cleanup);
|
|
287
|
-
}
|
|
288
|
-
} catch (error) {
|
|
289
|
-
spinner.fail("Registry generation failed");
|
|
290
|
-
if (error instanceof Error) {
|
|
291
|
-
console.error(chalk3.red(error.message));
|
|
292
|
-
}
|
|
293
|
-
process.exit(1);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// src/commands/registry.ts
|
|
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";
|
|
301
|
-
import chalk4 from "chalk";
|
|
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
|
-
}
|
|
324
|
-
async function registryBuildCommand() {
|
|
325
|
-
const spinner = ora4("Building registries...").start();
|
|
326
|
-
try {
|
|
327
|
-
const coreDir = getCoreDir();
|
|
328
|
-
const projectRoot = getProjectRoot();
|
|
329
|
-
const mode = isMonorepoMode() ? "monorepo" : "npm";
|
|
330
|
-
const projectEnv = loadProjectEnv3(projectRoot);
|
|
331
|
-
spinner.text = `Building registries (${mode} mode)...`;
|
|
332
|
-
const buildProcess = spawn4("node", ["scripts/build/registry.mjs"], {
|
|
333
|
-
cwd: coreDir,
|
|
334
|
-
stdio: "pipe",
|
|
335
|
-
env: {
|
|
336
|
-
...projectEnv,
|
|
337
|
-
...process.env,
|
|
338
|
-
NEXTSPARK_PROJECT_ROOT: projectRoot
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
let stdout = "";
|
|
342
|
-
let stderr = "";
|
|
343
|
-
buildProcess.stdout?.on("data", (data) => {
|
|
344
|
-
stdout += data.toString();
|
|
345
|
-
});
|
|
346
|
-
buildProcess.stderr?.on("data", (data) => {
|
|
347
|
-
stderr += data.toString();
|
|
348
|
-
});
|
|
349
|
-
buildProcess.on("close", (code) => {
|
|
350
|
-
if (code === 0) {
|
|
351
|
-
spinner.succeed("Registries built successfully");
|
|
352
|
-
if (stdout.trim()) {
|
|
353
|
-
console.log(chalk4.gray(stdout.trim()));
|
|
354
|
-
}
|
|
355
|
-
process.exit(0);
|
|
356
|
-
} else {
|
|
357
|
-
spinner.fail("Registry build failed");
|
|
358
|
-
if (stderr.trim()) {
|
|
359
|
-
console.error(chalk4.red(stderr.trim()));
|
|
360
|
-
}
|
|
361
|
-
process.exit(code ?? 1);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
buildProcess.on("error", (err) => {
|
|
365
|
-
spinner.fail("Registry build failed");
|
|
366
|
-
console.error(chalk4.red(err.message));
|
|
367
|
-
process.exit(1);
|
|
368
|
-
});
|
|
369
|
-
} catch (error) {
|
|
370
|
-
spinner.fail("Registry build failed");
|
|
371
|
-
if (error instanceof Error) {
|
|
372
|
-
console.error(chalk4.red(error.message));
|
|
373
|
-
}
|
|
374
|
-
process.exit(1);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
async function registryWatchCommand() {
|
|
378
|
-
const spinner = ora4("Starting registry watcher...").start();
|
|
379
|
-
try {
|
|
380
|
-
const coreDir = getCoreDir();
|
|
381
|
-
const projectRoot = getProjectRoot();
|
|
382
|
-
const mode = isMonorepoMode() ? "monorepo" : "npm";
|
|
383
|
-
spinner.succeed(`Registry watcher started (${mode} mode)`);
|
|
384
|
-
console.log(chalk4.blue("\nWatching for changes... Press Ctrl+C to stop.\n"));
|
|
385
|
-
const projectEnv = loadProjectEnv3(projectRoot);
|
|
386
|
-
const watchProcess = spawn4("node", ["scripts/build/registry.mjs", "--watch"], {
|
|
387
|
-
cwd: coreDir,
|
|
388
|
-
stdio: "inherit",
|
|
389
|
-
env: {
|
|
390
|
-
...projectEnv,
|
|
391
|
-
...process.env,
|
|
392
|
-
NEXTSPARK_PROJECT_ROOT: projectRoot
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
watchProcess.on("error", (err) => {
|
|
396
|
-
console.error(chalk4.red(`Watcher error: ${err.message}`));
|
|
397
|
-
process.exit(1);
|
|
398
|
-
});
|
|
399
|
-
const cleanup = () => {
|
|
400
|
-
console.log(chalk4.yellow("\nStopping registry watcher..."));
|
|
401
|
-
if (!watchProcess.killed) {
|
|
402
|
-
watchProcess.kill("SIGTERM");
|
|
403
|
-
}
|
|
404
|
-
process.exit(0);
|
|
405
|
-
};
|
|
406
|
-
process.on("SIGINT", cleanup);
|
|
407
|
-
process.on("SIGTERM", cleanup);
|
|
408
|
-
watchProcess.on("close", (code) => {
|
|
409
|
-
if (code !== 0) {
|
|
410
|
-
console.error(chalk4.red(`
|
|
411
|
-
Watcher exited with code ${code}`));
|
|
412
|
-
}
|
|
413
|
-
process.exit(code ?? 0);
|
|
414
|
-
});
|
|
415
|
-
} catch (error) {
|
|
416
|
-
spinner.fail("Failed to start registry watcher");
|
|
417
|
-
if (error instanceof Error) {
|
|
418
|
-
console.error(chalk4.red(error.message));
|
|
419
|
-
}
|
|
420
|
-
process.exit(1);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
27
|
+
import chalk16 from "chalk";
|
|
28
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
423
29
|
|
|
424
30
|
// src/commands/init.ts
|
|
425
|
-
import { existsSync as
|
|
426
|
-
import { join as
|
|
427
|
-
import
|
|
428
|
-
import
|
|
31
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
|
|
32
|
+
import { join as join5 } from "path";
|
|
33
|
+
import chalk8 from "chalk";
|
|
34
|
+
import ora4 from "ora";
|
|
429
35
|
|
|
430
36
|
// src/wizard/index.ts
|
|
431
|
-
import
|
|
432
|
-
import
|
|
433
|
-
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
37
|
+
import chalk7 from "chalk";
|
|
38
|
+
import ora3 from "ora";
|
|
39
|
+
import { confirm as confirm5, select as select6 } from "@inquirer/prompts";
|
|
434
40
|
import { execSync } from "child_process";
|
|
435
|
-
import { existsSync as
|
|
436
|
-
import { join as
|
|
41
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
42
|
+
import { join as join4 } from "path";
|
|
437
43
|
|
|
438
44
|
// src/wizard/banner.ts
|
|
439
|
-
import
|
|
440
|
-
import { readFileSync
|
|
441
|
-
import { fileURLToPath
|
|
442
|
-
import { dirname
|
|
443
|
-
var
|
|
444
|
-
var
|
|
45
|
+
import chalk from "chalk";
|
|
46
|
+
import { readFileSync } from "fs";
|
|
47
|
+
import { fileURLToPath } from "url";
|
|
48
|
+
import { dirname, join } from "path";
|
|
49
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
50
|
+
var __dirname = dirname(__filename);
|
|
445
51
|
function getCliVersion() {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
52
|
+
const possiblePaths = [
|
|
53
|
+
join(__dirname, "../package.json"),
|
|
54
|
+
// from dist/
|
|
55
|
+
join(__dirname, "../../package.json"),
|
|
56
|
+
// from dist/wizard/ or src/wizard/
|
|
57
|
+
join(__dirname, "../../../package.json")
|
|
58
|
+
// fallback
|
|
59
|
+
];
|
|
60
|
+
for (const packageJsonPath of possiblePaths) {
|
|
61
|
+
try {
|
|
62
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
63
|
+
if (packageJson.name === "@nextsparkjs/cli" && packageJson.version) {
|
|
64
|
+
return packageJson.version;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
452
68
|
}
|
|
69
|
+
return "unknown";
|
|
453
70
|
}
|
|
454
71
|
var BANNER = `
|
|
455
72
|
_ __ __ _____ __
|
|
@@ -461,26 +78,29 @@ var BANNER = `
|
|
|
461
78
|
`;
|
|
462
79
|
function showBanner() {
|
|
463
80
|
const version = getCliVersion();
|
|
464
|
-
console.log(
|
|
465
|
-
console.log(
|
|
466
|
-
console.log(
|
|
81
|
+
console.log(chalk.cyan(BANNER));
|
|
82
|
+
console.log(chalk.bold.white(" Welcome to NextSpark - The Complete SaaS Framework for Next.js"));
|
|
83
|
+
console.log(chalk.gray(` Version ${version} - Create production-ready SaaS applications in minutes
|
|
467
84
|
`));
|
|
468
|
-
console.log(
|
|
85
|
+
console.log(chalk.gray(" " + "=".repeat(60) + "\n"));
|
|
469
86
|
}
|
|
470
87
|
function showSection(title, step, totalSteps) {
|
|
471
88
|
console.log("");
|
|
472
|
-
console.log(
|
|
473
|
-
console.log(
|
|
89
|
+
console.log(chalk.cyan(` Step ${step}/${totalSteps}: ${title}`));
|
|
90
|
+
console.log(chalk.gray(" " + "-".repeat(40)));
|
|
474
91
|
console.log("");
|
|
475
92
|
}
|
|
93
|
+
function showSuccess(message) {
|
|
94
|
+
console.log(chalk.green(` \u2713 ${message}`));
|
|
95
|
+
}
|
|
476
96
|
function showWarning(message) {
|
|
477
|
-
console.log(
|
|
97
|
+
console.log(chalk.yellow(` \u26A0 ${message}`));
|
|
478
98
|
}
|
|
479
99
|
function showError(message) {
|
|
480
|
-
console.log(
|
|
100
|
+
console.log(chalk.red(` \u2717 ${message}`));
|
|
481
101
|
}
|
|
482
102
|
function showInfo(message) {
|
|
483
|
-
console.log(
|
|
103
|
+
console.log(chalk.blue(` \u2139 ${message}`));
|
|
484
104
|
}
|
|
485
105
|
|
|
486
106
|
// src/wizard/prompts/project-info.ts
|
|
@@ -516,7 +136,7 @@ function validateSlug(slug) {
|
|
|
516
136
|
return true;
|
|
517
137
|
}
|
|
518
138
|
async function promptProjectInfo() {
|
|
519
|
-
showSection("Project Information",
|
|
139
|
+
showSection("Project Information", 2, 10);
|
|
520
140
|
const projectName = await input({
|
|
521
141
|
message: "What is your project name?",
|
|
522
142
|
default: "My SaaS App",
|
|
@@ -539,8 +159,43 @@ async function promptProjectInfo() {
|
|
|
539
159
|
};
|
|
540
160
|
}
|
|
541
161
|
|
|
162
|
+
// src/wizard/prompts/project-type.ts
|
|
163
|
+
import { select } from "@inquirer/prompts";
|
|
164
|
+
var PROJECT_TYPE_OPTIONS = [
|
|
165
|
+
{
|
|
166
|
+
name: "Web only",
|
|
167
|
+
value: "web",
|
|
168
|
+
description: "Next.js web application with NextSpark. Standard flat project structure."
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "Web + Mobile",
|
|
172
|
+
value: "web-mobile",
|
|
173
|
+
description: "Monorepo with Next.js web app and Expo mobile app sharing the same backend."
|
|
174
|
+
}
|
|
175
|
+
];
|
|
176
|
+
async function promptProjectType() {
|
|
177
|
+
showSection("Project Type", 1, 10);
|
|
178
|
+
const projectType = await select({
|
|
179
|
+
message: "What type of project do you want to create?",
|
|
180
|
+
choices: PROJECT_TYPE_OPTIONS,
|
|
181
|
+
default: "web"
|
|
182
|
+
});
|
|
183
|
+
const selectedOption = PROJECT_TYPE_OPTIONS.find((o) => o.value === projectType);
|
|
184
|
+
if (selectedOption) {
|
|
185
|
+
showInfo(selectedOption.description);
|
|
186
|
+
}
|
|
187
|
+
if (projectType === "web-mobile") {
|
|
188
|
+
console.log("");
|
|
189
|
+
showInfo("Your project will be a pnpm monorepo with web/ and mobile/ directories.");
|
|
190
|
+
showInfo("The mobile app will use Expo and share the same backend API.");
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
projectType
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
542
197
|
// src/wizard/prompts/team-config.ts
|
|
543
|
-
import { select, checkbox } from "@inquirer/prompts";
|
|
198
|
+
import { select as select2, checkbox } from "@inquirer/prompts";
|
|
544
199
|
|
|
545
200
|
// src/wizard/types.ts
|
|
546
201
|
var AVAILABLE_LOCALES = {
|
|
@@ -588,8 +243,8 @@ var ROLE_OPTIONS = [
|
|
|
588
243
|
{ name: "Viewer (Read-only access)", value: "viewer", checked: true }
|
|
589
244
|
];
|
|
590
245
|
async function promptTeamConfig() {
|
|
591
|
-
showSection("Team Configuration",
|
|
592
|
-
const teamMode = await
|
|
246
|
+
showSection("Team Configuration", 3, 10);
|
|
247
|
+
const teamMode = await select2({
|
|
593
248
|
message: "What team mode do you need?",
|
|
594
249
|
choices: TEAM_MODE_OPTIONS,
|
|
595
250
|
default: "multi-tenant"
|
|
@@ -620,7 +275,7 @@ async function promptTeamConfig() {
|
|
|
620
275
|
}
|
|
621
276
|
|
|
622
277
|
// src/wizard/prompts/i18n-config.ts
|
|
623
|
-
import { select as
|
|
278
|
+
import { select as select3, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
624
279
|
var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
|
|
625
280
|
name: `${name} (${value})`,
|
|
626
281
|
value,
|
|
@@ -628,7 +283,7 @@ var LOCALE_OPTIONS = Object.entries(AVAILABLE_LOCALES).map(([value, name]) => ({
|
|
|
628
283
|
// English selected by default
|
|
629
284
|
}));
|
|
630
285
|
async function promptI18nConfig() {
|
|
631
|
-
showSection("Internationalization",
|
|
286
|
+
showSection("Internationalization", 4, 10);
|
|
632
287
|
const supportedLocales = await checkbox2({
|
|
633
288
|
message: "Which languages do you want to support?",
|
|
634
289
|
choices: LOCALE_OPTIONS,
|
|
@@ -644,7 +299,7 @@ async function promptI18nConfig() {
|
|
|
644
299
|
showInfo(`Default language set to ${AVAILABLE_LOCALES[defaultLocale]}`);
|
|
645
300
|
} else {
|
|
646
301
|
console.log("");
|
|
647
|
-
defaultLocale = await
|
|
302
|
+
defaultLocale = await select3({
|
|
648
303
|
message: "Which should be the default language?",
|
|
649
304
|
choices: supportedLocales.map((locale) => ({
|
|
650
305
|
name: `${AVAILABLE_LOCALES[locale]} (${locale})`,
|
|
@@ -660,7 +315,7 @@ async function promptI18nConfig() {
|
|
|
660
315
|
}
|
|
661
316
|
|
|
662
317
|
// src/wizard/prompts/billing-config.ts
|
|
663
|
-
import { select as
|
|
318
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
664
319
|
var BILLING_MODEL_OPTIONS = [
|
|
665
320
|
{
|
|
666
321
|
name: "Free (No payments)",
|
|
@@ -679,8 +334,8 @@ var BILLING_MODEL_OPTIONS = [
|
|
|
679
334
|
}
|
|
680
335
|
];
|
|
681
336
|
async function promptBillingConfig() {
|
|
682
|
-
showSection("Billing Configuration",
|
|
683
|
-
const billingModel = await
|
|
337
|
+
showSection("Billing Configuration", 5, 10);
|
|
338
|
+
const billingModel = await select4({
|
|
684
339
|
message: "What billing model do you want to use?",
|
|
685
340
|
choices: BILLING_MODEL_OPTIONS,
|
|
686
341
|
default: "freemium"
|
|
@@ -692,7 +347,7 @@ async function promptBillingConfig() {
|
|
|
692
347
|
let currency = "usd";
|
|
693
348
|
if (billingModel !== "free") {
|
|
694
349
|
console.log("");
|
|
695
|
-
currency = await
|
|
350
|
+
currency = await select4({
|
|
696
351
|
message: "What currency will you use?",
|
|
697
352
|
choices: CURRENCIES.map((c) => ({
|
|
698
353
|
name: c.label,
|
|
@@ -744,7 +399,7 @@ var FEATURE_OPTIONS = [
|
|
|
744
399
|
}
|
|
745
400
|
];
|
|
746
401
|
async function promptFeaturesConfig() {
|
|
747
|
-
showSection("Features",
|
|
402
|
+
showSection("Features", 6, 10);
|
|
748
403
|
showInfo("Select the features you want to include in your project.");
|
|
749
404
|
showInfo("You can add or remove features later by editing your config files.");
|
|
750
405
|
console.log("");
|
|
@@ -781,7 +436,7 @@ var CONTENT_FEATURE_OPTIONS = [
|
|
|
781
436
|
}
|
|
782
437
|
];
|
|
783
438
|
async function promptContentFeaturesConfig(mode = "interactive", totalSteps = 9) {
|
|
784
|
-
showSection("Content Features",
|
|
439
|
+
showSection("Content Features", 7, totalSteps);
|
|
785
440
|
showInfo("Enable optional content features for your project.");
|
|
786
441
|
showInfo("These features add entities and blocks for building pages and blog posts.");
|
|
787
442
|
console.log("");
|
|
@@ -832,7 +487,7 @@ function getDefaultAuthConfig() {
|
|
|
832
487
|
};
|
|
833
488
|
}
|
|
834
489
|
async function promptAuthConfig(mode = "interactive", totalSteps = 8) {
|
|
835
|
-
showSection("Authentication",
|
|
490
|
+
showSection("Authentication", 8, totalSteps);
|
|
836
491
|
showInfo("Configure how users will authenticate to your application.");
|
|
837
492
|
console.log("");
|
|
838
493
|
const selectedMethods = await checkbox5({
|
|
@@ -916,7 +571,7 @@ function getDefaultDashboardConfig() {
|
|
|
916
571
|
};
|
|
917
572
|
}
|
|
918
573
|
async function promptDashboardConfig(mode = "interactive", totalSteps = 8) {
|
|
919
|
-
showSection("Dashboard",
|
|
574
|
+
showSection("Dashboard", 9, totalSteps);
|
|
920
575
|
showInfo("Configure your dashboard user interface.");
|
|
921
576
|
showInfo("These settings can be changed later in your theme config.");
|
|
922
577
|
console.log("");
|
|
@@ -968,7 +623,7 @@ function getDefaultDevConfig() {
|
|
|
968
623
|
};
|
|
969
624
|
}
|
|
970
625
|
async function promptDevConfig(mode = "interactive", totalSteps = 8) {
|
|
971
|
-
showSection("Development Tools",
|
|
626
|
+
showSection("Development Tools", 10, totalSteps);
|
|
972
627
|
showInfo("Configure development and debugging tools.");
|
|
973
628
|
showWarning("These tools are disabled in production builds.");
|
|
974
629
|
console.log("");
|
|
@@ -995,17 +650,17 @@ async function promptDevConfig(mode = "interactive", totalSteps = 8) {
|
|
|
995
650
|
}
|
|
996
651
|
|
|
997
652
|
// src/wizard/prompts/theme-selection.ts
|
|
998
|
-
import { select as
|
|
999
|
-
import
|
|
653
|
+
import { select as select5 } from "@inquirer/prompts";
|
|
654
|
+
import chalk2 from "chalk";
|
|
1000
655
|
async function promptThemeSelection() {
|
|
1001
656
|
console.log("");
|
|
1002
|
-
console.log(
|
|
1003
|
-
console.log(
|
|
657
|
+
console.log(chalk2.cyan(" Reference Theme Installation"));
|
|
658
|
+
console.log(chalk2.gray(" " + "-".repeat(40)));
|
|
1004
659
|
console.log("");
|
|
1005
|
-
console.log(
|
|
1006
|
-
console.log(
|
|
660
|
+
console.log(chalk2.gray(" A reference theme provides a complete example to learn from."));
|
|
661
|
+
console.log(chalk2.gray(" Your custom theme (based on starter) will be your active theme."));
|
|
1007
662
|
console.log("");
|
|
1008
|
-
const theme = await
|
|
663
|
+
const theme = await select5({
|
|
1009
664
|
message: "Which reference theme would you like to install?",
|
|
1010
665
|
choices: [
|
|
1011
666
|
{
|
|
@@ -1041,7 +696,7 @@ async function promptThemeSelection() {
|
|
|
1041
696
|
|
|
1042
697
|
// src/wizard/prompts/plugins-selection.ts
|
|
1043
698
|
import { checkbox as checkbox8 } from "@inquirer/prompts";
|
|
1044
|
-
import
|
|
699
|
+
import chalk3 from "chalk";
|
|
1045
700
|
var THEME_REQUIRED_PLUGINS = {
|
|
1046
701
|
"default": ["langchain"],
|
|
1047
702
|
"blog": [],
|
|
@@ -1051,11 +706,11 @@ var THEME_REQUIRED_PLUGINS = {
|
|
|
1051
706
|
async function promptPluginsSelection(selectedTheme) {
|
|
1052
707
|
const requiredPlugins = selectedTheme ? THEME_REQUIRED_PLUGINS[selectedTheme] || [] : [];
|
|
1053
708
|
console.log("");
|
|
1054
|
-
console.log(
|
|
1055
|
-
console.log(
|
|
709
|
+
console.log(chalk3.cyan(" Plugin Selection"));
|
|
710
|
+
console.log(chalk3.gray(" " + "-".repeat(40)));
|
|
1056
711
|
if (requiredPlugins.length > 0) {
|
|
1057
712
|
console.log("");
|
|
1058
|
-
console.log(
|
|
713
|
+
console.log(chalk3.gray(` Note: ${requiredPlugins.join(", ")} will be installed (required by theme)`));
|
|
1059
714
|
}
|
|
1060
715
|
console.log("");
|
|
1061
716
|
const plugins = await checkbox8({
|
|
@@ -1098,17 +753,19 @@ import { confirm as confirm4, input as input3 } from "@inquirer/prompts";
|
|
|
1098
753
|
|
|
1099
754
|
// src/wizard/prompts/index.ts
|
|
1100
755
|
async function runAllPrompts() {
|
|
756
|
+
const projectTypeConfig = await promptProjectType();
|
|
1101
757
|
const projectInfo = await promptProjectInfo();
|
|
1102
758
|
const teamConfig = await promptTeamConfig();
|
|
1103
759
|
const i18nConfig = await promptI18nConfig();
|
|
1104
760
|
const billingConfig = await promptBillingConfig();
|
|
1105
761
|
const featuresConfig = await promptFeaturesConfig();
|
|
1106
|
-
const contentFeaturesConfig = await promptContentFeaturesConfig("interactive",
|
|
1107
|
-
const authConfig = await promptAuthConfig("interactive",
|
|
1108
|
-
const dashboardConfig = await promptDashboardConfig("interactive",
|
|
1109
|
-
const devConfig = await promptDevConfig("interactive",
|
|
762
|
+
const contentFeaturesConfig = await promptContentFeaturesConfig("interactive", 10);
|
|
763
|
+
const authConfig = await promptAuthConfig("interactive", 10);
|
|
764
|
+
const dashboardConfig = await promptDashboardConfig("interactive", 10);
|
|
765
|
+
const devConfig = await promptDevConfig("interactive", 10);
|
|
1110
766
|
return {
|
|
1111
767
|
...projectInfo,
|
|
768
|
+
...projectTypeConfig,
|
|
1112
769
|
...teamConfig,
|
|
1113
770
|
...i18nConfig,
|
|
1114
771
|
...billingConfig,
|
|
@@ -1120,17 +777,19 @@ async function runAllPrompts() {
|
|
|
1120
777
|
};
|
|
1121
778
|
}
|
|
1122
779
|
async function runQuickPrompts() {
|
|
780
|
+
const projectTypeConfig = await promptProjectType();
|
|
1123
781
|
const projectInfo = await promptProjectInfo();
|
|
1124
782
|
const teamConfig = await promptTeamConfig();
|
|
1125
783
|
const i18nConfig = await promptI18nConfig();
|
|
1126
784
|
const billingConfig = await promptBillingConfig();
|
|
1127
785
|
const featuresConfig = await promptFeaturesConfig();
|
|
1128
|
-
const contentFeaturesConfig = await promptContentFeaturesConfig("quick",
|
|
786
|
+
const contentFeaturesConfig = await promptContentFeaturesConfig("quick", 10);
|
|
1129
787
|
const authConfig = { auth: getDefaultAuthConfig() };
|
|
1130
788
|
const dashboardConfig = { dashboard: getDefaultDashboardConfig() };
|
|
1131
789
|
const devConfig = { dev: getDefaultDevConfig() };
|
|
1132
790
|
return {
|
|
1133
791
|
...projectInfo,
|
|
792
|
+
...projectTypeConfig,
|
|
1134
793
|
...teamConfig,
|
|
1135
794
|
...i18nConfig,
|
|
1136
795
|
...billingConfig,
|
|
@@ -1142,17 +801,19 @@ async function runQuickPrompts() {
|
|
|
1142
801
|
};
|
|
1143
802
|
}
|
|
1144
803
|
async function runExpertPrompts() {
|
|
804
|
+
const projectTypeConfig = await promptProjectType();
|
|
1145
805
|
const projectInfo = await promptProjectInfo();
|
|
1146
806
|
const teamConfig = await promptTeamConfig();
|
|
1147
807
|
const i18nConfig = await promptI18nConfig();
|
|
1148
808
|
const billingConfig = await promptBillingConfig();
|
|
1149
809
|
const featuresConfig = await promptFeaturesConfig();
|
|
1150
|
-
const contentFeaturesConfig = await promptContentFeaturesConfig("expert",
|
|
1151
|
-
const authConfig = await promptAuthConfig("expert",
|
|
1152
|
-
const dashboardConfig = await promptDashboardConfig("expert",
|
|
1153
|
-
const devConfig = await promptDevConfig("expert",
|
|
810
|
+
const contentFeaturesConfig = await promptContentFeaturesConfig("expert", 10);
|
|
811
|
+
const authConfig = await promptAuthConfig("expert", 10);
|
|
812
|
+
const dashboardConfig = await promptDashboardConfig("expert", 10);
|
|
813
|
+
const devConfig = await promptDevConfig("expert", 10);
|
|
1154
814
|
return {
|
|
1155
815
|
...projectInfo,
|
|
816
|
+
...projectTypeConfig,
|
|
1156
817
|
...teamConfig,
|
|
1157
818
|
...i18nConfig,
|
|
1158
819
|
...billingConfig,
|
|
@@ -1165,42 +826,42 @@ async function runExpertPrompts() {
|
|
|
1165
826
|
}
|
|
1166
827
|
|
|
1167
828
|
// src/wizard/generators/index.ts
|
|
1168
|
-
import
|
|
1169
|
-
import
|
|
1170
|
-
import { fileURLToPath as
|
|
829
|
+
import fs8 from "fs-extra";
|
|
830
|
+
import path7 from "path";
|
|
831
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
1171
832
|
|
|
1172
833
|
// src/wizard/generators/theme-renamer.ts
|
|
1173
834
|
import fs from "fs-extra";
|
|
1174
835
|
import path from "path";
|
|
1175
|
-
import { fileURLToPath as
|
|
1176
|
-
var
|
|
1177
|
-
var
|
|
836
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
837
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
838
|
+
var __dirname2 = path.dirname(__filename2);
|
|
1178
839
|
function getTemplatesDir() {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
840
|
+
const rootDir = process.cwd();
|
|
841
|
+
const possiblePaths = [
|
|
842
|
+
// From project root node_modules (most common for installed packages)
|
|
843
|
+
path.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
|
|
844
|
+
// From CLI dist folder for development
|
|
845
|
+
path.resolve(__dirname2, "../../core/templates"),
|
|
846
|
+
// Legacy paths for different build structures
|
|
847
|
+
path.resolve(__dirname2, "../../../../../core/templates"),
|
|
848
|
+
path.resolve(__dirname2, "../../../../core/templates")
|
|
849
|
+
];
|
|
850
|
+
for (const p of possiblePaths) {
|
|
851
|
+
if (fs.existsSync(p)) {
|
|
852
|
+
return p;
|
|
1192
853
|
}
|
|
1193
|
-
throw new Error("Could not find @nextsparkjs/core templates directory");
|
|
1194
854
|
}
|
|
855
|
+
throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
|
|
1195
856
|
}
|
|
1196
857
|
function getTargetThemesDir() {
|
|
1197
858
|
return path.resolve(process.cwd(), "contents", "themes");
|
|
1198
859
|
}
|
|
1199
|
-
async function copyStarterTheme(
|
|
860
|
+
async function copyStarterTheme(config2) {
|
|
1200
861
|
const templatesDir = getTemplatesDir();
|
|
1201
862
|
const starterThemePath = path.join(templatesDir, "contents", "themes", "starter");
|
|
1202
863
|
const targetThemesDir = getTargetThemesDir();
|
|
1203
|
-
const newThemePath = path.join(targetThemesDir,
|
|
864
|
+
const newThemePath = path.join(targetThemesDir, config2.projectSlug);
|
|
1204
865
|
if (!await fs.pathExists(starterThemePath)) {
|
|
1205
866
|
throw new Error(`Starter theme not found at: ${starterThemePath}`);
|
|
1206
867
|
}
|
|
@@ -1210,97 +871,97 @@ async function copyStarterTheme(config) {
|
|
|
1210
871
|
await fs.ensureDir(targetThemesDir);
|
|
1211
872
|
await fs.copy(starterThemePath, newThemePath);
|
|
1212
873
|
}
|
|
1213
|
-
async function updateThemeConfig(
|
|
1214
|
-
const themeConfigPath = path.join(getTargetThemesDir(),
|
|
874
|
+
async function updateThemeConfig(config2) {
|
|
875
|
+
const themeConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "theme.config.ts");
|
|
1215
876
|
if (!await fs.pathExists(themeConfigPath)) {
|
|
1216
877
|
throw new Error(`theme.config.ts not found at: ${themeConfigPath}`);
|
|
1217
878
|
}
|
|
1218
879
|
let content = await fs.readFile(themeConfigPath, "utf-8");
|
|
1219
880
|
content = content.replace(
|
|
1220
881
|
/name:\s*['"]starter['"]/g,
|
|
1221
|
-
`name: '${
|
|
882
|
+
`name: '${config2.projectSlug}'`
|
|
1222
883
|
);
|
|
1223
884
|
content = content.replace(
|
|
1224
885
|
/displayName:\s*['"]Starter['"]/g,
|
|
1225
|
-
`displayName: '${
|
|
886
|
+
`displayName: '${config2.projectName}'`
|
|
1226
887
|
);
|
|
1227
888
|
content = content.replace(
|
|
1228
889
|
/description:\s*['"]Minimal starter theme for NextSpark['"]/g,
|
|
1229
|
-
`description: '${
|
|
890
|
+
`description: '${config2.projectDescription}'`
|
|
1230
891
|
);
|
|
1231
892
|
content = content.replace(
|
|
1232
893
|
/export const starterThemeConfig/g,
|
|
1233
|
-
`export const ${toCamelCase(
|
|
894
|
+
`export const ${toCamelCase(config2.projectSlug)}ThemeConfig`
|
|
1234
895
|
);
|
|
1235
896
|
content = content.replace(
|
|
1236
897
|
/export default starterThemeConfig/g,
|
|
1237
|
-
`export default ${toCamelCase(
|
|
898
|
+
`export default ${toCamelCase(config2.projectSlug)}ThemeConfig`
|
|
1238
899
|
);
|
|
1239
900
|
await fs.writeFile(themeConfigPath, content, "utf-8");
|
|
1240
901
|
}
|
|
1241
|
-
async function updateDevConfig(
|
|
1242
|
-
const devConfigPath = path.join(getTargetThemesDir(),
|
|
902
|
+
async function updateDevConfig(config2) {
|
|
903
|
+
const devConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "dev.config.ts");
|
|
1243
904
|
if (!await fs.pathExists(devConfigPath)) {
|
|
1244
905
|
return;
|
|
1245
906
|
}
|
|
1246
907
|
let content = await fs.readFile(devConfigPath, "utf-8");
|
|
1247
|
-
content = content.replace(/STARTER THEME/g, `${
|
|
1248
|
-
content = content.replace(/Starter Theme/g,
|
|
908
|
+
content = content.replace(/STARTER THEME/g, `${config2.projectName.toUpperCase()}`);
|
|
909
|
+
content = content.replace(/Starter Theme/g, config2.projectName);
|
|
1249
910
|
await fs.writeFile(devConfigPath, content, "utf-8");
|
|
1250
911
|
}
|
|
1251
|
-
async function updateAppConfig(
|
|
1252
|
-
const appConfigPath = path.join(getTargetThemesDir(),
|
|
912
|
+
async function updateAppConfig(config2) {
|
|
913
|
+
const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
|
|
1253
914
|
if (!await fs.pathExists(appConfigPath)) {
|
|
1254
915
|
throw new Error(`app.config.ts not found at: ${appConfigPath}`);
|
|
1255
916
|
}
|
|
1256
917
|
let content = await fs.readFile(appConfigPath, "utf-8");
|
|
1257
918
|
content = content.replace(
|
|
1258
919
|
/name:\s*['"]Starter['"]/g,
|
|
1259
|
-
`name: '${
|
|
920
|
+
`name: '${config2.projectName}'`
|
|
1260
921
|
);
|
|
1261
922
|
content = content.replace(
|
|
1262
923
|
/mode:\s*['"]multi-tenant['"]\s*as\s*const/g,
|
|
1263
|
-
`mode: '${
|
|
924
|
+
`mode: '${config2.teamMode}' as const`
|
|
1264
925
|
);
|
|
1265
|
-
const localesArray =
|
|
926
|
+
const localesArray = config2.supportedLocales.map((l) => `'${l}'`).join(", ");
|
|
1266
927
|
content = content.replace(
|
|
1267
928
|
/supportedLocales:\s*\[.*?\]/g,
|
|
1268
929
|
`supportedLocales: [${localesArray}]`
|
|
1269
930
|
);
|
|
1270
931
|
content = content.replace(
|
|
1271
932
|
/defaultLocale:\s*['"]en['"]\s*as\s*const/g,
|
|
1272
|
-
`defaultLocale: '${
|
|
933
|
+
`defaultLocale: '${config2.defaultLocale}' as const`
|
|
1273
934
|
);
|
|
1274
935
|
content = content.replace(
|
|
1275
936
|
/label:\s*['"]Starter['"]/g,
|
|
1276
|
-
`label: '${
|
|
937
|
+
`label: '${config2.projectName}'`
|
|
1277
938
|
);
|
|
1278
939
|
await fs.writeFile(appConfigPath, content, "utf-8");
|
|
1279
940
|
}
|
|
1280
|
-
async function updateRolesConfig(
|
|
1281
|
-
const appConfigPath = path.join(getTargetThemesDir(),
|
|
941
|
+
async function updateRolesConfig(config2) {
|
|
942
|
+
const appConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "app.config.ts");
|
|
1282
943
|
if (!await fs.pathExists(appConfigPath)) {
|
|
1283
944
|
return;
|
|
1284
945
|
}
|
|
1285
946
|
let content = await fs.readFile(appConfigPath, "utf-8");
|
|
1286
|
-
const rolesArray =
|
|
947
|
+
const rolesArray = config2.teamRoles.map((r) => `'${r}'`).join(", ");
|
|
1287
948
|
content = content.replace(
|
|
1288
949
|
/availableTeamRoles:\s*\[.*?\]/g,
|
|
1289
950
|
`availableTeamRoles: [${rolesArray}]`
|
|
1290
951
|
);
|
|
1291
952
|
await fs.writeFile(appConfigPath, content, "utf-8");
|
|
1292
953
|
}
|
|
1293
|
-
async function updateBillingConfig(
|
|
1294
|
-
const billingConfigPath = path.join(getTargetThemesDir(),
|
|
954
|
+
async function updateBillingConfig(config2) {
|
|
955
|
+
const billingConfigPath = path.join(getTargetThemesDir(), config2.projectSlug, "config", "billing.config.ts");
|
|
1295
956
|
if (!await fs.pathExists(billingConfigPath)) {
|
|
1296
957
|
return;
|
|
1297
958
|
}
|
|
1298
959
|
let content = await fs.readFile(billingConfigPath, "utf-8");
|
|
1299
960
|
content = content.replace(
|
|
1300
961
|
/currency:\s*['"]usd['"]/g,
|
|
1301
|
-
`currency: '${
|
|
962
|
+
`currency: '${config2.currency}'`
|
|
1302
963
|
);
|
|
1303
|
-
const plansContent = generateBillingPlans(
|
|
964
|
+
const plansContent = generateBillingPlans(config2.billingModel, config2.currency);
|
|
1304
965
|
content = content.replace(
|
|
1305
966
|
/plans:\s*\[[\s\S]*?\],\s*\n\s*\/\/ ===+\s*\n\s*\/\/ ACTION MAPPINGS/,
|
|
1306
967
|
`plans: ${plansContent},
|
|
@@ -1450,8 +1111,8 @@ function generateBillingPlans(billingModel, currency) {
|
|
|
1450
1111
|
},
|
|
1451
1112
|
]`;
|
|
1452
1113
|
}
|
|
1453
|
-
async function updateMigrations(
|
|
1454
|
-
const migrationsDir = path.join(getTargetThemesDir(),
|
|
1114
|
+
async function updateMigrations(config2) {
|
|
1115
|
+
const migrationsDir = path.join(getTargetThemesDir(), config2.projectSlug, "migrations");
|
|
1455
1116
|
if (!await fs.pathExists(migrationsDir)) {
|
|
1456
1117
|
return;
|
|
1457
1118
|
}
|
|
@@ -1460,12 +1121,39 @@ async function updateMigrations(config) {
|
|
|
1460
1121
|
for (const file of sqlFiles) {
|
|
1461
1122
|
const filePath = path.join(migrationsDir, file);
|
|
1462
1123
|
let content = await fs.readFile(filePath, "utf-8");
|
|
1463
|
-
content = content.replace(/@starter\.dev/g, `@${
|
|
1464
|
-
content = content.replace(/Starter Theme/g,
|
|
1465
|
-
content = content.replace(/starter theme/g,
|
|
1124
|
+
content = content.replace(/@starter\.dev/g, `@${config2.projectSlug}.dev`);
|
|
1125
|
+
content = content.replace(/Starter Theme/g, config2.projectName);
|
|
1126
|
+
content = content.replace(/starter theme/g, config2.projectSlug);
|
|
1466
1127
|
await fs.writeFile(filePath, content, "utf-8");
|
|
1467
1128
|
}
|
|
1468
1129
|
}
|
|
1130
|
+
async function updateTestFiles(config2) {
|
|
1131
|
+
const testsDir = path.join(getTargetThemesDir(), config2.projectSlug, "tests");
|
|
1132
|
+
if (!await fs.pathExists(testsDir)) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const processDir = async (dir) => {
|
|
1136
|
+
const items = await fs.readdir(dir);
|
|
1137
|
+
for (const item of items) {
|
|
1138
|
+
const itemPath = path.join(dir, item);
|
|
1139
|
+
const stat = await fs.stat(itemPath);
|
|
1140
|
+
if (stat.isDirectory()) {
|
|
1141
|
+
await processDir(itemPath);
|
|
1142
|
+
} else if (item.endsWith(".ts") || item.endsWith(".tsx")) {
|
|
1143
|
+
let content = await fs.readFile(itemPath, "utf-8");
|
|
1144
|
+
const hasChanges = content.includes("@/contents/themes/starter/");
|
|
1145
|
+
if (hasChanges) {
|
|
1146
|
+
content = content.replace(
|
|
1147
|
+
/@\/contents\/themes\/starter\//g,
|
|
1148
|
+
`@/contents/themes/${config2.projectSlug}/`
|
|
1149
|
+
);
|
|
1150
|
+
await fs.writeFile(itemPath, content, "utf-8");
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
await processDir(testsDir);
|
|
1156
|
+
}
|
|
1469
1157
|
function toCamelCase(str) {
|
|
1470
1158
|
return str.split("-").map((word, index) => {
|
|
1471
1159
|
if (index === 0) {
|
|
@@ -1481,10 +1169,10 @@ import path2 from "path";
|
|
|
1481
1169
|
function getTargetThemesDir2() {
|
|
1482
1170
|
return path2.resolve(process.cwd(), "contents", "themes");
|
|
1483
1171
|
}
|
|
1484
|
-
async function updateAuthConfig(
|
|
1172
|
+
async function updateAuthConfig(config2) {
|
|
1485
1173
|
const authConfigPath = path2.join(
|
|
1486
1174
|
getTargetThemesDir2(),
|
|
1487
|
-
|
|
1175
|
+
config2.projectSlug,
|
|
1488
1176
|
"config",
|
|
1489
1177
|
"auth.config.ts"
|
|
1490
1178
|
);
|
|
@@ -1494,22 +1182,22 @@ async function updateAuthConfig(config) {
|
|
|
1494
1182
|
let content = await fs2.readFile(authConfigPath, "utf-8");
|
|
1495
1183
|
content = content.replace(
|
|
1496
1184
|
/(emailPassword:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1497
|
-
`$1${
|
|
1185
|
+
`$1${config2.auth.emailPassword}`
|
|
1498
1186
|
);
|
|
1499
1187
|
content = content.replace(
|
|
1500
1188
|
/(google:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1501
|
-
`$1${
|
|
1189
|
+
`$1${config2.auth.googleOAuth}`
|
|
1502
1190
|
);
|
|
1503
1191
|
content = content.replace(
|
|
1504
1192
|
/(emailVerification:\s*)(?:true|false)/g,
|
|
1505
|
-
`$1${
|
|
1193
|
+
`$1${config2.auth.emailVerification}`
|
|
1506
1194
|
);
|
|
1507
1195
|
await fs2.writeFile(authConfigPath, content, "utf-8");
|
|
1508
1196
|
}
|
|
1509
|
-
async function updateDashboardUIConfig(
|
|
1197
|
+
async function updateDashboardUIConfig(config2) {
|
|
1510
1198
|
const dashboardConfigPath = path2.join(
|
|
1511
1199
|
getTargetThemesDir2(),
|
|
1512
|
-
|
|
1200
|
+
config2.projectSlug,
|
|
1513
1201
|
"config",
|
|
1514
1202
|
"dashboard.config.ts"
|
|
1515
1203
|
);
|
|
@@ -1519,42 +1207,42 @@ async function updateDashboardUIConfig(config) {
|
|
|
1519
1207
|
let content = await fs2.readFile(dashboardConfigPath, "utf-8");
|
|
1520
1208
|
content = content.replace(
|
|
1521
1209
|
/(search:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1522
|
-
`$1${
|
|
1210
|
+
`$1${config2.dashboard.search}`
|
|
1523
1211
|
);
|
|
1524
1212
|
content = content.replace(
|
|
1525
1213
|
/(notifications:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1526
|
-
`$1${
|
|
1214
|
+
`$1${config2.dashboard.notifications}`
|
|
1527
1215
|
);
|
|
1528
1216
|
content = content.replace(
|
|
1529
1217
|
/(themeToggle:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1530
|
-
`$1${
|
|
1218
|
+
`$1${config2.dashboard.themeToggle}`
|
|
1531
1219
|
);
|
|
1532
1220
|
content = content.replace(
|
|
1533
1221
|
/(support:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1534
|
-
`$1${
|
|
1222
|
+
`$1${config2.dashboard.support}`
|
|
1535
1223
|
);
|
|
1536
1224
|
content = content.replace(
|
|
1537
1225
|
/(quickCreate:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1538
|
-
`$1${
|
|
1226
|
+
`$1${config2.dashboard.quickCreate}`
|
|
1539
1227
|
);
|
|
1540
1228
|
content = content.replace(
|
|
1541
1229
|
/(adminAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1542
|
-
`$1${
|
|
1230
|
+
`$1${config2.dashboard.superadminAccess}`
|
|
1543
1231
|
);
|
|
1544
1232
|
content = content.replace(
|
|
1545
1233
|
/(devtoolsAccess:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1546
|
-
`$1${
|
|
1234
|
+
`$1${config2.dashboard.devtoolsAccess}`
|
|
1547
1235
|
);
|
|
1548
1236
|
content = content.replace(
|
|
1549
1237
|
/(defaultCollapsed:\s*)(?:true|false)/g,
|
|
1550
|
-
`$1${
|
|
1238
|
+
`$1${config2.dashboard.sidebarCollapsed}`
|
|
1551
1239
|
);
|
|
1552
1240
|
await fs2.writeFile(dashboardConfigPath, content, "utf-8");
|
|
1553
1241
|
}
|
|
1554
|
-
async function updateDevToolsConfig(
|
|
1242
|
+
async function updateDevToolsConfig(config2) {
|
|
1555
1243
|
const devConfigPath = path2.join(
|
|
1556
1244
|
getTargetThemesDir2(),
|
|
1557
|
-
|
|
1245
|
+
config2.projectSlug,
|
|
1558
1246
|
"config",
|
|
1559
1247
|
"dev.config.ts"
|
|
1560
1248
|
);
|
|
@@ -1564,18 +1252,18 @@ async function updateDevToolsConfig(config) {
|
|
|
1564
1252
|
let content = await fs2.readFile(devConfigPath, "utf-8");
|
|
1565
1253
|
content = content.replace(
|
|
1566
1254
|
/(devKeyring:\s*{[^}]*enabled:\s*)(?:true|false)/gs,
|
|
1567
|
-
`$1${
|
|
1255
|
+
`$1${config2.dev.devKeyring}`
|
|
1568
1256
|
);
|
|
1569
1257
|
content = content.replace(
|
|
1570
1258
|
/(debugMode:\s*)(?:true|false)/g,
|
|
1571
|
-
`$1${
|
|
1259
|
+
`$1${config2.dev.debugMode}`
|
|
1572
1260
|
);
|
|
1573
1261
|
await fs2.writeFile(devConfigPath, content, "utf-8");
|
|
1574
1262
|
}
|
|
1575
|
-
async function updatePermissionsConfig(
|
|
1263
|
+
async function updatePermissionsConfig(config2) {
|
|
1576
1264
|
const permissionsConfigPath = path2.join(
|
|
1577
1265
|
getTargetThemesDir2(),
|
|
1578
|
-
|
|
1266
|
+
config2.projectSlug,
|
|
1579
1267
|
"config",
|
|
1580
1268
|
"permissions.config.ts"
|
|
1581
1269
|
);
|
|
@@ -1583,7 +1271,7 @@ async function updatePermissionsConfig(config) {
|
|
|
1583
1271
|
return;
|
|
1584
1272
|
}
|
|
1585
1273
|
let content = await fs2.readFile(permissionsConfigPath, "utf-8");
|
|
1586
|
-
const availableRoles =
|
|
1274
|
+
const availableRoles = config2.teamRoles;
|
|
1587
1275
|
const roleArrayPattern = /roles:\s*\[(.*?)\]/g;
|
|
1588
1276
|
content = content.replace(roleArrayPattern, (match, rolesStr) => {
|
|
1589
1277
|
const currentRoles = rolesStr.split(",").map((r) => r.trim().replace(/['"]/g, "")).filter((r) => r.length > 0);
|
|
@@ -1595,10 +1283,48 @@ async function updatePermissionsConfig(config) {
|
|
|
1595
1283
|
});
|
|
1596
1284
|
await fs2.writeFile(permissionsConfigPath, content, "utf-8");
|
|
1597
1285
|
}
|
|
1598
|
-
async function
|
|
1286
|
+
async function updateEntityPermissions(config2) {
|
|
1287
|
+
const permissionsConfigPath = path2.join(
|
|
1288
|
+
getTargetThemesDir2(),
|
|
1289
|
+
config2.projectSlug,
|
|
1290
|
+
"config",
|
|
1291
|
+
"permissions.config.ts"
|
|
1292
|
+
);
|
|
1293
|
+
if (!await fs2.pathExists(permissionsConfigPath)) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
let content = await fs2.readFile(permissionsConfigPath, "utf-8");
|
|
1297
|
+
if (config2.contentFeatures.pages) {
|
|
1298
|
+
content = uncommentPermissionBlock(content, "PAGES");
|
|
1299
|
+
}
|
|
1300
|
+
if (config2.contentFeatures.blog) {
|
|
1301
|
+
content = uncommentPermissionBlock(content, "POSTS");
|
|
1302
|
+
}
|
|
1303
|
+
await fs2.writeFile(permissionsConfigPath, content, "utf-8");
|
|
1304
|
+
}
|
|
1305
|
+
function uncommentPermissionBlock(content, markerName) {
|
|
1306
|
+
const startMarker = `// __${markerName}_PERMISSIONS_START__`;
|
|
1307
|
+
const endMarker = `// __${markerName}_PERMISSIONS_END__`;
|
|
1308
|
+
const startIndex = content.indexOf(startMarker);
|
|
1309
|
+
const endIndex = content.indexOf(endMarker);
|
|
1310
|
+
if (startIndex === -1 || endIndex === -1) {
|
|
1311
|
+
return content;
|
|
1312
|
+
}
|
|
1313
|
+
const beforeBlock = content.slice(0, startIndex);
|
|
1314
|
+
const block = content.slice(startIndex + startMarker.length, endIndex);
|
|
1315
|
+
const afterBlock = content.slice(endIndex + endMarker.length);
|
|
1316
|
+
const uncommentedBlock = block.split("\n").map((line) => {
|
|
1317
|
+
if (line.match(/^\s*\/\/\s+/)) {
|
|
1318
|
+
return line.replace(/^(\s*)\/\/\s*/, "$1");
|
|
1319
|
+
}
|
|
1320
|
+
return line;
|
|
1321
|
+
}).join("\n");
|
|
1322
|
+
return beforeBlock + uncommentedBlock + afterBlock;
|
|
1323
|
+
}
|
|
1324
|
+
async function updateDashboardConfig(config2) {
|
|
1599
1325
|
const dashboardConfigPath = path2.join(
|
|
1600
1326
|
getTargetThemesDir2(),
|
|
1601
|
-
|
|
1327
|
+
config2.projectSlug,
|
|
1602
1328
|
"config",
|
|
1603
1329
|
"dashboard.config.ts"
|
|
1604
1330
|
);
|
|
@@ -1606,19 +1332,19 @@ async function updateDashboardConfig(config) {
|
|
|
1606
1332
|
return;
|
|
1607
1333
|
}
|
|
1608
1334
|
let content = await fs2.readFile(dashboardConfigPath, "utf-8");
|
|
1609
|
-
if (!
|
|
1335
|
+
if (!config2.features.analytics) {
|
|
1610
1336
|
content = content.replace(
|
|
1611
1337
|
/(id:\s*['"]analytics['"].*?enabled:\s*)true/gs,
|
|
1612
1338
|
"$1false"
|
|
1613
1339
|
);
|
|
1614
1340
|
}
|
|
1615
|
-
if (!
|
|
1341
|
+
if (!config2.features.billing) {
|
|
1616
1342
|
content = content.replace(
|
|
1617
1343
|
/(id:\s*['"]billing['"].*?enabled:\s*)true/gs,
|
|
1618
1344
|
"$1false"
|
|
1619
1345
|
);
|
|
1620
1346
|
}
|
|
1621
|
-
if (
|
|
1347
|
+
if (config2.teamMode === "single-user") {
|
|
1622
1348
|
content = content.replace(
|
|
1623
1349
|
/(id:\s*['"]team['"].*?enabled:\s*)true/gs,
|
|
1624
1350
|
"$1false"
|
|
@@ -1630,10 +1356,10 @@ async function updateDashboardConfig(config) {
|
|
|
1630
1356
|
}
|
|
1631
1357
|
await fs2.writeFile(dashboardConfigPath, content, "utf-8");
|
|
1632
1358
|
}
|
|
1633
|
-
async function generateEnvExample(
|
|
1359
|
+
async function generateEnvExample(config2) {
|
|
1634
1360
|
const envExamplePath = path2.resolve(process.cwd(), ".env.example");
|
|
1635
1361
|
let oauthSection = "";
|
|
1636
|
-
if (
|
|
1362
|
+
if (config2.auth.googleOAuth) {
|
|
1637
1363
|
oauthSection = `# =============================================================================
|
|
1638
1364
|
# OAUTH PROVIDERS
|
|
1639
1365
|
# =============================================================================
|
|
@@ -1649,7 +1375,7 @@ GOOGLE_CLIENT_SECRET="your-google-client-secret"
|
|
|
1649
1375
|
`;
|
|
1650
1376
|
}
|
|
1651
1377
|
const envContent = `# NextSpark Environment Configuration
|
|
1652
|
-
# Generated for: ${
|
|
1378
|
+
# Generated for: ${config2.projectName}
|
|
1653
1379
|
|
|
1654
1380
|
# =============================================================================
|
|
1655
1381
|
# DATABASE
|
|
@@ -1665,7 +1391,7 @@ BETTER_AUTH_SECRET="your-secret-key-here"
|
|
|
1665
1391
|
# =============================================================================
|
|
1666
1392
|
# THEME
|
|
1667
1393
|
# =============================================================================
|
|
1668
|
-
NEXT_PUBLIC_ACTIVE_THEME="${
|
|
1394
|
+
NEXT_PUBLIC_ACTIVE_THEME="${config2.projectSlug}"
|
|
1669
1395
|
|
|
1670
1396
|
# =============================================================================
|
|
1671
1397
|
# APPLICATION
|
|
@@ -1673,7 +1399,7 @@ NEXT_PUBLIC_ACTIVE_THEME="${config.projectSlug}"
|
|
|
1673
1399
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
1674
1400
|
NODE_ENV="development"
|
|
1675
1401
|
|
|
1676
|
-
${
|
|
1402
|
+
${config2.features.billing ? `# =============================================================================
|
|
1677
1403
|
# STRIPE (Billing)
|
|
1678
1404
|
# =============================================================================
|
|
1679
1405
|
STRIPE_SECRET_KEY="sk_test_..."
|
|
@@ -1691,16 +1417,16 @@ ${oauthSection}`;
|
|
|
1691
1417
|
await fs2.writeFile(envExamplePath, envContent, "utf-8");
|
|
1692
1418
|
}
|
|
1693
1419
|
}
|
|
1694
|
-
async function updateReadme(
|
|
1695
|
-
const readmePath = path2.join(getTargetThemesDir2(),
|
|
1420
|
+
async function updateReadme(config2) {
|
|
1421
|
+
const readmePath = path2.join(getTargetThemesDir2(), config2.projectSlug, "README.md");
|
|
1696
1422
|
if (!await fs2.pathExists(readmePath)) {
|
|
1697
1423
|
return;
|
|
1698
1424
|
}
|
|
1699
1425
|
let content = await fs2.readFile(readmePath, "utf-8");
|
|
1700
|
-
content = content.replace(/# Starter Theme/g, `# ${
|
|
1426
|
+
content = content.replace(/# Starter Theme/g, `# ${config2.projectName}`);
|
|
1701
1427
|
content = content.replace(
|
|
1702
1428
|
/Minimal starter theme for NextSpark/g,
|
|
1703
|
-
|
|
1429
|
+
config2.projectDescription
|
|
1704
1430
|
);
|
|
1705
1431
|
await fs2.writeFile(readmePath, content, "utf-8");
|
|
1706
1432
|
}
|
|
@@ -1712,6 +1438,18 @@ async function copyEnvExampleToEnv() {
|
|
|
1712
1438
|
await fs2.copy(envExamplePath, envPath);
|
|
1713
1439
|
}
|
|
1714
1440
|
}
|
|
1441
|
+
async function updateGlobalsCss(config2) {
|
|
1442
|
+
const globalsCssPath = path2.resolve(process.cwd(), "app", "globals.css");
|
|
1443
|
+
if (!await fs2.pathExists(globalsCssPath)) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
let content = await fs2.readFile(globalsCssPath, "utf-8");
|
|
1447
|
+
content = content.replace(
|
|
1448
|
+
/@import\s+["']\.\.\/contents\/themes\/[^/]+\/styles\/globals\.css["'];?/,
|
|
1449
|
+
`@import "../contents/themes/${config2.projectSlug}/styles/globals.css";`
|
|
1450
|
+
);
|
|
1451
|
+
await fs2.writeFile(globalsCssPath, content, "utf-8");
|
|
1452
|
+
}
|
|
1715
1453
|
|
|
1716
1454
|
// src/wizard/generators/messages-generator.ts
|
|
1717
1455
|
import fs3 from "fs-extra";
|
|
@@ -1719,8 +1457,8 @@ import path3 from "path";
|
|
|
1719
1457
|
function getTargetThemesDir3() {
|
|
1720
1458
|
return path3.resolve(process.cwd(), "contents", "themes");
|
|
1721
1459
|
}
|
|
1722
|
-
async function removeUnusedLanguages(
|
|
1723
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
1460
|
+
async function removeUnusedLanguages(config2) {
|
|
1461
|
+
const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
|
|
1724
1462
|
if (!await fs3.pathExists(messagesDir)) {
|
|
1725
1463
|
return;
|
|
1726
1464
|
}
|
|
@@ -1730,13 +1468,13 @@ async function removeUnusedLanguages(config) {
|
|
|
1730
1468
|
return fs3.statSync(folderPath).isDirectory() && Object.keys(AVAILABLE_LOCALES).includes(f);
|
|
1731
1469
|
});
|
|
1732
1470
|
for (const folder of languageFolders) {
|
|
1733
|
-
if (!
|
|
1471
|
+
if (!config2.supportedLocales.includes(folder)) {
|
|
1734
1472
|
await fs3.remove(path3.join(messagesDir, folder));
|
|
1735
1473
|
}
|
|
1736
1474
|
}
|
|
1737
1475
|
}
|
|
1738
|
-
async function removeUnusedEntityMessages(
|
|
1739
|
-
const entitiesDir = path3.join(getTargetThemesDir3(),
|
|
1476
|
+
async function removeUnusedEntityMessages(config2) {
|
|
1477
|
+
const entitiesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "entities");
|
|
1740
1478
|
if (!await fs3.pathExists(entitiesDir)) {
|
|
1741
1479
|
return;
|
|
1742
1480
|
}
|
|
@@ -1749,45 +1487,45 @@ async function removeUnusedEntityMessages(config) {
|
|
|
1749
1487
|
const files = await fs3.readdir(messagesDir);
|
|
1750
1488
|
for (const file of files) {
|
|
1751
1489
|
const locale = path3.basename(file, ".json");
|
|
1752
|
-
if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !
|
|
1490
|
+
if (Object.keys(AVAILABLE_LOCALES).includes(locale) && !config2.supportedLocales.includes(locale)) {
|
|
1753
1491
|
await fs3.remove(path3.join(messagesDir, file));
|
|
1754
1492
|
}
|
|
1755
1493
|
}
|
|
1756
1494
|
}
|
|
1757
1495
|
}
|
|
1758
|
-
async function ensureLanguageFolders(
|
|
1759
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
1496
|
+
async function ensureLanguageFolders(config2) {
|
|
1497
|
+
const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
|
|
1760
1498
|
if (!await fs3.pathExists(messagesDir)) {
|
|
1761
1499
|
return;
|
|
1762
1500
|
}
|
|
1763
|
-
const defaultLocaleDir = path3.join(messagesDir,
|
|
1501
|
+
const defaultLocaleDir = path3.join(messagesDir, config2.defaultLocale);
|
|
1764
1502
|
if (!await fs3.pathExists(defaultLocaleDir)) {
|
|
1765
1503
|
const enDir = path3.join(messagesDir, "en");
|
|
1766
1504
|
if (await fs3.pathExists(enDir)) {
|
|
1767
1505
|
await fs3.copy(enDir, defaultLocaleDir);
|
|
1768
1506
|
}
|
|
1769
1507
|
}
|
|
1770
|
-
for (const locale of
|
|
1508
|
+
for (const locale of config2.supportedLocales) {
|
|
1771
1509
|
const localeDir = path3.join(messagesDir, locale);
|
|
1772
1510
|
if (!await fs3.pathExists(localeDir) && await fs3.pathExists(defaultLocaleDir)) {
|
|
1773
1511
|
await fs3.copy(defaultLocaleDir, localeDir);
|
|
1774
1512
|
}
|
|
1775
1513
|
}
|
|
1776
1514
|
}
|
|
1777
|
-
async function updateMessageFiles(
|
|
1778
|
-
const messagesDir = path3.join(getTargetThemesDir3(),
|
|
1515
|
+
async function updateMessageFiles(config2) {
|
|
1516
|
+
const messagesDir = path3.join(getTargetThemesDir3(), config2.projectSlug, "messages");
|
|
1779
1517
|
if (!await fs3.pathExists(messagesDir)) {
|
|
1780
1518
|
return;
|
|
1781
1519
|
}
|
|
1782
|
-
for (const locale of
|
|
1520
|
+
for (const locale of config2.supportedLocales) {
|
|
1783
1521
|
const commonPath = path3.join(messagesDir, locale, "common.json");
|
|
1784
1522
|
if (await fs3.pathExists(commonPath)) {
|
|
1785
1523
|
try {
|
|
1786
1524
|
const content = await fs3.readJson(commonPath);
|
|
1787
1525
|
if (content.app) {
|
|
1788
|
-
content.app.name =
|
|
1526
|
+
content.app.name = config2.projectName;
|
|
1789
1527
|
if (content.app.description) {
|
|
1790
|
-
content.app.description =
|
|
1528
|
+
content.app.description = config2.projectDescription;
|
|
1791
1529
|
}
|
|
1792
1530
|
}
|
|
1793
1531
|
await fs3.writeJson(commonPath, content, { spaces: 2 });
|
|
@@ -1796,36 +1534,36 @@ async function updateMessageFiles(config) {
|
|
|
1796
1534
|
}
|
|
1797
1535
|
}
|
|
1798
1536
|
}
|
|
1799
|
-
async function processI18n(
|
|
1800
|
-
await removeUnusedLanguages(
|
|
1801
|
-
await removeUnusedEntityMessages(
|
|
1802
|
-
await ensureLanguageFolders(
|
|
1803
|
-
await updateMessageFiles(
|
|
1537
|
+
async function processI18n(config2) {
|
|
1538
|
+
await removeUnusedLanguages(config2);
|
|
1539
|
+
await removeUnusedEntityMessages(config2);
|
|
1540
|
+
await ensureLanguageFolders(config2);
|
|
1541
|
+
await updateMessageFiles(config2);
|
|
1804
1542
|
}
|
|
1805
1543
|
|
|
1806
1544
|
// src/wizard/generators/content-features-generator.ts
|
|
1807
1545
|
import fs4 from "fs-extra";
|
|
1808
1546
|
import path4 from "path";
|
|
1809
|
-
import { fileURLToPath as
|
|
1810
|
-
var
|
|
1811
|
-
var
|
|
1547
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1548
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
1549
|
+
var __dirname3 = path4.dirname(__filename3);
|
|
1812
1550
|
function getTemplatesDir2() {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1551
|
+
const rootDir = process.cwd();
|
|
1552
|
+
const possiblePaths = [
|
|
1553
|
+
// From project root node_modules (most common for installed packages)
|
|
1554
|
+
path4.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
|
|
1555
|
+
// From CLI dist folder for development
|
|
1556
|
+
path4.resolve(__dirname3, "../../core/templates"),
|
|
1557
|
+
// Legacy paths for different build structures
|
|
1558
|
+
path4.resolve(__dirname3, "../../../../../core/templates"),
|
|
1559
|
+
path4.resolve(__dirname3, "../../../../core/templates")
|
|
1560
|
+
];
|
|
1561
|
+
for (const p of possiblePaths) {
|
|
1562
|
+
if (fs4.existsSync(p)) {
|
|
1563
|
+
return p;
|
|
1826
1564
|
}
|
|
1827
|
-
throw new Error("Could not find @nextsparkjs/core templates directory");
|
|
1828
1565
|
}
|
|
1566
|
+
throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
|
|
1829
1567
|
}
|
|
1830
1568
|
function getFeaturesDir() {
|
|
1831
1569
|
return path4.join(getTemplatesDir2(), "features");
|
|
@@ -1833,9 +1571,9 @@ function getFeaturesDir() {
|
|
|
1833
1571
|
function getTargetThemeDir(projectSlug) {
|
|
1834
1572
|
return path4.resolve(process.cwd(), "contents", "themes", projectSlug);
|
|
1835
1573
|
}
|
|
1836
|
-
async function copyPagesFeature(
|
|
1574
|
+
async function copyPagesFeature(config2) {
|
|
1837
1575
|
const featuresDir = getFeaturesDir();
|
|
1838
|
-
const targetThemeDir = getTargetThemeDir(
|
|
1576
|
+
const targetThemeDir = getTargetThemeDir(config2.projectSlug);
|
|
1839
1577
|
const sourcePagesEntity = path4.join(featuresDir, "pages", "entities", "pages");
|
|
1840
1578
|
const targetEntitiesDir = path4.join(targetThemeDir, "entities", "pages");
|
|
1841
1579
|
if (await fs4.pathExists(sourcePagesEntity)) {
|
|
@@ -1851,9 +1589,9 @@ async function copyPagesFeature(config) {
|
|
|
1851
1589
|
console.warn(`Warning: Hero block not found at: ${sourceHeroBlock}`);
|
|
1852
1590
|
}
|
|
1853
1591
|
}
|
|
1854
|
-
async function copyBlogFeature(
|
|
1592
|
+
async function copyBlogFeature(config2) {
|
|
1855
1593
|
const featuresDir = getFeaturesDir();
|
|
1856
|
-
const targetThemeDir = getTargetThemeDir(
|
|
1594
|
+
const targetThemeDir = getTargetThemeDir(config2.projectSlug);
|
|
1857
1595
|
const sourcePostsEntity = path4.join(featuresDir, "blog", "entities", "posts");
|
|
1858
1596
|
const targetPostsEntity = path4.join(targetThemeDir, "entities", "posts");
|
|
1859
1597
|
if (await fs4.pathExists(sourcePostsEntity)) {
|
|
@@ -1869,35 +1607,35 @@ async function copyBlogFeature(config) {
|
|
|
1869
1607
|
console.warn(`Warning: Post-content block not found at: ${sourcePostContentBlock}`);
|
|
1870
1608
|
}
|
|
1871
1609
|
}
|
|
1872
|
-
async function copyContentFeatures(
|
|
1873
|
-
if (!
|
|
1610
|
+
async function copyContentFeatures(config2) {
|
|
1611
|
+
if (!config2.contentFeatures.pages && !config2.contentFeatures.blog) {
|
|
1874
1612
|
return;
|
|
1875
1613
|
}
|
|
1876
|
-
if (
|
|
1877
|
-
await copyPagesFeature(
|
|
1614
|
+
if (config2.contentFeatures.pages) {
|
|
1615
|
+
await copyPagesFeature(config2);
|
|
1878
1616
|
}
|
|
1879
|
-
if (
|
|
1880
|
-
await copyBlogFeature(
|
|
1617
|
+
if (config2.contentFeatures.blog) {
|
|
1618
|
+
await copyBlogFeature(config2);
|
|
1881
1619
|
}
|
|
1882
1620
|
}
|
|
1883
1621
|
|
|
1884
1622
|
// src/wizard/generators/theme-plugins-installer.ts
|
|
1885
|
-
import { existsSync as
|
|
1886
|
-
import { join as
|
|
1887
|
-
import
|
|
1888
|
-
import
|
|
1623
|
+
import { existsSync as existsSync2, cpSync, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1624
|
+
import { join as join3, resolve } from "path";
|
|
1625
|
+
import chalk5 from "chalk";
|
|
1626
|
+
import ora2 from "ora";
|
|
1889
1627
|
|
|
1890
1628
|
// src/commands/add-theme.ts
|
|
1891
|
-
import
|
|
1892
|
-
import
|
|
1893
|
-
import { existsSync
|
|
1894
|
-
import { join as
|
|
1629
|
+
import chalk4 from "chalk";
|
|
1630
|
+
import ora from "ora";
|
|
1631
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1632
|
+
import { join as join2 } from "path";
|
|
1895
1633
|
async function addTheme(packageSpec, options = {}) {
|
|
1896
|
-
const spinner =
|
|
1634
|
+
const spinner = ora(`Adding theme ${packageSpec}`).start();
|
|
1897
1635
|
let cleanup = null;
|
|
1898
1636
|
try {
|
|
1899
|
-
const contentsDir =
|
|
1900
|
-
if (!
|
|
1637
|
+
const contentsDir = join2(process.cwd(), "contents");
|
|
1638
|
+
if (!existsSync(contentsDir)) {
|
|
1901
1639
|
spinner.fail('contents/ directory not found. Run "nextspark init" first.');
|
|
1902
1640
|
return;
|
|
1903
1641
|
}
|
|
@@ -1911,15 +1649,15 @@ async function addTheme(packageSpec, options = {}) {
|
|
|
1911
1649
|
const validation = validateTheme(packageJson, extractedPath);
|
|
1912
1650
|
if (!validation.valid) {
|
|
1913
1651
|
spinner.fail("Invalid theme");
|
|
1914
|
-
validation.errors.forEach((e) => console.log(
|
|
1652
|
+
validation.errors.forEach((e) => console.log(chalk4.red(` \u2717 ${e}`)));
|
|
1915
1653
|
return;
|
|
1916
1654
|
}
|
|
1917
1655
|
if (validation.warnings.length > 0) {
|
|
1918
|
-
validation.warnings.forEach((w) => console.log(
|
|
1656
|
+
validation.warnings.forEach((w) => console.log(chalk4.yellow(` \u26A0 ${w}`)));
|
|
1919
1657
|
}
|
|
1920
1658
|
if (packageJson.requiredPlugins?.length && !options.skipPostinstall) {
|
|
1921
1659
|
spinner.stop();
|
|
1922
|
-
console.log(
|
|
1660
|
+
console.log(chalk4.blue("\n Installing required plugins..."));
|
|
1923
1661
|
const installingPlugins = /* @__PURE__ */ new Set();
|
|
1924
1662
|
for (const plugin of packageJson.requiredPlugins) {
|
|
1925
1663
|
if (!checkPluginExists(plugin)) {
|
|
@@ -1943,14 +1681,14 @@ async function addTheme(packageSpec, options = {}) {
|
|
|
1943
1681
|
};
|
|
1944
1682
|
await runPostinstall(packageJson, result.installedPath, context);
|
|
1945
1683
|
}
|
|
1946
|
-
console.log(
|
|
1684
|
+
console.log(chalk4.green(`
|
|
1947
1685
|
\u2713 Theme ${result.name} installed successfully!`));
|
|
1948
|
-
console.log(
|
|
1949
|
-
console.log(
|
|
1686
|
+
console.log(chalk4.gray(` Location: contents/themes/${result.name}/`));
|
|
1687
|
+
console.log(chalk4.gray(` Set NEXT_PUBLIC_ACTIVE_THEME=${result.name} to activate`));
|
|
1950
1688
|
} catch (error) {
|
|
1951
1689
|
spinner.fail("Failed to add theme");
|
|
1952
1690
|
if (error instanceof Error) {
|
|
1953
|
-
console.log(
|
|
1691
|
+
console.log(chalk4.red(` ${error.message}`));
|
|
1954
1692
|
}
|
|
1955
1693
|
throw error;
|
|
1956
1694
|
} finally {
|
|
@@ -1959,14 +1697,14 @@ async function addTheme(packageSpec, options = {}) {
|
|
|
1959
1697
|
}
|
|
1960
1698
|
function checkPluginExists(pluginName) {
|
|
1961
1699
|
const name = pluginName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
|
|
1962
|
-
return
|
|
1700
|
+
return existsSync(join2(process.cwd(), "contents", "plugins", name));
|
|
1963
1701
|
}
|
|
1964
1702
|
function getCoreVersion() {
|
|
1965
|
-
const pkgPath =
|
|
1966
|
-
if (
|
|
1703
|
+
const pkgPath = join2(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
|
|
1704
|
+
if (existsSync(pkgPath)) {
|
|
1967
1705
|
try {
|
|
1968
|
-
const
|
|
1969
|
-
return
|
|
1706
|
+
const pkg2 = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1707
|
+
return pkg2.version || "0.0.0";
|
|
1970
1708
|
} catch {
|
|
1971
1709
|
return "0.0.0";
|
|
1972
1710
|
}
|
|
@@ -2001,23 +1739,23 @@ var THEME_REQUIRED_PLUGINS2 = {
|
|
|
2001
1739
|
"crm": [],
|
|
2002
1740
|
"productivity": []
|
|
2003
1741
|
};
|
|
2004
|
-
function
|
|
1742
|
+
function isMonorepoMode() {
|
|
2005
1743
|
const possiblePaths = [
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
1744
|
+
join3(process.cwd(), "pnpm-workspace.yaml"),
|
|
1745
|
+
join3(process.cwd(), "..", "pnpm-workspace.yaml"),
|
|
1746
|
+
join3(process.cwd(), "..", "..", "pnpm-workspace.yaml")
|
|
2009
1747
|
];
|
|
2010
|
-
return possiblePaths.some((p) =>
|
|
1748
|
+
return possiblePaths.some((p) => existsSync2(p));
|
|
2011
1749
|
}
|
|
2012
1750
|
function getMonorepoRoot() {
|
|
2013
1751
|
const possibleRoots = [
|
|
2014
1752
|
process.cwd(),
|
|
2015
|
-
|
|
2016
|
-
|
|
1753
|
+
join3(process.cwd(), ".."),
|
|
1754
|
+
join3(process.cwd(), "..", "..")
|
|
2017
1755
|
];
|
|
2018
1756
|
for (const root of possibleRoots) {
|
|
2019
|
-
if (
|
|
2020
|
-
return
|
|
1757
|
+
if (existsSync2(join3(root, "pnpm-workspace.yaml"))) {
|
|
1758
|
+
return resolve(root);
|
|
2021
1759
|
}
|
|
2022
1760
|
}
|
|
2023
1761
|
return null;
|
|
@@ -2026,20 +1764,20 @@ function getLocalPackageDir(type, name) {
|
|
|
2026
1764
|
const monorepoRoot = getMonorepoRoot();
|
|
2027
1765
|
if (!monorepoRoot) return null;
|
|
2028
1766
|
const baseDir = type === "theme" ? "themes" : "plugins";
|
|
2029
|
-
const packageDir =
|
|
2030
|
-
if (
|
|
1767
|
+
const packageDir = join3(monorepoRoot, baseDir, name);
|
|
1768
|
+
if (existsSync2(packageDir) && existsSync2(join3(packageDir, "package.json"))) {
|
|
2031
1769
|
return packageDir;
|
|
2032
1770
|
}
|
|
2033
1771
|
return null;
|
|
2034
1772
|
}
|
|
2035
1773
|
async function copyLocalTheme(name, sourceDir) {
|
|
2036
|
-
const targetDir =
|
|
2037
|
-
const themesDir =
|
|
2038
|
-
if (!
|
|
1774
|
+
const targetDir = join3(process.cwd(), "contents", "themes", name);
|
|
1775
|
+
const themesDir = join3(process.cwd(), "contents", "themes");
|
|
1776
|
+
if (!existsSync2(themesDir)) {
|
|
2039
1777
|
mkdirSync(themesDir, { recursive: true });
|
|
2040
1778
|
}
|
|
2041
|
-
if (
|
|
2042
|
-
console.log(
|
|
1779
|
+
if (existsSync2(targetDir)) {
|
|
1780
|
+
console.log(chalk5.gray(` Theme ${name} already exists, skipping...`));
|
|
2043
1781
|
return true;
|
|
2044
1782
|
}
|
|
2045
1783
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
@@ -2047,13 +1785,13 @@ async function copyLocalTheme(name, sourceDir) {
|
|
|
2047
1785
|
return true;
|
|
2048
1786
|
}
|
|
2049
1787
|
async function copyLocalPlugin(name, sourceDir) {
|
|
2050
|
-
const targetDir =
|
|
2051
|
-
const pluginsDir =
|
|
2052
|
-
if (!
|
|
1788
|
+
const targetDir = join3(process.cwd(), "contents", "plugins", name);
|
|
1789
|
+
const pluginsDir = join3(process.cwd(), "contents", "plugins");
|
|
1790
|
+
if (!existsSync2(pluginsDir)) {
|
|
2053
1791
|
mkdirSync(pluginsDir, { recursive: true });
|
|
2054
1792
|
}
|
|
2055
|
-
if (
|
|
2056
|
-
console.log(
|
|
1793
|
+
if (existsSync2(targetDir)) {
|
|
1794
|
+
console.log(chalk5.gray(` Plugin ${name} already exists, skipping...`));
|
|
2057
1795
|
return true;
|
|
2058
1796
|
}
|
|
2059
1797
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
@@ -2061,12 +1799,12 @@ async function copyLocalPlugin(name, sourceDir) {
|
|
|
2061
1799
|
return true;
|
|
2062
1800
|
}
|
|
2063
1801
|
async function updateTsConfigPaths(name, type) {
|
|
2064
|
-
const tsconfigPath =
|
|
2065
|
-
if (!
|
|
1802
|
+
const tsconfigPath = join3(process.cwd(), "tsconfig.json");
|
|
1803
|
+
if (!existsSync2(tsconfigPath)) {
|
|
2066
1804
|
return;
|
|
2067
1805
|
}
|
|
2068
1806
|
try {
|
|
2069
|
-
const content =
|
|
1807
|
+
const content = readFileSync3(tsconfigPath, "utf-8");
|
|
2070
1808
|
const tsconfig = JSON.parse(content);
|
|
2071
1809
|
if (!tsconfig.compilerOptions) {
|
|
2072
1810
|
tsconfig.compilerOptions = {};
|
|
@@ -2101,17 +1839,17 @@ async function installTheme2(theme) {
|
|
|
2101
1839
|
if (!theme) {
|
|
2102
1840
|
return true;
|
|
2103
1841
|
}
|
|
2104
|
-
const spinner =
|
|
1842
|
+
const spinner = ora2({
|
|
2105
1843
|
text: `Installing reference theme: ${theme}...`,
|
|
2106
1844
|
prefixText: " "
|
|
2107
1845
|
}).start();
|
|
2108
1846
|
try {
|
|
2109
|
-
const targetDir =
|
|
2110
|
-
if (
|
|
2111
|
-
spinner.info(
|
|
1847
|
+
const targetDir = join3(process.cwd(), "contents", "themes", theme);
|
|
1848
|
+
if (existsSync2(targetDir)) {
|
|
1849
|
+
spinner.info(chalk5.gray(`Reference theme ${theme} already exists`));
|
|
2112
1850
|
return true;
|
|
2113
1851
|
}
|
|
2114
|
-
if (
|
|
1852
|
+
if (isMonorepoMode()) {
|
|
2115
1853
|
const localDir = getLocalPackageDir("theme", theme);
|
|
2116
1854
|
if (localDir) {
|
|
2117
1855
|
spinner.text = `Copying reference theme from local: ${theme}...`;
|
|
@@ -2124,7 +1862,7 @@ async function installTheme2(theme) {
|
|
|
2124
1862
|
await copyLocalPlugin(plugin, pluginDir);
|
|
2125
1863
|
}
|
|
2126
1864
|
}
|
|
2127
|
-
spinner.succeed(
|
|
1865
|
+
spinner.succeed(chalk5.green(`Reference theme ${theme} installed!`));
|
|
2128
1866
|
return true;
|
|
2129
1867
|
}
|
|
2130
1868
|
}
|
|
@@ -2133,17 +1871,17 @@ async function installTheme2(theme) {
|
|
|
2133
1871
|
const packageSpec = THEME_PACKAGES[theme];
|
|
2134
1872
|
const success = await installThemeViaCli(packageSpec);
|
|
2135
1873
|
if (success) {
|
|
2136
|
-
spinner.succeed(
|
|
1874
|
+
spinner.succeed(chalk5.green(`Reference theme ${theme} installed!`));
|
|
2137
1875
|
return true;
|
|
2138
1876
|
} else {
|
|
2139
|
-
spinner.fail(
|
|
2140
|
-
console.log(
|
|
1877
|
+
spinner.fail(chalk5.red(`Failed to install theme: ${theme}`));
|
|
1878
|
+
console.log(chalk5.gray(" Hint: Make sure @nextsparkjs/cli is installed or the theme package is published"));
|
|
2141
1879
|
return false;
|
|
2142
1880
|
}
|
|
2143
1881
|
} catch (error) {
|
|
2144
|
-
spinner.fail(
|
|
1882
|
+
spinner.fail(chalk5.red(`Failed to install theme: ${theme}`));
|
|
2145
1883
|
if (error instanceof Error) {
|
|
2146
|
-
console.log(
|
|
1884
|
+
console.log(chalk5.red(` Error: ${error.message}`));
|
|
2147
1885
|
}
|
|
2148
1886
|
return false;
|
|
2149
1887
|
}
|
|
@@ -2154,23 +1892,23 @@ async function installPlugins(plugins) {
|
|
|
2154
1892
|
}
|
|
2155
1893
|
let allSuccess = true;
|
|
2156
1894
|
for (const plugin of plugins) {
|
|
2157
|
-
const spinner =
|
|
1895
|
+
const spinner = ora2({
|
|
2158
1896
|
text: `Installing plugin: ${plugin}...`,
|
|
2159
1897
|
prefixText: " "
|
|
2160
1898
|
}).start();
|
|
2161
1899
|
try {
|
|
2162
|
-
const pluginDir =
|
|
2163
|
-
if (
|
|
2164
|
-
spinner.info(
|
|
1900
|
+
const pluginDir = join3(process.cwd(), "contents", "plugins", plugin);
|
|
1901
|
+
if (existsSync2(pluginDir)) {
|
|
1902
|
+
spinner.info(chalk5.gray(`Plugin ${plugin} already installed`));
|
|
2165
1903
|
continue;
|
|
2166
1904
|
}
|
|
2167
|
-
if (
|
|
1905
|
+
if (isMonorepoMode()) {
|
|
2168
1906
|
const localDir = getLocalPackageDir("plugin", plugin);
|
|
2169
1907
|
if (localDir) {
|
|
2170
1908
|
spinner.text = `Copying plugin from local: ${plugin}...`;
|
|
2171
1909
|
const success2 = await copyLocalPlugin(plugin, localDir);
|
|
2172
1910
|
if (success2) {
|
|
2173
|
-
spinner.succeed(
|
|
1911
|
+
spinner.succeed(chalk5.green(`Plugin ${plugin} installed!`));
|
|
2174
1912
|
continue;
|
|
2175
1913
|
}
|
|
2176
1914
|
}
|
|
@@ -2179,16 +1917,16 @@ async function installPlugins(plugins) {
|
|
|
2179
1917
|
const packageSpec = PLUGIN_PACKAGES[plugin];
|
|
2180
1918
|
const success = await installPluginViaCli(packageSpec);
|
|
2181
1919
|
if (success) {
|
|
2182
|
-
spinner.succeed(
|
|
1920
|
+
spinner.succeed(chalk5.green(`Plugin ${plugin} installed!`));
|
|
2183
1921
|
} else {
|
|
2184
|
-
spinner.fail(
|
|
2185
|
-
console.log(
|
|
1922
|
+
spinner.fail(chalk5.red(`Failed to install plugin: ${plugin}`));
|
|
1923
|
+
console.log(chalk5.gray(" Hint: Make sure @nextsparkjs/cli is installed or the plugin package is published"));
|
|
2186
1924
|
allSuccess = false;
|
|
2187
1925
|
}
|
|
2188
1926
|
} catch (error) {
|
|
2189
|
-
spinner.fail(
|
|
1927
|
+
spinner.fail(chalk5.red(`Failed to install plugin: ${plugin}`));
|
|
2190
1928
|
if (error instanceof Error) {
|
|
2191
|
-
console.log(
|
|
1929
|
+
console.log(chalk5.red(` Error: ${error.message}`));
|
|
2192
1930
|
}
|
|
2193
1931
|
allSuccess = false;
|
|
2194
1932
|
}
|
|
@@ -2200,81 +1938,627 @@ async function installThemeAndPlugins(theme, plugins) {
|
|
|
2200
1938
|
return true;
|
|
2201
1939
|
}
|
|
2202
1940
|
console.log("");
|
|
2203
|
-
console.log(
|
|
2204
|
-
console.log(
|
|
1941
|
+
console.log(chalk5.cyan(" Installing Reference Theme & Plugins"));
|
|
1942
|
+
console.log(chalk5.gray(" " + "-".repeat(40)));
|
|
2205
1943
|
console.log("");
|
|
2206
1944
|
const themeSuccess = await installTheme2(theme);
|
|
2207
1945
|
if (!themeSuccess && theme) {
|
|
2208
|
-
console.log(
|
|
1946
|
+
console.log(chalk5.yellow(" Warning: Theme installation failed, continuing with plugins..."));
|
|
2209
1947
|
}
|
|
2210
1948
|
const pluginsSuccess = await installPlugins(plugins);
|
|
2211
1949
|
console.log("");
|
|
2212
1950
|
if (themeSuccess && pluginsSuccess) {
|
|
2213
|
-
console.log(
|
|
1951
|
+
console.log(chalk5.green(" All installations completed successfully!"));
|
|
2214
1952
|
} else {
|
|
2215
|
-
console.log(
|
|
1953
|
+
console.log(chalk5.yellow(" Some installations had issues. Check the messages above."));
|
|
2216
1954
|
}
|
|
2217
1955
|
return themeSuccess && pluginsSuccess;
|
|
2218
1956
|
}
|
|
2219
1957
|
|
|
2220
1958
|
// src/wizard/generators/env-setup.ts
|
|
2221
1959
|
import fs5 from "fs-extra";
|
|
1960
|
+
import path5 from "path";
|
|
1961
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1962
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1963
|
+
var __dirname4 = path5.dirname(__filename4);
|
|
1964
|
+
var ENV_TEMPLATE_PATH = path5.resolve(__dirname4, "../../../templates/env.template");
|
|
2222
1965
|
|
|
2223
1966
|
// src/wizard/generators/git-init.ts
|
|
2224
1967
|
import fs6 from "fs-extra";
|
|
2225
1968
|
|
|
2226
|
-
// src/wizard/generators/
|
|
1969
|
+
// src/wizard/generators/monorepo-generator.ts
|
|
1970
|
+
import fs7 from "fs-extra";
|
|
1971
|
+
import path6 from "path";
|
|
1972
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
2227
1973
|
var __filename5 = fileURLToPath5(import.meta.url);
|
|
2228
|
-
var __dirname5 =
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
1974
|
+
var __dirname5 = path6.dirname(__filename5);
|
|
1975
|
+
var DIRS = {
|
|
1976
|
+
WEB: "web",
|
|
1977
|
+
MOBILE: "mobile",
|
|
1978
|
+
ASSETS: "assets"
|
|
1979
|
+
};
|
|
1980
|
+
var FILES = {
|
|
1981
|
+
PNPM_WORKSPACE: "pnpm-workspace.yaml",
|
|
1982
|
+
NPMRC: ".npmrc",
|
|
1983
|
+
GITIGNORE: ".gitignore",
|
|
1984
|
+
TSCONFIG: "tsconfig.json",
|
|
1985
|
+
PACKAGE_JSON: "package.json",
|
|
1986
|
+
README: "README.md",
|
|
1987
|
+
APP_CONFIG: "app.config.ts"
|
|
1988
|
+
};
|
|
1989
|
+
var REQUIRED_MOBILE_TEMPLATE_FILES = [
|
|
1990
|
+
"app",
|
|
1991
|
+
"src",
|
|
1992
|
+
"babel.config.js",
|
|
1993
|
+
"metro.config.js"
|
|
1994
|
+
];
|
|
1995
|
+
var MOBILE_TEMPLATE_FILES = [
|
|
1996
|
+
"app",
|
|
1997
|
+
"src",
|
|
1998
|
+
"assets",
|
|
1999
|
+
"babel.config.js",
|
|
2000
|
+
"metro.config.js",
|
|
2001
|
+
"tailwind.config.js",
|
|
2002
|
+
"tsconfig.json",
|
|
2003
|
+
"jest.config.js",
|
|
2004
|
+
"eas.json"
|
|
2005
|
+
];
|
|
2006
|
+
var VERSIONS = {
|
|
2007
|
+
// NextSpark packages - use 'latest' for consistency with web packages
|
|
2008
|
+
NEXTSPARK_MOBILE: "latest",
|
|
2009
|
+
NEXTSPARK_UI: "latest",
|
|
2010
|
+
// Core dependencies
|
|
2011
|
+
TANSTACK_QUERY: "^5.62.0",
|
|
2012
|
+
EXPO: "^54.0.0",
|
|
2013
|
+
REACT: "19.1.0",
|
|
2014
|
+
REACT_NATIVE: "0.81.5",
|
|
2015
|
+
TYPESCRIPT: "^5.3.0",
|
|
2016
|
+
// Expo modules (use ~ for patch compatibility)
|
|
2017
|
+
EXPO_CONSTANTS: "~18.0.13",
|
|
2018
|
+
EXPO_LINKING: "~8.0.11",
|
|
2019
|
+
EXPO_ROUTER: "~6.0.22",
|
|
2020
|
+
EXPO_SECURE_STORE: "~15.0.8",
|
|
2021
|
+
EXPO_STATUS_BAR: "~3.0.9",
|
|
2022
|
+
// React Native modules
|
|
2023
|
+
RN_GESTURE_HANDLER: "~2.28.0",
|
|
2024
|
+
RN_REANIMATED: "~4.1.1",
|
|
2025
|
+
RN_SAFE_AREA: "~5.6.0",
|
|
2026
|
+
RN_SCREENS: "~4.16.0",
|
|
2027
|
+
RN_SVG: "15.12.1",
|
|
2028
|
+
RN_WEB: "^0.21.0",
|
|
2029
|
+
// Styling
|
|
2030
|
+
NATIVEWIND: "^4.2.1",
|
|
2031
|
+
TAILWINDCSS: "^3",
|
|
2032
|
+
TAILWIND_MERGE: "^3.4.0",
|
|
2033
|
+
LUCIDE_RN: "^0.563.0",
|
|
2034
|
+
// Dev dependencies
|
|
2035
|
+
BABEL_CORE: "^7.25.0",
|
|
2036
|
+
JEST: "^29.7.0",
|
|
2037
|
+
JEST_EXPO: "^54.0.16",
|
|
2038
|
+
TESTING_LIBRARY_JEST_NATIVE: "^5.4.3",
|
|
2039
|
+
TESTING_LIBRARY_RN: "^13.3.3"
|
|
2040
|
+
};
|
|
2041
|
+
function getMobileTemplatesDir() {
|
|
2042
|
+
const possiblePaths = [
|
|
2043
|
+
// From CWD node_modules (installed package)
|
|
2044
|
+
path6.resolve(process.cwd(), "node_modules/@nextsparkjs/mobile/templates"),
|
|
2045
|
+
// From CLI dist folder: ../../mobile/templates (development)
|
|
2046
|
+
path6.resolve(__dirname5, "../../mobile/templates"),
|
|
2047
|
+
// Legacy paths for different build structures
|
|
2048
|
+
path6.resolve(__dirname5, "../../../../../mobile/templates"),
|
|
2049
|
+
path6.resolve(__dirname5, "../../../../mobile/templates")
|
|
2050
|
+
];
|
|
2051
|
+
for (const p of possiblePaths) {
|
|
2052
|
+
if (fs7.existsSync(p)) {
|
|
2053
|
+
return p;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
const searchedPaths = possiblePaths.map((p) => ` - ${p}`).join("\n");
|
|
2057
|
+
throw new Error(
|
|
2058
|
+
`Could not find @nextsparkjs/mobile templates directory.
|
|
2059
|
+
|
|
2060
|
+
Searched paths:
|
|
2061
|
+
${searchedPaths}
|
|
2062
|
+
|
|
2063
|
+
To fix this, ensure @nextsparkjs/mobile is installed:
|
|
2064
|
+
pnpm add @nextsparkjs/mobile
|
|
2065
|
+
|
|
2066
|
+
If you're developing locally, make sure the mobile package is built:
|
|
2067
|
+
cd packages/mobile && pnpm build`
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
async function validateMobileTemplate(templateDir) {
|
|
2071
|
+
const missing = [];
|
|
2072
|
+
for (const file of REQUIRED_MOBILE_TEMPLATE_FILES) {
|
|
2073
|
+
const filePath = path6.join(templateDir, file);
|
|
2074
|
+
if (!await fs7.pathExists(filePath)) {
|
|
2075
|
+
missing.push(file);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (missing.length > 0) {
|
|
2079
|
+
throw new Error(
|
|
2080
|
+
`Mobile template is incomplete. Missing required files:
|
|
2081
|
+
` + missing.map((f) => ` - ${f}`).join("\n") + `
|
|
2082
|
+
|
|
2083
|
+
Template location: ${templateDir}
|
|
2084
|
+
Please ensure @nextsparkjs/mobile is properly installed and built.`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
function slugToBundleId(slug) {
|
|
2089
|
+
return slug.toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "").replace(/\.{2,}/g, ".");
|
|
2090
|
+
}
|
|
2091
|
+
async function createRootPackageJson(targetDir, config2) {
|
|
2092
|
+
const rootPkg = {
|
|
2093
|
+
name: config2.projectSlug,
|
|
2094
|
+
version: "0.1.0",
|
|
2095
|
+
private: true,
|
|
2096
|
+
scripts: {
|
|
2097
|
+
// Web commands
|
|
2098
|
+
"dev": `pnpm --filter ${DIRS.WEB} dev`,
|
|
2099
|
+
"build": `pnpm --filter ${DIRS.WEB} build`,
|
|
2100
|
+
"start": `pnpm --filter ${DIRS.WEB} start`,
|
|
2101
|
+
"lint": "pnpm -r lint",
|
|
2102
|
+
// Mobile commands
|
|
2103
|
+
"dev:mobile": `pnpm --filter ${DIRS.MOBILE} start`,
|
|
2104
|
+
"ios": `pnpm --filter ${DIRS.MOBILE} ios`,
|
|
2105
|
+
"android": `pnpm --filter ${DIRS.MOBILE} android`,
|
|
2106
|
+
// Shared commands
|
|
2107
|
+
"typecheck": "pnpm -r typecheck",
|
|
2108
|
+
"test": "pnpm -r test",
|
|
2109
|
+
// Web-specific CLI commands (run from root)
|
|
2110
|
+
"db:migrate": `pnpm --filter ${DIRS.WEB} db:migrate`,
|
|
2111
|
+
"db:seed": `pnpm --filter ${DIRS.WEB} db:seed`,
|
|
2112
|
+
"build:registries": `pnpm --filter ${DIRS.WEB} build:registries`
|
|
2113
|
+
},
|
|
2114
|
+
devDependencies: {
|
|
2115
|
+
"typescript": VERSIONS.TYPESCRIPT
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
await fs7.writeJson(path6.join(targetDir, FILES.PACKAGE_JSON), rootPkg, { spaces: 2 });
|
|
2119
|
+
}
|
|
2120
|
+
async function createPnpmWorkspace(targetDir) {
|
|
2121
|
+
const workspaceContent = `packages:
|
|
2122
|
+
- '${DIRS.WEB}'
|
|
2123
|
+
- '${DIRS.MOBILE}'
|
|
2124
|
+
`;
|
|
2125
|
+
await fs7.writeFile(path6.join(targetDir, FILES.PNPM_WORKSPACE), workspaceContent);
|
|
2126
|
+
}
|
|
2127
|
+
async function createNpmrc(targetDir) {
|
|
2128
|
+
const npmrcContent = `# Enable proper hoisting for monorepo
|
|
2129
|
+
public-hoist-pattern[]=*@nextsparkjs/*
|
|
2130
|
+
public-hoist-pattern[]=*expo*
|
|
2131
|
+
public-hoist-pattern[]=*react-native*
|
|
2132
|
+
shamefully-hoist=true
|
|
2133
|
+
`;
|
|
2134
|
+
await fs7.writeFile(path6.join(targetDir, FILES.NPMRC), npmrcContent);
|
|
2135
|
+
}
|
|
2136
|
+
async function createGitignore(targetDir) {
|
|
2137
|
+
const gitignoreContent = `# Dependencies
|
|
2138
|
+
node_modules/
|
|
2139
|
+
|
|
2140
|
+
# Build outputs
|
|
2141
|
+
.next/
|
|
2142
|
+
dist/
|
|
2143
|
+
out/
|
|
2144
|
+
build/
|
|
2145
|
+
.expo/
|
|
2146
|
+
|
|
2147
|
+
# NextSpark
|
|
2148
|
+
.nextspark/
|
|
2149
|
+
|
|
2150
|
+
# Environment
|
|
2151
|
+
.env
|
|
2152
|
+
.env.local
|
|
2153
|
+
.env.*.local
|
|
2154
|
+
|
|
2155
|
+
# IDE
|
|
2156
|
+
.idea/
|
|
2157
|
+
.vscode/
|
|
2158
|
+
*.swp
|
|
2159
|
+
*.swo
|
|
2160
|
+
|
|
2161
|
+
# OS
|
|
2162
|
+
.DS_Store
|
|
2163
|
+
Thumbs.db
|
|
2164
|
+
|
|
2165
|
+
# Testing
|
|
2166
|
+
coverage/
|
|
2167
|
+
.nyc_output/
|
|
2168
|
+
|
|
2169
|
+
# Cypress (theme-based in web/)
|
|
2170
|
+
${DIRS.WEB}/contents/themes/*/tests/cypress/videos
|
|
2171
|
+
${DIRS.WEB}/contents/themes/*/tests/cypress/screenshots
|
|
2172
|
+
${DIRS.WEB}/contents/themes/*/tests/cypress/allure-results
|
|
2173
|
+
${DIRS.WEB}/contents/themes/*/tests/cypress/allure-report
|
|
2174
|
+
|
|
2175
|
+
# Jest (theme-based in web/)
|
|
2176
|
+
${DIRS.WEB}/contents/themes/*/tests/jest/coverage
|
|
2177
|
+
|
|
2178
|
+
# Mobile specific
|
|
2179
|
+
${DIRS.MOBILE}/.expo/
|
|
2180
|
+
*.jks
|
|
2181
|
+
*.p8
|
|
2182
|
+
*.p12
|
|
2183
|
+
*.key
|
|
2184
|
+
*.mobileprovision
|
|
2185
|
+
*.orig.*
|
|
2186
|
+
web-build/
|
|
2187
|
+
`;
|
|
2188
|
+
await fs7.writeFile(path6.join(targetDir, FILES.GITIGNORE), gitignoreContent);
|
|
2189
|
+
}
|
|
2190
|
+
async function createRootTsConfig(targetDir) {
|
|
2191
|
+
const tsConfig = {
|
|
2192
|
+
compilerOptions: {
|
|
2193
|
+
target: "ES2022",
|
|
2194
|
+
module: "ESNext",
|
|
2195
|
+
moduleResolution: "bundler",
|
|
2196
|
+
strict: true,
|
|
2197
|
+
skipLibCheck: true,
|
|
2198
|
+
esModuleInterop: true
|
|
2199
|
+
},
|
|
2200
|
+
references: [
|
|
2201
|
+
{ path: `./${DIRS.WEB}` },
|
|
2202
|
+
{ path: `./${DIRS.MOBILE}` }
|
|
2203
|
+
]
|
|
2204
|
+
};
|
|
2205
|
+
await fs7.writeJson(path6.join(targetDir, FILES.TSCONFIG), tsConfig, { spaces: 2 });
|
|
2206
|
+
}
|
|
2207
|
+
async function copyMobileTemplate(targetDir, config2) {
|
|
2208
|
+
const mobileTemplatesDir = getMobileTemplatesDir();
|
|
2209
|
+
await validateMobileTemplate(mobileTemplatesDir);
|
|
2210
|
+
const mobileDir = path6.join(targetDir, DIRS.MOBILE);
|
|
2211
|
+
await fs7.ensureDir(mobileDir);
|
|
2212
|
+
for (const item of MOBILE_TEMPLATE_FILES) {
|
|
2213
|
+
const srcPath = path6.join(mobileTemplatesDir, item);
|
|
2214
|
+
const destPath = path6.join(mobileDir, item);
|
|
2215
|
+
if (await fs7.pathExists(srcPath)) {
|
|
2216
|
+
await fs7.copy(srcPath, destPath);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
await createMobilePackageJson(mobileDir, config2);
|
|
2220
|
+
await createMobileAppConfig(mobileDir, config2);
|
|
2221
|
+
await createMobileAssets(mobileDir, config2);
|
|
2222
|
+
}
|
|
2223
|
+
async function createMobilePackageJson(mobileDir, config2) {
|
|
2224
|
+
const mobileSlug = `${config2.projectSlug}-mobile`;
|
|
2225
|
+
const packageJson = {
|
|
2226
|
+
name: mobileSlug,
|
|
2227
|
+
version: "0.1.0",
|
|
2228
|
+
private: true,
|
|
2229
|
+
main: "expo-router/entry",
|
|
2230
|
+
scripts: {
|
|
2231
|
+
start: "expo start",
|
|
2232
|
+
android: "expo start --android",
|
|
2233
|
+
ios: "expo start --ios",
|
|
2234
|
+
web: "expo start --web",
|
|
2235
|
+
lint: "eslint .",
|
|
2236
|
+
typecheck: "tsc --noEmit",
|
|
2237
|
+
test: "jest",
|
|
2238
|
+
"test:watch": "jest --watch",
|
|
2239
|
+
"test:coverage": "jest --coverage"
|
|
2240
|
+
},
|
|
2241
|
+
dependencies: {
|
|
2242
|
+
"@nextsparkjs/mobile": VERSIONS.NEXTSPARK_MOBILE,
|
|
2243
|
+
"@nextsparkjs/ui": VERSIONS.NEXTSPARK_UI,
|
|
2244
|
+
"@tanstack/react-query": VERSIONS.TANSTACK_QUERY,
|
|
2245
|
+
"expo": VERSIONS.EXPO,
|
|
2246
|
+
"expo-constants": VERSIONS.EXPO_CONSTANTS,
|
|
2247
|
+
"expo-linking": VERSIONS.EXPO_LINKING,
|
|
2248
|
+
"expo-router": VERSIONS.EXPO_ROUTER,
|
|
2249
|
+
"expo-secure-store": VERSIONS.EXPO_SECURE_STORE,
|
|
2250
|
+
"expo-status-bar": VERSIONS.EXPO_STATUS_BAR,
|
|
2251
|
+
"lucide-react-native": VERSIONS.LUCIDE_RN,
|
|
2252
|
+
"nativewind": VERSIONS.NATIVEWIND,
|
|
2253
|
+
"react": VERSIONS.REACT,
|
|
2254
|
+
"react-dom": VERSIONS.REACT,
|
|
2255
|
+
"react-native": VERSIONS.REACT_NATIVE,
|
|
2256
|
+
"react-native-web": VERSIONS.RN_WEB,
|
|
2257
|
+
"react-native-gesture-handler": VERSIONS.RN_GESTURE_HANDLER,
|
|
2258
|
+
"react-native-reanimated": VERSIONS.RN_REANIMATED,
|
|
2259
|
+
"react-native-safe-area-context": VERSIONS.RN_SAFE_AREA,
|
|
2260
|
+
"react-native-screens": VERSIONS.RN_SCREENS,
|
|
2261
|
+
"react-native-svg": VERSIONS.RN_SVG,
|
|
2262
|
+
"tailwind-merge": VERSIONS.TAILWIND_MERGE,
|
|
2263
|
+
"tailwindcss": VERSIONS.TAILWINDCSS
|
|
2264
|
+
},
|
|
2265
|
+
devDependencies: {
|
|
2266
|
+
"@babel/core": VERSIONS.BABEL_CORE,
|
|
2267
|
+
"@testing-library/jest-native": VERSIONS.TESTING_LIBRARY_JEST_NATIVE,
|
|
2268
|
+
"@testing-library/react-native": VERSIONS.TESTING_LIBRARY_RN,
|
|
2269
|
+
"@types/jest": "^29.5.0",
|
|
2270
|
+
"@types/react": "^19",
|
|
2271
|
+
"jest": VERSIONS.JEST,
|
|
2272
|
+
"jest-expo": VERSIONS.JEST_EXPO,
|
|
2273
|
+
"react-test-renderer": VERSIONS.REACT,
|
|
2274
|
+
"typescript": VERSIONS.TYPESCRIPT
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
await fs7.writeJson(path6.join(mobileDir, FILES.PACKAGE_JSON), packageJson, { spaces: 2 });
|
|
2278
|
+
}
|
|
2279
|
+
async function createMobileAppConfig(mobileDir, config2) {
|
|
2280
|
+
const bundleId = slugToBundleId(config2.projectSlug);
|
|
2281
|
+
const appConfigContent = `import { ExpoConfig, ConfigContext } from 'expo/config'
|
|
2282
|
+
|
|
2283
|
+
export default ({ config }: ConfigContext): ExpoConfig => ({
|
|
2284
|
+
...config,
|
|
2285
|
+
name: '${config2.projectName}',
|
|
2286
|
+
slug: '${config2.projectSlug}',
|
|
2287
|
+
version: '1.0.0',
|
|
2288
|
+
orientation: 'portrait',
|
|
2289
|
+
icon: './${DIRS.ASSETS}/icon.png',
|
|
2290
|
+
userInterfaceStyle: 'automatic',
|
|
2291
|
+
splash: {
|
|
2292
|
+
image: './${DIRS.ASSETS}/splash.png',
|
|
2293
|
+
resizeMode: 'contain',
|
|
2294
|
+
backgroundColor: '#ffffff',
|
|
2295
|
+
},
|
|
2296
|
+
assetBundlePatterns: ['**/*'],
|
|
2297
|
+
ios: {
|
|
2298
|
+
supportsTablet: true,
|
|
2299
|
+
bundleIdentifier: 'com.${bundleId}.app',
|
|
2300
|
+
},
|
|
2301
|
+
android: {
|
|
2302
|
+
adaptiveIcon: {
|
|
2303
|
+
foregroundImage: './${DIRS.ASSETS}/adaptive-icon.png',
|
|
2304
|
+
backgroundColor: '#ffffff',
|
|
2305
|
+
},
|
|
2306
|
+
package: 'com.${bundleId}.app',
|
|
2307
|
+
},
|
|
2308
|
+
web: {
|
|
2309
|
+
favicon: './${DIRS.ASSETS}/favicon.png',
|
|
2310
|
+
},
|
|
2311
|
+
extra: {
|
|
2312
|
+
// API URL - Configure for your environment
|
|
2313
|
+
// Development: point to your local web app (e.g., http://localhost:3000)
|
|
2314
|
+
// Production: set via EAS environment variables
|
|
2315
|
+
apiUrl: process.env.EXPO_PUBLIC_API_URL,
|
|
2316
|
+
},
|
|
2317
|
+
plugins: ['expo-router', 'expo-secure-store'],
|
|
2318
|
+
scheme: '${config2.projectSlug}',
|
|
2319
|
+
})
|
|
2320
|
+
`;
|
|
2321
|
+
await fs7.writeFile(path6.join(mobileDir, FILES.APP_CONFIG), appConfigContent);
|
|
2322
|
+
}
|
|
2323
|
+
async function createMobileAssets(mobileDir, config2) {
|
|
2324
|
+
const assetsDir = path6.join(mobileDir, DIRS.ASSETS);
|
|
2325
|
+
await fs7.ensureDir(assetsDir);
|
|
2326
|
+
await fs7.writeFile(
|
|
2327
|
+
path6.join(assetsDir, ".gitkeep"),
|
|
2328
|
+
"# Placeholder - replace with your app icons and splash screens\n"
|
|
2329
|
+
);
|
|
2330
|
+
const assetsReadme = `# Mobile App Assets for ${config2.projectName}
|
|
2331
|
+
|
|
2332
|
+
This directory contains your mobile app icons and splash screens.
|
|
2333
|
+
|
|
2334
|
+
## Required Files
|
|
2335
|
+
|
|
2336
|
+
| File | Size | Description |
|
|
2337
|
+
|------|------|-------------|
|
|
2338
|
+
| \`icon.png\` | 1024x1024px | Main app icon (iOS & Android) |
|
|
2339
|
+
| \`splash.png\` | 1284x2778px | Splash screen image |
|
|
2340
|
+
| \`adaptive-icon.png\` | 1024x1024px | Android adaptive icon (foreground) |
|
|
2341
|
+
| \`favicon.png\` | 48x48px | Web favicon |
|
|
2342
|
+
|
|
2343
|
+
## How to Generate
|
|
2344
|
+
|
|
2345
|
+
### Option 1: Expo Asset Generator (Recommended)
|
|
2346
|
+
|
|
2347
|
+
1. Create a 1024x1024px icon image
|
|
2348
|
+
2. Use Expo's icon generator:
|
|
2349
|
+
\`\`\`bash
|
|
2350
|
+
npx expo-optimize
|
|
2351
|
+
\`\`\`
|
|
2352
|
+
|
|
2353
|
+
### Option 2: Online Tools
|
|
2354
|
+
|
|
2355
|
+
- [Expo Icon Generator](https://docs.expo.dev/develop/user-interface/app-icons/)
|
|
2356
|
+
- [App Icon Generator](https://appicon.co/)
|
|
2357
|
+
- [Figma App Icon Template](https://www.figma.com/community/file/824894885635013116)
|
|
2358
|
+
|
|
2359
|
+
### Option 3: Manual Creation
|
|
2360
|
+
|
|
2361
|
+
Create each file at the specified sizes above. Use PNG format with transparency for icons.
|
|
2362
|
+
|
|
2363
|
+
## Tips
|
|
2364
|
+
|
|
2365
|
+
- Use a simple, recognizable design that works at small sizes
|
|
2366
|
+
- Test your icon on both light and dark backgrounds
|
|
2367
|
+
- Avoid text in the icon (it becomes illegible at small sizes)
|
|
2368
|
+
- Keep important content within the "safe zone" (center 80%)
|
|
2369
|
+
|
|
2370
|
+
## Resources
|
|
2371
|
+
|
|
2372
|
+
- [Expo App Icons Documentation](https://docs.expo.dev/develop/user-interface/app-icons/)
|
|
2373
|
+
- [iOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/app-icons)
|
|
2374
|
+
- [Android Adaptive Icons](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive)
|
|
2375
|
+
`;
|
|
2376
|
+
await fs7.writeFile(path6.join(assetsDir, "README.md"), assetsReadme);
|
|
2377
|
+
}
|
|
2378
|
+
async function createMonorepoReadme(targetDir, config2) {
|
|
2379
|
+
const readmeContent = `# ${config2.projectName}
|
|
2380
|
+
|
|
2381
|
+
${config2.projectDescription}
|
|
2382
|
+
|
|
2383
|
+
## Project Structure
|
|
2384
|
+
|
|
2385
|
+
This is a monorepo containing both web and mobile applications:
|
|
2386
|
+
|
|
2387
|
+
\`\`\`
|
|
2388
|
+
${config2.projectSlug}/
|
|
2389
|
+
\u251C\u2500\u2500 ${DIRS.WEB}/ # Next.js web application
|
|
2390
|
+
\u2502 \u251C\u2500\u2500 app/ # Next.js App Router
|
|
2391
|
+
\u2502 \u251C\u2500\u2500 contents/ # Themes and plugins
|
|
2392
|
+
\u2502 \u2514\u2500\u2500 package.json
|
|
2393
|
+
\u251C\u2500\u2500 ${DIRS.MOBILE}/ # Expo mobile application
|
|
2394
|
+
\u2502 \u251C\u2500\u2500 app/ # Expo Router screens
|
|
2395
|
+
\u2502 \u251C\u2500\u2500 src/ # Mobile-specific code
|
|
2396
|
+
\u2502 \u2514\u2500\u2500 package.json
|
|
2397
|
+
\u251C\u2500\u2500 package.json # Root monorepo
|
|
2398
|
+
\u2514\u2500\u2500 ${FILES.PNPM_WORKSPACE}
|
|
2399
|
+
\`\`\`
|
|
2400
|
+
|
|
2401
|
+
## Getting Started
|
|
2402
|
+
|
|
2403
|
+
### Prerequisites
|
|
2404
|
+
|
|
2405
|
+
- Node.js 20+
|
|
2406
|
+
- pnpm 9+
|
|
2407
|
+
- For mobile: Expo CLI (\`npm install -g expo-cli\`)
|
|
2408
|
+
|
|
2409
|
+
### Installation
|
|
2410
|
+
|
|
2411
|
+
\`\`\`bash
|
|
2412
|
+
# Install all dependencies
|
|
2413
|
+
pnpm install
|
|
2414
|
+
|
|
2415
|
+
# Set up environment variables
|
|
2416
|
+
cp ${DIRS.WEB}/.env.example ${DIRS.WEB}/.env
|
|
2417
|
+
# Edit ${DIRS.WEB}/.env with your configuration
|
|
2418
|
+
\`\`\`
|
|
2419
|
+
|
|
2420
|
+
### Development
|
|
2421
|
+
|
|
2422
|
+
**Web Application:**
|
|
2423
|
+
\`\`\`bash
|
|
2424
|
+
# From root directory
|
|
2425
|
+
pnpm dev
|
|
2426
|
+
|
|
2427
|
+
# Or from web directory
|
|
2428
|
+
cd ${DIRS.WEB} && pnpm dev
|
|
2429
|
+
\`\`\`
|
|
2430
|
+
|
|
2431
|
+
**Mobile Application:**
|
|
2432
|
+
\`\`\`bash
|
|
2433
|
+
# From root directory
|
|
2434
|
+
pnpm dev:mobile
|
|
2435
|
+
|
|
2436
|
+
# Or from mobile directory
|
|
2437
|
+
cd ${DIRS.MOBILE} && pnpm start
|
|
2438
|
+
\`\`\`
|
|
2439
|
+
|
|
2440
|
+
### Running Tests
|
|
2441
|
+
|
|
2442
|
+
\`\`\`bash
|
|
2443
|
+
# Run all tests
|
|
2444
|
+
pnpm test
|
|
2445
|
+
|
|
2446
|
+
# Run web tests only
|
|
2447
|
+
pnpm --filter ${DIRS.WEB} test
|
|
2448
|
+
|
|
2449
|
+
# Run mobile tests only
|
|
2450
|
+
pnpm --filter ${DIRS.MOBILE} test
|
|
2451
|
+
\`\`\`
|
|
2452
|
+
|
|
2453
|
+
## Mobile App Configuration
|
|
2454
|
+
|
|
2455
|
+
The mobile app connects to your web API. Configure the API URL:
|
|
2456
|
+
|
|
2457
|
+
- **Development:** The mobile app will auto-detect your local server
|
|
2458
|
+
- **Production:** Set \`EXPO_PUBLIC_API_URL\` in your EAS environment
|
|
2459
|
+
|
|
2460
|
+
## Building for Production
|
|
2461
|
+
|
|
2462
|
+
**Web:**
|
|
2463
|
+
\`\`\`bash
|
|
2464
|
+
pnpm build
|
|
2465
|
+
\`\`\`
|
|
2466
|
+
|
|
2467
|
+
**Mobile:**
|
|
2468
|
+
\`\`\`bash
|
|
2469
|
+
cd ${DIRS.MOBILE}
|
|
2470
|
+
eas build --platform ios
|
|
2471
|
+
eas build --platform android
|
|
2472
|
+
\`\`\`
|
|
2473
|
+
|
|
2474
|
+
## Learn More
|
|
2475
|
+
|
|
2476
|
+
- [NextSpark Documentation](https://nextspark.dev/docs)
|
|
2477
|
+
- [Expo Documentation](https://docs.expo.dev)
|
|
2478
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
2479
|
+
`;
|
|
2480
|
+
await fs7.writeFile(path6.join(targetDir, FILES.README), readmeContent);
|
|
2481
|
+
}
|
|
2482
|
+
async function generateMonorepoStructure(targetDir, config2) {
|
|
2483
|
+
await createRootPackageJson(targetDir, config2);
|
|
2484
|
+
await createPnpmWorkspace(targetDir);
|
|
2485
|
+
await createNpmrc(targetDir);
|
|
2486
|
+
await createGitignore(targetDir);
|
|
2487
|
+
await createRootTsConfig(targetDir);
|
|
2488
|
+
await createMonorepoReadme(targetDir, config2);
|
|
2489
|
+
const webDir = path6.join(targetDir, DIRS.WEB);
|
|
2490
|
+
await fs7.ensureDir(webDir);
|
|
2491
|
+
await copyMobileTemplate(targetDir, config2);
|
|
2492
|
+
}
|
|
2493
|
+
function isMonorepoProject(config2) {
|
|
2494
|
+
return config2.projectType === "web-mobile";
|
|
2495
|
+
}
|
|
2496
|
+
function getWebDir(targetDir, config2) {
|
|
2497
|
+
return config2.projectType === "web-mobile" ? path6.join(targetDir, DIRS.WEB) : targetDir;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// src/wizard/generators/index.ts
|
|
2501
|
+
var __filename6 = fileURLToPath6(import.meta.url);
|
|
2502
|
+
var __dirname6 = path7.dirname(__filename6);
|
|
2503
|
+
function getTemplatesDir3(projectRoot) {
|
|
2504
|
+
const rootDir = projectRoot || process.cwd();
|
|
2505
|
+
const possiblePaths = [
|
|
2506
|
+
// From project root node_modules (most common for installed packages)
|
|
2507
|
+
path7.resolve(rootDir, "node_modules/@nextsparkjs/core/templates"),
|
|
2508
|
+
// From CLI dist folder for development
|
|
2509
|
+
path7.resolve(__dirname6, "../../core/templates"),
|
|
2510
|
+
// Legacy paths for different build structures
|
|
2511
|
+
path7.resolve(__dirname6, "../../../../../core/templates"),
|
|
2512
|
+
path7.resolve(__dirname6, "../../../../core/templates")
|
|
2513
|
+
];
|
|
2514
|
+
for (const p of possiblePaths) {
|
|
2515
|
+
if (fs8.existsSync(p)) {
|
|
2516
|
+
return p;
|
|
2243
2517
|
}
|
|
2244
|
-
throw new Error("Could not find @nextsparkjs/core templates directory");
|
|
2245
2518
|
}
|
|
2519
|
+
throw new Error(`Could not find @nextsparkjs/core templates directory. Searched: ${possiblePaths.join(", ")}`);
|
|
2246
2520
|
}
|
|
2521
|
+
var cachedTemplatesDir = null;
|
|
2247
2522
|
async function copyProjectFiles() {
|
|
2248
|
-
|
|
2523
|
+
if (!cachedTemplatesDir) {
|
|
2524
|
+
throw new Error("Templates directory not cached. Call cacheTemplatesDir() first.");
|
|
2525
|
+
}
|
|
2526
|
+
const templatesDir = cachedTemplatesDir;
|
|
2249
2527
|
const projectDir = process.cwd();
|
|
2250
2528
|
const itemsToCopy = [
|
|
2251
2529
|
{ src: "app", dest: "app", force: true },
|
|
2252
2530
|
{ src: "public", dest: "public", force: true },
|
|
2531
|
+
{ src: "proxy.ts", dest: "proxy.ts", force: true },
|
|
2532
|
+
// Next.js 16+ proxy (formerly middleware.ts)
|
|
2253
2533
|
{ src: "next.config.mjs", dest: "next.config.mjs", force: true },
|
|
2254
2534
|
{ src: "tsconfig.json", dest: "tsconfig.json", force: true },
|
|
2255
2535
|
{ src: "postcss.config.mjs", dest: "postcss.config.mjs", force: true },
|
|
2256
2536
|
{ src: "i18n.ts", dest: "i18n.ts", force: true },
|
|
2257
|
-
{ src: "
|
|
2537
|
+
{ src: "pnpm-workspace.yaml", dest: "pnpm-workspace.yaml", force: true },
|
|
2538
|
+
// Enable workspace for themes/plugins - REQUIRED
|
|
2539
|
+
// Note: .npmrc is NOT copied for web-only projects (not needed, pnpm default hoisting works fine)
|
|
2540
|
+
// For monorepo projects, monorepo-generator.ts creates a specific .npmrc with expo/react-native patterns
|
|
2258
2541
|
{ src: "tsconfig.cypress.json", dest: "tsconfig.cypress.json", force: false },
|
|
2259
2542
|
{ src: "cypress.d.ts", dest: "cypress.d.ts", force: false },
|
|
2260
|
-
{ src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false }
|
|
2543
|
+
{ src: "eslint.config.mjs", dest: "eslint.config.mjs", force: false },
|
|
2544
|
+
{ src: "scripts/cy-run-prod.cjs", dest: "scripts/cy-run-prod.cjs", force: false }
|
|
2261
2545
|
];
|
|
2262
2546
|
for (const item of itemsToCopy) {
|
|
2263
|
-
const srcPath =
|
|
2264
|
-
const destPath =
|
|
2265
|
-
if (await
|
|
2266
|
-
if (item.force || !await
|
|
2267
|
-
await
|
|
2547
|
+
const srcPath = path7.join(templatesDir, item.src);
|
|
2548
|
+
const destPath = path7.join(projectDir, item.dest);
|
|
2549
|
+
if (await fs8.pathExists(srcPath)) {
|
|
2550
|
+
if (item.force || !await fs8.pathExists(destPath)) {
|
|
2551
|
+
await fs8.copy(srcPath, destPath);
|
|
2268
2552
|
}
|
|
2269
2553
|
}
|
|
2270
2554
|
}
|
|
2271
2555
|
}
|
|
2272
|
-
async function updatePackageJson(
|
|
2273
|
-
const packageJsonPath =
|
|
2556
|
+
async function updatePackageJson(config2) {
|
|
2557
|
+
const packageJsonPath = path7.resolve(process.cwd(), "package.json");
|
|
2274
2558
|
let packageJson;
|
|
2275
|
-
if (!await
|
|
2559
|
+
if (!await fs8.pathExists(packageJsonPath)) {
|
|
2276
2560
|
packageJson = {
|
|
2277
|
-
name:
|
|
2561
|
+
name: isMonorepoProject(config2) ? "web" : config2.projectSlug,
|
|
2278
2562
|
version: "0.1.0",
|
|
2279
2563
|
private: true,
|
|
2280
2564
|
scripts: {},
|
|
@@ -2282,7 +2566,7 @@ async function updatePackageJson(config) {
|
|
|
2282
2566
|
devDependencies: {}
|
|
2283
2567
|
};
|
|
2284
2568
|
} else {
|
|
2285
|
-
packageJson = await
|
|
2569
|
+
packageJson = await fs8.readJson(packageJsonPath);
|
|
2286
2570
|
}
|
|
2287
2571
|
packageJson.scripts = packageJson.scripts || {};
|
|
2288
2572
|
const scriptsToAdd = {
|
|
@@ -2293,11 +2577,13 @@ async function updatePackageJson(config) {
|
|
|
2293
2577
|
"build:registries": "nextspark registry:build",
|
|
2294
2578
|
"db:migrate": "nextspark db:migrate",
|
|
2295
2579
|
"db:seed": "nextspark db:seed",
|
|
2296
|
-
"test
|
|
2297
|
-
"cy:open":
|
|
2298
|
-
"cy:run":
|
|
2299
|
-
"
|
|
2300
|
-
"
|
|
2580
|
+
"test": "node node_modules/@nextsparkjs/core/scripts/test/jest-theme.mjs",
|
|
2581
|
+
"cy:open": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs open",
|
|
2582
|
+
"cy:run": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs run",
|
|
2583
|
+
"cy:tags": "node node_modules/@nextsparkjs/core/scripts/test/cy.mjs tags",
|
|
2584
|
+
"cy:run:prod": "node scripts/cy-run-prod.cjs",
|
|
2585
|
+
"allure:generate": `allure generate contents/themes/${config2.projectSlug}/tests/cypress/allure-results --clean -o contents/themes/${config2.projectSlug}/tests/cypress/allure-report`,
|
|
2586
|
+
"allure:open": `allure open contents/themes/${config2.projectSlug}/tests/cypress/allure-report`
|
|
2301
2587
|
};
|
|
2302
2588
|
for (const [name, command] of Object.entries(scriptsToAdd)) {
|
|
2303
2589
|
if (!packageJson.scripts[name]) {
|
|
@@ -2307,8 +2593,8 @@ async function updatePackageJson(config) {
|
|
|
2307
2593
|
packageJson.dependencies = packageJson.dependencies || {};
|
|
2308
2594
|
const depsToAdd = {
|
|
2309
2595
|
// NextSpark
|
|
2310
|
-
"@nextsparkjs/core": "
|
|
2311
|
-
"@nextsparkjs/cli": "
|
|
2596
|
+
"@nextsparkjs/core": "latest",
|
|
2597
|
+
"@nextsparkjs/cli": "latest",
|
|
2312
2598
|
// Next.js + React
|
|
2313
2599
|
"next": "^15.1.0",
|
|
2314
2600
|
"react": "^19.0.0",
|
|
@@ -2339,7 +2625,9 @@ async function updatePackageJson(config) {
|
|
|
2339
2625
|
"slugify": "^1.6.6"
|
|
2340
2626
|
};
|
|
2341
2627
|
for (const [name, version] of Object.entries(depsToAdd)) {
|
|
2342
|
-
if (
|
|
2628
|
+
if (name.startsWith("@nextsparkjs/")) {
|
|
2629
|
+
packageJson.dependencies[name] = version;
|
|
2630
|
+
} else if (!packageJson.dependencies[name]) {
|
|
2343
2631
|
packageJson.dependencies[name] = version;
|
|
2344
2632
|
}
|
|
2345
2633
|
}
|
|
@@ -2367,24 +2655,28 @@ async function updatePackageJson(config) {
|
|
|
2367
2655
|
"@testing-library/react": "^16.3.0",
|
|
2368
2656
|
"jest-environment-jsdom": "^29.7.0",
|
|
2369
2657
|
// Cypress
|
|
2370
|
-
"cypress": "^
|
|
2658
|
+
"cypress": "^15.8.2",
|
|
2371
2659
|
"@testing-library/cypress": "^10.0.2",
|
|
2372
2660
|
"@cypress/webpack-preprocessor": "^6.0.2",
|
|
2373
|
-
"@cypress/grep": "^
|
|
2661
|
+
"@cypress/grep": "^5.0.1",
|
|
2374
2662
|
"ts-loader": "^9.5.1",
|
|
2375
2663
|
"webpack": "^5.97.0",
|
|
2376
2664
|
"allure-cypress": "^3.0.0",
|
|
2377
|
-
"allure-commandline": "^2.27.0"
|
|
2665
|
+
"allure-commandline": "^2.27.0",
|
|
2666
|
+
// NextSpark Testing
|
|
2667
|
+
"@nextsparkjs/testing": "latest"
|
|
2378
2668
|
};
|
|
2379
2669
|
for (const [name, version] of Object.entries(devDepsToAdd)) {
|
|
2380
|
-
if (
|
|
2670
|
+
if (name.startsWith("@nextsparkjs/")) {
|
|
2671
|
+
packageJson.devDependencies[name] = version;
|
|
2672
|
+
} else if (!packageJson.devDependencies[name]) {
|
|
2381
2673
|
packageJson.devDependencies[name] = version;
|
|
2382
2674
|
}
|
|
2383
2675
|
}
|
|
2384
|
-
await
|
|
2676
|
+
await fs8.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2385
2677
|
}
|
|
2386
|
-
async function updateGitignore(
|
|
2387
|
-
const gitignorePath =
|
|
2678
|
+
async function updateGitignore(config2) {
|
|
2679
|
+
const gitignorePath = path7.resolve(process.cwd(), ".gitignore");
|
|
2388
2680
|
const entriesToAdd = `
|
|
2389
2681
|
# NextSpark
|
|
2390
2682
|
.nextspark/
|
|
@@ -2402,40 +2694,66 @@ contents/themes/*/tests/jest/coverage
|
|
|
2402
2694
|
.env
|
|
2403
2695
|
.env.local
|
|
2404
2696
|
`;
|
|
2405
|
-
if (await
|
|
2406
|
-
const currentContent = await
|
|
2697
|
+
if (await fs8.pathExists(gitignorePath)) {
|
|
2698
|
+
const currentContent = await fs8.readFile(gitignorePath, "utf-8");
|
|
2407
2699
|
if (!currentContent.includes(".nextspark/")) {
|
|
2408
|
-
await
|
|
2700
|
+
await fs8.appendFile(gitignorePath, entriesToAdd);
|
|
2409
2701
|
}
|
|
2410
2702
|
} else {
|
|
2411
|
-
await
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
async function generateProject(
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2703
|
+
await fs8.writeFile(gitignorePath, entriesToAdd.trim());
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
async function generateProject(config2) {
|
|
2707
|
+
const projectDir = process.cwd();
|
|
2708
|
+
cachedTemplatesDir = getTemplatesDir3(projectDir);
|
|
2709
|
+
const webDir = getWebDir(projectDir, config2);
|
|
2710
|
+
if (isMonorepoProject(config2)) {
|
|
2711
|
+
await generateMonorepoStructure(projectDir, config2);
|
|
2712
|
+
}
|
|
2713
|
+
const originalCwd = process.cwd();
|
|
2714
|
+
if (isMonorepoProject(config2)) {
|
|
2715
|
+
process.chdir(webDir);
|
|
2716
|
+
}
|
|
2717
|
+
try {
|
|
2718
|
+
await copyProjectFiles();
|
|
2719
|
+
await updateGlobalsCss(config2);
|
|
2720
|
+
await copyStarterTheme(config2);
|
|
2721
|
+
await fs8.ensureDir(path7.join(process.cwd(), "contents", "plugins"));
|
|
2722
|
+
await copyContentFeatures(config2);
|
|
2723
|
+
await updateThemeConfig(config2);
|
|
2724
|
+
await updateDevConfig(config2);
|
|
2725
|
+
await updateAppConfig(config2);
|
|
2726
|
+
await updateBillingConfig(config2);
|
|
2727
|
+
await updateRolesConfig(config2);
|
|
2728
|
+
await updateMigrations(config2);
|
|
2729
|
+
await updateTestFiles(config2);
|
|
2730
|
+
await updatePermissionsConfig(config2);
|
|
2731
|
+
await updateEntityPermissions(config2);
|
|
2732
|
+
await updateDashboardConfig(config2);
|
|
2733
|
+
await updateAuthConfig(config2);
|
|
2734
|
+
await updateDashboardUIConfig(config2);
|
|
2735
|
+
await updateDevToolsConfig(config2);
|
|
2736
|
+
await processI18n(config2);
|
|
2737
|
+
await updatePackageJson(config2);
|
|
2738
|
+
if (!isMonorepoProject(config2)) {
|
|
2739
|
+
await updateGitignore(config2);
|
|
2740
|
+
}
|
|
2741
|
+
await generateEnvExample(config2);
|
|
2742
|
+
if (!isMonorepoProject(config2)) {
|
|
2743
|
+
await updateReadme(config2);
|
|
2744
|
+
}
|
|
2745
|
+
await copyEnvExampleToEnv();
|
|
2746
|
+
} finally {
|
|
2747
|
+
if (isMonorepoProject(config2)) {
|
|
2748
|
+
process.chdir(originalCwd);
|
|
2749
|
+
}
|
|
2750
|
+
cachedTemplatesDir = null;
|
|
2751
|
+
}
|
|
2435
2752
|
}
|
|
2436
2753
|
|
|
2437
2754
|
// src/wizard/presets.ts
|
|
2438
2755
|
var SAAS_PRESET = {
|
|
2756
|
+
projectType: "web",
|
|
2439
2757
|
teamMode: "multi-tenant",
|
|
2440
2758
|
teamRoles: ["owner", "admin", "member", "viewer"],
|
|
2441
2759
|
defaultLocale: "en",
|
|
@@ -2474,6 +2792,7 @@ var SAAS_PRESET = {
|
|
|
2474
2792
|
}
|
|
2475
2793
|
};
|
|
2476
2794
|
var BLOG_PRESET = {
|
|
2795
|
+
projectType: "web",
|
|
2477
2796
|
teamMode: "single-user",
|
|
2478
2797
|
teamRoles: ["owner"],
|
|
2479
2798
|
defaultLocale: "en",
|
|
@@ -2512,6 +2831,7 @@ var BLOG_PRESET = {
|
|
|
2512
2831
|
}
|
|
2513
2832
|
};
|
|
2514
2833
|
var CRM_PRESET = {
|
|
2834
|
+
projectType: "web",
|
|
2515
2835
|
teamMode: "single-tenant",
|
|
2516
2836
|
teamRoles: ["owner", "admin", "member"],
|
|
2517
2837
|
defaultLocale: "en",
|
|
@@ -2566,16 +2886,18 @@ function getPreset(name) {
|
|
|
2566
2886
|
}
|
|
2567
2887
|
return preset;
|
|
2568
2888
|
}
|
|
2569
|
-
function applyPreset(projectInfo, presetName) {
|
|
2889
|
+
function applyPreset(projectInfo, presetName, typeOverride) {
|
|
2570
2890
|
const preset = getPreset(presetName);
|
|
2571
2891
|
return {
|
|
2572
2892
|
...projectInfo,
|
|
2573
|
-
...preset
|
|
2893
|
+
...preset,
|
|
2894
|
+
// Apply type override if provided
|
|
2895
|
+
...typeOverride && { projectType: typeOverride }
|
|
2574
2896
|
};
|
|
2575
2897
|
}
|
|
2576
2898
|
|
|
2577
2899
|
// src/wizard/preview.ts
|
|
2578
|
-
import
|
|
2900
|
+
import chalk6 from "chalk";
|
|
2579
2901
|
var BOX = {
|
|
2580
2902
|
vertical: "\u2502",
|
|
2581
2903
|
// |
|
|
@@ -2594,9 +2916,9 @@ var BOX = {
|
|
|
2594
2916
|
bottomRight: "\u2518"
|
|
2595
2917
|
// bottom-right corner
|
|
2596
2918
|
};
|
|
2597
|
-
function getFileTree(
|
|
2919
|
+
function getFileTree(config2) {
|
|
2598
2920
|
const files = [];
|
|
2599
|
-
const themeDir = `contents/themes/${
|
|
2921
|
+
const themeDir = `contents/themes/${config2.projectSlug}`;
|
|
2600
2922
|
files.push(`${themeDir}/config/app.config.ts`);
|
|
2601
2923
|
files.push(`${themeDir}/config/billing.config.ts`);
|
|
2602
2924
|
files.push(`${themeDir}/config/dashboard.config.ts`);
|
|
@@ -2612,7 +2934,7 @@ function getFileTree(config) {
|
|
|
2612
2934
|
files.push(`${themeDir}/blocks/hero/block.tsx`);
|
|
2613
2935
|
files.push(`${themeDir}/blocks/hero/schema.ts`);
|
|
2614
2936
|
files.push(`${themeDir}/blocks/hero/styles.ts`);
|
|
2615
|
-
for (const locale of
|
|
2937
|
+
for (const locale of config2.supportedLocales) {
|
|
2616
2938
|
files.push(`${themeDir}/messages/${locale}/common.json`);
|
|
2617
2939
|
files.push(`${themeDir}/messages/${locale}/auth.json`);
|
|
2618
2940
|
files.push(`${themeDir}/messages/${locale}/dashboard.json`);
|
|
@@ -2625,7 +2947,7 @@ function getFileTree(config) {
|
|
|
2625
2947
|
files.push(`${themeDir}/styles/theme.css`);
|
|
2626
2948
|
files.push(`${themeDir}/styles/components.css`);
|
|
2627
2949
|
files.push(`${themeDir}/tests/cypress.config.ts`);
|
|
2628
|
-
files.push(`${themeDir}/tests/jest/jest.config.
|
|
2950
|
+
files.push(`${themeDir}/tests/jest/jest.config.cjs`);
|
|
2629
2951
|
files.push(`${themeDir}/tests/cypress/e2e/auth.cy.ts`);
|
|
2630
2952
|
files.push(`${themeDir}/tests/cypress/e2e/dashboard.cy.ts`);
|
|
2631
2953
|
files.push(`${themeDir}/tests/jest/components/hero.test.tsx`);
|
|
@@ -2663,17 +2985,17 @@ function groupFilesByCategory(files) {
|
|
|
2663
2985
|
function formatFilePath(file, themeDir) {
|
|
2664
2986
|
return file.replace(`${themeDir}/`, "");
|
|
2665
2987
|
}
|
|
2666
|
-
function showConfigPreview(
|
|
2667
|
-
const files = getFileTree(
|
|
2988
|
+
function showConfigPreview(config2) {
|
|
2989
|
+
const files = getFileTree(config2);
|
|
2668
2990
|
const groups = groupFilesByCategory(files);
|
|
2669
|
-
const themeDir = `contents/themes/${
|
|
2991
|
+
const themeDir = `contents/themes/${config2.projectSlug}`;
|
|
2670
2992
|
console.log("");
|
|
2671
|
-
console.log(
|
|
2672
|
-
console.log(
|
|
2993
|
+
console.log(chalk6.cyan.bold(" Theme Preview"));
|
|
2994
|
+
console.log(chalk6.gray(" " + "=".repeat(50)));
|
|
2673
2995
|
console.log("");
|
|
2674
|
-
console.log(
|
|
2675
|
-
console.log(
|
|
2676
|
-
console.log(
|
|
2996
|
+
console.log(chalk6.white.bold(` ${BOX.topLeft}${"\u2500".repeat(48)}${BOX.topRight}`));
|
|
2997
|
+
console.log(chalk6.white.bold(` ${BOX.vertical}`) + chalk6.cyan(` ${themeDir}/`) + " ".repeat(48 - themeDir.length - 2) + chalk6.white.bold(BOX.vertical));
|
|
2998
|
+
console.log(chalk6.white.bold(` ${BOX.vertical}${"\u2500".repeat(48)}${BOX.vertical}`));
|
|
2677
2999
|
const categoryLabels = {
|
|
2678
3000
|
config: "Configuration",
|
|
2679
3001
|
entities: "Entities",
|
|
@@ -2698,40 +3020,40 @@ function showConfigPreview(config) {
|
|
|
2698
3020
|
if (categoryFiles.length === 0) continue;
|
|
2699
3021
|
const label = categoryLabels[category];
|
|
2700
3022
|
const icon = categoryIcons[category];
|
|
2701
|
-
console.log(
|
|
3023
|
+
console.log(chalk6.white.bold(` ${BOX.vertical} `) + chalk6.yellow(`[${icon}] ${label}`) + chalk6.gray(` (${categoryFiles.length} files)`));
|
|
2702
3024
|
for (let i = 0; i < categoryFiles.length; i++) {
|
|
2703
3025
|
const file = categoryFiles[i];
|
|
2704
3026
|
const isLast = i === categoryFiles.length - 1;
|
|
2705
3027
|
const prefix = isLast ? BOX.corner : BOX.tee;
|
|
2706
3028
|
const formattedPath = formatFilePath(file, themeDir);
|
|
2707
|
-
console.log(
|
|
3029
|
+
console.log(chalk6.white.bold(` ${BOX.vertical} `) + chalk6.gray(` ${prefix}${BOX.horizontal} `) + chalk6.white(formattedPath));
|
|
2708
3030
|
}
|
|
2709
|
-
console.log(
|
|
3031
|
+
console.log(chalk6.white.bold(` ${BOX.vertical}`));
|
|
2710
3032
|
}
|
|
2711
|
-
console.log(
|
|
3033
|
+
console.log(chalk6.white.bold(` ${BOX.bottomLeft}${"\u2500".repeat(48)}${BOX.bottomRight}`));
|
|
2712
3034
|
console.log("");
|
|
2713
|
-
console.log(
|
|
2714
|
-
console.log(
|
|
3035
|
+
console.log(chalk6.cyan.bold(" Summary"));
|
|
3036
|
+
console.log(chalk6.gray(" " + "-".repeat(30)));
|
|
2715
3037
|
console.log("");
|
|
2716
3038
|
const totalFiles = files.length;
|
|
2717
3039
|
const estimatedSize = "~350KB";
|
|
2718
|
-
console.log(
|
|
2719
|
-
console.log(
|
|
3040
|
+
console.log(chalk6.white(` Total files: `) + chalk6.green.bold(totalFiles.toString()));
|
|
3041
|
+
console.log(chalk6.white(` Estimated size: `) + chalk6.green.bold(estimatedSize));
|
|
2720
3042
|
console.log("");
|
|
2721
|
-
console.log(
|
|
3043
|
+
console.log(chalk6.gray(" By category:"));
|
|
2722
3044
|
for (const category of categoryOrder) {
|
|
2723
3045
|
const count = groups[category].length;
|
|
2724
3046
|
if (count > 0) {
|
|
2725
3047
|
const label = categoryLabels[category].padEnd(16);
|
|
2726
|
-
console.log(
|
|
3048
|
+
console.log(chalk6.gray(` ${label}`) + chalk6.white(count.toString().padStart(3)) + chalk6.gray(" files"));
|
|
2727
3049
|
}
|
|
2728
3050
|
}
|
|
2729
3051
|
console.log("");
|
|
2730
|
-
console.log(
|
|
2731
|
-
for (const locale of
|
|
2732
|
-
const isDefault = locale ===
|
|
2733
|
-
const suffix = isDefault ?
|
|
2734
|
-
console.log(
|
|
3052
|
+
console.log(chalk6.gray(" Locales configured:"));
|
|
3053
|
+
for (const locale of config2.supportedLocales) {
|
|
3054
|
+
const isDefault = locale === config2.defaultLocale;
|
|
3055
|
+
const suffix = isDefault ? chalk6.cyan(" (default)") : "";
|
|
3056
|
+
console.log(chalk6.gray(` - `) + chalk6.white(locale) + suffix);
|
|
2735
3057
|
}
|
|
2736
3058
|
console.log("");
|
|
2737
3059
|
}
|
|
@@ -2753,43 +3075,41 @@ async function runWizard(options = { mode: "interactive" }) {
|
|
|
2753
3075
|
try {
|
|
2754
3076
|
let selectedTheme = null;
|
|
2755
3077
|
let selectedPlugins = [];
|
|
2756
|
-
|
|
2757
|
-
if (options.theme !== void 0) {
|
|
2758
|
-
selectedTheme = options.theme === "none" ? null : options.theme;
|
|
2759
|
-
showInfo(`Reference theme: ${selectedTheme || "None"}`);
|
|
2760
|
-
} else if (options.mode !== "quick") {
|
|
2761
|
-
selectedTheme = await promptThemeSelection();
|
|
2762
|
-
}
|
|
2763
|
-
if (options.plugins !== void 0) {
|
|
2764
|
-
selectedPlugins = options.plugins;
|
|
2765
|
-
if (selectedPlugins.length > 0) {
|
|
2766
|
-
showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
|
|
2767
|
-
}
|
|
2768
|
-
} else if (options.mode !== "quick" && !options.yes) {
|
|
2769
|
-
selectedPlugins = await promptPluginsSelection(selectedTheme);
|
|
2770
|
-
} else if (selectedTheme) {
|
|
2771
|
-
selectedPlugins = getRequiredPlugins(selectedTheme);
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
let config;
|
|
3078
|
+
let config2;
|
|
2775
3079
|
if (options.preset) {
|
|
2776
|
-
|
|
3080
|
+
config2 = await runPresetMode(options.preset, options);
|
|
2777
3081
|
} else {
|
|
2778
3082
|
switch (options.mode) {
|
|
2779
3083
|
case "quick":
|
|
2780
|
-
|
|
3084
|
+
config2 = await runQuickPrompts();
|
|
2781
3085
|
break;
|
|
2782
3086
|
case "expert":
|
|
2783
|
-
|
|
3087
|
+
config2 = await runExpertPrompts();
|
|
2784
3088
|
break;
|
|
2785
3089
|
case "interactive":
|
|
2786
3090
|
default:
|
|
2787
|
-
|
|
3091
|
+
config2 = await runAllPrompts();
|
|
2788
3092
|
break;
|
|
2789
3093
|
}
|
|
2790
3094
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
3095
|
+
if (options.theme !== void 0) {
|
|
3096
|
+
selectedTheme = options.theme === "none" ? null : options.theme;
|
|
3097
|
+
showInfo(`Reference theme: ${selectedTheme || "None"}`);
|
|
3098
|
+
} else if (!options.preset && options.mode !== "quick") {
|
|
3099
|
+
selectedTheme = await promptThemeSelection();
|
|
3100
|
+
}
|
|
3101
|
+
if (options.plugins !== void 0) {
|
|
3102
|
+
selectedPlugins = options.plugins;
|
|
3103
|
+
if (selectedPlugins.length > 0) {
|
|
3104
|
+
showInfo(`Selected plugins: ${selectedPlugins.join(", ")}`);
|
|
3105
|
+
}
|
|
3106
|
+
} else if (!options.preset && options.mode !== "quick" && !options.yes) {
|
|
3107
|
+
selectedPlugins = await promptPluginsSelection(selectedTheme);
|
|
3108
|
+
} else if (selectedTheme) {
|
|
3109
|
+
selectedPlugins = getRequiredPlugins(selectedTheme);
|
|
3110
|
+
}
|
|
3111
|
+
showConfigSummary(config2);
|
|
3112
|
+
showConfigPreview(config2);
|
|
2793
3113
|
if (!options.yes) {
|
|
2794
3114
|
console.log("");
|
|
2795
3115
|
const proceed = await confirm5({
|
|
@@ -2802,20 +3122,27 @@ async function runWizard(options = { mode: "interactive" }) {
|
|
|
2802
3122
|
process.exit(0);
|
|
2803
3123
|
}
|
|
2804
3124
|
}
|
|
2805
|
-
await copyNpmrc();
|
|
2806
3125
|
console.log("");
|
|
2807
3126
|
const coreInstalled = await installCore();
|
|
2808
3127
|
if (!coreInstalled) {
|
|
2809
3128
|
showError("Failed to install @nextsparkjs/core. Cannot generate project.");
|
|
2810
3129
|
process.exit(1);
|
|
2811
3130
|
}
|
|
3131
|
+
if (config2.projectType === "web-mobile") {
|
|
3132
|
+
console.log("");
|
|
3133
|
+
const mobileInstalled = await installMobile();
|
|
3134
|
+
if (!mobileInstalled) {
|
|
3135
|
+
showError("Failed to install @nextsparkjs/mobile. Cannot generate monorepo project.");
|
|
3136
|
+
process.exit(1);
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
2812
3139
|
console.log("");
|
|
2813
|
-
const spinner =
|
|
3140
|
+
const spinner = ora3({
|
|
2814
3141
|
text: "Generating your NextSpark project...",
|
|
2815
3142
|
prefixText: " "
|
|
2816
3143
|
}).start();
|
|
2817
3144
|
try {
|
|
2818
|
-
await generateProject(
|
|
3145
|
+
await generateProject(config2);
|
|
2819
3146
|
spinner.succeed("Project generated successfully!");
|
|
2820
3147
|
} catch (error) {
|
|
2821
3148
|
spinner.fail("Failed to generate project");
|
|
@@ -2824,41 +3151,49 @@ async function runWizard(options = { mode: "interactive" }) {
|
|
|
2824
3151
|
if (selectedTheme || selectedPlugins.length > 0) {
|
|
2825
3152
|
await installThemeAndPlugins(selectedTheme, selectedPlugins);
|
|
2826
3153
|
}
|
|
2827
|
-
const
|
|
2828
|
-
|
|
3154
|
+
const projectRoot = process.cwd();
|
|
3155
|
+
const webDir = getWebDir(projectRoot, config2);
|
|
3156
|
+
const isMonorepo = isMonorepoProject(config2);
|
|
3157
|
+
const installSpinner = ora3({
|
|
3158
|
+
text: isMonorepo ? "Installing dependencies (monorepo)..." : "Installing dependencies...",
|
|
2829
3159
|
prefixText: " "
|
|
2830
3160
|
}).start();
|
|
2831
3161
|
try {
|
|
3162
|
+
installSpinner.stop();
|
|
2832
3163
|
execSync("pnpm install --force", {
|
|
2833
|
-
cwd:
|
|
2834
|
-
|
|
3164
|
+
cwd: projectRoot,
|
|
3165
|
+
// Always install from root (works for both flat and monorepo)
|
|
3166
|
+
stdio: "inherit"
|
|
2835
3167
|
});
|
|
2836
3168
|
installSpinner.succeed("Dependencies installed!");
|
|
2837
3169
|
} catch (error) {
|
|
2838
3170
|
installSpinner.fail("Failed to install dependencies");
|
|
2839
|
-
console.log(
|
|
3171
|
+
console.log(chalk7.yellow(' Run "pnpm install" manually to install dependencies'));
|
|
2840
3172
|
}
|
|
2841
|
-
const registrySpinner =
|
|
3173
|
+
const registrySpinner = ora3({
|
|
2842
3174
|
text: "Building registries...",
|
|
2843
3175
|
prefixText: " "
|
|
2844
3176
|
}).start();
|
|
2845
3177
|
try {
|
|
2846
|
-
const
|
|
2847
|
-
|
|
3178
|
+
const registryScript = join4(webDir, "node_modules/@nextsparkjs/core/scripts/build/registry.mjs");
|
|
3179
|
+
registrySpinner.stop();
|
|
2848
3180
|
execSync(`node "${registryScript}" --build`, {
|
|
2849
|
-
cwd:
|
|
2850
|
-
|
|
3181
|
+
cwd: webDir,
|
|
3182
|
+
// Run from web directory
|
|
3183
|
+
stdio: "inherit",
|
|
2851
3184
|
env: {
|
|
2852
3185
|
...process.env,
|
|
2853
|
-
NEXTSPARK_PROJECT_ROOT:
|
|
3186
|
+
NEXTSPARK_PROJECT_ROOT: webDir
|
|
2854
3187
|
}
|
|
2855
3188
|
});
|
|
2856
3189
|
registrySpinner.succeed("Registries built!");
|
|
2857
3190
|
} catch (error) {
|
|
2858
3191
|
registrySpinner.fail("Failed to build registries");
|
|
2859
|
-
|
|
3192
|
+
const devCmd = isMonorepo ? "pnpm dev" : "pnpm dev";
|
|
3193
|
+
console.log(chalk7.yellow(` Registries will be built automatically when you run "${devCmd}"`));
|
|
2860
3194
|
}
|
|
2861
|
-
|
|
3195
|
+
const aiChoice = await promptAIWorkflowSetup(config2);
|
|
3196
|
+
showNextSteps(config2, selectedTheme, aiChoice);
|
|
2862
3197
|
} catch (error) {
|
|
2863
3198
|
if (error instanceof Error) {
|
|
2864
3199
|
if (error.message.includes("User force closed")) {
|
|
@@ -2873,7 +3208,7 @@ async function runWizard(options = { mode: "interactive" }) {
|
|
|
2873
3208
|
}
|
|
2874
3209
|
function showModeIndicator(options) {
|
|
2875
3210
|
if (options.preset) {
|
|
2876
|
-
showInfo(`Using preset: ${
|
|
3211
|
+
showInfo(`Using preset: ${chalk7.cyan(options.preset)} - ${PRESET_DESCRIPTIONS[options.preset]}`);
|
|
2877
3212
|
console.log("");
|
|
2878
3213
|
} else if (options.mode === "quick") {
|
|
2879
3214
|
showInfo("Quick mode: Running essential prompts only (steps 1-5)");
|
|
@@ -2898,47 +3233,48 @@ async function runPresetMode(presetName, options) {
|
|
|
2898
3233
|
console.log("");
|
|
2899
3234
|
projectInfo = await promptProjectInfo();
|
|
2900
3235
|
}
|
|
2901
|
-
const
|
|
2902
|
-
return
|
|
3236
|
+
const config2 = applyPreset(projectInfo, presetName, options.type);
|
|
3237
|
+
return config2;
|
|
2903
3238
|
}
|
|
2904
|
-
function showConfigSummary(
|
|
3239
|
+
function showConfigSummary(config2) {
|
|
2905
3240
|
console.log("");
|
|
2906
|
-
console.log(
|
|
2907
|
-
console.log(
|
|
2908
|
-
console.log(
|
|
3241
|
+
console.log(chalk7.cyan(" " + "=".repeat(60)));
|
|
3242
|
+
console.log(chalk7.bold.white(" Configuration Summary"));
|
|
3243
|
+
console.log(chalk7.cyan(" " + "=".repeat(60)));
|
|
2909
3244
|
console.log("");
|
|
2910
|
-
console.log(
|
|
2911
|
-
console.log(
|
|
2912
|
-
console.log(
|
|
2913
|
-
console.log(
|
|
3245
|
+
console.log(chalk7.white(" Project:"));
|
|
3246
|
+
console.log(chalk7.gray(` Name: ${chalk7.white(config2.projectName)}`));
|
|
3247
|
+
console.log(chalk7.gray(` Slug: ${chalk7.white(config2.projectSlug)}`));
|
|
3248
|
+
console.log(chalk7.gray(` Description: ${chalk7.white(config2.projectDescription)}`));
|
|
3249
|
+
console.log(chalk7.gray(` Type: ${chalk7.white(config2.projectType === "web-mobile" ? "Web + Mobile (Monorepo)" : "Web only")}`));
|
|
2914
3250
|
console.log("");
|
|
2915
|
-
console.log(
|
|
2916
|
-
console.log(
|
|
2917
|
-
console.log(
|
|
3251
|
+
console.log(chalk7.white(" Team Mode:"));
|
|
3252
|
+
console.log(chalk7.gray(` Mode: ${chalk7.white(config2.teamMode)}`));
|
|
3253
|
+
console.log(chalk7.gray(` Roles: ${chalk7.white(config2.teamRoles.join(", "))}`));
|
|
2918
3254
|
console.log("");
|
|
2919
|
-
console.log(
|
|
2920
|
-
console.log(
|
|
2921
|
-
console.log(
|
|
3255
|
+
console.log(chalk7.white(" Internationalization:"));
|
|
3256
|
+
console.log(chalk7.gray(` Default: ${chalk7.white(config2.defaultLocale)}`));
|
|
3257
|
+
console.log(chalk7.gray(` Languages: ${chalk7.white(config2.supportedLocales.join(", "))}`));
|
|
2922
3258
|
console.log("");
|
|
2923
|
-
console.log(
|
|
2924
|
-
console.log(
|
|
2925
|
-
console.log(
|
|
3259
|
+
console.log(chalk7.white(" Billing:"));
|
|
3260
|
+
console.log(chalk7.gray(` Model: ${chalk7.white(config2.billingModel)}`));
|
|
3261
|
+
console.log(chalk7.gray(` Currency: ${chalk7.white(config2.currency.toUpperCase())}`));
|
|
2926
3262
|
console.log("");
|
|
2927
|
-
console.log(
|
|
2928
|
-
const enabledFeatures = Object.entries(
|
|
2929
|
-
console.log(
|
|
3263
|
+
console.log(chalk7.white(" Features:"));
|
|
3264
|
+
const enabledFeatures = Object.entries(config2.features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
|
|
3265
|
+
console.log(chalk7.gray(` Enabled: ${chalk7.white(enabledFeatures.join(", ") || "None")}`));
|
|
2930
3266
|
console.log("");
|
|
2931
|
-
console.log(
|
|
2932
|
-
const enabledAuth = Object.entries(
|
|
2933
|
-
console.log(
|
|
3267
|
+
console.log(chalk7.white(" Authentication:"));
|
|
3268
|
+
const enabledAuth = Object.entries(config2.auth).filter(([_, enabled]) => enabled).map(([method]) => formatAuthMethod(method));
|
|
3269
|
+
console.log(chalk7.gray(` Methods: ${chalk7.white(enabledAuth.join(", ") || "None")}`));
|
|
2934
3270
|
console.log("");
|
|
2935
|
-
console.log(
|
|
2936
|
-
const enabledDashboard = Object.entries(
|
|
2937
|
-
console.log(
|
|
3271
|
+
console.log(chalk7.white(" Dashboard:"));
|
|
3272
|
+
const enabledDashboard = Object.entries(config2.dashboard).filter(([_, enabled]) => enabled).map(([feature]) => formatDashboardFeature(feature));
|
|
3273
|
+
console.log(chalk7.gray(` Features: ${chalk7.white(enabledDashboard.join(", ") || "None")}`));
|
|
2938
3274
|
console.log("");
|
|
2939
|
-
console.log(
|
|
2940
|
-
const enabledDevTools = Object.entries(
|
|
2941
|
-
console.log(
|
|
3275
|
+
console.log(chalk7.white(" Dev Tools:"));
|
|
3276
|
+
const enabledDevTools = Object.entries(config2.dev).filter(([_, enabled]) => enabled).map(([tool]) => formatDevTool(tool));
|
|
3277
|
+
console.log(chalk7.gray(` Enabled: ${chalk7.white(enabledDevTools.join(", ") || "None")}`));
|
|
2942
3278
|
}
|
|
2943
3279
|
function formatAuthMethod(method) {
|
|
2944
3280
|
const mapping = {
|
|
@@ -2964,39 +3300,112 @@ function formatDevTool(tool) {
|
|
|
2964
3300
|
};
|
|
2965
3301
|
return mapping[tool] || tool;
|
|
2966
3302
|
}
|
|
2967
|
-
function showNextSteps(
|
|
3303
|
+
function showNextSteps(config2, referenceTheme = null, aiChoice = "skip") {
|
|
3304
|
+
const isMonorepo = config2.projectType === "web-mobile";
|
|
3305
|
+
const aiSetupDone = aiChoice === "claude";
|
|
2968
3306
|
console.log("");
|
|
2969
|
-
console.log(
|
|
2970
|
-
console.log(
|
|
2971
|
-
console.log(
|
|
3307
|
+
console.log(chalk7.cyan(" " + "=".repeat(60)));
|
|
3308
|
+
console.log(chalk7.bold.green(" \u2728 NextSpark project ready!"));
|
|
3309
|
+
console.log(chalk7.cyan(" " + "=".repeat(60)));
|
|
2972
3310
|
console.log("");
|
|
2973
|
-
console.log(
|
|
3311
|
+
console.log(chalk7.bold.white(" Next steps:"));
|
|
2974
3312
|
console.log("");
|
|
2975
|
-
|
|
2976
|
-
console.log(
|
|
3313
|
+
const envPath = isMonorepo ? "web/.env" : ".env";
|
|
3314
|
+
console.log(chalk7.white(" 1. Configure your environment:"));
|
|
3315
|
+
console.log(chalk7.gray(` Edit ${envPath} with your credentials:`));
|
|
2977
3316
|
console.log("");
|
|
2978
|
-
console.log(
|
|
2979
|
-
console.log(
|
|
2980
|
-
console.log(
|
|
3317
|
+
console.log(chalk7.yellow(" DATABASE_URL"));
|
|
3318
|
+
console.log(chalk7.gray(" PostgreSQL connection string"));
|
|
3319
|
+
console.log(chalk7.gray(` Recommended: ${chalk7.cyan("https://supabase.com")} | ${chalk7.cyan("https://neon.com")}`));
|
|
2981
3320
|
console.log("");
|
|
2982
|
-
console.log(
|
|
2983
|
-
console.log(
|
|
2984
|
-
console.log(
|
|
3321
|
+
console.log(chalk7.yellow(" BETTER_AUTH_SECRET"));
|
|
3322
|
+
console.log(chalk7.gray(" Generate with:"));
|
|
3323
|
+
console.log(chalk7.cyan(" openssl rand -base64 32"));
|
|
2985
3324
|
console.log("");
|
|
2986
|
-
console.log(
|
|
2987
|
-
console.log(
|
|
3325
|
+
console.log(chalk7.white(" 2. Run database migrations:"));
|
|
3326
|
+
console.log(chalk7.cyan(" pnpm db:migrate"));
|
|
2988
3327
|
console.log("");
|
|
2989
|
-
console.log(
|
|
2990
|
-
console.log(
|
|
3328
|
+
console.log(chalk7.white(" 3. Start the development server:"));
|
|
3329
|
+
console.log(chalk7.cyan(" pnpm dev"));
|
|
2991
3330
|
console.log("");
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3331
|
+
let nextStep = 4;
|
|
3332
|
+
if (isMonorepo) {
|
|
3333
|
+
console.log(chalk7.white(` ${nextStep}. (Optional) Start the mobile app:`));
|
|
3334
|
+
console.log(chalk7.cyan(" pnpm dev:mobile"));
|
|
3335
|
+
console.log(chalk7.gray(" Or: cd mobile && pnpm start"));
|
|
3336
|
+
console.log("");
|
|
3337
|
+
nextStep++;
|
|
3338
|
+
}
|
|
3339
|
+
if (aiSetupDone) {
|
|
3340
|
+
console.log(chalk7.white(` ${nextStep}. Start building with AI:`));
|
|
3341
|
+
console.log(chalk7.gray(" Open Claude Code in your project and run:"));
|
|
3342
|
+
console.log(chalk7.cyan(" /how-to:start"));
|
|
3343
|
+
console.log("");
|
|
3344
|
+
} else {
|
|
3345
|
+
console.log(chalk7.white(` ${nextStep}. (Optional) Setup AI workflows:`));
|
|
3346
|
+
console.log(chalk7.cyan(" nextspark setup:ai"));
|
|
3347
|
+
console.log("");
|
|
3348
|
+
}
|
|
3349
|
+
console.log(chalk7.gray(" " + "-".repeat(60)));
|
|
3350
|
+
if (isMonorepo) {
|
|
3351
|
+
console.log(chalk7.gray(` Structure: ${chalk7.white("Monorepo (web/ + mobile/)")}`));
|
|
3352
|
+
console.log(chalk7.gray(` Web theme: ${chalk7.white(`web/contents/themes/${config2.projectSlug}/`)}`));
|
|
3353
|
+
console.log(chalk7.gray(` Mobile app: ${chalk7.white("mobile/")}`));
|
|
3354
|
+
console.log(chalk7.gray(` Active theme: ${chalk7.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
|
|
3355
|
+
} else {
|
|
3356
|
+
console.log(chalk7.gray(` Theme: ${chalk7.white(`contents/themes/${config2.projectSlug}/`)}`));
|
|
3357
|
+
console.log(chalk7.gray(` Active theme: ${chalk7.green(`NEXT_PUBLIC_ACTIVE_THEME=${config2.projectSlug}`)}`));
|
|
3358
|
+
}
|
|
2995
3359
|
if (referenceTheme) {
|
|
2996
|
-
|
|
3360
|
+
const refPath = isMonorepo ? `web/contents/themes/${referenceTheme}/` : `contents/themes/${referenceTheme}/`;
|
|
3361
|
+
console.log(chalk7.gray(` Reference: ${chalk7.white(refPath)}`));
|
|
2997
3362
|
}
|
|
2998
|
-
console.log(
|
|
3363
|
+
console.log(chalk7.gray(` Docs: ${chalk7.cyan("https://nextspark.dev/docs")}`));
|
|
3364
|
+
console.log("");
|
|
3365
|
+
}
|
|
3366
|
+
async function promptAIWorkflowSetup(config2) {
|
|
2999
3367
|
console.log("");
|
|
3368
|
+
const choice = await select6({
|
|
3369
|
+
message: "Setup AI-assisted development workflows?",
|
|
3370
|
+
choices: [
|
|
3371
|
+
{ name: "Claude Code (Recommended)", value: "claude" },
|
|
3372
|
+
{ name: "Cursor (Coming soon)", value: "cursor" },
|
|
3373
|
+
{ name: "Antigravity (Coming soon)", value: "antigravity" },
|
|
3374
|
+
{ name: "Skip for now", value: "skip" }
|
|
3375
|
+
]
|
|
3376
|
+
});
|
|
3377
|
+
if (choice === "skip") {
|
|
3378
|
+
return "skip";
|
|
3379
|
+
}
|
|
3380
|
+
if (choice === "cursor" || choice === "antigravity") {
|
|
3381
|
+
showInfo(`${choice} support is coming soon. For now, use Claude Code.`);
|
|
3382
|
+
return "skip";
|
|
3383
|
+
}
|
|
3384
|
+
const projectRoot = process.cwd();
|
|
3385
|
+
const isMonorepo = isMonorepoProject(config2);
|
|
3386
|
+
try {
|
|
3387
|
+
execSync("pnpm add -D -w @nextsparkjs/ai-workflow", {
|
|
3388
|
+
cwd: projectRoot,
|
|
3389
|
+
stdio: "inherit"
|
|
3390
|
+
});
|
|
3391
|
+
let setupScript = join4(projectRoot, "node_modules", "@nextsparkjs", "ai-workflow", "scripts", "setup.mjs");
|
|
3392
|
+
if (!existsSync3(setupScript) && isMonorepo) {
|
|
3393
|
+
setupScript = join4(projectRoot, "web", "node_modules", "@nextsparkjs", "ai-workflow", "scripts", "setup.mjs");
|
|
3394
|
+
}
|
|
3395
|
+
if (existsSync3(setupScript)) {
|
|
3396
|
+
execSync(`node "${setupScript}" ${choice}`, {
|
|
3397
|
+
cwd: projectRoot,
|
|
3398
|
+
stdio: "inherit"
|
|
3399
|
+
});
|
|
3400
|
+
showSuccess("AI workflow setup complete!");
|
|
3401
|
+
} else {
|
|
3402
|
+
showWarning('AI workflow package installed but setup script not found. Run "nextspark setup:ai" manually.');
|
|
3403
|
+
}
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
showError('Failed to install AI workflow package. Run "nextspark setup:ai" later.');
|
|
3406
|
+
return "skip";
|
|
3407
|
+
}
|
|
3408
|
+
return choice;
|
|
3000
3409
|
}
|
|
3001
3410
|
function findLocalCoreTarball() {
|
|
3002
3411
|
const cwd = process.cwd();
|
|
@@ -3005,34 +3414,93 @@ function findLocalCoreTarball() {
|
|
|
3005
3414
|
const coreTarball = files.find(
|
|
3006
3415
|
(f) => f.includes("nextsparkjs-core") && f.endsWith(".tgz")
|
|
3007
3416
|
);
|
|
3008
|
-
if (coreTarball) {
|
|
3009
|
-
return
|
|
3417
|
+
if (coreTarball) {
|
|
3418
|
+
return join4(cwd, coreTarball);
|
|
3419
|
+
}
|
|
3420
|
+
} catch {
|
|
3421
|
+
}
|
|
3422
|
+
return null;
|
|
3423
|
+
}
|
|
3424
|
+
function isCoreInstalled() {
|
|
3425
|
+
const corePath = join4(process.cwd(), "node_modules", "@nextsparkjs", "core");
|
|
3426
|
+
return existsSync3(corePath);
|
|
3427
|
+
}
|
|
3428
|
+
async function installCore() {
|
|
3429
|
+
if (isCoreInstalled()) {
|
|
3430
|
+
return true;
|
|
3431
|
+
}
|
|
3432
|
+
const spinner = ora3({
|
|
3433
|
+
text: "Installing @nextsparkjs/core...",
|
|
3434
|
+
prefixText: " "
|
|
3435
|
+
}).start();
|
|
3436
|
+
try {
|
|
3437
|
+
const localTarball = findLocalCoreTarball();
|
|
3438
|
+
let packageSpec = "@nextsparkjs/core";
|
|
3439
|
+
if (localTarball) {
|
|
3440
|
+
packageSpec = localTarball;
|
|
3441
|
+
spinner.text = "Installing @nextsparkjs/core from local tarball...";
|
|
3442
|
+
}
|
|
3443
|
+
const useYarn = existsSync3(join4(process.cwd(), "yarn.lock"));
|
|
3444
|
+
const usePnpm = existsSync3(join4(process.cwd(), "pnpm-lock.yaml"));
|
|
3445
|
+
let installCmd;
|
|
3446
|
+
if (usePnpm) {
|
|
3447
|
+
installCmd = `pnpm add ${packageSpec}`;
|
|
3448
|
+
} else if (useYarn) {
|
|
3449
|
+
installCmd = `yarn add ${packageSpec}`;
|
|
3450
|
+
} else {
|
|
3451
|
+
installCmd = `npm install ${packageSpec}`;
|
|
3452
|
+
}
|
|
3453
|
+
spinner.stop();
|
|
3454
|
+
execSync(installCmd, {
|
|
3455
|
+
stdio: "inherit",
|
|
3456
|
+
cwd: process.cwd()
|
|
3457
|
+
});
|
|
3458
|
+
spinner.succeed(chalk7.green("@nextsparkjs/core installed successfully!"));
|
|
3459
|
+
return true;
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
spinner.fail(chalk7.red("Failed to install @nextsparkjs/core"));
|
|
3462
|
+
if (error instanceof Error) {
|
|
3463
|
+
console.log(chalk7.red(` Error: ${error.message}`));
|
|
3464
|
+
}
|
|
3465
|
+
console.log(chalk7.gray(" Hint: Make sure the package is available (npm registry or local tarball)"));
|
|
3466
|
+
return false;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
function isMobileInstalled() {
|
|
3470
|
+
const mobilePath = join4(process.cwd(), "node_modules", "@nextsparkjs", "mobile");
|
|
3471
|
+
return existsSync3(mobilePath);
|
|
3472
|
+
}
|
|
3473
|
+
function findLocalMobileTarball() {
|
|
3474
|
+
const cwd = process.cwd();
|
|
3475
|
+
try {
|
|
3476
|
+
const files = readdirSync(cwd);
|
|
3477
|
+
const mobileTarball = files.find(
|
|
3478
|
+
(f) => f.includes("nextsparkjs-mobile") && f.endsWith(".tgz")
|
|
3479
|
+
);
|
|
3480
|
+
if (mobileTarball) {
|
|
3481
|
+
return join4(cwd, mobileTarball);
|
|
3010
3482
|
}
|
|
3011
3483
|
} catch {
|
|
3012
3484
|
}
|
|
3013
3485
|
return null;
|
|
3014
3486
|
}
|
|
3015
|
-
function
|
|
3016
|
-
|
|
3017
|
-
return existsSync7(corePath);
|
|
3018
|
-
}
|
|
3019
|
-
async function installCore() {
|
|
3020
|
-
if (isCoreInstalled()) {
|
|
3487
|
+
async function installMobile() {
|
|
3488
|
+
if (isMobileInstalled()) {
|
|
3021
3489
|
return true;
|
|
3022
3490
|
}
|
|
3023
|
-
const spinner =
|
|
3024
|
-
text: "Installing @nextsparkjs/
|
|
3491
|
+
const spinner = ora3({
|
|
3492
|
+
text: "Installing @nextsparkjs/mobile...",
|
|
3025
3493
|
prefixText: " "
|
|
3026
3494
|
}).start();
|
|
3027
3495
|
try {
|
|
3028
|
-
const localTarball =
|
|
3029
|
-
let packageSpec = "@nextsparkjs/
|
|
3496
|
+
const localTarball = findLocalMobileTarball();
|
|
3497
|
+
let packageSpec = "@nextsparkjs/mobile";
|
|
3030
3498
|
if (localTarball) {
|
|
3031
3499
|
packageSpec = localTarball;
|
|
3032
|
-
spinner.text = "Installing @nextsparkjs/
|
|
3500
|
+
spinner.text = "Installing @nextsparkjs/mobile from local tarball...";
|
|
3033
3501
|
}
|
|
3034
|
-
const useYarn =
|
|
3035
|
-
const usePnpm =
|
|
3502
|
+
const useYarn = existsSync3(join4(process.cwd(), "yarn.lock"));
|
|
3503
|
+
const usePnpm = existsSync3(join4(process.cwd(), "pnpm-lock.yaml"));
|
|
3036
3504
|
let installCmd;
|
|
3037
3505
|
if (usePnpm) {
|
|
3038
3506
|
installCmd = `pnpm add ${packageSpec}`;
|
|
@@ -3041,33 +3509,22 @@ async function installCore() {
|
|
|
3041
3509
|
} else {
|
|
3042
3510
|
installCmd = `npm install ${packageSpec}`;
|
|
3043
3511
|
}
|
|
3512
|
+
spinner.stop();
|
|
3044
3513
|
execSync(installCmd, {
|
|
3045
|
-
stdio: "
|
|
3514
|
+
stdio: "inherit",
|
|
3046
3515
|
cwd: process.cwd()
|
|
3047
3516
|
});
|
|
3048
|
-
spinner.succeed(
|
|
3517
|
+
spinner.succeed(chalk7.green("@nextsparkjs/mobile installed successfully!"));
|
|
3049
3518
|
return true;
|
|
3050
3519
|
} catch (error) {
|
|
3051
|
-
spinner.fail(
|
|
3520
|
+
spinner.fail(chalk7.red("Failed to install @nextsparkjs/mobile"));
|
|
3052
3521
|
if (error instanceof Error) {
|
|
3053
|
-
console.log(
|
|
3522
|
+
console.log(chalk7.red(` Error: ${error.message}`));
|
|
3054
3523
|
}
|
|
3055
|
-
console.log(
|
|
3524
|
+
console.log(chalk7.gray(" Hint: Make sure the package is available (npm registry or local tarball)"));
|
|
3056
3525
|
return false;
|
|
3057
3526
|
}
|
|
3058
3527
|
}
|
|
3059
|
-
async function copyNpmrc() {
|
|
3060
|
-
const npmrcPath = join7(process.cwd(), ".npmrc");
|
|
3061
|
-
if (existsSync7(npmrcPath)) {
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
const npmrcContent = `# Hoist @nextsparkjs/core dependencies so they're accessible from the project
|
|
3065
|
-
# This is required for pnpm to make peer dependencies available
|
|
3066
|
-
public-hoist-pattern[]=*
|
|
3067
|
-
`;
|
|
3068
|
-
const { writeFileSync: writeFileSync3 } = await import("fs");
|
|
3069
|
-
writeFileSync3(npmrcPath, npmrcContent);
|
|
3070
|
-
}
|
|
3071
3528
|
|
|
3072
3529
|
// src/commands/init.ts
|
|
3073
3530
|
function getWizardMode(options) {
|
|
@@ -3081,10 +3538,10 @@ function parsePlugins(pluginsStr) {
|
|
|
3081
3538
|
}
|
|
3082
3539
|
function hasExistingProject() {
|
|
3083
3540
|
const projectRoot = process.cwd();
|
|
3084
|
-
return
|
|
3541
|
+
return existsSync4(join5(projectRoot, "contents")) || existsSync4(join5(projectRoot, ".nextspark"));
|
|
3085
3542
|
}
|
|
3086
3543
|
function generateInitialRegistries(registriesDir) {
|
|
3087
|
-
writeFileSync2(
|
|
3544
|
+
writeFileSync2(join5(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init
|
|
3088
3545
|
import type { ComponentType } from 'react'
|
|
3089
3546
|
|
|
3090
3547
|
export const BLOCK_REGISTRY: Record<string, {
|
|
@@ -3097,26 +3554,26 @@ export const BLOCK_REGISTRY: Record<string, {
|
|
|
3097
3554
|
|
|
3098
3555
|
export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
|
|
3099
3556
|
`);
|
|
3100
|
-
writeFileSync2(
|
|
3557
|
+
writeFileSync2(join5(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init
|
|
3101
3558
|
export const THEME_REGISTRY: Record<string, unknown> = {}
|
|
3102
3559
|
`);
|
|
3103
|
-
writeFileSync2(
|
|
3560
|
+
writeFileSync2(join5(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init
|
|
3104
3561
|
export const ENTITY_REGISTRY: Record<string, unknown> = {}
|
|
3105
3562
|
`);
|
|
3106
|
-
writeFileSync2(
|
|
3563
|
+
writeFileSync2(join5(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init
|
|
3107
3564
|
export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
|
|
3108
3565
|
export function parseChildEntity(path: string) { return null }
|
|
3109
3566
|
export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
|
|
3110
3567
|
export function clientMetaSystemAdapter() { return {} }
|
|
3111
3568
|
export type ClientEntityConfig = Record<string, unknown>
|
|
3112
3569
|
`);
|
|
3113
|
-
writeFileSync2(
|
|
3570
|
+
writeFileSync2(join5(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init
|
|
3114
3571
|
export const BILLING_REGISTRY = { plans: [], features: [] }
|
|
3115
3572
|
`);
|
|
3116
|
-
writeFileSync2(
|
|
3573
|
+
writeFileSync2(join5(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init
|
|
3117
3574
|
export const PLUGIN_REGISTRY: Record<string, unknown> = {}
|
|
3118
3575
|
`);
|
|
3119
|
-
writeFileSync2(
|
|
3576
|
+
writeFileSync2(join5(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init
|
|
3120
3577
|
export const FLOW_REGISTRY: Record<string, unknown> = {}
|
|
3121
3578
|
export const FEATURE_REGISTRY: Record<string, unknown> = {}
|
|
3122
3579
|
export const TAGS_REGISTRY: Record<string, unknown> = {}
|
|
@@ -3124,11 +3581,11 @@ export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
|
|
|
3124
3581
|
export type FlowEntry = unknown
|
|
3125
3582
|
export type FeatureEntry = unknown
|
|
3126
3583
|
`);
|
|
3127
|
-
writeFileSync2(
|
|
3584
|
+
writeFileSync2(join5(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init
|
|
3128
3585
|
export const DOCS_REGISTRY = { sections: [], pages: [] }
|
|
3129
3586
|
export type DocSectionMeta = { title: string; slug: string }
|
|
3130
3587
|
`);
|
|
3131
|
-
writeFileSync2(
|
|
3588
|
+
writeFileSync2(join5(registriesDir, "index.ts"), `// Auto-generated by nextspark init
|
|
3132
3589
|
export * from './block-registry'
|
|
3133
3590
|
export * from './theme-registry'
|
|
3134
3591
|
export * from './entity-registry'
|
|
@@ -3140,21 +3597,21 @@ export * from './docs-registry'
|
|
|
3140
3597
|
`);
|
|
3141
3598
|
}
|
|
3142
3599
|
async function simpleInit(options) {
|
|
3143
|
-
const spinner =
|
|
3600
|
+
const spinner = ora4("Initializing NextSpark project...").start();
|
|
3144
3601
|
const projectRoot = process.cwd();
|
|
3145
3602
|
try {
|
|
3146
|
-
const nextspark =
|
|
3147
|
-
const registriesDir =
|
|
3148
|
-
if (!
|
|
3603
|
+
const nextspark = join5(projectRoot, ".nextspark");
|
|
3604
|
+
const registriesDir = join5(nextspark, "registries");
|
|
3605
|
+
if (!existsSync4(registriesDir) || options.force) {
|
|
3149
3606
|
mkdirSync2(registriesDir, { recursive: true });
|
|
3150
3607
|
spinner.text = "Creating .nextspark/registries/";
|
|
3151
3608
|
generateInitialRegistries(registriesDir);
|
|
3152
3609
|
spinner.text = "Generated initial registries";
|
|
3153
3610
|
}
|
|
3154
|
-
const tsconfigPath =
|
|
3155
|
-
if (
|
|
3611
|
+
const tsconfigPath = join5(projectRoot, "tsconfig.json");
|
|
3612
|
+
if (existsSync4(tsconfigPath)) {
|
|
3156
3613
|
spinner.text = "Updating tsconfig.json paths...";
|
|
3157
|
-
const tsconfig = JSON.parse(
|
|
3614
|
+
const tsconfig = JSON.parse(readFileSync4(tsconfigPath, "utf-8"));
|
|
3158
3615
|
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
3159
3616
|
tsconfig.compilerOptions.paths = {
|
|
3160
3617
|
...tsconfig.compilerOptions.paths,
|
|
@@ -3163,8 +3620,8 @@ async function simpleInit(options) {
|
|
|
3163
3620
|
};
|
|
3164
3621
|
writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
3165
3622
|
}
|
|
3166
|
-
const envExample =
|
|
3167
|
-
if (!
|
|
3623
|
+
const envExample = join5(projectRoot, ".env.example");
|
|
3624
|
+
if (!existsSync4(envExample)) {
|
|
3168
3625
|
const envContent = `# NextSpark Configuration
|
|
3169
3626
|
DATABASE_URL="postgresql://user:password@localhost:5432/db"
|
|
3170
3627
|
BETTER_AUTH_SECRET="your-secret-here-min-32-chars"
|
|
@@ -3175,14 +3632,14 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
|
3175
3632
|
spinner.text = "Created .env.example";
|
|
3176
3633
|
}
|
|
3177
3634
|
spinner.succeed("NextSpark initialized successfully!");
|
|
3178
|
-
console.log(
|
|
3635
|
+
console.log(chalk8.blue("\nNext steps:"));
|
|
3179
3636
|
console.log(" 1. Copy .env.example to .env and configure");
|
|
3180
3637
|
console.log(" 2. Run: nextspark generate");
|
|
3181
3638
|
console.log(" 3. Run: nextspark dev");
|
|
3182
3639
|
} catch (error) {
|
|
3183
3640
|
spinner.fail("Initialization failed");
|
|
3184
3641
|
if (error instanceof Error) {
|
|
3185
|
-
console.error(
|
|
3642
|
+
console.error(chalk8.red(error.message));
|
|
3186
3643
|
}
|
|
3187
3644
|
process.exit(1);
|
|
3188
3645
|
}
|
|
@@ -3193,8 +3650,8 @@ async function initCommand(options) {
|
|
|
3193
3650
|
return;
|
|
3194
3651
|
}
|
|
3195
3652
|
if (hasExistingProject() && !options.wizard && !options.preset && !options.theme) {
|
|
3196
|
-
console.log(
|
|
3197
|
-
console.log(
|
|
3653
|
+
console.log(chalk8.yellow("Existing NextSpark project detected."));
|
|
3654
|
+
console.log(chalk8.gray("Use --wizard to run the full wizard, or --registries-only for just registries."));
|
|
3198
3655
|
await simpleInit(options);
|
|
3199
3656
|
return;
|
|
3200
3657
|
}
|
|
@@ -3206,25 +3663,133 @@ async function initCommand(options) {
|
|
|
3206
3663
|
yes: options.yes,
|
|
3207
3664
|
name: options.name,
|
|
3208
3665
|
slug: options.slug,
|
|
3209
|
-
description: options.description
|
|
3666
|
+
description: options.description,
|
|
3667
|
+
type: options.type
|
|
3210
3668
|
};
|
|
3211
3669
|
await runWizard(wizardOptions);
|
|
3212
3670
|
}
|
|
3213
3671
|
|
|
3672
|
+
// src/commands/add-mobile.ts
|
|
3673
|
+
import { existsSync as existsSync5, cpSync as cpSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync, mkdirSync as mkdirSync3 } from "fs";
|
|
3674
|
+
import { join as join6 } from "path";
|
|
3675
|
+
import chalk9 from "chalk";
|
|
3676
|
+
import ora5 from "ora";
|
|
3677
|
+
import { execSync as execSync2 } from "child_process";
|
|
3678
|
+
function findMobileCoreDir() {
|
|
3679
|
+
const projectRoot = process.cwd();
|
|
3680
|
+
const npmPath = join6(projectRoot, "node_modules", "@nextsparkjs", "mobile");
|
|
3681
|
+
if (existsSync5(npmPath)) {
|
|
3682
|
+
return npmPath;
|
|
3683
|
+
}
|
|
3684
|
+
const monoPath = join6(projectRoot, "packages", "mobile");
|
|
3685
|
+
if (existsSync5(monoPath)) {
|
|
3686
|
+
return monoPath;
|
|
3687
|
+
}
|
|
3688
|
+
const parentNpmPath = join6(projectRoot, "..", "node_modules", "@nextsparkjs", "mobile");
|
|
3689
|
+
if (existsSync5(parentNpmPath)) {
|
|
3690
|
+
return parentNpmPath;
|
|
3691
|
+
}
|
|
3692
|
+
throw new Error(
|
|
3693
|
+
"Could not find @nextsparkjs/mobile package.\nRun: npm install @nextsparkjs/mobile"
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
async function addMobileCommand(options = {}) {
|
|
3697
|
+
const projectRoot = process.cwd();
|
|
3698
|
+
const mobileDir = join6(projectRoot, "mobile");
|
|
3699
|
+
console.log();
|
|
3700
|
+
console.log(chalk9.bold("Adding NextSpark Mobile App"));
|
|
3701
|
+
console.log();
|
|
3702
|
+
if (existsSync5(mobileDir) && !options.force) {
|
|
3703
|
+
console.log(chalk9.red("Error: Mobile app already exists at mobile/"));
|
|
3704
|
+
console.log(chalk9.gray("Use --force to overwrite"));
|
|
3705
|
+
process.exit(1);
|
|
3706
|
+
}
|
|
3707
|
+
let mobileCoreDir;
|
|
3708
|
+
try {
|
|
3709
|
+
mobileCoreDir = findMobileCoreDir();
|
|
3710
|
+
} catch (error) {
|
|
3711
|
+
console.log(chalk9.red(error.message));
|
|
3712
|
+
process.exit(1);
|
|
3713
|
+
}
|
|
3714
|
+
const templatesDir = join6(mobileCoreDir, "templates");
|
|
3715
|
+
if (!existsSync5(templatesDir)) {
|
|
3716
|
+
console.log(chalk9.red("Error: Could not find mobile templates"));
|
|
3717
|
+
console.log(chalk9.gray(`Expected at: ${templatesDir}`));
|
|
3718
|
+
process.exit(1);
|
|
3719
|
+
}
|
|
3720
|
+
const copySpinner = ora5("Copying mobile app template...").start();
|
|
3721
|
+
try {
|
|
3722
|
+
mkdirSync3(mobileDir, { recursive: true });
|
|
3723
|
+
cpSync2(templatesDir, mobileDir, { recursive: true });
|
|
3724
|
+
const pkgTemplatePath = join6(mobileDir, "package.json.template");
|
|
3725
|
+
const pkgPath = join6(mobileDir, "package.json");
|
|
3726
|
+
if (existsSync5(pkgTemplatePath)) {
|
|
3727
|
+
renameSync(pkgTemplatePath, pkgPath);
|
|
3728
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
3729
|
+
const rootPkgPath = join6(projectRoot, "package.json");
|
|
3730
|
+
if (existsSync5(rootPkgPath)) {
|
|
3731
|
+
const rootPkg = JSON.parse(readFileSync5(rootPkgPath, "utf-8"));
|
|
3732
|
+
const rawName = rootPkg.name || "my-project";
|
|
3733
|
+
if (rawName.startsWith("@")) {
|
|
3734
|
+
const scopeMatch = rawName.match(/^@[\w-]+/);
|
|
3735
|
+
const scope = scopeMatch ? scopeMatch[0] : "";
|
|
3736
|
+
const projectName = rawName.replace(/^@[\w-]+\//, "");
|
|
3737
|
+
pkg2.name = `${scope}/${projectName}-mobile`;
|
|
3738
|
+
} else {
|
|
3739
|
+
pkg2.name = `${rawName}-mobile`;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
writeFileSync3(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
3743
|
+
}
|
|
3744
|
+
copySpinner.succeed("Mobile app template copied");
|
|
3745
|
+
} catch (error) {
|
|
3746
|
+
copySpinner.fail("Failed to copy templates");
|
|
3747
|
+
console.log(chalk9.red(error.message));
|
|
3748
|
+
process.exit(1);
|
|
3749
|
+
}
|
|
3750
|
+
if (!options.skipInstall) {
|
|
3751
|
+
const installSpinner = ora5("Installing dependencies...").start();
|
|
3752
|
+
try {
|
|
3753
|
+
execSync2("npm install", {
|
|
3754
|
+
cwd: mobileDir,
|
|
3755
|
+
stdio: "pipe",
|
|
3756
|
+
timeout: 3e5
|
|
3757
|
+
// 5 minutes
|
|
3758
|
+
});
|
|
3759
|
+
installSpinner.succeed("Dependencies installed");
|
|
3760
|
+
} catch (error) {
|
|
3761
|
+
installSpinner.fail("Failed to install dependencies");
|
|
3762
|
+
console.log(chalk9.yellow(" Run `npm install` in mobile/ manually"));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
console.log();
|
|
3766
|
+
console.log(chalk9.green.bold(" Mobile app created successfully!"));
|
|
3767
|
+
console.log();
|
|
3768
|
+
console.log(chalk9.bold(" Next steps:"));
|
|
3769
|
+
console.log();
|
|
3770
|
+
console.log(` ${chalk9.cyan("1.")} cd mobile`);
|
|
3771
|
+
console.log(` ${chalk9.cyan("2.")} Update ${chalk9.bold("app.config.ts")} with your app name and bundle ID`);
|
|
3772
|
+
console.log(` ${chalk9.cyan("3.")} Add your entities in ${chalk9.bold("src/entities/")}`);
|
|
3773
|
+
console.log(` ${chalk9.cyan("4.")} npm start`);
|
|
3774
|
+
console.log();
|
|
3775
|
+
console.log(chalk9.gray(" Documentation: https://nextspark.dev/docs/mobile"));
|
|
3776
|
+
console.log();
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3214
3779
|
// src/commands/doctor.ts
|
|
3215
|
-
import
|
|
3780
|
+
import chalk11 from "chalk";
|
|
3216
3781
|
|
|
3217
3782
|
// src/doctor/index.ts
|
|
3218
|
-
import
|
|
3783
|
+
import chalk10 from "chalk";
|
|
3219
3784
|
|
|
3220
3785
|
// src/doctor/checks/dependencies.ts
|
|
3221
|
-
import
|
|
3222
|
-
import
|
|
3786
|
+
import fs9 from "fs-extra";
|
|
3787
|
+
import path8 from "path";
|
|
3223
3788
|
async function checkDependencies() {
|
|
3224
3789
|
const cwd = process.cwd();
|
|
3225
|
-
const nodeModulesPath =
|
|
3226
|
-
const packageJsonPath =
|
|
3227
|
-
if (!await
|
|
3790
|
+
const nodeModulesPath = path8.join(cwd, "node_modules");
|
|
3791
|
+
const packageJsonPath = path8.join(cwd, "package.json");
|
|
3792
|
+
if (!await fs9.pathExists(packageJsonPath)) {
|
|
3228
3793
|
return {
|
|
3229
3794
|
name: "Dependencies",
|
|
3230
3795
|
status: "fail",
|
|
@@ -3232,7 +3797,7 @@ async function checkDependencies() {
|
|
|
3232
3797
|
fix: "Ensure you are in a NextSpark project directory"
|
|
3233
3798
|
};
|
|
3234
3799
|
}
|
|
3235
|
-
if (!await
|
|
3800
|
+
if (!await fs9.pathExists(nodeModulesPath)) {
|
|
3236
3801
|
return {
|
|
3237
3802
|
name: "Dependencies",
|
|
3238
3803
|
status: "fail",
|
|
@@ -3242,7 +3807,7 @@ async function checkDependencies() {
|
|
|
3242
3807
|
}
|
|
3243
3808
|
let packageJson;
|
|
3244
3809
|
try {
|
|
3245
|
-
packageJson = await
|
|
3810
|
+
packageJson = await fs9.readJson(packageJsonPath);
|
|
3246
3811
|
} catch {
|
|
3247
3812
|
return {
|
|
3248
3813
|
name: "Dependencies",
|
|
@@ -3259,14 +3824,14 @@ async function checkDependencies() {
|
|
|
3259
3824
|
const criticalDeps = ["next", "react", "react-dom"];
|
|
3260
3825
|
for (const dep of criticalDeps) {
|
|
3261
3826
|
if (allDependencies[dep]) {
|
|
3262
|
-
const depPath =
|
|
3263
|
-
if (!await
|
|
3827
|
+
const depPath = path8.join(nodeModulesPath, dep);
|
|
3828
|
+
if (!await fs9.pathExists(depPath)) {
|
|
3264
3829
|
missingDeps.push(dep);
|
|
3265
3830
|
}
|
|
3266
3831
|
}
|
|
3267
3832
|
}
|
|
3268
|
-
const nextsparksCorePath =
|
|
3269
|
-
const hasNextSparkCore = await
|
|
3833
|
+
const nextsparksCorePath = path8.join(nodeModulesPath, "@nextsparkjs", "core");
|
|
3834
|
+
const hasNextSparkCore = await fs9.pathExists(nextsparksCorePath);
|
|
3270
3835
|
if (missingDeps.length > 0) {
|
|
3271
3836
|
return {
|
|
3272
3837
|
name: "Dependencies",
|
|
@@ -3291,8 +3856,8 @@ async function checkDependencies() {
|
|
|
3291
3856
|
}
|
|
3292
3857
|
|
|
3293
3858
|
// src/doctor/checks/config.ts
|
|
3294
|
-
import
|
|
3295
|
-
import
|
|
3859
|
+
import fs10 from "fs-extra";
|
|
3860
|
+
import path9 from "path";
|
|
3296
3861
|
var REQUIRED_CONFIG_FILES = [
|
|
3297
3862
|
"next.config.ts",
|
|
3298
3863
|
"tailwind.config.ts",
|
|
@@ -3303,13 +3868,13 @@ async function checkConfigs() {
|
|
|
3303
3868
|
const missingFiles = [];
|
|
3304
3869
|
const invalidFiles = [];
|
|
3305
3870
|
for (const file of REQUIRED_CONFIG_FILES) {
|
|
3306
|
-
const filePath =
|
|
3871
|
+
const filePath = path9.join(cwd, file);
|
|
3307
3872
|
if (file.endsWith(".ts")) {
|
|
3308
|
-
const jsPath =
|
|
3309
|
-
const mjsPath =
|
|
3310
|
-
const tsExists = await
|
|
3311
|
-
const jsExists = await
|
|
3312
|
-
const mjsExists = await
|
|
3873
|
+
const jsPath = path9.join(cwd, file.replace(".ts", ".js"));
|
|
3874
|
+
const mjsPath = path9.join(cwd, file.replace(".ts", ".mjs"));
|
|
3875
|
+
const tsExists = await fs10.pathExists(filePath);
|
|
3876
|
+
const jsExists = await fs10.pathExists(jsPath);
|
|
3877
|
+
const mjsExists = await fs10.pathExists(mjsPath);
|
|
3313
3878
|
if (!tsExists && !jsExists && !mjsExists) {
|
|
3314
3879
|
if (!file.includes("next.config") && !file.includes("tailwind.config")) {
|
|
3315
3880
|
missingFiles.push(file);
|
|
@@ -3317,14 +3882,14 @@ async function checkConfigs() {
|
|
|
3317
3882
|
}
|
|
3318
3883
|
continue;
|
|
3319
3884
|
}
|
|
3320
|
-
if (!await
|
|
3885
|
+
if (!await fs10.pathExists(filePath)) {
|
|
3321
3886
|
missingFiles.push(file);
|
|
3322
3887
|
}
|
|
3323
3888
|
}
|
|
3324
|
-
const tsconfigPath =
|
|
3325
|
-
if (await
|
|
3889
|
+
const tsconfigPath = path9.join(cwd, "tsconfig.json");
|
|
3890
|
+
if (await fs10.pathExists(tsconfigPath)) {
|
|
3326
3891
|
try {
|
|
3327
|
-
const content = await
|
|
3892
|
+
const content = await fs10.readFile(tsconfigPath, "utf-8");
|
|
3328
3893
|
JSON.parse(content);
|
|
3329
3894
|
} catch {
|
|
3330
3895
|
invalidFiles.push("tsconfig.json");
|
|
@@ -3332,14 +3897,14 @@ async function checkConfigs() {
|
|
|
3332
3897
|
} else {
|
|
3333
3898
|
missingFiles.push("tsconfig.json");
|
|
3334
3899
|
}
|
|
3335
|
-
const configDir =
|
|
3336
|
-
if (await
|
|
3337
|
-
const configFiles = await
|
|
3900
|
+
const configDir = path9.join(cwd, "config");
|
|
3901
|
+
if (await fs10.pathExists(configDir)) {
|
|
3902
|
+
const configFiles = await fs10.readdir(configDir);
|
|
3338
3903
|
const tsConfigFiles = configFiles.filter((f) => f.endsWith(".ts"));
|
|
3339
3904
|
for (const file of tsConfigFiles) {
|
|
3340
|
-
const filePath =
|
|
3905
|
+
const filePath = path9.join(configDir, file);
|
|
3341
3906
|
try {
|
|
3342
|
-
const content = await
|
|
3907
|
+
const content = await fs10.readFile(filePath, "utf-8");
|
|
3343
3908
|
if (content.trim().length === 0) {
|
|
3344
3909
|
invalidFiles.push(`config/${file}`);
|
|
3345
3910
|
}
|
|
@@ -3372,8 +3937,8 @@ async function checkConfigs() {
|
|
|
3372
3937
|
}
|
|
3373
3938
|
|
|
3374
3939
|
// src/doctor/checks/database.ts
|
|
3375
|
-
import
|
|
3376
|
-
import
|
|
3940
|
+
import fs11 from "fs-extra";
|
|
3941
|
+
import path10 from "path";
|
|
3377
3942
|
function parseEnvFile(content) {
|
|
3378
3943
|
const result = {};
|
|
3379
3944
|
const lines = content.split("\n");
|
|
@@ -3396,25 +3961,25 @@ function parseEnvFile(content) {
|
|
|
3396
3961
|
}
|
|
3397
3962
|
async function checkDatabase() {
|
|
3398
3963
|
const cwd = process.cwd();
|
|
3399
|
-
const envPath =
|
|
3400
|
-
const envLocalPath =
|
|
3964
|
+
const envPath = path10.join(cwd, ".env");
|
|
3965
|
+
const envLocalPath = path10.join(cwd, ".env.local");
|
|
3401
3966
|
let envVars = {};
|
|
3402
|
-
if (await
|
|
3967
|
+
if (await fs11.pathExists(envLocalPath)) {
|
|
3403
3968
|
try {
|
|
3404
|
-
const content = await
|
|
3969
|
+
const content = await fs11.readFile(envLocalPath, "utf-8");
|
|
3405
3970
|
envVars = { ...envVars, ...parseEnvFile(content) };
|
|
3406
3971
|
} catch {
|
|
3407
3972
|
}
|
|
3408
3973
|
}
|
|
3409
|
-
if (await
|
|
3974
|
+
if (await fs11.pathExists(envPath)) {
|
|
3410
3975
|
try {
|
|
3411
|
-
const content = await
|
|
3976
|
+
const content = await fs11.readFile(envPath, "utf-8");
|
|
3412
3977
|
envVars = { ...envVars, ...parseEnvFile(content) };
|
|
3413
3978
|
} catch {
|
|
3414
3979
|
}
|
|
3415
3980
|
}
|
|
3416
3981
|
const databaseUrl = envVars["DATABASE_URL"] || process.env.DATABASE_URL;
|
|
3417
|
-
if (!await
|
|
3982
|
+
if (!await fs11.pathExists(envPath) && !await fs11.pathExists(envLocalPath)) {
|
|
3418
3983
|
return {
|
|
3419
3984
|
name: "Database",
|
|
3420
3985
|
status: "warn",
|
|
@@ -3455,22 +4020,22 @@ async function checkDatabase() {
|
|
|
3455
4020
|
}
|
|
3456
4021
|
|
|
3457
4022
|
// src/doctor/checks/imports.ts
|
|
3458
|
-
import
|
|
3459
|
-
import
|
|
4023
|
+
import fs12 from "fs-extra";
|
|
4024
|
+
import path11 from "path";
|
|
3460
4025
|
async function checkCorePackage(cwd) {
|
|
3461
|
-
const nodeModulesPath =
|
|
3462
|
-
if (!await
|
|
4026
|
+
const nodeModulesPath = path11.join(cwd, "node_modules", "@nextsparkjs", "core");
|
|
4027
|
+
if (!await fs12.pathExists(nodeModulesPath)) {
|
|
3463
4028
|
return { accessible: false, reason: "@nextsparkjs/core not found in node_modules" };
|
|
3464
4029
|
}
|
|
3465
|
-
const packageJsonPath =
|
|
3466
|
-
if (!await
|
|
4030
|
+
const packageJsonPath = path11.join(nodeModulesPath, "package.json");
|
|
4031
|
+
if (!await fs12.pathExists(packageJsonPath)) {
|
|
3467
4032
|
return { accessible: false, reason: "@nextsparkjs/core package.json not found" };
|
|
3468
4033
|
}
|
|
3469
4034
|
try {
|
|
3470
|
-
const packageJson = await
|
|
4035
|
+
const packageJson = await fs12.readJson(packageJsonPath);
|
|
3471
4036
|
const mainEntry = packageJson.main || packageJson.module || "./dist/index.js";
|
|
3472
|
-
const mainPath =
|
|
3473
|
-
if (!await
|
|
4037
|
+
const mainPath = path11.join(nodeModulesPath, mainEntry);
|
|
4038
|
+
if (!await fs12.pathExists(mainPath)) {
|
|
3474
4039
|
return { accessible: false, reason: "@nextsparkjs/core entry point not found" };
|
|
3475
4040
|
}
|
|
3476
4041
|
} catch {
|
|
@@ -3483,8 +4048,8 @@ async function scanForBrokenImports(cwd) {
|
|
|
3483
4048
|
const srcDirs = ["src", "app", "pages", "components", "lib"];
|
|
3484
4049
|
const existingDirs = [];
|
|
3485
4050
|
for (const dir of srcDirs) {
|
|
3486
|
-
const dirPath =
|
|
3487
|
-
if (await
|
|
4051
|
+
const dirPath = path11.join(cwd, dir);
|
|
4052
|
+
if (await fs12.pathExists(dirPath)) {
|
|
3488
4053
|
existingDirs.push(dirPath);
|
|
3489
4054
|
}
|
|
3490
4055
|
}
|
|
@@ -3493,32 +4058,32 @@ async function scanForBrokenImports(cwd) {
|
|
|
3493
4058
|
for (const dir of existingDirs) {
|
|
3494
4059
|
if (filesScanned >= maxFilesToScan) break;
|
|
3495
4060
|
try {
|
|
3496
|
-
const files = await
|
|
4061
|
+
const files = await fs12.readdir(dir, { withFileTypes: true });
|
|
3497
4062
|
for (const file of files) {
|
|
3498
4063
|
if (filesScanned >= maxFilesToScan) break;
|
|
3499
4064
|
if (file.isFile() && (file.name.endsWith(".ts") || file.name.endsWith(".tsx"))) {
|
|
3500
|
-
const filePath =
|
|
4065
|
+
const filePath = path11.join(dir, file.name);
|
|
3501
4066
|
try {
|
|
3502
|
-
const content = await
|
|
4067
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
3503
4068
|
const importMatches = content.match(/from ['"](@?[^'"]+)['"]/g);
|
|
3504
4069
|
if (importMatches) {
|
|
3505
4070
|
for (const match of importMatches) {
|
|
3506
4071
|
const importPath = match.replace(/from ['"]/g, "").replace(/['"]/g, "");
|
|
3507
4072
|
if (importPath.startsWith(".")) {
|
|
3508
|
-
const absoluteImportPath =
|
|
4073
|
+
const absoluteImportPath = path11.resolve(path11.dirname(filePath), importPath);
|
|
3509
4074
|
const possiblePaths = [
|
|
3510
4075
|
absoluteImportPath,
|
|
3511
4076
|
`${absoluteImportPath}.ts`,
|
|
3512
4077
|
`${absoluteImportPath}.tsx`,
|
|
3513
4078
|
`${absoluteImportPath}.js`,
|
|
3514
4079
|
`${absoluteImportPath}.jsx`,
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
4080
|
+
path11.join(absoluteImportPath, "index.ts"),
|
|
4081
|
+
path11.join(absoluteImportPath, "index.tsx"),
|
|
4082
|
+
path11.join(absoluteImportPath, "index.js")
|
|
3518
4083
|
];
|
|
3519
4084
|
const exists = await Promise.any(
|
|
3520
4085
|
possiblePaths.map(async (p) => {
|
|
3521
|
-
if (await
|
|
4086
|
+
if (await fs12.pathExists(p)) return true;
|
|
3522
4087
|
throw new Error("Not found");
|
|
3523
4088
|
})
|
|
3524
4089
|
).catch(() => false);
|
|
@@ -3542,11 +4107,11 @@ async function checkImports() {
|
|
|
3542
4107
|
const cwd = process.cwd();
|
|
3543
4108
|
const coreCheck = await checkCorePackage(cwd);
|
|
3544
4109
|
if (!coreCheck.accessible) {
|
|
3545
|
-
const packageJsonPath =
|
|
4110
|
+
const packageJsonPath = path11.join(cwd, "package.json");
|
|
3546
4111
|
let hasCoreDep = false;
|
|
3547
|
-
if (await
|
|
4112
|
+
if (await fs12.pathExists(packageJsonPath)) {
|
|
3548
4113
|
try {
|
|
3549
|
-
const packageJson = await
|
|
4114
|
+
const packageJson = await fs12.readJson(packageJsonPath);
|
|
3550
4115
|
hasCoreDep = !!(packageJson.dependencies?.["@nextsparkjs/core"] || packageJson.devDependencies?.["@nextsparkjs/core"]);
|
|
3551
4116
|
} catch {
|
|
3552
4117
|
}
|
|
@@ -3583,25 +4148,25 @@ async function checkImports() {
|
|
|
3583
4148
|
|
|
3584
4149
|
// src/doctor/index.ts
|
|
3585
4150
|
var STATUS_ICONS = {
|
|
3586
|
-
pass:
|
|
3587
|
-
warn:
|
|
3588
|
-
fail:
|
|
4151
|
+
pass: chalk10.green("\u2713"),
|
|
4152
|
+
warn: chalk10.yellow("\u26A0"),
|
|
4153
|
+
fail: chalk10.red("\u2717")
|
|
3589
4154
|
};
|
|
3590
4155
|
function formatResult(result) {
|
|
3591
4156
|
const icon = STATUS_ICONS[result.status];
|
|
3592
|
-
const nameColor = result.status === "fail" ?
|
|
4157
|
+
const nameColor = result.status === "fail" ? chalk10.red : result.status === "warn" ? chalk10.yellow : chalk10.white;
|
|
3593
4158
|
const name = nameColor(result.name.padEnd(18));
|
|
3594
|
-
const message =
|
|
4159
|
+
const message = chalk10.gray(result.message);
|
|
3595
4160
|
let output = `${icon} ${name} ${message}`;
|
|
3596
4161
|
if (result.fix && result.status !== "pass") {
|
|
3597
4162
|
output += `
|
|
3598
|
-
${" ".repeat(22)}${
|
|
4163
|
+
${" ".repeat(22)}${chalk10.cyan("\u2192")} ${chalk10.cyan(result.fix)}`;
|
|
3599
4164
|
}
|
|
3600
4165
|
return output;
|
|
3601
4166
|
}
|
|
3602
4167
|
function showHeader() {
|
|
3603
4168
|
console.log("");
|
|
3604
|
-
console.log(
|
|
4169
|
+
console.log(chalk10.cyan("\u{1FA7A} NextSpark Health Check"));
|
|
3605
4170
|
console.log("");
|
|
3606
4171
|
}
|
|
3607
4172
|
function showSummary(results) {
|
|
@@ -3609,11 +4174,11 @@ function showSummary(results) {
|
|
|
3609
4174
|
const warnings = results.filter((r) => r.status === "warn").length;
|
|
3610
4175
|
const failed = results.filter((r) => r.status === "fail").length;
|
|
3611
4176
|
console.log("");
|
|
3612
|
-
console.log(
|
|
4177
|
+
console.log(chalk10.gray("-".repeat(50)));
|
|
3613
4178
|
const summary = [
|
|
3614
|
-
|
|
3615
|
-
warnings > 0 ?
|
|
3616
|
-
failed > 0 ?
|
|
4179
|
+
chalk10.green(`${passed} passed`),
|
|
4180
|
+
warnings > 0 ? chalk10.yellow(`${warnings} warning${warnings > 1 ? "s" : ""}`) : null,
|
|
4181
|
+
failed > 0 ? chalk10.red(`${failed} failed`) : null
|
|
3617
4182
|
].filter(Boolean).join(", ");
|
|
3618
4183
|
console.log(`Summary: ${summary}`);
|
|
3619
4184
|
console.log("");
|
|
@@ -3655,7 +4220,7 @@ async function runDoctorCommand() {
|
|
|
3655
4220
|
var isDirectExecution = process.argv[1]?.includes("doctor") || process.argv.includes("doctor");
|
|
3656
4221
|
if (isDirectExecution && typeof __require !== "undefined") {
|
|
3657
4222
|
runDoctorCommand().catch((error) => {
|
|
3658
|
-
console.error(
|
|
4223
|
+
console.error(chalk10.red("An unexpected error occurred:"), error.message);
|
|
3659
4224
|
process.exit(1);
|
|
3660
4225
|
});
|
|
3661
4226
|
}
|
|
@@ -3666,16 +4231,458 @@ async function doctorCommand() {
|
|
|
3666
4231
|
await runDoctorCommand();
|
|
3667
4232
|
} catch (error) {
|
|
3668
4233
|
if (error instanceof Error) {
|
|
3669
|
-
console.error(
|
|
4234
|
+
console.error(chalk11.red(`Error: ${error.message}`));
|
|
4235
|
+
}
|
|
4236
|
+
process.exit(1);
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
// src/commands/db.ts
|
|
4241
|
+
import { spawn } from "child_process";
|
|
4242
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
4243
|
+
import { join as join7 } from "path";
|
|
4244
|
+
import chalk12 from "chalk";
|
|
4245
|
+
import ora6 from "ora";
|
|
4246
|
+
function loadProjectEnv(projectRoot) {
|
|
4247
|
+
const envPath = join7(projectRoot, ".env");
|
|
4248
|
+
const envVars = {};
|
|
4249
|
+
if (existsSync6(envPath)) {
|
|
4250
|
+
const content = readFileSync6(envPath, "utf-8");
|
|
4251
|
+
for (const line of content.split("\n")) {
|
|
4252
|
+
const trimmed = line.trim();
|
|
4253
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
4254
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
4255
|
+
if (key && valueParts.length > 0) {
|
|
4256
|
+
let value = valueParts.join("=");
|
|
4257
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
4258
|
+
value = value.slice(1, -1);
|
|
4259
|
+
}
|
|
4260
|
+
envVars[key] = value;
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
return envVars;
|
|
4266
|
+
}
|
|
4267
|
+
async function dbMigrateCommand() {
|
|
4268
|
+
const spinner = ora6("Preparing to run migrations...").start();
|
|
4269
|
+
try {
|
|
4270
|
+
const coreDir = getCoreDir();
|
|
4271
|
+
const projectRoot = getProjectRoot();
|
|
4272
|
+
const migrationsScript = join7(coreDir, "scripts", "db", "run-migrations.mjs");
|
|
4273
|
+
if (!existsSync6(migrationsScript)) {
|
|
4274
|
+
spinner.fail("Migrations script not found");
|
|
4275
|
+
console.error(chalk12.red(`Expected script at: ${migrationsScript}`));
|
|
4276
|
+
process.exit(1);
|
|
4277
|
+
}
|
|
4278
|
+
spinner.succeed("Core package found");
|
|
4279
|
+
const projectEnv = loadProjectEnv(projectRoot);
|
|
4280
|
+
if (!projectEnv.DATABASE_URL) {
|
|
4281
|
+
spinner.fail("DATABASE_URL not found in .env file");
|
|
4282
|
+
console.error(chalk12.red("Please configure DATABASE_URL in your .env file"));
|
|
4283
|
+
process.exit(1);
|
|
4284
|
+
}
|
|
4285
|
+
if (!projectEnv.NEXT_PUBLIC_ACTIVE_THEME) {
|
|
4286
|
+
spinner.fail("NEXT_PUBLIC_ACTIVE_THEME not found in .env file");
|
|
4287
|
+
console.error(chalk12.red("Please configure NEXT_PUBLIC_ACTIVE_THEME in your .env file"));
|
|
4288
|
+
process.exit(1);
|
|
4289
|
+
}
|
|
4290
|
+
spinner.start("Running database migrations...");
|
|
4291
|
+
const migrateProcess = spawn("node", [migrationsScript], {
|
|
4292
|
+
cwd: projectRoot,
|
|
4293
|
+
stdio: "inherit",
|
|
4294
|
+
env: {
|
|
4295
|
+
...projectEnv,
|
|
4296
|
+
...process.env,
|
|
4297
|
+
NEXTSPARK_PROJECT_ROOT: projectRoot,
|
|
4298
|
+
NEXTSPARK_CORE_DIR: coreDir
|
|
4299
|
+
}
|
|
4300
|
+
});
|
|
4301
|
+
migrateProcess.on("error", (err) => {
|
|
4302
|
+
spinner.fail("Migration failed");
|
|
4303
|
+
console.error(chalk12.red(err.message));
|
|
4304
|
+
process.exit(1);
|
|
4305
|
+
});
|
|
4306
|
+
migrateProcess.on("close", (code) => {
|
|
4307
|
+
if (code === 0) {
|
|
4308
|
+
console.log(chalk12.green("\n\u2705 Migrations completed successfully!"));
|
|
4309
|
+
process.exit(0);
|
|
4310
|
+
} else {
|
|
4311
|
+
console.error(chalk12.red(`
|
|
4312
|
+
\u274C Migrations failed with exit code ${code}`));
|
|
4313
|
+
process.exit(code ?? 1);
|
|
4314
|
+
}
|
|
4315
|
+
});
|
|
4316
|
+
} catch (error) {
|
|
4317
|
+
spinner.fail("Migration preparation failed");
|
|
4318
|
+
if (error instanceof Error) {
|
|
4319
|
+
console.error(chalk12.red(error.message));
|
|
4320
|
+
}
|
|
4321
|
+
process.exit(1);
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
async function dbSeedCommand() {
|
|
4325
|
+
console.log(chalk12.cyan("\u2139\uFE0F Sample data is included as part of the migration process."));
|
|
4326
|
+
console.log(chalk12.cyan(" Running db:migrate to apply all migrations including sample data...\n"));
|
|
4327
|
+
await dbMigrateCommand();
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
// src/commands/sync-app.ts
|
|
4331
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, mkdirSync as mkdirSync4, copyFileSync, readFileSync as readFileSync7 } from "fs";
|
|
4332
|
+
import { join as join8, dirname as dirname3, relative } from "path";
|
|
4333
|
+
import chalk13 from "chalk";
|
|
4334
|
+
import ora7 from "ora";
|
|
4335
|
+
var EXCLUDED_TEMPLATE_PATTERNS = ["(templates)"];
|
|
4336
|
+
var ROOT_TEMPLATE_FILES = [
|
|
4337
|
+
"proxy.ts",
|
|
4338
|
+
// Next.js 16+ proxy (formerly middleware.ts) - required for auth/permission validation
|
|
4339
|
+
"next.config.mjs",
|
|
4340
|
+
// Required for webpack aliases, transpilePackages, security headers
|
|
4341
|
+
"tsconfig.json",
|
|
4342
|
+
// Required for proper path aliases and test file exclusions
|
|
4343
|
+
"i18n.ts"
|
|
4344
|
+
// Required for next-intl configuration
|
|
4345
|
+
];
|
|
4346
|
+
var MAX_VERBOSE_FILES = 10;
|
|
4347
|
+
var MAX_SUMMARY_FILES = 5;
|
|
4348
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
4349
|
+
const files = [];
|
|
4350
|
+
if (!existsSync7(dir)) {
|
|
4351
|
+
return files;
|
|
4352
|
+
}
|
|
4353
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
4354
|
+
for (const entry of entries) {
|
|
4355
|
+
const fullPath = join8(dir, entry.name);
|
|
4356
|
+
const relativePath = relative(baseDir, fullPath);
|
|
4357
|
+
if (entry.isDirectory()) {
|
|
4358
|
+
files.push(...getAllFiles(fullPath, baseDir));
|
|
4359
|
+
} else if (entry.isFile()) {
|
|
4360
|
+
if (entry.name !== ".DS_Store" && entry.name !== "README.md") {
|
|
4361
|
+
files.push(relativePath);
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
return files;
|
|
4366
|
+
}
|
|
4367
|
+
function copyFile(source, target) {
|
|
4368
|
+
const targetDir = dirname3(target);
|
|
4369
|
+
if (!existsSync7(targetDir)) {
|
|
4370
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
4371
|
+
}
|
|
4372
|
+
copyFileSync(source, target);
|
|
4373
|
+
}
|
|
4374
|
+
function backupDirectory(source, target) {
|
|
4375
|
+
if (!existsSync7(source)) {
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
const files = getAllFiles(source);
|
|
4379
|
+
for (const file of files) {
|
|
4380
|
+
const sourcePath = join8(source, file);
|
|
4381
|
+
const targetPath = join8(target, file);
|
|
4382
|
+
copyFile(sourcePath, targetPath);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
function getCoreVersion2(coreDir) {
|
|
4386
|
+
try {
|
|
4387
|
+
const pkgPath = join8(coreDir, "package.json");
|
|
4388
|
+
const pkg2 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4389
|
+
return pkg2.version || "unknown";
|
|
4390
|
+
} catch {
|
|
4391
|
+
return "unknown";
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
async function syncAppCommand(options) {
|
|
4395
|
+
const spinner = ora7({ text: "Preparing sync...", isSilent: options.dryRun }).start();
|
|
4396
|
+
try {
|
|
4397
|
+
const coreDir = getCoreDir();
|
|
4398
|
+
const projectRoot = getProjectRoot();
|
|
4399
|
+
const coreVersion = getCoreVersion2(coreDir);
|
|
4400
|
+
const templatesDir = join8(coreDir, "templates", "app");
|
|
4401
|
+
const appDir = join8(projectRoot, "app");
|
|
4402
|
+
if (!existsSync7(templatesDir)) {
|
|
4403
|
+
spinner.fail("Templates directory not found in @nextsparkjs/core");
|
|
4404
|
+
console.error(chalk13.red(`
|
|
4405
|
+
Expected path: ${templatesDir}`));
|
|
4406
|
+
process.exit(1);
|
|
4407
|
+
}
|
|
4408
|
+
if (!existsSync7(appDir)) {
|
|
4409
|
+
spinner.fail("No /app directory found");
|
|
4410
|
+
console.error(chalk13.red("\n This project does not have an /app folder."));
|
|
4411
|
+
console.error(chalk13.yellow(' Run "nextspark init" first to initialize your project.\n'));
|
|
4412
|
+
process.exit(1);
|
|
4413
|
+
}
|
|
4414
|
+
spinner.text = "Scanning template files...";
|
|
4415
|
+
const templateFiles = getAllFiles(templatesDir).filter((f) => !EXCLUDED_TEMPLATE_PATTERNS.some((pattern) => f.startsWith(pattern)));
|
|
4416
|
+
const existingAppFiles = getAllFiles(appDir);
|
|
4417
|
+
const customFiles = existingAppFiles.filter((f) => !templateFiles.includes(f));
|
|
4418
|
+
const filesToUpdate = templateFiles;
|
|
4419
|
+
spinner.succeed("Scan complete");
|
|
4420
|
+
console.log(chalk13.cyan(`
|
|
4421
|
+
Syncing /app with @nextsparkjs/core@${coreVersion}...
|
|
4422
|
+
`));
|
|
4423
|
+
if (options.dryRun) {
|
|
4424
|
+
console.log(chalk13.yellow(" [DRY RUN] No changes will be made\n"));
|
|
4425
|
+
}
|
|
4426
|
+
if (options.verbose) {
|
|
4427
|
+
console.log(chalk13.gray(" Files to sync:"));
|
|
4428
|
+
for (const file of filesToUpdate) {
|
|
4429
|
+
const targetPath = join8(appDir, file);
|
|
4430
|
+
const status = existsSync7(targetPath) ? chalk13.yellow("update") : chalk13.green("create");
|
|
4431
|
+
console.log(chalk13.gray(` ${status} ${file}`));
|
|
4432
|
+
}
|
|
4433
|
+
console.log();
|
|
4434
|
+
}
|
|
4435
|
+
console.log(chalk13.white(` Template files: ${filesToUpdate.length}`));
|
|
4436
|
+
console.log(chalk13.white(` Custom files preserved: ${customFiles.length}`));
|
|
4437
|
+
if (customFiles.length > 0 && options.verbose) {
|
|
4438
|
+
console.log(chalk13.gray("\n Custom files (will be preserved):"));
|
|
4439
|
+
for (const file of customFiles.slice(0, MAX_VERBOSE_FILES)) {
|
|
4440
|
+
console.log(chalk13.gray(` - ${file}`));
|
|
4441
|
+
}
|
|
4442
|
+
if (customFiles.length > MAX_VERBOSE_FILES) {
|
|
4443
|
+
console.log(chalk13.gray(` ... and ${customFiles.length - MAX_VERBOSE_FILES} more`));
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if (!options.force && !options.dryRun) {
|
|
4447
|
+
console.log(chalk13.yellow(`
|
|
4448
|
+
This will overwrite ${filesToUpdate.length} core template files.`));
|
|
4449
|
+
console.log(chalk13.gray(" Run with --dry-run to preview changes, or --force to skip this prompt.\n"));
|
|
4450
|
+
try {
|
|
4451
|
+
const { confirm: confirm6 } = await import("@inquirer/prompts");
|
|
4452
|
+
const confirmed = await confirm6({
|
|
4453
|
+
message: "Proceed with sync?",
|
|
4454
|
+
default: true
|
|
4455
|
+
});
|
|
4456
|
+
if (!confirmed) {
|
|
4457
|
+
console.log(chalk13.yellow("\n Sync cancelled.\n"));
|
|
4458
|
+
process.exit(0);
|
|
4459
|
+
}
|
|
4460
|
+
} catch (promptError) {
|
|
4461
|
+
console.error(chalk13.red("\n Failed to load confirmation prompt. Use --force to skip.\n"));
|
|
4462
|
+
process.exit(1);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
if (options.backup && !options.dryRun) {
|
|
4466
|
+
const backupDir = join8(projectRoot, `app.backup.v${coreVersion}.${Date.now()}`);
|
|
4467
|
+
spinner.start("Creating backup...");
|
|
4468
|
+
backupDirectory(appDir, backupDir);
|
|
4469
|
+
spinner.succeed(`Backup created: ${relative(projectRoot, backupDir)}`);
|
|
4470
|
+
}
|
|
4471
|
+
if (!options.dryRun) {
|
|
4472
|
+
spinner.start("Syncing files...");
|
|
4473
|
+
let updated = 0;
|
|
4474
|
+
let created = 0;
|
|
4475
|
+
for (const file of filesToUpdate) {
|
|
4476
|
+
const sourcePath = join8(templatesDir, file);
|
|
4477
|
+
const targetPath = join8(appDir, file);
|
|
4478
|
+
const isNew = !existsSync7(targetPath);
|
|
4479
|
+
copyFile(sourcePath, targetPath);
|
|
4480
|
+
if (isNew) {
|
|
4481
|
+
created++;
|
|
4482
|
+
} else {
|
|
4483
|
+
updated++;
|
|
4484
|
+
}
|
|
4485
|
+
if (options.verbose) {
|
|
4486
|
+
spinner.text = `Syncing: ${file}`;
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
spinner.succeed(`Synced ${filesToUpdate.length} files (${updated} updated, ${created} created)`);
|
|
4490
|
+
const rootTemplatesDir = join8(coreDir, "templates");
|
|
4491
|
+
let rootUpdated = 0;
|
|
4492
|
+
let rootCreated = 0;
|
|
4493
|
+
for (const file of ROOT_TEMPLATE_FILES) {
|
|
4494
|
+
const sourcePath = join8(rootTemplatesDir, file);
|
|
4495
|
+
const targetPath = join8(projectRoot, file);
|
|
4496
|
+
if (existsSync7(sourcePath)) {
|
|
4497
|
+
const isNew = !existsSync7(targetPath);
|
|
4498
|
+
copyFile(sourcePath, targetPath);
|
|
4499
|
+
if (isNew) {
|
|
4500
|
+
rootCreated++;
|
|
4501
|
+
if (options.verbose) {
|
|
4502
|
+
console.log(chalk13.green(` + Created: ${file}`));
|
|
4503
|
+
}
|
|
4504
|
+
} else {
|
|
4505
|
+
rootUpdated++;
|
|
4506
|
+
if (options.verbose) {
|
|
4507
|
+
console.log(chalk13.yellow(` ~ Updated: ${file}`));
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
if (rootUpdated + rootCreated > 0) {
|
|
4513
|
+
console.log(chalk13.gray(` Root files: ${rootUpdated} updated, ${rootCreated} created`));
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
console.log(chalk13.green("\n \u2705 Sync complete!\n"));
|
|
4517
|
+
if (customFiles.length > 0) {
|
|
4518
|
+
console.log(chalk13.gray(` Preserved ${customFiles.length} custom file(s):`));
|
|
4519
|
+
for (const file of customFiles.slice(0, MAX_SUMMARY_FILES)) {
|
|
4520
|
+
console.log(chalk13.gray(` - app/${file}`));
|
|
4521
|
+
}
|
|
4522
|
+
if (customFiles.length > MAX_SUMMARY_FILES) {
|
|
4523
|
+
console.log(chalk13.gray(` ... and ${customFiles.length - MAX_SUMMARY_FILES} more
|
|
4524
|
+
`));
|
|
4525
|
+
} else {
|
|
4526
|
+
console.log();
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
} catch (error) {
|
|
4530
|
+
spinner.fail("Sync failed");
|
|
4531
|
+
if (error instanceof Error) {
|
|
4532
|
+
console.error(chalk13.red(`
|
|
4533
|
+
Error: ${error.message}
|
|
4534
|
+
`));
|
|
4535
|
+
if (options.verbose && error.stack) {
|
|
4536
|
+
console.error(chalk13.gray(` Stack trace:
|
|
4537
|
+
${error.stack}
|
|
4538
|
+
`));
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
process.exit(1);
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// src/commands/setup-ai.ts
|
|
4546
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4547
|
+
import { join as join9 } from "path";
|
|
4548
|
+
import { execSync as execSync3 } from "child_process";
|
|
4549
|
+
import chalk14 from "chalk";
|
|
4550
|
+
import ora8 from "ora";
|
|
4551
|
+
var VALID_EDITORS = ["claude", "cursor", "antigravity", "all"];
|
|
4552
|
+
async function setupAICommand(options) {
|
|
4553
|
+
const editor = options.editor || "claude";
|
|
4554
|
+
if (!VALID_EDITORS.includes(editor)) {
|
|
4555
|
+
console.log(chalk14.red(` Unknown editor: ${editor}`));
|
|
4556
|
+
console.log(chalk14.gray(` Available: ${VALID_EDITORS.join(", ")}`));
|
|
4557
|
+
process.exit(1);
|
|
4558
|
+
}
|
|
4559
|
+
console.log("");
|
|
4560
|
+
console.log(chalk14.cyan(" AI Workflow Setup"));
|
|
4561
|
+
console.log(chalk14.gray(" " + "-".repeat(40)));
|
|
4562
|
+
console.log("");
|
|
4563
|
+
const pkgPath = getAIWorkflowDir();
|
|
4564
|
+
if (!pkgPath) {
|
|
4565
|
+
console.log(chalk14.red(" @nextsparkjs/ai-workflow package not found."));
|
|
4566
|
+
console.log("");
|
|
4567
|
+
console.log(chalk14.gray(" Install it first:"));
|
|
4568
|
+
console.log(chalk14.cyan(" pnpm add -D -w @nextsparkjs/ai-workflow"));
|
|
4569
|
+
console.log("");
|
|
4570
|
+
process.exit(1);
|
|
4571
|
+
}
|
|
4572
|
+
const setupScript = join9(pkgPath, "scripts", "setup.mjs");
|
|
4573
|
+
if (!existsSync8(setupScript)) {
|
|
4574
|
+
console.log(chalk14.red(" Setup script not found in ai-workflow package."));
|
|
4575
|
+
console.log(chalk14.gray(` Expected: ${setupScript}`));
|
|
4576
|
+
process.exit(1);
|
|
4577
|
+
}
|
|
4578
|
+
const spinner = ora8({
|
|
4579
|
+
text: `Setting up AI workflow for ${editor}...`,
|
|
4580
|
+
prefixText: " "
|
|
4581
|
+
}).start();
|
|
4582
|
+
try {
|
|
4583
|
+
spinner.stop();
|
|
4584
|
+
execSync3(`node "${setupScript}" ${editor}`, {
|
|
4585
|
+
cwd: process.cwd(),
|
|
4586
|
+
stdio: "inherit"
|
|
4587
|
+
});
|
|
4588
|
+
} catch (error) {
|
|
4589
|
+
console.log("");
|
|
4590
|
+
console.log(chalk14.red(" AI workflow setup failed."));
|
|
4591
|
+
if (error instanceof Error) {
|
|
4592
|
+
console.log(chalk14.gray(` ${error.message}`));
|
|
4593
|
+
}
|
|
4594
|
+
process.exit(1);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
// src/commands/sync-ai.ts
|
|
4599
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4600
|
+
import { join as join10 } from "path";
|
|
4601
|
+
import { execSync as execSync4 } from "child_process";
|
|
4602
|
+
import chalk15 from "chalk";
|
|
4603
|
+
import ora9 from "ora";
|
|
4604
|
+
var VALID_EDITORS2 = ["claude", "cursor", "antigravity", "all"];
|
|
4605
|
+
async function syncAICommand(options) {
|
|
4606
|
+
const editor = options.editor || "claude";
|
|
4607
|
+
if (!VALID_EDITORS2.includes(editor)) {
|
|
4608
|
+
console.log(chalk15.red(` Unknown editor: ${editor}`));
|
|
4609
|
+
console.log(chalk15.gray(` Available: ${VALID_EDITORS2.join(", ")}`));
|
|
4610
|
+
process.exit(1);
|
|
4611
|
+
}
|
|
4612
|
+
console.log("");
|
|
4613
|
+
console.log(chalk15.cyan(" AI Workflow Sync"));
|
|
4614
|
+
console.log(chalk15.gray(" " + "-".repeat(40)));
|
|
4615
|
+
console.log("");
|
|
4616
|
+
const editorDir = editor === "cursor" ? ".cursor" : ".claude";
|
|
4617
|
+
const editorDirPath = join10(process.cwd(), editorDir);
|
|
4618
|
+
if (!existsSync9(editorDirPath)) {
|
|
4619
|
+
console.log(chalk15.red(` No ${editorDir}/ directory found.`));
|
|
4620
|
+
console.log("");
|
|
4621
|
+
console.log(chalk15.gray(" AI workflow must be set up first. Run:"));
|
|
4622
|
+
console.log(chalk15.cyan(" nextspark setup:ai --editor " + editor));
|
|
4623
|
+
console.log("");
|
|
4624
|
+
process.exit(1);
|
|
4625
|
+
}
|
|
4626
|
+
const pkgPath = getAIWorkflowDir();
|
|
4627
|
+
if (!pkgPath) {
|
|
4628
|
+
console.log(chalk15.red(" @nextsparkjs/ai-workflow package not found."));
|
|
4629
|
+
console.log("");
|
|
4630
|
+
console.log(chalk15.gray(" Install it first:"));
|
|
4631
|
+
console.log(chalk15.cyan(" pnpm add -D -w @nextsparkjs/ai-workflow"));
|
|
4632
|
+
console.log("");
|
|
4633
|
+
process.exit(1);
|
|
4634
|
+
}
|
|
4635
|
+
const setupScript = join10(pkgPath, "scripts", "setup.mjs");
|
|
4636
|
+
if (!existsSync9(setupScript)) {
|
|
4637
|
+
console.log(chalk15.red(" Setup script not found in ai-workflow package."));
|
|
4638
|
+
console.log(chalk15.gray(` Expected: ${setupScript}`));
|
|
4639
|
+
process.exit(1);
|
|
4640
|
+
}
|
|
4641
|
+
if (!options.force) {
|
|
4642
|
+
console.log(chalk15.yellow(" This will sync AI workflow files from @nextsparkjs/ai-workflow."));
|
|
4643
|
+
console.log(chalk15.gray(" Framework files will be overwritten. Custom files will be preserved."));
|
|
4644
|
+
console.log(chalk15.gray(" Config JSON files will never be overwritten.\n"));
|
|
4645
|
+
try {
|
|
4646
|
+
const { confirm: confirm6 } = await import("@inquirer/prompts");
|
|
4647
|
+
const confirmed = await confirm6({
|
|
4648
|
+
message: "Proceed with sync?",
|
|
4649
|
+
default: true
|
|
4650
|
+
});
|
|
4651
|
+
if (!confirmed) {
|
|
4652
|
+
console.log(chalk15.yellow("\n Sync cancelled.\n"));
|
|
4653
|
+
process.exit(0);
|
|
4654
|
+
}
|
|
4655
|
+
} catch {
|
|
4656
|
+
console.error(chalk15.red("\n Failed to load confirmation prompt. Use --force to skip.\n"));
|
|
4657
|
+
process.exit(1);
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
const spinner = ora9({
|
|
4661
|
+
text: `Syncing AI workflow for ${editor}...`,
|
|
4662
|
+
prefixText: " "
|
|
4663
|
+
}).start();
|
|
4664
|
+
try {
|
|
4665
|
+
spinner.stop();
|
|
4666
|
+
execSync4(`node "${setupScript}" ${editor}`, {
|
|
4667
|
+
cwd: process.cwd(),
|
|
4668
|
+
stdio: "inherit"
|
|
4669
|
+
});
|
|
4670
|
+
} catch (error) {
|
|
4671
|
+
console.log("");
|
|
4672
|
+
console.log(chalk15.red(" AI workflow sync failed."));
|
|
4673
|
+
if (error instanceof Error) {
|
|
4674
|
+
console.log(chalk15.gray(` ${error.message}`));
|
|
3670
4675
|
}
|
|
3671
4676
|
process.exit(1);
|
|
3672
4677
|
}
|
|
3673
4678
|
}
|
|
3674
4679
|
|
|
3675
4680
|
// src/cli.ts
|
|
4681
|
+
config();
|
|
4682
|
+
var pkg = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf-8"));
|
|
3676
4683
|
var program = new Command();
|
|
3677
|
-
program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version(
|
|
3678
|
-
program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
|
|
4684
|
+
program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version(pkg.version);
|
|
4685
|
+
program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", process.env.PORT || "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
|
|
3679
4686
|
program.command("build").description("Build for production").option("--no-registry", "Skip registry generation before build").action(buildCommand);
|
|
3680
4687
|
program.command("generate").description("Generate all registries").option("-w, --watch", "Watch for changes").action(generateCommand);
|
|
3681
4688
|
var registry = program.command("registry").description("Registry management commands");
|
|
@@ -3683,12 +4690,22 @@ registry.command("build").description("Build all registries").action(registryBui
|
|
|
3683
4690
|
registry.command("watch").description("Watch and rebuild registries on changes").action(registryWatchCommand);
|
|
3684
4691
|
program.command("registry:build").description("Build all registries (alias)").action(registryBuildCommand);
|
|
3685
4692
|
program.command("registry:watch").description("Watch and rebuild registries (alias)").action(registryWatchCommand);
|
|
3686
|
-
program.command("init").description("Initialize NextSpark project").option("-f, --force", "Overwrite existing configuration").option("--wizard", "Run full project wizard").option("--quick", "Quick wizard mode (essential steps only)").option("--expert", "Expert wizard mode (all options)").option("--preset <name>", "Use preset configuration (saas, blog, crm)").option("--theme <name>", "Pre-select theme (default, blog, crm, productivity, none)").option("--plugins <list>", "Pre-select plugins (comma-separated)").option("-y, --yes", "Skip confirmations").option("--registries-only", "Only create registries (no wizard)").option("--name <name>", "Project name (non-interactive mode)").option("--slug <slug>", "Project slug (non-interactive mode)").option("--description <desc>", "Project description (non-interactive mode)").action(initCommand);
|
|
4693
|
+
program.command("init").description("Initialize NextSpark project").option("-f, --force", "Overwrite existing configuration").option("--wizard", "Run full project wizard").option("--quick", "Quick wizard mode (essential steps only)").option("--expert", "Expert wizard mode (all options)").option("--preset <name>", "Use preset configuration (saas, blog, crm)").option("--theme <name>", "Pre-select theme (default, blog, crm, productivity, none)").option("--plugins <list>", "Pre-select plugins (comma-separated)").option("-y, --yes", "Skip confirmations").option("--registries-only", "Only create registries (no wizard)").option("--name <name>", "Project name (non-interactive mode)").option("--slug <slug>", "Project slug (non-interactive mode)").option("--description <desc>", "Project description (non-interactive mode)").option("--type <type>", "Project type: web or web-mobile (non-interactive mode)").action(initCommand);
|
|
3687
4694
|
program.command("add:plugin <package>").description("Add a plugin to your project").option("-v, --version <version>", "Specific version to install").option("-f, --force", "Overwrite if already exists").option("--skip-postinstall", "Skip postinstall hooks").option("--no-deps", "Skip installing dependencies").option("--dry-run", "Show what would be done without making changes").action(addPluginCommand);
|
|
3688
4695
|
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);
|
|
4696
|
+
program.command("add:mobile").description("Add mobile app to your project").option("-f, --force", "Overwrite if already exists").option("--skip-install", "Skip npm install").action(addMobileCommand);
|
|
3689
4697
|
program.command("doctor").description("Run health check on NextSpark project").action(doctorCommand);
|
|
4698
|
+
var db = program.command("db").description("Database management commands");
|
|
4699
|
+
db.command("migrate").description("Run database migrations").action(dbMigrateCommand);
|
|
4700
|
+
db.command("seed").description("Seed database with sample data").action(dbSeedCommand);
|
|
4701
|
+
program.command("db:migrate").description("Run database migrations (alias)").action(dbMigrateCommand);
|
|
4702
|
+
program.command("db:seed").description("Seed database with sample data (alias)").action(dbSeedCommand);
|
|
4703
|
+
program.command("sync:app").description("Sync /app folder with @nextsparkjs/core templates").option("--dry-run", "Preview changes without applying").option("-f, --force", "Skip confirmation prompt").option("--backup", "Backup existing files before overwriting").option("-v, --verbose", "Show detailed file operations").action(syncAppCommand);
|
|
4704
|
+
program.command("setup:ai").description("Setup AI workflow for your editor (Claude Code, Cursor, Antigravity)").option("-e, --editor <editor>", "Editor to setup (claude, cursor, antigravity, all)", "claude").action(setupAICommand);
|
|
4705
|
+
program.command("sync:ai").description("Sync AI workflow files from @nextsparkjs/ai-workflow").option("-e, --editor <editor>", "Editor to sync (claude, cursor, antigravity, all)", "claude").option("-f, --force", "Skip confirmation prompt").action(syncAICommand);
|
|
3690
4706
|
program.showHelpAfterError();
|
|
3691
4707
|
program.configureOutput({
|
|
3692
|
-
writeErr: (str) => process.stderr.write(
|
|
4708
|
+
writeErr: (str) => process.stderr.write(chalk16.red(str))
|
|
3693
4709
|
});
|
|
3694
4710
|
program.parse();
|
|
4711
|
+
//# sourceMappingURL=cli.js.map
|