@nextsparkjs/cli 0.1.0-beta.1

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/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @nextsparkjs/cli
2
+
3
+ CLI tool for NextSpark development workflow.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @nextsparkjs/cli
9
+ # or use with npx
10
+ npx @nextsparkjs/cli <command>
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ ```bash
16
+ nextspark init # Initialize NextSpark in an existing Next.js project
17
+ nextspark migrate # Run database migrations
18
+ nextspark registry # Build component registries
19
+ ```
20
+
21
+ ## Requirements
22
+
23
+ - Node.js 18+
24
+ - @nextsparkjs/core installed in your project
25
+
26
+ ## Documentation
27
+
28
+ Visit [nextspark.dev/docs](https://nextspark.dev/docs) for full documentation.
29
+
30
+ ## License
31
+
32
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/cli.js';
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import chalk6 from "chalk";
6
+
7
+ // src/commands/dev.ts
8
+ import { spawn } from "child_process";
9
+ import chalk from "chalk";
10
+ import ora from "ora";
11
+
12
+ // src/utils/paths.ts
13
+ import { existsSync } from "fs";
14
+ import { resolve, dirname } from "path";
15
+ import { fileURLToPath } from "url";
16
+ var __filename = fileURLToPath(import.meta.url);
17
+ var __dirname = dirname(__filename);
18
+ function getCoreDir() {
19
+ const cwd = process.cwd();
20
+ const npmCorePath = resolve(cwd, "node_modules", "@nextsparkjs", "core");
21
+ if (existsSync(npmCorePath)) {
22
+ return npmCorePath;
23
+ }
24
+ const monorepoCorePath = resolve(__dirname, "..", "..", "..", "..", "core");
25
+ if (existsSync(monorepoCorePath)) {
26
+ return monorepoCorePath;
27
+ }
28
+ const cwdMonorepoCorePath = resolve(cwd, "..", "..", "packages", "core");
29
+ if (existsSync(cwdMonorepoCorePath)) {
30
+ return cwdMonorepoCorePath;
31
+ }
32
+ throw new Error(
33
+ "Could not find @nextsparkjs/core. Make sure it is installed as a dependency or you are running from within the monorepo."
34
+ );
35
+ }
36
+ function getProjectRoot() {
37
+ return process.cwd();
38
+ }
39
+ function isMonorepoMode() {
40
+ const cwd = process.cwd();
41
+ const npmCorePath = resolve(cwd, "node_modules", "@nextsparkjs", "core");
42
+ return !existsSync(npmCorePath);
43
+ }
44
+
45
+ // src/commands/dev.ts
46
+ async function devCommand(options) {
47
+ const spinner = ora("Starting development environment...").start();
48
+ try {
49
+ const coreDir = getCoreDir();
50
+ const projectRoot = getProjectRoot();
51
+ const mode = isMonorepoMode() ? "monorepo" : "npm";
52
+ spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
53
+ const processes = [];
54
+ if (options.registry) {
55
+ console.log(chalk.blue("\n[Registry] Starting registry watcher..."));
56
+ const registryProcess = spawn("node", ["scripts/registry-watch.js"], {
57
+ cwd: coreDir,
58
+ stdio: "inherit",
59
+ env: {
60
+ ...process.env,
61
+ NEXTSPARK_PROJECT_ROOT: projectRoot
62
+ }
63
+ });
64
+ processes.push(registryProcess);
65
+ registryProcess.on("error", (err) => {
66
+ console.error(chalk.red(`[Registry] Error: ${err.message}`));
67
+ });
68
+ }
69
+ console.log(chalk.green(`
70
+ [Dev] Starting Next.js dev server on port ${options.port}...`));
71
+ const devProcess = spawn("npx", ["next", "dev", "-p", options.port], {
72
+ cwd: projectRoot,
73
+ stdio: "inherit",
74
+ env: {
75
+ ...process.env,
76
+ NEXTSPARK_CORE_DIR: coreDir
77
+ }
78
+ });
79
+ processes.push(devProcess);
80
+ devProcess.on("error", (err) => {
81
+ console.error(chalk.red(`[Dev] Error: ${err.message}`));
82
+ process.exit(1);
83
+ });
84
+ const cleanup = () => {
85
+ console.log(chalk.yellow("\nShutting down..."));
86
+ processes.forEach((p) => {
87
+ if (!p.killed) {
88
+ p.kill("SIGTERM");
89
+ }
90
+ });
91
+ process.exit(0);
92
+ };
93
+ process.on("SIGINT", cleanup);
94
+ process.on("SIGTERM", cleanup);
95
+ devProcess.on("exit", (code) => {
96
+ cleanup();
97
+ process.exit(code ?? 0);
98
+ });
99
+ } catch (error) {
100
+ spinner.fail("Failed to start development environment");
101
+ if (error instanceof Error) {
102
+ console.error(chalk.red(error.message));
103
+ }
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ // src/commands/build.ts
109
+ import { spawn as spawn2 } from "child_process";
110
+ import chalk2 from "chalk";
111
+ import ora2 from "ora";
112
+ async function buildCommand(options) {
113
+ const spinner = ora2("Preparing production build...").start();
114
+ try {
115
+ const coreDir = getCoreDir();
116
+ const projectRoot = getProjectRoot();
117
+ spinner.succeed("Core package found");
118
+ if (options.registry) {
119
+ spinner.start("Generating registries...");
120
+ await new Promise((resolve2, reject) => {
121
+ const registryProcess = spawn2("node", ["scripts/registry-build.js"], {
122
+ cwd: coreDir,
123
+ stdio: "pipe",
124
+ env: {
125
+ ...process.env,
126
+ NEXTSPARK_PROJECT_ROOT: projectRoot
127
+ }
128
+ });
129
+ let stderr = "";
130
+ registryProcess.stderr?.on("data", (data) => {
131
+ stderr += data.toString();
132
+ });
133
+ registryProcess.on("close", (code) => {
134
+ if (code === 0) {
135
+ resolve2();
136
+ } else {
137
+ reject(new Error(`Registry generation failed: ${stderr}`));
138
+ }
139
+ });
140
+ registryProcess.on("error", reject);
141
+ });
142
+ spinner.succeed("Registries generated");
143
+ }
144
+ spinner.start("Building for production...");
145
+ const buildProcess = spawn2("npx", ["next", "build"], {
146
+ cwd: projectRoot,
147
+ stdio: "inherit",
148
+ env: {
149
+ ...process.env,
150
+ NEXTSPARK_CORE_DIR: coreDir,
151
+ NODE_ENV: "production"
152
+ }
153
+ });
154
+ buildProcess.on("error", (err) => {
155
+ spinner.fail("Build failed");
156
+ console.error(chalk2.red(err.message));
157
+ process.exit(1);
158
+ });
159
+ buildProcess.on("close", (code) => {
160
+ if (code === 0) {
161
+ console.log(chalk2.green("\nBuild completed successfully!"));
162
+ process.exit(0);
163
+ } else {
164
+ console.error(chalk2.red(`
165
+ Build failed with exit code ${code}`));
166
+ process.exit(code ?? 1);
167
+ }
168
+ });
169
+ } catch (error) {
170
+ spinner.fail("Build preparation failed");
171
+ if (error instanceof Error) {
172
+ console.error(chalk2.red(error.message));
173
+ }
174
+ process.exit(1);
175
+ }
176
+ }
177
+
178
+ // src/commands/generate.ts
179
+ import { spawn as spawn3 } from "child_process";
180
+ import chalk3 from "chalk";
181
+ import ora3 from "ora";
182
+ async function generateCommand(options) {
183
+ const spinner = ora3("Preparing registry generation...").start();
184
+ try {
185
+ const coreDir = getCoreDir();
186
+ const projectRoot = getProjectRoot();
187
+ const mode = isMonorepoMode() ? "monorepo" : "npm";
188
+ spinner.succeed(`Core found at: ${coreDir} (${mode} mode)`);
189
+ const scriptName = options.watch ? "registry-watch.js" : "registry-build.js";
190
+ const action = options.watch ? "Watching" : "Generating";
191
+ console.log(chalk3.blue(`
192
+ ${action} registries...`));
193
+ const generateProcess = spawn3("node", [`scripts/${scriptName}`], {
194
+ cwd: coreDir,
195
+ stdio: "inherit",
196
+ env: {
197
+ ...process.env,
198
+ NEXTSPARK_PROJECT_ROOT: projectRoot
199
+ }
200
+ });
201
+ generateProcess.on("error", (err) => {
202
+ console.error(chalk3.red(`Error: ${err.message}`));
203
+ process.exit(1);
204
+ });
205
+ generateProcess.on("close", (code) => {
206
+ if (code === 0) {
207
+ if (!options.watch) {
208
+ console.log(chalk3.green("\nRegistry generation completed!"));
209
+ }
210
+ process.exit(0);
211
+ } else {
212
+ console.error(chalk3.red(`
213
+ Registry generation failed with exit code ${code}`));
214
+ process.exit(code ?? 1);
215
+ }
216
+ });
217
+ if (options.watch) {
218
+ const cleanup = () => {
219
+ console.log(chalk3.yellow("\nStopping watcher..."));
220
+ if (!generateProcess.killed) {
221
+ generateProcess.kill("SIGTERM");
222
+ }
223
+ process.exit(0);
224
+ };
225
+ process.on("SIGINT", cleanup);
226
+ process.on("SIGTERM", cleanup);
227
+ }
228
+ } catch (error) {
229
+ spinner.fail("Registry generation failed");
230
+ if (error instanceof Error) {
231
+ console.error(chalk3.red(error.message));
232
+ }
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ // src/commands/registry.ts
238
+ import { spawn as spawn4 } from "child_process";
239
+ import chalk4 from "chalk";
240
+ import ora4 from "ora";
241
+ async function registryBuildCommand() {
242
+ const spinner = ora4("Building registries...").start();
243
+ try {
244
+ const coreDir = getCoreDir();
245
+ const projectRoot = getProjectRoot();
246
+ const mode = isMonorepoMode() ? "monorepo" : "npm";
247
+ spinner.text = `Building registries (${mode} mode)...`;
248
+ const buildProcess = spawn4("node", ["scripts/registry-build.js"], {
249
+ cwd: coreDir,
250
+ stdio: "pipe",
251
+ env: {
252
+ ...process.env,
253
+ NEXTSPARK_PROJECT_ROOT: projectRoot
254
+ }
255
+ });
256
+ let stdout = "";
257
+ let stderr = "";
258
+ buildProcess.stdout?.on("data", (data) => {
259
+ stdout += data.toString();
260
+ });
261
+ buildProcess.stderr?.on("data", (data) => {
262
+ stderr += data.toString();
263
+ });
264
+ buildProcess.on("close", (code) => {
265
+ if (code === 0) {
266
+ spinner.succeed("Registries built successfully");
267
+ if (stdout.trim()) {
268
+ console.log(chalk4.gray(stdout.trim()));
269
+ }
270
+ process.exit(0);
271
+ } else {
272
+ spinner.fail("Registry build failed");
273
+ if (stderr.trim()) {
274
+ console.error(chalk4.red(stderr.trim()));
275
+ }
276
+ process.exit(code ?? 1);
277
+ }
278
+ });
279
+ buildProcess.on("error", (err) => {
280
+ spinner.fail("Registry build failed");
281
+ console.error(chalk4.red(err.message));
282
+ process.exit(1);
283
+ });
284
+ } catch (error) {
285
+ spinner.fail("Registry build failed");
286
+ if (error instanceof Error) {
287
+ console.error(chalk4.red(error.message));
288
+ }
289
+ process.exit(1);
290
+ }
291
+ }
292
+ async function registryWatchCommand() {
293
+ const spinner = ora4("Starting registry watcher...").start();
294
+ try {
295
+ const coreDir = getCoreDir();
296
+ const projectRoot = getProjectRoot();
297
+ const mode = isMonorepoMode() ? "monorepo" : "npm";
298
+ spinner.succeed(`Registry watcher started (${mode} mode)`);
299
+ console.log(chalk4.blue("\nWatching for changes... Press Ctrl+C to stop.\n"));
300
+ const watchProcess = spawn4("node", ["scripts/registry-watch.js"], {
301
+ cwd: coreDir,
302
+ stdio: "inherit",
303
+ env: {
304
+ ...process.env,
305
+ NEXTSPARK_PROJECT_ROOT: projectRoot
306
+ }
307
+ });
308
+ watchProcess.on("error", (err) => {
309
+ console.error(chalk4.red(`Watcher error: ${err.message}`));
310
+ process.exit(1);
311
+ });
312
+ const cleanup = () => {
313
+ console.log(chalk4.yellow("\nStopping registry watcher..."));
314
+ if (!watchProcess.killed) {
315
+ watchProcess.kill("SIGTERM");
316
+ }
317
+ process.exit(0);
318
+ };
319
+ process.on("SIGINT", cleanup);
320
+ process.on("SIGTERM", cleanup);
321
+ watchProcess.on("close", (code) => {
322
+ if (code !== 0) {
323
+ console.error(chalk4.red(`
324
+ Watcher exited with code ${code}`));
325
+ }
326
+ process.exit(code ?? 0);
327
+ });
328
+ } catch (error) {
329
+ spinner.fail("Failed to start registry watcher");
330
+ if (error instanceof Error) {
331
+ console.error(chalk4.red(error.message));
332
+ }
333
+ process.exit(1);
334
+ }
335
+ }
336
+
337
+ // src/commands/init.ts
338
+ import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync } from "fs";
339
+ import { join } from "path";
340
+ import chalk5 from "chalk";
341
+ import ora5 from "ora";
342
+ function generateInitialRegistries(registriesDir) {
343
+ writeFileSync(join(registriesDir, "block-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
344
+ import type { ComponentType } from 'react'
345
+
346
+ export const BLOCK_REGISTRY: Record<string, {
347
+ name: string
348
+ slug: string
349
+ componentPath: string
350
+ fields?: unknown[]
351
+ examples?: unknown[]
352
+ }> = {}
353
+
354
+ // Lazy-loaded block components - populated by 'nextspark generate'
355
+ export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<ComponentType<any>>> = {}
356
+ `);
357
+ writeFileSync(join(registriesDir, "theme-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
358
+ export const THEME_REGISTRY: Record<string, unknown> = {}
359
+ `);
360
+ writeFileSync(join(registriesDir, "entity-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
361
+ export const ENTITY_REGISTRY: Record<string, unknown> = {}
362
+ `);
363
+ writeFileSync(join(registriesDir, "entity-registry.client.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
364
+ export const CLIENT_ENTITY_REGISTRY: Record<string, unknown> = {}
365
+ export function parseChildEntity(path: string) { return null }
366
+ export function getEntityApiPath(entity: string) { return \`/api/\${entity}\` }
367
+ export function clientMetaSystemAdapter() { return {} }
368
+ export type ClientEntityConfig = Record<string, unknown>
369
+ `);
370
+ writeFileSync(join(registriesDir, "billing-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
371
+ export const BILLING_REGISTRY = { plans: [], features: [] }
372
+ `);
373
+ writeFileSync(join(registriesDir, "plugin-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
374
+ export const PLUGIN_REGISTRY: Record<string, unknown> = {}
375
+ `);
376
+ writeFileSync(join(registriesDir, "testing-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
377
+ export const FLOW_REGISTRY: Record<string, unknown> = {}
378
+ export const FEATURE_REGISTRY: Record<string, unknown> = {}
379
+ export const TAGS_REGISTRY: Record<string, unknown> = {}
380
+ export const COVERAGE_SUMMARY = { total: 0, covered: 0 }
381
+ export type FlowEntry = unknown
382
+ export type FeatureEntry = unknown
383
+ `);
384
+ writeFileSync(join(registriesDir, "docs-registry.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
385
+ export const DOCS_REGISTRY = { sections: [], pages: [] }
386
+ export type DocSectionMeta = { title: string; slug: string }
387
+ `);
388
+ writeFileSync(join(registriesDir, "index.ts"), `// Auto-generated by nextspark init - Run 'nextspark generate' to populate
389
+ export * from './block-registry'
390
+ export * from './theme-registry'
391
+ export * from './entity-registry'
392
+ export * from './entity-registry.client'
393
+ export * from './billing-registry'
394
+ export * from './plugin-registry'
395
+ export * from './testing-registry'
396
+ export * from './docs-registry'
397
+ `);
398
+ }
399
+ async function initCommand(options) {
400
+ const spinner = ora5("Initializing NextSpark project...").start();
401
+ const projectRoot = process.cwd();
402
+ try {
403
+ const nextspark = join(projectRoot, ".nextspark");
404
+ const registriesDir = join(nextspark, "registries");
405
+ if (!existsSync2(registriesDir) || options.force) {
406
+ mkdirSync(registriesDir, { recursive: true });
407
+ spinner.text = "Creating .nextspark/registries/";
408
+ generateInitialRegistries(registriesDir);
409
+ spinner.text = "Generated initial registries";
410
+ }
411
+ const tsconfigPath = join(projectRoot, "tsconfig.json");
412
+ if (existsSync2(tsconfigPath)) {
413
+ spinner.text = "Updating tsconfig.json paths...";
414
+ const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
415
+ tsconfig.compilerOptions = tsconfig.compilerOptions || {};
416
+ tsconfig.compilerOptions.paths = {
417
+ ...tsconfig.compilerOptions.paths,
418
+ "@nextsparkjs/registries": ["./.nextspark/registries/index.ts"],
419
+ "@nextsparkjs/registries/*": ["./.nextspark/registries/*"]
420
+ };
421
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
422
+ }
423
+ const envExample = join(projectRoot, ".env.example");
424
+ if (!existsSync2(envExample)) {
425
+ const envContent = `# NextSpark Configuration
426
+ DATABASE_URL="postgresql://user:password@localhost:5432/db"
427
+ BETTER_AUTH_SECRET="your-secret-here-min-32-chars"
428
+ BETTER_AUTH_URL=http://localhost:3000
429
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
430
+ `;
431
+ writeFileSync(envExample, envContent);
432
+ spinner.text = "Created .env.example";
433
+ }
434
+ spinner.succeed("NextSpark initialized successfully!");
435
+ console.log(chalk5.blue("\nNext steps:"));
436
+ console.log(" 1. Copy .env.example to .env and configure");
437
+ console.log(" 2. Run: nextspark generate (to populate registries)");
438
+ console.log(" 3. Run: nextspark dev");
439
+ } catch (error) {
440
+ spinner.fail("Initialization failed");
441
+ if (error instanceof Error) {
442
+ console.error(chalk5.red(error.message));
443
+ }
444
+ process.exit(1);
445
+ }
446
+ }
447
+
448
+ // src/cli.ts
449
+ var program = new Command();
450
+ program.name("nextspark").description("NextSpark CLI - Professional SaaS Boilerplate").version("0.3.0");
451
+ program.command("dev").description("Start development server with registry watcher").option("-p, --port <port>", "Port to run the dev server on", "3000").option("--no-registry", "Disable registry watcher").action(devCommand);
452
+ program.command("build").description("Build for production").option("--no-registry", "Skip registry generation before build").action(buildCommand);
453
+ program.command("generate").description("Generate all registries").option("-w, --watch", "Watch for changes").action(generateCommand);
454
+ var registry = program.command("registry").description("Registry management commands");
455
+ registry.command("build").description("Build all registries").action(registryBuildCommand);
456
+ registry.command("watch").description("Watch and rebuild registries on changes").action(registryWatchCommand);
457
+ program.command("registry:build").description("Build all registries (alias)").action(registryBuildCommand);
458
+ program.command("registry:watch").description("Watch and rebuild registries (alias)").action(registryWatchCommand);
459
+ program.command("init").description("Initialize NextSpark in current project").option("-f, --force", "Overwrite existing configuration").action(initCommand);
460
+ program.showHelpAfterError();
461
+ program.configureOutput({
462
+ writeErr: (str) => process.stderr.write(chalk6.red(str))
463
+ });
464
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@nextsparkjs/cli",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "CLI for NextSpark - wrap core scripts with a professional interface",
5
+ "type": "module",
6
+ "bin": {
7
+ "nextspark": "./bin/nextspark.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "bin"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsup src/cli.ts --format esm --dts --outDir dist",
17
+ "dev": "tsup src/cli.ts --format esm --watch"
18
+ },
19
+ "dependencies": {
20
+ "commander": "^12.0.0",
21
+ "chalk": "^5.3.0",
22
+ "ora": "^8.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@nextsparkjs/core": "workspace:*",
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.0.0",
28
+ "@types/node": "^20.0.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@nextsparkjs/core": ">=0.1.0-beta.1"
32
+ },
33
+ "keywords": [
34
+ "nextspark",
35
+ "cli",
36
+ "nextjs",
37
+ "saas",
38
+ "boilerplate"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/NextSpark-js/nextspark.git",
44
+ "directory": "packages/cli"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }