@nextsparkjs/cli 0.1.0-beta.1 → 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.
@@ -0,0 +1,698 @@
1
+ // src/commands/add-plugin.ts
2
+ import chalk7 from "chalk";
3
+ import ora from "ora";
4
+
5
+ // src/lib/package-fetcher.ts
6
+ import { execSync } from "child_process";
7
+ import { mkdtempSync, readdirSync, rmSync, existsSync, readFileSync, copyFileSync } from "fs";
8
+ import { tmpdir } from "os";
9
+ import { join, isAbsolute } from "path";
10
+ import * as tar from "tar";
11
+ async function fetchPackage(packageSpec, version) {
12
+ if (packageSpec.endsWith(".tgz") || packageSpec.startsWith("./") || packageSpec.startsWith("/") || isAbsolute(packageSpec)) {
13
+ return fetchLocalPackage(packageSpec);
14
+ }
15
+ return fetchNpmPackage(packageSpec, version);
16
+ }
17
+ async function fetchLocalPackage(filePath) {
18
+ const absolutePath = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
19
+ if (!existsSync(absolutePath)) {
20
+ throw new Error(`Local package not found: ${absolutePath}`);
21
+ }
22
+ const tempDir = mkdtempSync(join(tmpdir(), "nextspark-add-"));
23
+ try {
24
+ const tgzPath = join(tempDir, "package.tgz");
25
+ copyFileSync(absolutePath, tgzPath);
26
+ await tar.x({
27
+ file: tgzPath,
28
+ cwd: tempDir
29
+ });
30
+ const extractedPath = join(tempDir, "package");
31
+ const packageJson = JSON.parse(
32
+ readFileSync(join(extractedPath, "package.json"), "utf-8")
33
+ );
34
+ return {
35
+ packageJson,
36
+ extractedPath,
37
+ cleanup: () => rmSync(tempDir, { recursive: true, force: true })
38
+ };
39
+ } catch (error) {
40
+ rmSync(tempDir, { recursive: true, force: true });
41
+ throw error;
42
+ }
43
+ }
44
+ async function fetchNpmPackage(packageName, version) {
45
+ const spec = version ? `${packageName}@${version}` : packageName;
46
+ const tempDir = mkdtempSync(join(tmpdir(), "nextspark-add-"));
47
+ try {
48
+ console.log(` Downloading ${spec}...`);
49
+ execSync(`npm pack ${spec} --pack-destination "${tempDir}"`, {
50
+ stdio: "pipe",
51
+ encoding: "utf-8"
52
+ });
53
+ const tgzFile = readdirSync(tempDir).find((f) => f.endsWith(".tgz"));
54
+ if (!tgzFile) {
55
+ throw new Error(`Failed to download package: ${spec}`);
56
+ }
57
+ await tar.x({
58
+ file: join(tempDir, tgzFile),
59
+ cwd: tempDir
60
+ });
61
+ const extractedPath = join(tempDir, "package");
62
+ if (!existsSync(extractedPath)) {
63
+ throw new Error(`Package extraction failed for: ${spec}`);
64
+ }
65
+ const packageJson = JSON.parse(
66
+ readFileSync(join(extractedPath, "package.json"), "utf-8")
67
+ );
68
+ return {
69
+ packageJson,
70
+ extractedPath,
71
+ cleanup: () => rmSync(tempDir, { recursive: true, force: true })
72
+ };
73
+ } catch (error) {
74
+ rmSync(tempDir, { recursive: true, force: true });
75
+ if (error instanceof Error) {
76
+ if (error.message.includes("404") || error.message.includes("not found")) {
77
+ throw new Error(`Package not found: ${spec}. Verify the package name exists on npm.`);
78
+ }
79
+ if (error.message.includes("ENETUNREACH") || error.message.includes("ENOTFOUND")) {
80
+ throw new Error(`Network error. Check your internet connection.`);
81
+ }
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ // src/lib/validator.ts
88
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
89
+ import { join as join2 } from "path";
90
+ function validatePlugin(packageJson, extractedPath) {
91
+ const errors = [];
92
+ const warnings = [];
93
+ const peerDepIssues = [];
94
+ if (!existsSync2(join2(extractedPath, "plugin.config.ts"))) {
95
+ errors.push("Missing plugin.config.ts - not a valid NextSpark plugin");
96
+ }
97
+ if (!packageJson.nextspark) {
98
+ warnings.push("Missing nextspark field in package.json (recommended)");
99
+ } else if (packageJson.nextspark.type !== "plugin") {
100
+ warnings.push(`nextspark.type is "${packageJson.nextspark.type}", expected "plugin"`);
101
+ }
102
+ const peerDeps = packageJson.peerDependencies || {};
103
+ if (!peerDeps["@nextsparkjs/core"]) {
104
+ warnings.push("Plugin should have @nextsparkjs/core as peerDependency");
105
+ }
106
+ const projectPkgPath = join2(process.cwd(), "package.json");
107
+ if (existsSync2(projectPkgPath)) {
108
+ const projectPkg = JSON.parse(readFileSync2(projectPkgPath, "utf-8"));
109
+ const projectDeps = {
110
+ ...projectPkg.dependencies,
111
+ ...projectPkg.devDependencies
112
+ };
113
+ for (const [name, required] of Object.entries(peerDeps)) {
114
+ const installed = projectDeps[name];
115
+ if (!installed) {
116
+ peerDepIssues.push({
117
+ name,
118
+ required,
119
+ installed: null,
120
+ severity: "warning"
121
+ });
122
+ }
123
+ }
124
+ }
125
+ return {
126
+ valid: errors.length === 0,
127
+ errors,
128
+ warnings,
129
+ peerDepIssues
130
+ };
131
+ }
132
+ function validateTheme(packageJson, extractedPath) {
133
+ const errors = [];
134
+ const warnings = [];
135
+ const peerDepIssues = [];
136
+ const configPath = join2(extractedPath, "config", "theme.config.ts");
137
+ if (!existsSync2(configPath)) {
138
+ errors.push("Missing config/theme.config.ts - not a valid NextSpark theme");
139
+ }
140
+ if (!packageJson.nextspark) {
141
+ warnings.push("Missing nextspark field in package.json (recommended)");
142
+ } else if (packageJson.nextspark.type !== "theme") {
143
+ warnings.push(`nextspark.type is "${packageJson.nextspark.type}", expected "theme"`);
144
+ }
145
+ return {
146
+ valid: errors.length === 0,
147
+ errors,
148
+ warnings,
149
+ peerDepIssues
150
+ };
151
+ }
152
+
153
+ // src/lib/installer.ts
154
+ import { existsSync as existsSync4, cpSync, rmSync as rmSync2, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
155
+ import { join as join4 } from "path";
156
+ import chalk from "chalk";
157
+
158
+ // src/lib/config-updater.ts
159
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
160
+ import { join as join3 } from "path";
161
+ async function updateTsConfig(name, type) {
162
+ const tsconfigPath = join3(process.cwd(), "tsconfig.json");
163
+ if (!existsSync3(tsconfigPath)) {
164
+ console.log(" Warning: tsconfig.json not found, skipping path update");
165
+ return;
166
+ }
167
+ try {
168
+ const content = readFileSync3(tsconfigPath, "utf-8");
169
+ const tsconfig = JSON.parse(content);
170
+ if (!tsconfig.compilerOptions) {
171
+ tsconfig.compilerOptions = {};
172
+ }
173
+ if (!tsconfig.compilerOptions.paths) {
174
+ tsconfig.compilerOptions.paths = {};
175
+ }
176
+ const pathKey = type === "plugin" ? `@plugins/${name}/*` : `@themes/${name}/*`;
177
+ const pathValue = type === "plugin" ? [`./contents/plugins/${name}/*`] : [`./contents/themes/${name}/*`];
178
+ if (!tsconfig.compilerOptions.paths[pathKey]) {
179
+ tsconfig.compilerOptions.paths[pathKey] = pathValue;
180
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
181
+ console.log(` Updated tsconfig.json with ${pathKey} path`);
182
+ }
183
+ } catch (error) {
184
+ console.log(" Warning: Could not update tsconfig.json");
185
+ }
186
+ }
187
+ async function registerInPackageJson(npmName, version, type) {
188
+ const pkgPath = join3(process.cwd(), "package.json");
189
+ if (!existsSync3(pkgPath)) {
190
+ console.log(" Warning: package.json not found, skipping registration");
191
+ return;
192
+ }
193
+ try {
194
+ const content = readFileSync3(pkgPath, "utf-8");
195
+ const pkg = JSON.parse(content);
196
+ if (!pkg.nextspark) {
197
+ pkg.nextspark = {};
198
+ }
199
+ const key = type === "plugin" ? "plugins" : "themes";
200
+ if (!pkg.nextspark[key]) {
201
+ pkg.nextspark[key] = [];
202
+ }
203
+ const entry = { name: npmName, version };
204
+ const existingIndex = pkg.nextspark[key].findIndex(
205
+ (e) => e.name === npmName
206
+ );
207
+ if (existingIndex >= 0) {
208
+ pkg.nextspark[key][existingIndex] = entry;
209
+ } else {
210
+ pkg.nextspark[key].push(entry);
211
+ }
212
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
213
+ console.log(` Registered ${type} in package.json`);
214
+ } catch (error) {
215
+ console.log(" Warning: Could not update package.json");
216
+ }
217
+ }
218
+
219
+ // src/lib/installer.ts
220
+ async function installPlugin(extractedPath, packageJson, options = {}) {
221
+ const pluginName = extractPluginName(packageJson.name);
222
+ const targetDir = join4(process.cwd(), "contents", "plugins", pluginName);
223
+ if (options.dryRun) {
224
+ console.log(chalk.cyan("\n [Dry Run] Would perform:"));
225
+ console.log(` - Copy to: contents/plugins/${pluginName}/`);
226
+ if (packageJson.dependencies && Object.keys(packageJson.dependencies).length > 0) {
227
+ console.log(` - Install deps: ${Object.keys(packageJson.dependencies).join(", ")}`);
228
+ }
229
+ console.log(` - Update tsconfig.json paths`);
230
+ console.log(` - Register in package.json`);
231
+ return { success: true, installedPath: targetDir, name: pluginName };
232
+ }
233
+ if (existsSync4(targetDir)) {
234
+ if (!options.force) {
235
+ throw new Error(
236
+ `Plugin "${pluginName}" already exists at ${targetDir}.
237
+ Use --force to overwrite.`
238
+ );
239
+ }
240
+ console.log(chalk.yellow(` Removing existing plugin...`));
241
+ rmSync2(targetDir, { recursive: true, force: true });
242
+ }
243
+ const pluginsDir = join4(process.cwd(), "contents", "plugins");
244
+ if (!existsSync4(pluginsDir)) {
245
+ mkdirSync(pluginsDir, { recursive: true });
246
+ }
247
+ console.log(` Copying to contents/plugins/${pluginName}/...`);
248
+ cpSync(extractedPath, targetDir, { recursive: true });
249
+ const deps = packageJson.dependencies || {};
250
+ const depCount = Object.keys(deps).length;
251
+ if (depCount > 0) {
252
+ console.log(` Plugin has ${depCount} dependencies (will be installed via workspace)`);
253
+ }
254
+ await updateTsConfig(pluginName, "plugin");
255
+ await registerInPackageJson(packageJson.name, packageJson.version || "0.0.0", "plugin");
256
+ await registerPluginInThemeConfig(pluginName);
257
+ return {
258
+ success: true,
259
+ installedPath: targetDir,
260
+ name: pluginName
261
+ };
262
+ }
263
+ async function installTheme(extractedPath, packageJson, options = {}) {
264
+ const themeName = extractThemeName(packageJson.name);
265
+ const targetDir = join4(process.cwd(), "contents", "themes", themeName);
266
+ if (options.dryRun) {
267
+ console.log(chalk.cyan("\n [Dry Run] Would perform:"));
268
+ console.log(` - Copy to: contents/themes/${themeName}/`);
269
+ if (packageJson.requiredPlugins?.length) {
270
+ console.log(` - Install required plugins: ${packageJson.requiredPlugins.join(", ")}`);
271
+ }
272
+ console.log(` - Update tsconfig.json paths`);
273
+ console.log(` - Register in package.json`);
274
+ return { success: true, installedPath: targetDir, name: themeName };
275
+ }
276
+ if (existsSync4(targetDir)) {
277
+ if (!options.force) {
278
+ throw new Error(
279
+ `Theme "${themeName}" already exists at ${targetDir}.
280
+ Use --force to overwrite.`
281
+ );
282
+ }
283
+ console.log(chalk.yellow(` Removing existing theme...`));
284
+ rmSync2(targetDir, { recursive: true, force: true });
285
+ }
286
+ const themesDir = join4(process.cwd(), "contents", "themes");
287
+ if (!existsSync4(themesDir)) {
288
+ mkdirSync(themesDir, { recursive: true });
289
+ }
290
+ console.log(` Copying to contents/themes/${themeName}/...`);
291
+ cpSync(extractedPath, targetDir, { recursive: true });
292
+ await updateTsConfig(themeName, "theme");
293
+ await registerInPackageJson(packageJson.name, packageJson.version || "0.0.0", "theme");
294
+ return {
295
+ success: true,
296
+ installedPath: targetDir,
297
+ name: themeName
298
+ };
299
+ }
300
+ function extractPluginName(npmName) {
301
+ return npmName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
302
+ }
303
+ function extractThemeName(npmName) {
304
+ return npmName.replace(/^@[^/]+\//, "").replace(/^nextspark-theme-/, "").replace(/^theme-/, "");
305
+ }
306
+ async function registerPluginInThemeConfig(pluginName) {
307
+ const envPath = join4(process.cwd(), ".env");
308
+ let activeTheme = "starter";
309
+ if (existsSync4(envPath)) {
310
+ const envContent = readFileSync4(envPath, "utf-8");
311
+ const match = envContent.match(/NEXT_PUBLIC_ACTIVE_THEME=["']?([^"'\s\n]+)["']?/);
312
+ if (match) {
313
+ activeTheme = match[1];
314
+ }
315
+ }
316
+ const themeConfigPath = join4(process.cwd(), "contents", "themes", activeTheme, "config", "theme.config.ts");
317
+ if (!existsSync4(themeConfigPath)) {
318
+ console.log(chalk.gray(` Theme config not found, skipping plugin registration`));
319
+ return;
320
+ }
321
+ try {
322
+ let content = readFileSync4(themeConfigPath, "utf-8");
323
+ if (content.includes(`'${pluginName}'`) || content.includes(`"${pluginName}"`)) {
324
+ console.log(chalk.gray(` Plugin ${pluginName} already registered in theme config`));
325
+ return;
326
+ }
327
+ const pluginsArrayMatch = content.match(/plugins:\s*\[([^\]]*)\]/s);
328
+ if (pluginsArrayMatch) {
329
+ const existingPlugins = pluginsArrayMatch[1].trim();
330
+ const newPlugins = existingPlugins ? `${existingPlugins}, '${pluginName}'` : `'${pluginName}'`;
331
+ content = content.replace(
332
+ /plugins:\s*\[([^\]]*)\]/s,
333
+ `plugins: [${newPlugins}]`
334
+ );
335
+ writeFileSync2(themeConfigPath, content);
336
+ console.log(` Registered plugin in theme.config.ts`);
337
+ }
338
+ } catch (error) {
339
+ console.log(chalk.yellow(` Could not register plugin in theme config: ${error}`));
340
+ }
341
+ }
342
+
343
+ // src/lib/postinstall/index.ts
344
+ import chalk6 from "chalk";
345
+
346
+ // src/lib/postinstall/templates.ts
347
+ import { existsSync as existsSync5, cpSync as cpSync2, mkdirSync as mkdirSync2 } from "fs";
348
+ import { join as join5, dirname } from "path";
349
+ import chalk2 from "chalk";
350
+ async function processTemplates(templates, sourcePath, context) {
351
+ for (const template of templates) {
352
+ const from = join5(sourcePath, template.from);
353
+ const to = resolveVariables(template.to, context);
354
+ if (!existsSync5(from)) {
355
+ console.log(chalk2.yellow(` Warning: Template source not found: ${template.from}`));
356
+ continue;
357
+ }
358
+ const targetExists = existsSync5(to);
359
+ const condition = template.condition || "!exists";
360
+ const desc = template.description || template.to;
361
+ switch (condition) {
362
+ case "!exists":
363
+ if (targetExists) {
364
+ console.log(chalk2.gray(` Skipping ${desc} (already exists)`));
365
+ continue;
366
+ }
367
+ break;
368
+ case "exists":
369
+ if (!targetExists) {
370
+ console.log(chalk2.gray(` Skipping ${desc} (target doesn't exist)`));
371
+ continue;
372
+ }
373
+ break;
374
+ case "prompt":
375
+ if (targetExists) {
376
+ console.log(chalk2.gray(` Skipping ${desc} (already exists, use --force to overwrite)`));
377
+ continue;
378
+ }
379
+ break;
380
+ case "always":
381
+ break;
382
+ }
383
+ const parentDir = dirname(to);
384
+ if (!existsSync5(parentDir)) {
385
+ mkdirSync2(parentDir, { recursive: true });
386
+ }
387
+ console.log(chalk2.gray(` Copying ${desc}...`));
388
+ cpSync2(from, to, { recursive: true });
389
+ }
390
+ }
391
+ function resolveVariables(path, context) {
392
+ let resolved = path;
393
+ if (context.activeTheme) {
394
+ resolved = resolved.replace(/\$\{activeTheme\}/g, context.activeTheme);
395
+ }
396
+ resolved = resolved.replace(/\$\{projectRoot\}/g, context.projectRoot);
397
+ resolved = resolved.replace(/\$\{timestamp\}/g, context.timestamp.toString());
398
+ resolved = resolved.replace(/\$\{coreVersion\}/g, context.coreVersion);
399
+ if (context.pluginName) {
400
+ resolved = resolved.replace(/\$\{pluginName\}/g, context.pluginName);
401
+ }
402
+ if (context.themeName) {
403
+ resolved = resolved.replace(/\$\{themeName\}/g, context.themeName);
404
+ }
405
+ const unresolvedMatch = resolved.match(/\$\{([^}]+)\}/);
406
+ if (unresolvedMatch) {
407
+ throw new Error(`Unresolved template variable: ${unresolvedMatch[0]}`);
408
+ }
409
+ if (!resolved.startsWith("/")) {
410
+ resolved = join5(context.projectRoot, resolved);
411
+ }
412
+ return resolved;
413
+ }
414
+
415
+ // src/lib/postinstall/env-vars.ts
416
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3, appendFileSync } from "fs";
417
+ import { join as join6 } from "path";
418
+ import chalk3 from "chalk";
419
+ async function processEnvVars(envVars) {
420
+ const envExamplePath = join6(process.cwd(), ".env.example");
421
+ const envPath = join6(process.cwd(), ".env");
422
+ let added = 0;
423
+ const existingVars = /* @__PURE__ */ new Set();
424
+ if (existsSync6(envExamplePath)) {
425
+ const content = readFileSync5(envExamplePath, "utf-8");
426
+ const matches = content.match(/^[A-Z_][A-Z0-9_]*=/gm);
427
+ if (matches) {
428
+ matches.forEach((m) => existingVars.add(m.replace("=", "")));
429
+ }
430
+ }
431
+ const newLines = [];
432
+ for (const envVar of envVars) {
433
+ if (existingVars.has(envVar.key)) {
434
+ continue;
435
+ }
436
+ const value = envVar.default || "";
437
+ const requiredMark = envVar.required ? " (required)" : "";
438
+ newLines.push(`# ${envVar.description}${requiredMark}`);
439
+ newLines.push(`${envVar.key}=${value}`);
440
+ newLines.push("");
441
+ added++;
442
+ }
443
+ if (newLines.length > 0) {
444
+ const content = "\n" + newLines.join("\n");
445
+ if (existsSync6(envExamplePath)) {
446
+ appendFileSync(envExamplePath, content);
447
+ } else {
448
+ writeFileSync3(envExamplePath, content.trim() + "\n");
449
+ }
450
+ console.log(chalk3.gray(` Added ${added} env vars to .env.example`));
451
+ }
452
+ }
453
+
454
+ // src/lib/postinstall/migrations.ts
455
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, cpSync as cpSync3 } from "fs";
456
+ import { join as join7, basename } from "path";
457
+ import chalk4 from "chalk";
458
+ async function registerMigrations(migrations, installedPath) {
459
+ const migrationsDir = join7(process.cwd(), "migrations");
460
+ if (!existsSync7(migrationsDir)) {
461
+ mkdirSync3(migrationsDir, { recursive: true });
462
+ }
463
+ let copied = 0;
464
+ for (const migration of migrations) {
465
+ const sourcePath = join7(installedPath, migration);
466
+ if (!existsSync7(sourcePath)) {
467
+ console.log(chalk4.yellow(` Warning: Migration not found: ${migration}`));
468
+ continue;
469
+ }
470
+ const fileName = basename(migration);
471
+ const targetPath = join7(migrationsDir, fileName);
472
+ if (existsSync7(targetPath)) {
473
+ console.log(chalk4.gray(` Migration ${fileName} already exists, skipping`));
474
+ continue;
475
+ }
476
+ cpSync3(sourcePath, targetPath);
477
+ copied++;
478
+ }
479
+ if (copied > 0) {
480
+ console.log(chalk4.gray(` Copied ${copied} migration(s) to /migrations`));
481
+ }
482
+ }
483
+
484
+ // src/lib/postinstall/script-runner.ts
485
+ import { existsSync as existsSync8 } from "fs";
486
+ import { join as join8 } from "path";
487
+ import chalk5 from "chalk";
488
+ async function runCustomScript(scriptPath, installedPath, context) {
489
+ const fullPath = join8(installedPath, scriptPath);
490
+ if (!existsSync8(fullPath)) {
491
+ console.log(chalk5.yellow(` Warning: Postinstall script not found: ${scriptPath}`));
492
+ return;
493
+ }
494
+ console.log("");
495
+ console.log(chalk5.yellow(" Warning: This package wants to run a custom postinstall script:"));
496
+ console.log(chalk5.gray(` ${scriptPath}`));
497
+ console.log("");
498
+ console.log(chalk5.gray(" Custom scripts can execute arbitrary code."));
499
+ console.log(chalk5.gray(" Only allow if you trust the package author."));
500
+ console.log(chalk5.gray(" Skipping script execution (manual approval required)."));
501
+ console.log("");
502
+ }
503
+
504
+ // src/lib/postinstall/index.ts
505
+ import { existsSync as existsSync9 } from "fs";
506
+ import { join as join9 } from "path";
507
+ async function runPostinstall(packageJson, installedPath, context) {
508
+ const postinstall = packageJson.nextspark?.postinstall;
509
+ if (!postinstall) {
510
+ return;
511
+ }
512
+ console.log(chalk6.blue("\n Running postinstall hooks..."));
513
+ if (postinstall.requiredPlugins?.length) {
514
+ for (const plugin of postinstall.requiredPlugins) {
515
+ if (context.installingPlugins.has(plugin)) {
516
+ console.log(chalk6.yellow(` Skipping ${plugin} (already being installed)`));
517
+ continue;
518
+ }
519
+ const pluginExists = await checkPluginExists(plugin);
520
+ if (!pluginExists) {
521
+ console.log(` Installing required plugin: ${plugin}`);
522
+ context.installingPlugins.add(plugin);
523
+ const { addPlugin: addPlugin2 } = await import("./add-plugin-Q7DOR7XO.js");
524
+ await addPlugin2(plugin, { installingPlugins: context.installingPlugins });
525
+ }
526
+ }
527
+ }
528
+ if (postinstall.templates?.length) {
529
+ const needsTheme = postinstall.templates.some(
530
+ (t) => t.to.includes("${activeTheme}")
531
+ );
532
+ if (needsTheme && !context.activeTheme) {
533
+ console.log(chalk6.yellow("\n Warning: Templates require an active theme but none detected."));
534
+ console.log(chalk6.gray(" Set NEXT_PUBLIC_ACTIVE_THEME or install a theme first."));
535
+ console.log(chalk6.gray(" Skipping template installation.\n"));
536
+ } else {
537
+ await processTemplates(postinstall.templates, installedPath, context);
538
+ }
539
+ }
540
+ if (postinstall.envVars?.length) {
541
+ await processEnvVars(postinstall.envVars);
542
+ }
543
+ if (postinstall.migrations?.length) {
544
+ await registerMigrations(postinstall.migrations, installedPath);
545
+ }
546
+ if (postinstall.script) {
547
+ await runCustomScript(postinstall.script, installedPath, context);
548
+ }
549
+ if (postinstall.messages) {
550
+ console.log("");
551
+ if (postinstall.messages.success) {
552
+ console.log(chalk6.green(` ${postinstall.messages.success}`));
553
+ }
554
+ if (postinstall.messages.docs) {
555
+ console.log(chalk6.gray(` Docs: ${postinstall.messages.docs}`));
556
+ }
557
+ if (postinstall.messages.nextSteps?.length) {
558
+ console.log(chalk6.blue("\n Next steps:"));
559
+ postinstall.messages.nextSteps.forEach((step, i) => {
560
+ console.log(chalk6.gray(` ${i + 1}. ${step}`));
561
+ });
562
+ }
563
+ }
564
+ }
565
+ async function checkPluginExists(pluginName) {
566
+ const name = pluginName.replace(/^@[^/]+\//, "").replace(/^nextspark-plugin-/, "").replace(/^plugin-/, "");
567
+ return existsSync9(join9(process.cwd(), "contents", "plugins", name));
568
+ }
569
+
570
+ // src/lib/theme-detector.ts
571
+ import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
572
+ import { join as join10 } from "path";
573
+ function detectActiveTheme() {
574
+ if (process.env.NEXT_PUBLIC_ACTIVE_THEME) {
575
+ return process.env.NEXT_PUBLIC_ACTIVE_THEME;
576
+ }
577
+ const configPath = join10(process.cwd(), "nextspark.config.ts");
578
+ if (existsSync10(configPath)) {
579
+ try {
580
+ const content = readFileSync6(configPath, "utf-8");
581
+ const match = content.match(/activeTheme\s*:\s*['"]([^'"]+)['"]/);
582
+ if (match) {
583
+ return match[1];
584
+ }
585
+ } catch {
586
+ }
587
+ }
588
+ const envPath = join10(process.cwd(), ".env");
589
+ if (existsSync10(envPath)) {
590
+ try {
591
+ const content = readFileSync6(envPath, "utf-8");
592
+ const match = content.match(/NEXT_PUBLIC_ACTIVE_THEME=(.+)/);
593
+ if (match) {
594
+ return match[1].trim();
595
+ }
596
+ } catch {
597
+ }
598
+ }
599
+ const themesDir = join10(process.cwd(), "contents", "themes");
600
+ if (existsSync10(themesDir)) {
601
+ try {
602
+ const themes = readdirSync2(themesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
603
+ if (themes.length === 1) {
604
+ return themes[0];
605
+ }
606
+ } catch {
607
+ }
608
+ }
609
+ return null;
610
+ }
611
+
612
+ // src/commands/add-plugin.ts
613
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
614
+ import { join as join11 } from "path";
615
+ async function addPlugin(packageSpec, options = {}) {
616
+ const spinner = ora(`Adding plugin ${packageSpec}`).start();
617
+ let cleanup = null;
618
+ try {
619
+ const contentsDir = join11(process.cwd(), "contents");
620
+ if (!existsSync11(contentsDir)) {
621
+ spinner.fail('contents/ directory not found. Run "nextspark init" first.');
622
+ return;
623
+ }
624
+ spinner.text = "Downloading package...";
625
+ const { packageJson, extractedPath, cleanup: cleanupFn } = await fetchPackage(
626
+ packageSpec,
627
+ options.version
628
+ );
629
+ cleanup = cleanupFn;
630
+ spinner.text = "Validating plugin...";
631
+ const validation = validatePlugin(packageJson, extractedPath);
632
+ if (!validation.valid) {
633
+ spinner.fail("Invalid plugin");
634
+ validation.errors.forEach((e) => console.log(chalk7.red(` \u2717 ${e}`)));
635
+ return;
636
+ }
637
+ if (validation.warnings.length > 0) {
638
+ validation.warnings.forEach((w) => console.log(chalk7.yellow(` \u26A0 ${w}`)));
639
+ }
640
+ spinner.text = "Installing plugin...";
641
+ spinner.stop();
642
+ const result = await installPlugin(extractedPath, packageJson, options);
643
+ if (!options.skipPostinstall) {
644
+ const coreVersion = getCoreVersion();
645
+ const context = {
646
+ activeTheme: detectActiveTheme(),
647
+ projectRoot: process.cwd(),
648
+ pluginName: result.name,
649
+ coreVersion,
650
+ timestamp: Date.now(),
651
+ installingPlugins: options.installingPlugins || /* @__PURE__ */ new Set([packageSpec])
652
+ };
653
+ await runPostinstall(packageJson, result.installedPath, context);
654
+ }
655
+ console.log(chalk7.green(`
656
+ \u2713 Plugin ${result.name} installed successfully!`));
657
+ console.log(chalk7.gray(` Location: contents/plugins/${result.name}/`));
658
+ } catch (error) {
659
+ spinner.fail("Failed to add plugin");
660
+ if (error instanceof Error) {
661
+ console.log(chalk7.red(` ${error.message}`));
662
+ }
663
+ throw error;
664
+ } finally {
665
+ if (cleanup) cleanup();
666
+ }
667
+ }
668
+ function getCoreVersion() {
669
+ const pkgPath = join11(process.cwd(), "node_modules", "@nextsparkjs", "core", "package.json");
670
+ if (existsSync11(pkgPath)) {
671
+ try {
672
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
673
+ return pkg.version || "0.0.0";
674
+ } catch {
675
+ return "0.0.0";
676
+ }
677
+ }
678
+ return "0.0.0";
679
+ }
680
+ function addPluginCommand(packageSpec, options) {
681
+ return addPlugin(packageSpec, {
682
+ force: options.force,
683
+ skipDeps: options.noDeps,
684
+ dryRun: options.dryRun,
685
+ skipPostinstall: options.skipPostinstall,
686
+ version: options.version
687
+ });
688
+ }
689
+
690
+ export {
691
+ fetchPackage,
692
+ validateTheme,
693
+ installTheme,
694
+ addPlugin,
695
+ addPluginCommand,
696
+ runPostinstall
697
+ };
698
+ //# sourceMappingURL=chunk-5GBCARGX.js.map