novaapp-sdk 1.4.1 → 1.4.2

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,871 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/cli/index.ts
31
+ var cli_exports = {};
32
+ __export(cli_exports, {
33
+ run: () => run
34
+ });
35
+ module.exports = __toCommonJS(cli_exports);
36
+
37
+ // src/cli/utils.ts
38
+ var fs = __toESM(require("fs"));
39
+ var path = __toESM(require("path"));
40
+ var isTTY = Boolean(process.stdout.isTTY);
41
+ var c = {
42
+ reset: (s) => isTTY ? `\x1B[0m${s}\x1B[0m` : s,
43
+ bold: (s) => isTTY ? `\x1B[1m${s}\x1B[0m` : s,
44
+ dim: (s) => isTTY ? `\x1B[2m${s}\x1B[0m` : s,
45
+ green: (s) => isTTY ? `\x1B[32m${s}\x1B[0m` : s,
46
+ yellow: (s) => isTTY ? `\x1B[33m${s}\x1B[0m` : s,
47
+ cyan: (s) => isTTY ? `\x1B[36m${s}\x1B[0m` : s,
48
+ red: (s) => isTTY ? `\x1B[31m${s}\x1B[0m` : s
49
+ };
50
+ var colors = c;
51
+ function print(msg) {
52
+ process.stdout.write(msg + "\n");
53
+ }
54
+ function printInfo(msg) {
55
+ print(c.cyan("\u2139") + " " + msg);
56
+ }
57
+ function printSuccess(msg) {
58
+ print(c.green("\u2714") + " " + msg);
59
+ }
60
+ function printWarning(msg) {
61
+ print(c.yellow("\u26A0") + " " + msg);
62
+ }
63
+ function printError(msg) {
64
+ print(c.red("\u2716") + " " + msg);
65
+ }
66
+ function printHeader(title) {
67
+ print("\n" + c.bold(title));
68
+ }
69
+ function printStep(step) {
70
+ print(c.dim(" \u2192") + " " + step);
71
+ }
72
+ function printTable(rows, pad = 20) {
73
+ for (const [left, right] of rows) {
74
+ print(" " + c.cyan(left.padEnd(pad)) + right);
75
+ }
76
+ }
77
+ function fatal(msg) {
78
+ printError(msg);
79
+ process.exit(1);
80
+ }
81
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set(["new", "generate", "deploy", "dev", "help"]);
82
+ function parseArgs(argv) {
83
+ const flags = {};
84
+ const positional = [];
85
+ let command = null;
86
+ let i = 0;
87
+ while (i < argv.length) {
88
+ const arg = argv[i];
89
+ if (arg.startsWith("--")) {
90
+ const [key, ...valueParts] = arg.slice(2).split("=");
91
+ if (valueParts.length) {
92
+ flags[key] = valueParts.join("=");
93
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
94
+ flags[key] = argv[++i];
95
+ } else {
96
+ flags[key] = true;
97
+ }
98
+ } else if (arg.startsWith("-") && arg.length === 2) {
99
+ flags[arg[1]] = true;
100
+ } else {
101
+ if (command === null && KNOWN_COMMANDS.has(arg)) {
102
+ command = arg;
103
+ } else {
104
+ positional.push(arg);
105
+ }
106
+ }
107
+ i++;
108
+ }
109
+ return { command, positional, flags };
110
+ }
111
+ function mkdirp(dir) {
112
+ fs.mkdirSync(dir, { recursive: true });
113
+ }
114
+ function writeFile(filePath, content, force = false) {
115
+ const dir = path.dirname(filePath);
116
+ mkdirp(dir);
117
+ if (!force && fs.existsSync(filePath)) {
118
+ printWarning(`Skipped (already exists): ${filePath}`);
119
+ return;
120
+ }
121
+ fs.writeFileSync(filePath, content, "utf8");
122
+ printSuccess(`Created: ${filePath}`);
123
+ }
124
+ function slugify(str) {
125
+ return str.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
126
+ }
127
+ function pascalCase(str) {
128
+ return str.replace(/[-_](.)/g, (_, c2) => c2.toUpperCase()).replace(/^(.)/, (_, c2) => c2.toUpperCase());
129
+ }
130
+ function camelCase(str) {
131
+ const p = pascalCase(str);
132
+ return p.charAt(0).toLowerCase() + p.slice(1);
133
+ }
134
+
135
+ // src/cli/commands/new.ts
136
+ var path2 = __toESM(require("path"));
137
+ var cp = __toESM(require("child_process"));
138
+ async function runNew(args, flags) {
139
+ const name = args[0];
140
+ if (!name) fatal("Usage: nova new <project-name>");
141
+ const options = {
142
+ name,
143
+ skipInstall: flags["skip-install"] === true || flags["y"] === true
144
+ };
145
+ scaffold(options);
146
+ if (!options.skipInstall) {
147
+ printHeader("Installing dependencies\u2026");
148
+ printStep("npm install");
149
+ try {
150
+ cp.execSync("npm install", {
151
+ cwd: path2.resolve(slugify(name)),
152
+ stdio: "inherit"
153
+ });
154
+ } catch {
155
+ printWarning("npm install failed \u2014 run it manually inside the project folder.");
156
+ }
157
+ }
158
+ printHeader("Done!");
159
+ print("");
160
+ printInfo(` cd ${slugify(name)}`);
161
+ printInfo(" cp .env.example .env # add your BOT_TOKEN");
162
+ printInfo(" npm run dev");
163
+ print("");
164
+ }
165
+ function scaffold(options) {
166
+ const { name } = options;
167
+ const slug = slugify(name);
168
+ const root = path2.resolve(slug);
169
+ printHeader(`Creating Nova project "${slug}"\u2026`);
170
+ writeFile(
171
+ path2.join(root, "package.json"),
172
+ JSON.stringify(
173
+ {
174
+ name: slug,
175
+ version: "1.0.0",
176
+ type: "module",
177
+ scripts: {
178
+ build: "tsc",
179
+ start: "node dist/index.js",
180
+ dev: "nova dev"
181
+ },
182
+ dependencies: {
183
+ "novaapp-sdk": "latest"
184
+ },
185
+ devDependencies: {
186
+ typescript: "^5.0.0"
187
+ }
188
+ },
189
+ null,
190
+ 2
191
+ ) + "\n"
192
+ );
193
+ writeFile(
194
+ path2.join(root, "tsconfig.json"),
195
+ JSON.stringify(
196
+ {
197
+ compilerOptions: {
198
+ target: "ES2022",
199
+ module: "NodeNext",
200
+ moduleResolution: "NodeNext",
201
+ strict: true,
202
+ outDir: "./dist",
203
+ rootDir: "./src",
204
+ declaration: true,
205
+ skipLibCheck: true
206
+ },
207
+ include: ["src"]
208
+ },
209
+ null,
210
+ 2
211
+ ) + "\n"
212
+ );
213
+ writeFile(
214
+ path2.join(root, ".gitignore"),
215
+ [
216
+ "node_modules/",
217
+ "dist/",
218
+ ".env",
219
+ "*.log"
220
+ ].join("\n") + "\n"
221
+ );
222
+ writeFile(
223
+ path2.join(root, ".env.example"),
224
+ [
225
+ "# Copy this file to .env and fill in your values",
226
+ "BOT_TOKEN=your_token_here",
227
+ "SERVER_URL=https://nova.example.com"
228
+ ].join("\n") + "\n"
229
+ );
230
+ writeFile(
231
+ path2.join(root, "src", "index.ts"),
232
+ indexTemplate(slug)
233
+ );
234
+ writeFile(
235
+ path2.join(root, "src", "commands", "ping.ts"),
236
+ pingCommandTemplate()
237
+ );
238
+ writeFile(
239
+ path2.join(root, "src", "events", "ready.ts"),
240
+ readyEventTemplate()
241
+ );
242
+ printSuccess(`Project "${slug}" created at ./${slug}`);
243
+ }
244
+ function indexTemplate(botName) {
245
+ return `import { NovaClient } from 'novaapp-sdk'
246
+ import { ping } from './commands/ping.js'
247
+ import { onReady } from './events/ready.js'
248
+
249
+ const client = new NovaClient({
250
+ token: process.env.BOT_TOKEN ?? '',
251
+ serverUrl: process.env.SERVER_URL ?? 'https://nova.example.com',
252
+ })
253
+
254
+ // Register commands
255
+ client.commands.register(ping)
256
+
257
+ // Register events
258
+ client.on('ready', onReady)
259
+
260
+ client.connect()
261
+ `;
262
+ }
263
+ function pingCommandTemplate() {
264
+ return `import type { NovaClient, Interaction } from 'novaapp-sdk'
265
+
266
+ export const ping = {
267
+ name: 'ping',
268
+ description: 'Replies with Pong!',
269
+
270
+ async execute(client: NovaClient, interaction: Interaction) {
271
+ await client.interactions.respond(interaction.id, {
272
+ content: 'Pong! \u{1F3D3}',
273
+ })
274
+ },
275
+ }
276
+ `;
277
+ }
278
+ function readyEventTemplate() {
279
+ return `import type { BotApplication } from 'novaapp-sdk'
280
+
281
+ export function onReady(bot: BotApplication) {
282
+ console.log(\`\u2714 Logged in as \${bot.botUser?.username ?? 'Unknown'}\`)
283
+ }
284
+ `;
285
+ }
286
+
287
+ // src/cli/commands/generate.ts
288
+ var path3 = __toESM(require("path"));
289
+ async function runGenerate(args, flags) {
290
+ const [kindRaw, name] = args;
291
+ if (!kindRaw) {
292
+ fatal(
293
+ "Usage: nova generate <command|event|plugin|middleware> <name>\n nova generate command ban\n nova generate event messageDelete\n nova generate plugin moderation\n nova generate middleware rateLimit"
294
+ );
295
+ }
296
+ const kind = kindRaw.toLowerCase();
297
+ const KINDS = ["command", "event", "plugin", "middleware"];
298
+ if (!KINDS.includes(kind)) {
299
+ fatal(`Unknown kind "${kindRaw}". Must be one of: ${KINDS.join(", ")}`);
300
+ }
301
+ if (!name) fatal(`Usage: nova generate ${kind} <name>`);
302
+ const options = {
303
+ kind,
304
+ name,
305
+ outDir: typeof flags["out"] === "string" ? flags["out"] : void 0
306
+ };
307
+ generate(options);
308
+ }
309
+ function generate(options) {
310
+ const { kind, name } = options;
311
+ const defaultDirs = {
312
+ command: "src/commands",
313
+ event: "src/events",
314
+ plugin: "src/plugins",
315
+ middleware: "src/middleware"
316
+ };
317
+ const outDir = options.outDir ?? defaultDirs[kind];
318
+ const fileName = camelCase(name) + ".ts";
319
+ const filePath = path3.resolve(outDir, fileName);
320
+ const generators = {
321
+ command: () => commandTemplate(name),
322
+ event: () => eventTemplate(name),
323
+ plugin: () => pluginTemplate(name),
324
+ middleware: () => middlewareTemplate(name)
325
+ };
326
+ printHeader(`Generating ${kind} "${name}"\u2026`);
327
+ writeFile(filePath, generators[kind]());
328
+ print("");
329
+ printInfo(`Register it in your bot entry:
330
+ `);
331
+ const hints = {
332
+ command: ` client.commands.register(${camelCase(name)})`,
333
+ event: ` client.on('${camelCase(name)}', handle${pascalCase(name)})`,
334
+ plugin: ` client.plugins.use(${camelCase(name)}Plugin)`,
335
+ middleware: ` client.middleware.use(${camelCase(name)}Middleware)`
336
+ };
337
+ printInfo(hints[kind]);
338
+ print("");
339
+ }
340
+ function commandTemplate(name) {
341
+ const identName = camelCase(name);
342
+ const pascal = pascalCase(name);
343
+ return `import type { NovaClient, Interaction } from 'novaapp-sdk'
344
+
345
+ /**
346
+ * /${name} command
347
+ */
348
+ export const ${identName} = {
349
+ name: '${name}',
350
+ description: 'Description of the ${name} command',
351
+
352
+ async execute(client: NovaClient, interaction: Interaction) {
353
+ await client.interactions.respond(interaction.id, {
354
+ content: '${pascal} executed!',
355
+ })
356
+ },
357
+ }
358
+ `;
359
+ }
360
+ function eventTemplate(name) {
361
+ const pascal = pascalCase(name);
362
+ return `import type { NovaClient } from 'novaapp-sdk'
363
+
364
+ /**
365
+ * Handler for the \`${name}\` event.
366
+ */
367
+ export async function handle${pascal}(
368
+ ..._args: unknown[]
369
+ ): Promise<void> {
370
+ // TODO: implement ${name} handler
371
+ }
372
+
373
+ /**
374
+ * Register this handler with a client.
375
+ */
376
+ export function register${pascal}(client: NovaClient): void {
377
+ client.on('${name}' as never, handle${pascal})
378
+ }
379
+ `;
380
+ }
381
+ function pluginTemplate(name) {
382
+ const pascal = pascalCase(name);
383
+ const ident = camelCase(name);
384
+ return `import type { NovaPlugin, NovaClient } from 'novaapp-sdk'
385
+
386
+ /**
387
+ * ${pascal} plugin.
388
+ */
389
+ interface ${pascal}PluginOptions {
390
+ // TODO: add your plugin options
391
+ }
392
+
393
+ export function ${ident}Plugin(options: ${pascal}PluginOptions = {}): NovaPlugin {
394
+ return {
395
+ name: '${ident}',
396
+
397
+ install(client: NovaClient) {
398
+ // TODO: implement plugin logic
399
+ void options // remove this line once you use options
400
+ },
401
+ }
402
+ }
403
+ `;
404
+ }
405
+ function middlewareTemplate(name) {
406
+ const pascal = pascalCase(name);
407
+ const ident = camelCase(name);
408
+ return `import type { NovaMiddleware, MiddlewareContext, MiddlewareNext } from 'novaapp-sdk'
409
+
410
+ /**
411
+ * ${pascal} middleware.
412
+ */
413
+ export const ${ident}Middleware: NovaMiddleware = async (
414
+ ctx: MiddlewareContext,
415
+ next: MiddlewareNext,
416
+ ): Promise<void> => {
417
+ // TODO: add ${name} logic here (before next())
418
+ await next()
419
+ // TODO: add ${name} logic here (after next())
420
+ }
421
+ `;
422
+ }
423
+
424
+ // src/cli/commands/deploy.ts
425
+ var cp2 = __toESM(require("child_process"));
426
+ async function runDeploy(args, flags) {
427
+ const options = {
428
+ skipBuild: flags["skip-build"] === true
429
+ };
430
+ const provider = typeof flags["provider"] === "string" ? flags["provider"] : "generic";
431
+ if (!options.skipBuild) {
432
+ await build();
433
+ } else {
434
+ printWarning("Skipping build (--skip-build)");
435
+ }
436
+ printDeployInstructions(provider);
437
+ }
438
+ function build() {
439
+ return new Promise((resolve5, reject) => {
440
+ printHeader("Building project\u2026");
441
+ printStep("npm run build");
442
+ const child = cp2.spawn("npm", ["run", "build"], {
443
+ stdio: "inherit",
444
+ shell: process.platform === "win32"
445
+ });
446
+ child.on("close", (code) => {
447
+ if (code === 0) {
448
+ printSuccess("Build succeeded");
449
+ resolve5();
450
+ } else {
451
+ reject(new Error(`Build failed with exit code ${code}`));
452
+ }
453
+ });
454
+ child.on("error", reject);
455
+ });
456
+ }
457
+ function printDeployInstructions(provider) {
458
+ printHeader("Deployment guide");
459
+ print("");
460
+ if (provider === "railway" || provider === "rail") {
461
+ printRailway();
462
+ } else if (provider === "docker") {
463
+ printDocker();
464
+ } else if (provider === "vps") {
465
+ printVps();
466
+ } else {
467
+ printGeneric();
468
+ }
469
+ print("");
470
+ printInfo("Tip: pass --provider=railway|docker|vps for provider-specific instructions.");
471
+ print("");
472
+ }
473
+ function printGeneric() {
474
+ print(colors.bold(" Generic (any Node.js host)"));
475
+ print("");
476
+ printStep("Ensure BOT_TOKEN and SERVER_URL are set as environment variables");
477
+ printStep("Upload your project (dist/, package.json, .env) to the host");
478
+ printStep("Run: node dist/index.js");
479
+ print("");
480
+ print(" For platform-specific guides run:");
481
+ print(colors.cyan(" nova deploy --provider=railway"));
482
+ print(colors.cyan(" nova deploy --provider=docker"));
483
+ print(colors.cyan(" nova deploy --provider=vps"));
484
+ }
485
+ function printRailway() {
486
+ print(colors.bold(" Railway"));
487
+ print("");
488
+ printStep("Install Railway CLI: npm i -g @railway/cli");
489
+ printStep("Login: railway login");
490
+ printStep("Create project: railway init");
491
+ printStep("Set secrets: railway variables set BOT_TOKEN=xxx SERVER_URL=xxx");
492
+ printStep("Deploy: railway up");
493
+ print("");
494
+ print(" Nova bot will auto-restart on crashes (Railway default).");
495
+ }
496
+ function printDocker() {
497
+ print(colors.bold(" Docker"));
498
+ print("");
499
+ printStep("Build image: docker build -t my-nova-bot .");
500
+ printStep("Run: docker run -e BOT_TOKEN=xxx -e SERVER_URL=xxx my-nova-bot");
501
+ print("");
502
+ print(" Sample Dockerfile:");
503
+ print(colors.dim([
504
+ " FROM node:20-alpine",
505
+ " WORKDIR /app",
506
+ " COPY package*.json ./",
507
+ " RUN npm ci --omit=dev",
508
+ " COPY dist/ ./dist/",
509
+ ' CMD ["node", "dist/index.js"]'
510
+ ].join("\n")));
511
+ }
512
+ function printVps() {
513
+ print(colors.bold(" VPS / bare metal"));
514
+ print("");
515
+ printStep("Copy dist/ and package.json to the server");
516
+ printStep("npm ci --omit=dev");
517
+ printStep("Set BOT_TOKEN and SERVER_URL in your environment / .env");
518
+ printStep("Use PM2 for process management:");
519
+ print("");
520
+ print(colors.dim([
521
+ " npm install -g pm2",
522
+ " pm2 start dist/index.js --name my-nova-bot",
523
+ " pm2 save",
524
+ " pm2 startup # persist across reboots"
525
+ ].join("\n")));
526
+ }
527
+
528
+ // src/cli/commands/dev.ts
529
+ var cp3 = __toESM(require("child_process"));
530
+ var fs3 = __toESM(require("fs"));
531
+ var path5 = __toESM(require("path"));
532
+
533
+ // src/devtools/HotLoader.ts
534
+ var import_node_events = require("events");
535
+ var fs2 = __toESM(require("fs"));
536
+ var path4 = __toESM(require("path"));
537
+ var HotLoader = class extends import_node_events.EventEmitter {
538
+ constructor(watchPath, options = {}) {
539
+ super();
540
+ this.watcher = null;
541
+ this.debounceTimer = null;
542
+ this.lastChanged = null;
543
+ this.restartFn = null;
544
+ this.restarting = false;
545
+ this.watchPath = path4.resolve(watchPath);
546
+ this.debounceMs = options.debounce ?? 250;
547
+ this.recursive = options.recursive ?? true;
548
+ this.extensions = new Set(
549
+ options.extensions ?? [".ts", ".js", ".mjs", ".cjs", ".json"]
550
+ );
551
+ }
552
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
553
+ /**
554
+ * Start watching for file changes.
555
+ *
556
+ * Calling `start()` again after `stop()` is safe — it creates a new watcher.
557
+ */
558
+ start() {
559
+ if (this.watcher) return;
560
+ this.watcher = fs2.watch(
561
+ this.watchPath,
562
+ { recursive: this.recursive, encoding: "utf8" },
563
+ (event, filename) => {
564
+ if (!filename) return;
565
+ const ext = path4.extname(filename);
566
+ if (!this.extensions.has(ext)) return;
567
+ const full = path4.join(this.watchPath, filename);
568
+ this.emit("change", full);
569
+ this.lastChanged = full;
570
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
571
+ this.debounceTimer = setTimeout(() => {
572
+ this.debounceTimer = null;
573
+ if (this.lastChanged) {
574
+ const f = this.lastChanged;
575
+ this.lastChanged = null;
576
+ this.emit("reload", f);
577
+ this.triggerRestart();
578
+ }
579
+ }, this.debounceMs);
580
+ }
581
+ );
582
+ this.watcher.on("error", (err) => {
583
+ this.emit("error", err);
584
+ });
585
+ }
586
+ /**
587
+ * Stop watching. Safe to call even if not started.
588
+ */
589
+ stop() {
590
+ if (this.debounceTimer) {
591
+ clearTimeout(this.debounceTimer);
592
+ this.debounceTimer = null;
593
+ }
594
+ if (this.watcher) {
595
+ this.watcher.close();
596
+ this.watcher = null;
597
+ }
598
+ }
599
+ // ── Restart helper ─────────────────────────────────────────────────────────
600
+ /**
601
+ * Register a callback that runs after every debounced `reload` event.
602
+ *
603
+ * If the callback is already running when another reload fires it will be
604
+ * queued once and run again after the current run finishes.
605
+ *
606
+ * @example
607
+ * ```ts
608
+ * loader.restart(async () => {
609
+ * await client.disconnect()
610
+ * await build()
611
+ * await client.connect()
612
+ * })
613
+ * ```
614
+ */
615
+ restart(fn) {
616
+ this.restartFn = fn;
617
+ }
618
+ triggerRestart() {
619
+ if (!this.restartFn || this.restarting) return;
620
+ const fn = this.restartFn;
621
+ this.restarting = true;
622
+ const result = fn();
623
+ const finish = () => {
624
+ this.restarting = false;
625
+ };
626
+ if (result instanceof Promise) {
627
+ result.then(finish, (err) => {
628
+ finish();
629
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
630
+ });
631
+ } else {
632
+ finish();
633
+ }
634
+ }
635
+ // ── Typed event overloads ──────────────────────────────────────────────────
636
+ on(event, listener) {
637
+ return super.on(event, listener);
638
+ }
639
+ once(event, listener) {
640
+ return super.once(event, listener);
641
+ }
642
+ off(event, listener) {
643
+ return super.off(event, listener);
644
+ }
645
+ emit(event, ...args) {
646
+ return super.emit(event, ...args);
647
+ }
648
+ /** Whether the loader is currently active. */
649
+ get watching() {
650
+ return this.watcher !== null;
651
+ }
652
+ };
653
+
654
+ // src/cli/commands/dev.ts
655
+ async function runDev(args, flags) {
656
+ const entry = typeof flags["entry"] === "string" ? flags["entry"] : detectEntry();
657
+ const debounce = typeof flags["debounce"] === "string" ? parseInt(flags["debounce"], 10) : 250;
658
+ const noWatch = flags["no-watch"] === true;
659
+ if (!fs3.existsSync(entry)) {
660
+ fatal(
661
+ `Entry file not found: ${entry}
662
+ Specify one with --entry src/index.ts or --entry dist/index.js`
663
+ );
664
+ }
665
+ printHeader("Nova dev mode");
666
+ printStep(`Entry: ${entry}`);
667
+ printStep(`Watch: ${noWatch ? "disabled" : "enabled"}`);
668
+ print("");
669
+ if (noWatch) {
670
+ spawnOnce(entry);
671
+ return;
672
+ }
673
+ const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
674
+ const useNodeWatch = nodeVersion >= 18 && entry.endsWith(".js");
675
+ if (useNodeWatch) {
676
+ spawnWithNodeWatch(entry);
677
+ } else {
678
+ await spawnWithHotLoader(entry, debounce);
679
+ }
680
+ }
681
+ function spawnOnce(entry) {
682
+ printInfo("Starting bot (no watch)\u2026");
683
+ const child = cp3.spawn(process.execPath, [entry], {
684
+ stdio: "inherit",
685
+ env: { ...process.env }
686
+ });
687
+ child.on("close", (code) => {
688
+ print(`
689
+ Bot exited with code ${code ?? 0}`);
690
+ process.exit(code ?? 0);
691
+ });
692
+ child.on("error", (err) => {
693
+ throw err;
694
+ });
695
+ }
696
+ function spawnWithNodeWatch(entry) {
697
+ printInfo("Starting bot with node --watch (Node 18+ built-in)\u2026");
698
+ printStep("Press Ctrl+C to stop");
699
+ print("");
700
+ const child = cp3.spawn(process.execPath, ["--watch", entry], {
701
+ stdio: "inherit",
702
+ env: { ...process.env }
703
+ });
704
+ child.on("error", (err) => {
705
+ throw err;
706
+ });
707
+ process.on("SIGINT", () => {
708
+ child.kill();
709
+ process.exit(0);
710
+ });
711
+ process.on("SIGTERM", () => {
712
+ child.kill();
713
+ process.exit(0);
714
+ });
715
+ }
716
+ async function spawnWithHotLoader(entry, debounce) {
717
+ const srcDir = path5.resolve("src");
718
+ if (!fs3.existsSync(srcDir)) {
719
+ printWarning("src/ directory not found \u2014 watching project root");
720
+ }
721
+ const watchDir = fs3.existsSync(srcDir) ? srcDir : process.cwd();
722
+ printInfo(`Watching ${watchDir} for changes\u2026`);
723
+ printStep("Press Ctrl+C to stop");
724
+ print("");
725
+ let current = null;
726
+ function spawnBot() {
727
+ if (current) {
728
+ current.kill();
729
+ current = null;
730
+ }
731
+ printStep("Starting bot\u2026");
732
+ current = cp3.spawn(process.execPath, [entry], {
733
+ stdio: "inherit",
734
+ env: { ...process.env }
735
+ });
736
+ current.on("close", (code) => {
737
+ if (code !== null && code !== 0) {
738
+ printWarning(`Bot exited with code ${code} \u2014 waiting for changes\u2026`);
739
+ }
740
+ });
741
+ current.on("error", (err) => {
742
+ printWarning(`Spawn error: ${err.message}`);
743
+ });
744
+ }
745
+ spawnBot();
746
+ const loader = new HotLoader(watchDir, { debounce });
747
+ loader.on("reload", (changed) => {
748
+ printInfo(`Reloading (${path5.relative(process.cwd(), changed)})`);
749
+ spawnBot();
750
+ });
751
+ loader.on("error", (err) => {
752
+ printWarning(`Watcher error: ${err.message}`);
753
+ });
754
+ loader.start();
755
+ await new Promise((resolve5) => {
756
+ process.on("SIGINT", () => {
757
+ loader.stop();
758
+ current?.kill();
759
+ resolve5();
760
+ });
761
+ process.on("SIGTERM", () => {
762
+ loader.stop();
763
+ current?.kill();
764
+ resolve5();
765
+ });
766
+ });
767
+ }
768
+ function detectEntry() {
769
+ const candidates = ["dist/index.js", "src/index.ts", "index.js", "index.ts"];
770
+ for (const c2 of candidates) {
771
+ if (fs3.existsSync(path5.resolve(c2))) {
772
+ if (c2.endsWith(".ts")) {
773
+ printWarning(
774
+ `Auto-detected TypeScript entry (${c2}).
775
+ Note: nova dev runs compiled JS. Run "npm run build" first, or
776
+ use ts-node / tsx: nova dev --entry dist/index.js`
777
+ );
778
+ }
779
+ return path5.resolve(c2);
780
+ }
781
+ }
782
+ return path5.resolve("dist/index.js");
783
+ }
784
+
785
+ // src/cli/runner.ts
786
+ var VERSION = "1.4.1";
787
+ async function run(argv) {
788
+ const parsed = parseArgs(argv);
789
+ const { command, positional, flags } = parsed;
790
+ if (flags["version"] || flags["v"]) {
791
+ print(`nova ${VERSION}`);
792
+ return;
793
+ }
794
+ if (!command || command === "help" || flags["help"] || flags["h"]) {
795
+ printHelp();
796
+ return;
797
+ }
798
+ try {
799
+ switch (command) {
800
+ case "new":
801
+ await runNew(positional, flags);
802
+ break;
803
+ case "generate":
804
+ await runGenerate(positional, flags);
805
+ break;
806
+ case "deploy":
807
+ await runDeploy(positional, flags);
808
+ break;
809
+ case "dev":
810
+ await runDev(positional, flags);
811
+ break;
812
+ default:
813
+ fatal(`Unknown command "${command}". Run "nova help" for usage.`);
814
+ }
815
+ } catch (err) {
816
+ const msg = err instanceof Error ? err.message : String(err);
817
+ fatal(msg);
818
+ }
819
+ }
820
+ function printHelp() {
821
+ print("");
822
+ print(colors.bold(` Nova CLI v${VERSION}`));
823
+ print(colors.dim(" The Nova Discord SDK command-line interface"));
824
+ print("");
825
+ print(colors.bold(" Usage"));
826
+ print(" nova <command> [options]");
827
+ print("");
828
+ print(colors.bold(" Commands"));
829
+ printTable(
830
+ [
831
+ ["new <name>", "Scaffold a new Nova bot project"],
832
+ ["generate <kind> <name>", "Generate a command, event, plugin, or middleware"],
833
+ ["dev", "Start the bot with hot reload"],
834
+ ["deploy", "Build and print deployment instructions"],
835
+ ["help", "Show this help message"]
836
+ ],
837
+ 30
838
+ );
839
+ print("");
840
+ print(colors.bold(" Generate kinds"));
841
+ printTable(
842
+ [
843
+ ["command", "Slash / prefix command handler"],
844
+ ["event", "Gateway event handler"],
845
+ ["plugin", "Reusable plugin"],
846
+ ["middleware", "Command middleware"]
847
+ ],
848
+ 14
849
+ );
850
+ print("");
851
+ print(colors.bold(" Examples"));
852
+ print(colors.dim(" nova new my-bot"));
853
+ print(colors.dim(" nova generate command ban"));
854
+ print(colors.dim(" nova generate event messageDelete"));
855
+ print(colors.dim(" nova dev --entry dist/index.js"));
856
+ print(colors.dim(" nova deploy --provider=railway"));
857
+ print("");
858
+ print(colors.bold(" Options"));
859
+ printTable(
860
+ [
861
+ ["--version, -v", "Print version number"],
862
+ ["--help, -h", "Print this help text"]
863
+ ],
864
+ 20
865
+ );
866
+ print("");
867
+ }
868
+ // Annotate the CommonJS export names for ESM import in node:
869
+ 0 && (module.exports = {
870
+ run
871
+ });