@nextsparkjs/cli 0.1.0-beta.2 → 0.1.0-beta.4

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