@nexusts/cli 0.9.6 → 0.9.8

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/dist/index.js CHANGED
@@ -45,6 +45,12 @@ function parseArgs(argv) {
45
45
  if (longMatch) {
46
46
  const [, name, inline] = longMatch;
47
47
  const flagName = name;
48
+ if (flagName.startsWith("no-")) {
49
+ const key = flagName.slice(3);
50
+ setFlag(flags, key, false);
51
+ i++;
52
+ continue;
53
+ }
48
54
  if (inline !== undefined) {
49
55
  setFlag(flags, flagName, inline);
50
56
  i++;
@@ -168,7 +174,8 @@ async function loadConfig(cwd = process.cwd()) {
168
174
  const mod = await import(path);
169
175
  config = mod.default ?? mod;
170
176
  } catch (importErr) {
171
- console.warn(`[nx] Could not dynamically import ${candidate}: ${importErr instanceof Error ? importErr.message : String(importErr)}. Falling back to defaults.`);
177
+ process.stderr.write(`[nx] Could not dynamically import ${candidate}: ${importErr instanceof Error ? importErr.message : String(importErr)}. Falling back to defaults.
178
+ `);
172
179
  config = {};
173
180
  }
174
181
  }
@@ -247,6 +254,118 @@ import {
247
254
  writeFileSync
248
255
  } from "fs";
249
256
  import { dirname, isAbsolute, relative, resolve as resolve2 } from "path";
257
+
258
+ // packages/cli/src/core/template.ts
259
+ var VAR_RE = /\{\{\s*([\w.]+)(?:\s*\|\s*(\w+))?\s*\}\}/;
260
+ var SECTION_RE = /\{\{\s*([#^])\s*([\w.]+)\s*\}\}([\s\S]*?)\{\{\s*\/\s*\2\s*\}\}/g;
261
+ function render(template, context) {
262
+ let out = template.replace(SECTION_RE, (_, kind, key, body) => {
263
+ const v = lookup(context, key);
264
+ const truthy = isTruthy(v);
265
+ if (kind === "#")
266
+ return truthy ? body : "";
267
+ return truthy ? "" : body;
268
+ });
269
+ let prev;
270
+ do {
271
+ prev = out;
272
+ out = out.replace(VAR_RE, (_, key, filter) => {
273
+ const v = lookup(context, key);
274
+ return applyFilter(v === undefined || v === null ? "" : String(v), filter);
275
+ });
276
+ } while (out !== prev);
277
+ return out;
278
+ }
279
+ function lookup(ctx, dotted) {
280
+ if (dotted in ctx)
281
+ return ctx[dotted];
282
+ const parts = dotted.split(".");
283
+ let cur = ctx;
284
+ for (const p of parts) {
285
+ if (cur == null || typeof cur !== "object")
286
+ return;
287
+ cur = cur[p];
288
+ }
289
+ return cur === undefined || cur === null ? undefined : cur;
290
+ }
291
+ function isTruthy(v) {
292
+ if (v === undefined || v === null)
293
+ return false;
294
+ if (typeof v === "string")
295
+ return v.length > 0 && v !== "false" && v !== "0";
296
+ if (typeof v === "number")
297
+ return v !== 0;
298
+ if (typeof v === "boolean")
299
+ return v;
300
+ if (Array.isArray(v))
301
+ return v.length > 0;
302
+ return Object.keys(v).length > 0;
303
+ }
304
+ function applyFilter(value, filter) {
305
+ switch (filter) {
306
+ case undefined:
307
+ case "raw":
308
+ return value;
309
+ case "upper":
310
+ return value.toUpperCase();
311
+ case "lower":
312
+ return value.toLowerCase();
313
+ case "pascal":
314
+ return toPascal(value);
315
+ case "camel":
316
+ return toCamel(value);
317
+ case "snake":
318
+ return toSnake(value);
319
+ case "kebab":
320
+ return toKebab(value);
321
+ case "plural":
322
+ return pluralize(value);
323
+ case "singular":
324
+ return singularize(value);
325
+ default:
326
+ throw new Error(`Unknown template filter: ${filter}`);
327
+ }
328
+ }
329
+ function splitWords(s) {
330
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean);
331
+ }
332
+ function toPascal(s) {
333
+ return splitWords(s).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
334
+ }
335
+ function toCamel(s) {
336
+ const p = toPascal(s);
337
+ return p.charAt(0).toLowerCase() + p.slice(1);
338
+ }
339
+ function toSnake(s) {
340
+ return splitWords(s).map((w) => w.toLowerCase()).join("_");
341
+ }
342
+ function toKebab(s) {
343
+ return splitWords(s).map((w) => w.toLowerCase()).join("-");
344
+ }
345
+ function pluralize(s) {
346
+ if (!s)
347
+ return s;
348
+ if (/(s|x|z|ch|sh)$/i.test(s))
349
+ return `${s}es`;
350
+ if (/[^aeiou]y$/i.test(s))
351
+ return `${s.slice(0, -1)}ies`;
352
+ if (/y$/i.test(s))
353
+ return `${s}s`;
354
+ return `${s}s`;
355
+ }
356
+ function singularize(s) {
357
+ if (!s)
358
+ return s;
359
+ if (/(ses|xes|zes|ches|shes)$/i.test(s))
360
+ return s.slice(0, -2);
361
+ if (/ies$/i.test(s))
362
+ return `${s.slice(0, -3)}y`;
363
+ if (/s$/i.test(s) && s.length > 1)
364
+ return s.slice(0, -1);
365
+ return s;
366
+ }
367
+
368
+ // packages/cli/src/core/fs.ts
250
369
  function writeFile(path, contents, opts = {}) {
251
370
  const base = opts.base ?? process.cwd();
252
371
  const target = isAbsolute(path) ? path : resolve2(base, path);
@@ -273,17 +392,6 @@ function nameVariants(input) {
273
392
  pluralKebab: pluralize(kebab)
274
393
  };
275
394
  }
276
- function pluralize(s) {
277
- if (!s)
278
- return s;
279
- if (/(s|x|z|ch|sh)$/i.test(s))
280
- return `${s}es`;
281
- if (/[^aeiou]y$/i.test(s))
282
- return `${s.slice(0, -1)}ies`;
283
- if (/y$/i.test(s))
284
- return `${s}s`;
285
- return `${s}s`;
286
- }
287
395
  // packages/cli/src/core/logger.ts
288
396
  var USE_COLOR = process.env.NO_COLOR === undefined && process.env.FORCE_COLOR !== "0" && process.stdout.isTTY === true;
289
397
  var wrap = (open, close) => (s) => USE_COLOR ? `\x1B[${open}m${s}\x1B[${close}m` : s;
@@ -384,115 +492,32 @@ async function prompt(message, options = {}) {
384
492
  async function select(message, choices, options = {}) {
385
493
  return prompt(message, { ...options, choices });
386
494
  }
387
- // packages/cli/src/core/template.ts
388
- var VAR_RE = /\{\{\s*([\w.]+)(?:\s*\|\s*(\w+))?\s*\}\}/;
389
- var SECTION_RE = /\{\{\s*([#^])\s*([\w.]+)\s*\}\}([\s\S]*?)\{\{\s*\/\s*\2\s*\}\}/g;
390
- function render(template, context) {
391
- let out = template.replace(SECTION_RE, (_, kind, key, body) => {
392
- const v = lookup(context, key);
393
- const truthy = isTruthy(v);
394
- if (kind === "#")
395
- return truthy ? body : "";
396
- return truthy ? "" : body;
397
- });
398
- let prev;
399
- do {
400
- prev = out;
401
- out = out.replace(VAR_RE, (_, key, filter) => {
402
- const v = lookup(context, key);
403
- return applyFilter(v === undefined || v === null ? "" : String(v), filter);
404
- });
405
- } while (out !== prev);
406
- return out;
407
- }
408
- function lookup(ctx, dotted) {
409
- if (dotted in ctx)
410
- return ctx[dotted];
411
- const parts = dotted.split(".");
412
- let cur = ctx;
413
- for (const p of parts) {
414
- if (cur == null || typeof cur !== "object")
415
- return;
416
- cur = cur[p];
495
+ var VALID_PROJECT_OPTIONS = {
496
+ style: ["nest", "adonis", "functional"],
497
+ view: ["rendu", "edge", "eta", "inertia", "none"],
498
+ orm: ["drizzle", "kysely", "none"],
499
+ db: ["bun-sqlite", "node-sqlite", "libsql", "postgres", "mysql", "none"],
500
+ frontend: ["react", "vue", "svelte", "solid"]
501
+ };
502
+ async function resolveProjectOption(flags, key, valid, defaultVal, interactive) {
503
+ const flagVal = flags[key];
504
+ if (flagVal) {
505
+ if (valid.includes(flagVal))
506
+ return flagVal;
507
+ if (!interactive) {
508
+ logger.error(`Invalid --${key} "${flagVal}". Valid values: ${valid.join(", ")}`);
509
+ process.exit(1);
510
+ }
511
+ logger.warn(`"${flagVal}" is not valid for --${key}. Please choose from the list.`);
417
512
  }
418
- return cur === undefined || cur === null ? undefined : cur;
419
- }
420
- function isTruthy(v) {
421
- if (v === undefined || v === null)
422
- return false;
423
- if (typeof v === "string")
424
- return v.length > 0 && v !== "false" && v !== "0";
425
- if (typeof v === "number")
426
- return v !== 0;
427
- if (typeof v === "boolean")
428
- return v;
429
- if (Array.isArray(v))
430
- return v.length > 0;
431
- return Object.keys(v).length > 0;
432
- }
433
- function applyFilter(value, filter) {
434
- switch (filter) {
435
- case undefined:
436
- case "raw":
437
- return value;
438
- case "upper":
439
- return value.toUpperCase();
440
- case "lower":
441
- return value.toLowerCase();
442
- case "pascal":
443
- return toPascal(value);
444
- case "camel":
445
- return toCamel(value);
446
- case "snake":
447
- return toSnake(value);
448
- case "kebab":
449
- return toKebab(value);
450
- case "plural":
451
- return pluralize2(value);
452
- case "singular":
453
- return singularize(value);
454
- default:
455
- throw new Error(`Unknown template filter: ${filter}`);
513
+ const label = key === "style" ? "Routing style" : key === "view" ? "View engine" : key === "orm" ? "ORM driver" : key === "db" ? "Database driver" : "Inertia frontend";
514
+ for (;; ) {
515
+ const answer = await select(label, [...valid], { default: defaultVal });
516
+ if (valid.includes(answer))
517
+ return answer;
518
+ logger.warn(`"${answer}" is not valid. Please choose from: ${valid.join(", ")}`);
456
519
  }
457
520
  }
458
- function splitWords(s) {
459
- return s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean);
460
- }
461
- function toPascal(s) {
462
- return splitWords(s).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
463
- }
464
- function toCamel(s) {
465
- const p = toPascal(s);
466
- return p.charAt(0).toLowerCase() + p.slice(1);
467
- }
468
- function toSnake(s) {
469
- return splitWords(s).map((w) => w.toLowerCase()).join("_");
470
- }
471
- function toKebab(s) {
472
- return splitWords(s).map((w) => w.toLowerCase()).join("-");
473
- }
474
- function pluralize2(s) {
475
- if (!s)
476
- return s;
477
- if (/(s|x|z|ch|sh)$/i.test(s))
478
- return `${s}es`;
479
- if (/[^aeiou]y$/i.test(s))
480
- return `${s.slice(0, -1)}ies`;
481
- if (/y$/i.test(s))
482
- return `${s}s`;
483
- return `${s}s`;
484
- }
485
- function singularize(s) {
486
- if (!s)
487
- return s;
488
- if (/(ses|xes|zes|ches|shes)$/i.test(s))
489
- return s.slice(0, -2);
490
- if (/ies$/i.test(s))
491
- return `${s.slice(0, -3)}y`;
492
- if (/s$/i.test(s) && s.length > 1)
493
- return s.slice(0, -1);
494
- return s;
495
- }
496
521
  // packages/cli/src/core/version.ts
497
522
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
498
523
  import { resolve as resolve3, dirname as dirname2 } from "path";
@@ -517,7 +542,9 @@ import { resolve as resolve4 } from "path";
517
542
 
518
543
  // packages/cli/src/templates/controller/adonis.ts
519
544
  var adonis_default = `
545
+ {{# hasService }}
520
546
  import { {{ service }} } from '../services/{{ kebab }}.service.js';
547
+ {{/ hasService }}
521
548
 
522
549
  export class {{ name }}Controller {
523
550
  async index() {
@@ -580,7 +607,9 @@ export const {{ camel }}Routes = {
580
607
  var nest_default = `
581
608
  import { Controller, Delete, Get, Inject, Post, Put, inputValue } from '@nexusts/core';
582
609
  import type { Context } from 'hono';
610
+ {{# hasService }}
583
611
  import { {{ service }} } from '../services/{{ kebab }}.service.js';
612
+ {{/ hasService }}
584
613
 
585
614
  @Controller('/{{ kebab }}s')
586
615
  export class {{ name }}Controller {
@@ -993,11 +1022,202 @@ export class {{ repository }} extends KyselyRepository<any, '{{ tableName }}'> {
993
1022
  }
994
1023
  `.trimStart();
995
1024
 
996
- // packages/cli/src/templates/service/service.ts
997
- var service_default = `
998
- import { Injectable, Inject } from '@nexusts/core';
999
- {{#hasRepo}}import { {{ repository }} } from '../repositories/{{ kebab }}.repository.js';{{/hasRepo}}
1000
-
1025
+ // packages/cli/src/templates/auth/auth-instance.ts
1026
+ var auth_instance_default = `/**
1027
+ * Better-auth instance \u2014 generated by \`nx make:auth\`.
1028
+ *
1029
+ * Edit \`nx.config.ts\` (\`auth\` section) instead of this file when possible.
1030
+ */
1031
+ import { createAuth } from '@nexusts/auth';
1032
+
1033
+ export const auth = createAuth({
1034
+ {{^jwt}}
1035
+ jwt: { enabled: false },
1036
+ {{/jwt}}
1037
+ {{#jwt}}
1038
+ jwt: { enabled: true },
1039
+ {{/jwt}}
1040
+ {{#passkey}}
1041
+ passkey: {
1042
+ enabled: true,
1043
+ rpName: '{{ passkeyRpName }}',
1044
+ rpId: '{{ passkeyRpId }}',
1045
+ origin: '{{ passkeyOrigin }}',
1046
+ },
1047
+ {{/passkey}}
1048
+ {{#providers}}
1049
+ socialProviders: {
1050
+ {{#entries}}
1051
+ {{ name }}: {
1052
+ clientId: process.env.{{ envVar }}_CLIENT_ID!,
1053
+ clientSecret: process.env.{{ envVar }}_CLIENT_SECRET!,
1054
+ },
1055
+ {{/entries}}
1056
+ },
1057
+ {{/providers}}
1058
+ });
1059
+ `.trimStart();
1060
+
1061
+ // packages/cli/src/templates/auth/env-example.ts
1062
+ var env_example_default = `# Better Auth \u2014 generated by \`nx make:auth\`.
1063
+ # Generate a secret with: openssl rand -base64 32
1064
+ BETTER_AUTH_SECRET=
1065
+ BETTER_AUTH_URL=http://localhost:3000
1066
+ {{#providers}}
1067
+ {{#entries}}
1068
+ {{ envVar }}_CLIENT_ID=
1069
+ {{ envVar }}_CLIENT_SECRET=
1070
+ {{/entries}}
1071
+ {{/providers}}
1072
+ `.trimStart();
1073
+
1074
+ // packages/cli/src/templates/listener/listener.ts
1075
+ var listener_default = `
1076
+ import { Inject, Injectable } from '@nexusts/core';
1077
+ import { EventService, OnEvent } from '@nexusts/events';
1078
+
1079
+ /**
1080
+ * {{ name }} listener \u2014 generated by \`nx make:listener {{ name }}\`.
1081
+ *
1082
+ * Register handlers below with \`@OnEvent(pattern)\`. Pair with
1083
+ * \`scanForListeners(this, eventService)\` at boot.
1084
+ */
1085
+ @Injectable()
1086
+ export class {{ name }}Listener {
1087
+ @Inject(EventService.TOKEN) declare private readonly events: EventService;
1088
+
1089
+ // TODO: add @OnEvent('your.event') handlers below.
1090
+
1091
+ // @OnEvent('user.created')
1092
+ // async onUserCreated(payload: { userId: string; email: string }) {
1093
+ // this.events; // unused \u2014 remove if you don't need it
1094
+ // }
1095
+ }
1096
+
1097
+ import { scanForListeners } from '@nexusts/events';
1098
+
1099
+ /**
1100
+ * Bootstrap helper \u2014 call this from your main.ts (or wherever you
1101
+ * wire up the application) to register every \`@OnEvent\` handler
1102
+ * on this listener class.
1103
+ */
1104
+ export async function register{{ name }}(listener: {{ name }}Listener, events: EventService) {
1105
+ return scanForListeners(listener, events);
1106
+ }
1107
+ `.trimStart();
1108
+
1109
+ // packages/cli/src/templates/queue/worker.ts
1110
+ var worker_default = `
1111
+ import { Inject, Injectable } from '@nexusts/core';
1112
+ import { QueueService, OnQueueReady } from '@nexusts/queue';
1113
+
1114
+ /**
1115
+ * {{ name }} worker \u2014 generated by \`nx make:queue {{ name }}\`.
1116
+ *
1117
+ * Registers a handler for the \`{{ name }}\` job name on boot.
1118
+ */
1119
+ @Injectable()
1120
+ export class {{ name }}Worker {
1121
+ @Inject(QueueService.TOKEN) declare private readonly queue: QueueService;
1122
+
1123
+ @OnQueueReady()
1124
+ async register(): Promise<void> {
1125
+ await this.queue.process('{{ name }}', async (data, ctx) => {
1126
+ ctx.prefix; // \u2192 "[queue:{{ name }}]"
1127
+ try {
1128
+ await this.handle(data as {{ name }}Data);
1129
+ return { status: 'completed' };
1130
+ } catch (err) {
1131
+ return {
1132
+ status: 'failed',
1133
+ error: err instanceof Error ? err : new Error(String(err)),
1134
+ willRetry: ctx.attempts < 3,
1135
+ };
1136
+ }
1137
+ });
1138
+ }
1139
+
1140
+ /**
1141
+ * Replace this body with your actual worker logic.
1142
+ */
1143
+ async handle(data: {{ name }}Data): Promise<void> {
1144
+ // TODO: implement
1145
+ }
1146
+ }
1147
+
1148
+ /**
1149
+ * Typed payload for the \`{{ name }}\` job.
1150
+ */
1151
+ export interface {{ name }}Data {
1152
+ // TODO: define fields
1153
+ [key: string]: unknown;
1154
+ }
1155
+ `.trimStart();
1156
+
1157
+ // packages/cli/src/templates/queue/job.ts
1158
+ var job_default = `
1159
+ import { Inject, Injectable } from '@nexusts/core';
1160
+ import { QueueService } from '@nexusts/queue';
1161
+ import type { {{ name }}Data } from './{{ kebab }}.worker.js';
1162
+
1163
+ /**
1164
+ * Helper for enqueuing \`{{ name }}\` jobs from controllers / services.
1165
+ * Generated by \`nx make:queue {{ name }}\`.
1166
+ */
1167
+ @Injectable()
1168
+ export class {{ name }}Job {
1169
+ @Inject(QueueService.TOKEN) declare private readonly queue: QueueService;
1170
+
1171
+ /** Enqueue a single {{ name }} job. */
1172
+ async enqueue(data: {{ name }}Data, options?: {
1173
+ delaySeconds?: number;
1174
+ attempts?: number;
1175
+ }) {
1176
+ return this.queue.add('{{ name }}', data, options);
1177
+ }
1178
+
1179
+ /** Enqueue many at once. */
1180
+ async enqueueBatch(items: {{ name }}Data[]) {
1181
+ return this.queue.addBatch(
1182
+ items.map((data) => ({ name: '{{ name }}', data })),
1183
+ );
1184
+ }
1185
+ }
1186
+ `.trimStart();
1187
+
1188
+ // packages/cli/src/templates/schedule/task.ts
1189
+ var task_default = `
1190
+ import { Injectable } from '@nexusts/core';
1191
+ import { Cron, Interval, Timeout } from '@nexusts/schedule';
1192
+
1193
+ /**
1194
+ * {{ name }} task \u2014 generated by \`nx make:schedule {{ name }}\`.
1195
+ *
1196
+ * Mark methods with \`@Cron\`, \`@Interval\`, or \`@Timeout\`.
1197
+ * Auto-detected at boot \u2014 no manual registration needed.
1198
+ */
1199
+ @Injectable()
1200
+ export class {{ name }}Task {
1201
+ constructor() {}
1202
+
1203
+ // TODO: add @Cron, @Interval, or @Timeout handlers below.
1204
+
1205
+ // @Cron('0 * * * *') // every hour
1206
+ // async hourly() { /* ... */ }
1207
+
1208
+ // @Interval(60_000) // every minute
1209
+ // async tick() { /* ... */ }
1210
+
1211
+ // @Timeout(5_000) // 5s after boot
1212
+ // async startup() { /* ... */ }
1213
+ }
1214
+ `.trimStart();
1215
+
1216
+ // packages/cli/src/templates/service/service.ts
1217
+ var service_default = `
1218
+ import { Injectable, Inject } from '@nexusts/core';
1219
+ {{#hasRepo}}import { {{ repository }} } from '../repositories/{{ kebab }}.repository.js';{{/hasRepo}}
1220
+
1001
1221
  @Injectable()
1002
1222
  export class {{ name }}Service {
1003
1223
  {{#hasRepo}}@Inject({{ repository }}) declare {{ repositoryCamel }}: {{ repository }};{{/hasRepo}}
@@ -1029,6 +1249,52 @@ export class {{ name }}Service {
1029
1249
  }
1030
1250
  `.trimStart();
1031
1251
 
1252
+ // packages/cli/src/templates/session/session.ts
1253
+ var session_default = `
1254
+ import { Inject, Injectable } from '@nexusts/core';
1255
+ import { SessionService } from '@nexusts/session';
1256
+ import type { SessionRecord } from '@nexusts/session';
1257
+
1258
+ /**
1259
+ * {{ name }} session helper \u2014 generated by \`nx make:session {{ name }}\`.
1260
+ *
1261
+ * Wraps SessionService with typed accessors for the {{ name }} session's
1262
+ * data. Use from controllers / services.
1263
+ */
1264
+ @Injectable()
1265
+ export class {{ name }}Session {
1266
+ @Inject(SessionService.TOKEN) declare private readonly sessions: SessionService;
1267
+
1268
+ /** Read the current session record (or null). */
1269
+ async getCurrent(sessionId: string | null | undefined) {
1270
+ if (!sessionId) return null;
1271
+ return this.sessions.read(sessionId);
1272
+ }
1273
+
1274
+ /** Patch the {{ name }} session's data. */
1275
+ async update(sessionId: string, patch: {{ name }}DataPatch) {
1276
+ return this.sessions.update(sessionId, { dataPatch: patch });
1277
+ }
1278
+
1279
+ /** Destroy the session (logout-everywhere equivalent). */
1280
+ async destroy(sessionId: string) {
1281
+ return this.sessions.destroy(sessionId, 'logout');
1282
+ }
1283
+ }
1284
+
1285
+ /**
1286
+ * Typed payload for the {{ name }} session.
1287
+ *
1288
+ * TODO: define the fields below to match your feature.
1289
+ */
1290
+ export interface {{ name }}Data {
1291
+ // userId?: string;
1292
+ // createdAt?: string;
1293
+ }
1294
+
1295
+ export type {{ name }}DataPatch = Partial<{{ name }}Data>;
1296
+ `.trimStart();
1297
+
1032
1298
  // packages/cli/src/templates/validator/validator.ts
1033
1299
  var validator_default = `
1034
1300
  import { z } from 'zod';
@@ -1053,7 +1319,18 @@ var templates = {
1053
1319
  adonis: adonis_default,
1054
1320
  functional: functional_default
1055
1321
  },
1322
+ auth: {
1323
+ instance: auth_instance_default,
1324
+ "env.example": env_example_default
1325
+ },
1326
+ listener: listener_default,
1327
+ queue: {
1328
+ worker: worker_default,
1329
+ job: job_default
1330
+ },
1331
+ schedule: task_default,
1056
1332
  service: service_default,
1333
+ session: session_default,
1057
1334
  repository: {
1058
1335
  drizzle: repository_default,
1059
1336
  kysely: kysely_repository_default
@@ -1445,6 +1722,22 @@ bunx nx make:crud Post
1445
1722
  `);
1446
1723
  return created;
1447
1724
  }
1725
+ // packages/cli/src/core/naming.ts
1726
+ function inferTableName(input) {
1727
+ if (!input)
1728
+ return "table";
1729
+ const m = /^create_(\w+)_table$/.exec(input);
1730
+ if (m)
1731
+ return m[1] ?? "table";
1732
+ const m2 = /^(?:add|remove|drop|alter)_(\w+)_to_(\w+)$/.exec(input);
1733
+ if (m2)
1734
+ return m2[2] ?? "table";
1735
+ return `${input.toLowerCase().replace(/s$/, "")}s`;
1736
+ }
1737
+ function formatTimestamp(d) {
1738
+ const pad = (n) => String(n).padStart(2, "0");
1739
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}` + `_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1740
+ }
1448
1741
  // packages/cli/src/commands/info.ts
1449
1742
  var infoCommand = {
1450
1743
  name: "info",
@@ -1506,32 +1799,6 @@ var info_default = infoCommand;
1506
1799
  // packages/cli/src/commands/init.ts
1507
1800
  import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1508
1801
  import { resolve as resolve6 } from "path";
1509
- var VALID_OPTIONS = {
1510
- style: ["nest", "adonis", "functional"],
1511
- view: ["rendu", "edge", "eta", "inertia", "none"],
1512
- orm: ["drizzle", "kysely", "none"],
1513
- db: ["bun-sqlite", "node-sqlite", "libsql", "postgres", "mysql", "none"],
1514
- frontend: ["react", "vue", "svelte", "solid"]
1515
- };
1516
- async function resolveOpt(flags, key, valid, defaultVal, interactive) {
1517
- const flagVal = flags[key];
1518
- if (flagVal) {
1519
- if (valid.includes(flagVal))
1520
- return flagVal;
1521
- if (!interactive) {
1522
- logger.error(`Invalid --${key} "${flagVal}". Valid values: ${valid.join(", ")}`);
1523
- process.exit(1);
1524
- }
1525
- logger.warn(`"${flagVal}" is not valid for --${key}. Please choose from the list.`);
1526
- }
1527
- const label = key === "style" ? "Routing style" : key === "view" ? "View engine" : key === "orm" ? "ORM driver" : key === "db" ? "Database driver" : "Inertia frontend";
1528
- for (;; ) {
1529
- const answer = await select(label, [...valid], { default: defaultVal });
1530
- if (valid.includes(answer))
1531
- return answer;
1532
- logger.warn(`"${answer}" is not valid. Please choose from: ${valid.join(", ")}`);
1533
- }
1534
- }
1535
1802
  var initCommand = {
1536
1803
  name: "init",
1537
1804
  aliases: ["i"],
@@ -1561,11 +1828,11 @@ var initCommand = {
1561
1828
  const interactive = flagBool(ctx.flags, "interaction", true);
1562
1829
  const force = flagBool(ctx.flags, "force", false);
1563
1830
  const target = resolve6(ctx.cwd, ctx.flags.target ?? ".");
1564
- const routing = await resolveOpt(ctx.flags, "style", VALID_OPTIONS.style, "nest", interactive);
1565
- const view = await resolveOpt(ctx.flags, "view", VALID_OPTIONS.view, "rendu", interactive);
1566
- const orm = await resolveOpt(ctx.flags, "orm", VALID_OPTIONS.orm, "drizzle", interactive);
1567
- const db = await resolveOpt(ctx.flags, "db", VALID_OPTIONS.db, "bun-sqlite", interactive);
1568
- const frontend = await resolveOpt(ctx.flags, "frontend", VALID_OPTIONS.frontend, "react", interactive);
1831
+ const routing = await resolveProjectOption(ctx.flags, "style", VALID_PROJECT_OPTIONS.style, "nest", interactive);
1832
+ const view = await resolveProjectOption(ctx.flags, "view", VALID_PROJECT_OPTIONS.view, "rendu", interactive);
1833
+ const orm = await resolveProjectOption(ctx.flags, "orm", VALID_PROJECT_OPTIONS.orm, "drizzle", interactive);
1834
+ const db = await resolveProjectOption(ctx.flags, "db", VALID_PROJECT_OPTIONS.db, "bun-sqlite", interactive);
1835
+ const frontend = await resolveProjectOption(ctx.flags, "frontend", VALID_PROJECT_OPTIONS.frontend, "react", interactive);
1569
1836
  const ssr = !flagBool(ctx.flags, "no-ssr", false);
1570
1837
  const name = target.split("/").pop() ?? "nexus-app";
1571
1838
  const dbUrl = db === "bun-sqlite" || db === "node-sqlite" ? "app.db" : "";
@@ -1751,51 +2018,6 @@ var init_default = initCommand;
1751
2018
 
1752
2019
  // packages/cli/src/commands/make-auth.ts
1753
2020
  import { resolve as resolve7 } from "path";
1754
- var AUTH_INSTANCE_TEMPLATE = `/**
1755
- * Better-auth instance \u2014 generated by \`nx make:auth\`.
1756
- *
1757
- * Edit \`nx.config.ts\` (\`auth\` section) instead of this file when possible.
1758
- */
1759
- import { createAuth } from '@nexusts/auth';
1760
-
1761
- export const auth = createAuth({
1762
- {{^jwt}}
1763
- jwt: { enabled: false },
1764
- {{/jwt}}
1765
- {{#jwt}}
1766
- jwt: { enabled: true },
1767
- {{/jwt}}
1768
- {{#passkey}}
1769
- passkey: {
1770
- enabled: true,
1771
- rpName: '{{ passkeyRpName }}',
1772
- rpId: '{{ passkeyRpId }}',
1773
- origin: '{{ passkeyOrigin }}',
1774
- },
1775
- {{/passkey}}
1776
- {{#providers}}
1777
- socialProviders: {
1778
- {{#entries}}
1779
- {{ name }}: {
1780
- clientId: process.env.{{ envVar }}_CLIENT_ID!,
1781
- clientSecret: process.env.{{ envVar }}_CLIENT_SECRET!,
1782
- },
1783
- {{/entries}}
1784
- },
1785
- {{/providers}}
1786
- });
1787
- `;
1788
- var ENV_EXAMPLE_TEMPLATE = `# Better Auth \u2014 generated by \`nx make:auth\`.
1789
- # Generate a secret with: openssl rand -base64 32
1790
- BETTER_AUTH_SECRET=
1791
- BETTER_AUTH_URL=http://localhost:3000
1792
- {{#providers}}
1793
- {{#entries}}
1794
- {{ envVar }}_CLIENT_ID=
1795
- {{ envVar }}_CLIENT_SECRET=
1796
- {{/entries}}
1797
- {{/providers}}
1798
- `;
1799
2021
  var MODULE_UPDATE_HINT = `import { AuthModule } from '@nexusts/auth';
1800
2022
  // In your AppModule.imports:
1801
2023
  imports: [AuthModule.forRoot({ /* ... */ })],
@@ -1855,7 +2077,7 @@ var makeAuthCommand = {
1855
2077
  envVar: known?.env ?? p.toUpperCase()
1856
2078
  };
1857
2079
  });
1858
- const authCode = render(AUTH_INSTANCE_TEMPLATE, {
2080
+ const authCode = render(templates.auth.instance, {
1859
2081
  jwt: jwtEnabled,
1860
2082
  passkey: passkeyEnabled,
1861
2083
  providers: providers.length > 0,
@@ -1870,7 +2092,7 @@ var makeAuthCommand = {
1870
2092
  } else {
1871
2093
  logger.warn(`skipped (exists): ${authOut}`);
1872
2094
  }
1873
- const envCode = render(ENV_EXAMPLE_TEMPLATE, {
2095
+ const envCode = render(templates.auth["env.example"], {
1874
2096
  providers: providers.length > 0,
1875
2097
  entries
1876
2098
  });
@@ -1944,8 +2166,9 @@ var makeControllerCommand = {
1944
2166
  snake: variants.snake,
1945
2167
  pascal: variants.pascal,
1946
2168
  service: serviceName,
1947
- serviceCamel
1948
- }).replace(/import .*\n/g, skipService ? (m) => m.includes("services/") ? "" : m : (m) => m);
2169
+ serviceCamel,
2170
+ hasService: !skipService
2171
+ });
1949
2172
  const out = resolve8(ctx.cwd, ctx.config.paths.controllers, `${variants.kebab}.controller.ts`);
1950
2173
  const ok = writeFile(out, code, { skipIfExists: false });
1951
2174
  if (!ok) {
@@ -2078,6 +2301,96 @@ function mapDrizzleType(dialect, type) {
2078
2301
  }
2079
2302
  return "text";
2080
2303
  }
2304
+ function mapSqlType(t, dialect) {
2305
+ const type = t.toLowerCase();
2306
+ switch (type) {
2307
+ case "text":
2308
+ case "string":
2309
+ case "varchar":
2310
+ return dialect === "mysql" ? "VARCHAR(255)" : "TEXT";
2311
+ case "int":
2312
+ case "integer":
2313
+ return dialect === "postgres" ? "INTEGER" : dialect === "mysql" ? "INT" : "INTEGER";
2314
+ case "bigint":
2315
+ case "bigintunsigned":
2316
+ return dialect === "postgres" ? "BIGINT" : "BIGINT UNSIGNED";
2317
+ case "serial":
2318
+ return dialect === "postgres" ? "SERIAL" : "INTEGER AUTO_INCREMENT";
2319
+ case "bool":
2320
+ case "boolean":
2321
+ return "BOOLEAN";
2322
+ case "float":
2323
+ case "number":
2324
+ case "real":
2325
+ case "double":
2326
+ return dialect === "mysql" ? "DOUBLE" : "REAL";
2327
+ case "datetime":
2328
+ case "timestamp":
2329
+ return dialect === "postgres" ? "TIMESTAMP" : dialect === "mysql" ? "DATETIME" : "INTEGER";
2330
+ case "date":
2331
+ return dialect === "mysql" ? "DATE" : "TEXT";
2332
+ case "json":
2333
+ return dialect === "postgres" ? "JSONB" : dialect === "mysql" ? "JSON" : "TEXT";
2334
+ case "jsonb":
2335
+ return dialect === "postgres" ? "JSONB" : "TEXT";
2336
+ default:
2337
+ return "TEXT";
2338
+ }
2339
+ }
2340
+ function mapKyselyType(type) {
2341
+ switch (type.toLowerCase()) {
2342
+ case "text":
2343
+ case "string":
2344
+ case "varchar":
2345
+ return "text";
2346
+ case "int":
2347
+ case "integer":
2348
+ return "integer";
2349
+ case "bigint":
2350
+ return "bigint";
2351
+ case "bool":
2352
+ case "boolean":
2353
+ return "boolean";
2354
+ case "float":
2355
+ case "number":
2356
+ case "real":
2357
+ case "double":
2358
+ return "real";
2359
+ case "datetime":
2360
+ case "timestamp":
2361
+ case "date":
2362
+ case "json":
2363
+ case "jsonb":
2364
+ return "text";
2365
+ default:
2366
+ return "text";
2367
+ }
2368
+ }
2369
+ function renderSqlColumns(cols, dialect) {
2370
+ return cols.map(([name, type]) => {
2371
+ const sqlType = mapSqlType(type, dialect);
2372
+ const notNull = /NOT NULL/i.test(sqlType) ? "" : " NOT NULL";
2373
+ return ` ${name} ${sqlType}${notNull},`;
2374
+ }).join(`
2375
+ `);
2376
+ }
2377
+ function renderKyselyColumns(cols) {
2378
+ return cols.map(([name, type]) => {
2379
+ const kyselyType = mapKyselyType(type);
2380
+ return ` .addColumn('${name}', '${kyselyType}', (col) => col.notNull())`;
2381
+ }).join(`
2382
+ `);
2383
+ }
2384
+ function renderDrizzleColumns(cols, dialect) {
2385
+ return cols.map(([name, type]) => {
2386
+ const helper = mapDrizzleType(dialect, type);
2387
+ return ` ${name}: ${helper}('${name}'),`;
2388
+ }).join(`
2389
+ `);
2390
+ }
2391
+ function isValidDialect(d) {
2392
+ return ["postgres", "mysql", "sqlite", "bun-sqlite", "d1"].includes(d);
2393
+ }
2081
2394
 
2082
2395
  // packages/cli/src/commands/make-crud.ts
2083
2396
  var makeCrudCommand = {
@@ -2173,7 +2486,7 @@ var makeCrudCommand = {
2173
2486
  kebab: variants.kebab,
2174
2487
  snake: variants.snake,
2175
2488
  tableName,
2176
- columns: renderDrizzleColumns(dialect)
2489
+ columns: renderDrizzleColumns2(dialect)
2177
2490
  });
2178
2491
  } else {
2179
2492
  const tpl = templates.model[orm];
@@ -2281,7 +2594,7 @@ function renderDefaultColumns(orm) {
2281
2594
  }
2282
2595
  return " title text,";
2283
2596
  }
2284
- function renderDrizzleColumns(dialect) {
2597
+ function renderDrizzleColumns2(dialect) {
2285
2598
  const helper = mapDrizzleType(dialect, "text");
2286
2599
  return ` title: ${helper}('title').notNull(),`;
2287
2600
  }
@@ -2289,39 +2602,6 @@ var make_crud_default = makeCrudCommand;
2289
2602
 
2290
2603
  // packages/cli/src/commands/make-listener.ts
2291
2604
  import { resolve as resolve10 } from "path";
2292
- var LISTENER_TEMPLATE = `
2293
- import { Inject, Injectable } from '@nexusts/core';
2294
- import { EventService, OnEvent } from '@nexusts/events';
2295
-
2296
- /**
2297
- * {{ name }} listener \u2014 generated by \`nx make:listener {{ name }}\`.
2298
- *
2299
- * Register handlers below with \`@OnEvent(pattern)\`. Pair with
2300
- * \`scanForListeners(this, eventService)\` at boot.
2301
- */
2302
- @Injectable()
2303
- export class {{ name }}Listener {
2304
- constructor(@Inject(EventService.TOKEN) private readonly events: EventService) {}
2305
-
2306
- // TODO: add @OnEvent('your.event') handlers below.
2307
-
2308
- // @OnEvent('user.created')
2309
- // async onUserCreated(payload: { userId: string; email: string }) {
2310
- // this.events; // unused \u2014 remove if you don't need it
2311
- // }
2312
- }
2313
-
2314
- import { scanForListeners } from '@nexusts/events';
2315
-
2316
- /**
2317
- * Bootstrap helper \u2014 call this from your main.ts (or wherever you
2318
- * wire up the application) to register every \`@OnEvent\` handler
2319
- * on this listener class.
2320
- */
2321
- export async function register{{ name }}(listener: {{ name }}Listener, events: EventService) {
2322
- return scanForListeners(listener, events);
2323
- }
2324
- `.trimStart();
2325
2605
  var makeListenerCommand = {
2326
2606
  name: "make:listener",
2327
2607
  aliases: ["ml", "make-listener"],
@@ -2335,7 +2615,7 @@ var makeListenerCommand = {
2335
2615
  return 1;
2336
2616
  }
2337
2617
  const variants = nameVariants(name);
2338
- const code = render(LISTENER_TEMPLATE, {
2618
+ const code = render(templates.listener, {
2339
2619
  name: variants.pascal,
2340
2620
  kebab: variants.kebab
2341
2621
  });
@@ -2405,192 +2685,87 @@ var makeMigrationCommand = {
2405
2685
  {
2406
2686
  name: "orm",
2407
2687
  description: "Override ORM driver (drizzle|kysely|none)"
2408
- },
2409
- {
2410
- name: "dialect",
2411
- description: "Drizzle dialect (postgres|mysql|sqlite|bun-sqlite|d1). Default: bun-sqlite"
2412
- }
2413
- ],
2414
- async run(ctx) {
2415
- const name = ctx.positional[0];
2416
- if (!name) {
2417
- logger.error("Usage: nx make:migration <Name> [--dialect ...]");
2418
- return 1;
2419
- }
2420
- const orm = ctx.flags.orm ?? ctx.config.orm;
2421
- const dialect = ctx.flags.dialect ?? ctx.config.dialect ?? "bun-sqlite";
2422
- const isDrizzle = orm === "drizzle";
2423
- const isKysely = orm === "kysely";
2424
- const useGenericSql = orm === "none";
2425
- const variants = nameVariants(name);
2426
- const tableName = inferTableName(name);
2427
- const colsFlag = ctx.flags.columns;
2428
- const cols = parseColumns(colsFlag ?? "title:text");
2429
- const drizzleColumns = renderDrizzleColumns2(cols, dialect);
2430
- const sqlColumns = renderSqlColumns(cols, dialect);
2431
- let code;
2432
- let extension;
2433
- if (isKysely) {
2434
- const tpl = templates.migration.kysely;
2435
- code = render(tpl, {
2436
- name: variants.pascal,
2437
- snake: variants.snake,
2438
- tableName,
2439
- columns: renderKyselyColumns(cols),
2440
- timestamp: formatTimestamp(new Date)
2441
- });
2442
- extension = "ts";
2443
- } else if (isDrizzle) {
2444
- if (!isValidDialect(dialect)) {
2445
- logger.error(`Unsupported drizzle dialect: ${dialect}. Allowed: postgres, mysql, sqlite, bun-sqlite, d1.`);
2446
- return 1;
2447
- }
2448
- code = renderDrizzleDialect(dialect);
2449
- code = render(code, {
2450
- name: variants.pascal,
2451
- snake: variants.snake,
2452
- tableName,
2453
- columns: drizzleColumns,
2454
- timestamp: formatTimestamp(new Date)
2455
- });
2456
- extension = "ts";
2457
- } else if (useGenericSql) {
2458
- const tpl = templates.migration.sql;
2459
- code = render(tpl, {
2460
- name: variants.pascal,
2461
- snake: variants.snake,
2462
- tableName,
2463
- columns: sqlColumns,
2464
- timestamp: formatTimestamp(new Date)
2465
- });
2466
- extension = "sql";
2467
- } else {
2468
- logger.error(`Unsupported ORM for migration: ${orm}. Allowed: drizzle, kysely, none.`);
2469
- return 1;
2470
- }
2471
- const filename = `${formatTimestamp(new Date)}_${variants.snake}.${extension}`;
2472
- const out = resolve12(ctx.cwd, ctx.config.paths.migrations, filename);
2473
- writeFile(out, code);
2474
- logger.success(`created ${out}`);
2475
- if (isDrizzle || isKysely) {
2476
- logger.finger(`run \`nx db:migrate\` to apply pending migrations.`);
2477
- } else {
2478
- logger.finger(`run \`bun nx db:migrate\` or your migration tool.`);
2479
- }
2480
- return 0;
2481
- }
2482
- };
2483
- function isValidDialect(d) {
2484
- return ["postgres", "mysql", "sqlite", "bun-sqlite", "d1"].includes(d);
2485
- }
2486
- function inferTableName(input) {
2487
- const m = /^create_(\w+)_table$/.exec(input);
2488
- if (m)
2489
- return m[1] ?? "";
2490
- const m2 = /^(?:add|remove|drop|alter)_(\w+)_to_(\w+)$/.exec(input);
2491
- if (m2)
2492
- return m2[2] ?? "";
2493
- return `${input.toLowerCase().replace(/s$/, "")}s`;
2494
- }
2495
- function parseColumns(input) {
2496
- const list = Array.isArray(input) ? input : input.split(",");
2497
- return list.map((s) => s.trim()).filter(Boolean).map((c2) => {
2498
- const [name, type = "text"] = c2.split(":");
2499
- return [name, type];
2500
- });
2501
- }
2502
- function renderSqlColumns(cols, dialect) {
2503
- return cols.map(([name, type]) => {
2504
- const sqlType = mapSqlType(type, dialect);
2505
- const notNull = /NOT NULL/i.test(sqlType) ? "" : " NOT NULL";
2506
- return ` ${name} ${sqlType}${notNull},`;
2507
- }).join(`
2508
- `);
2509
- }
2510
- function renderKyselyColumns(cols) {
2511
- return cols.map(([name, type]) => {
2512
- const kyselyType = mapKyselyType(type);
2513
- return ` .addColumn('${name}', '${kyselyType}', (col) => col.notNull())`;
2514
- }).join(`
2515
- `);
2516
- }
2517
- function mapKyselyType(type) {
2518
- switch (type.toLowerCase()) {
2519
- case "text":
2520
- case "string":
2521
- case "varchar":
2522
- return "text";
2523
- case "int":
2524
- case "integer":
2525
- return "integer";
2526
- case "bigint":
2527
- return "bigint";
2528
- case "bool":
2529
- case "boolean":
2530
- return "boolean";
2531
- case "float":
2532
- case "number":
2533
- case "real":
2534
- case "double":
2535
- return "real";
2536
- case "datetime":
2537
- case "timestamp":
2538
- return "text";
2539
- case "date":
2540
- return "text";
2541
- case "json":
2542
- case "jsonb":
2543
- return "text";
2544
- default:
2545
- return "text";
2546
- }
2547
- }
2548
- function renderDrizzleColumns2(cols, dialect) {
2549
- return cols.map(([name, type]) => {
2550
- const helper = mapDrizzleType(dialect, type);
2551
- return ` ${name}: ${helper}('${name}'),`;
2552
- }).join(`
2553
- `);
2554
- }
2555
- function mapSqlType(t, dialect) {
2556
- const type = t.toLowerCase();
2557
- switch (type) {
2558
- case "text":
2559
- case "string":
2560
- case "varchar":
2561
- return dialect === "mysql" ? "VARCHAR(255)" : "TEXT";
2562
- case "int":
2563
- case "integer":
2564
- return dialect === "postgres" ? "INTEGER" : dialect === "mysql" ? "INT" : "INTEGER";
2565
- case "bigint":
2566
- case "bigintunsigned":
2567
- return dialect === "postgres" ? "BIGINT" : "BIGINT UNSIGNED";
2568
- case "serial":
2569
- return dialect === "postgres" ? "SERIAL" : "INTEGER AUTO_INCREMENT";
2570
- case "bool":
2571
- case "boolean":
2572
- return dialect === "mysql" ? "BOOLEAN" : "BOOLEAN";
2573
- case "float":
2574
- case "number":
2575
- case "real":
2576
- case "double":
2577
- return dialect === "mysql" ? "DOUBLE" : "REAL";
2578
- case "datetime":
2579
- case "timestamp":
2580
- return dialect === "postgres" ? "TIMESTAMP" : dialect === "mysql" ? "DATETIME" : "INTEGER";
2581
- case "date":
2582
- return dialect === "mysql" ? "DATE" : "TEXT";
2583
- case "json":
2584
- return dialect === "postgres" ? "JSONB" : dialect === "mysql" ? "JSON" : "TEXT";
2585
- case "jsonb":
2586
- return dialect === "postgres" ? "JSONB" : "TEXT";
2587
- default:
2588
- return "TEXT";
2688
+ },
2689
+ {
2690
+ name: "dialect",
2691
+ description: "Drizzle dialect (postgres|mysql|sqlite|bun-sqlite|d1). Default: bun-sqlite"
2692
+ }
2693
+ ],
2694
+ async run(ctx) {
2695
+ const name = ctx.positional[0];
2696
+ if (!name) {
2697
+ logger.error("Usage: nx make:migration <Name> [--dialect ...]");
2698
+ return 1;
2699
+ }
2700
+ const orm = ctx.flags.orm ?? ctx.config.orm;
2701
+ const dialect = ctx.flags.dialect ?? ctx.config.dialect ?? "bun-sqlite";
2702
+ const isDrizzle = orm === "drizzle";
2703
+ const isKysely = orm === "kysely";
2704
+ const useGenericSql = orm === "none";
2705
+ const variants = nameVariants(name);
2706
+ const tableName = inferTableName(name);
2707
+ const colsFlag = ctx.flags.columns;
2708
+ const cols = parseColumns(colsFlag ?? "title:text");
2709
+ const drizzleColumns = renderDrizzleColumns(cols, dialect);
2710
+ const sqlColumns = renderSqlColumns(cols, dialect);
2711
+ let code;
2712
+ let extension;
2713
+ if (isKysely) {
2714
+ const tpl = templates.migration.kysely;
2715
+ code = render(tpl, {
2716
+ name: variants.pascal,
2717
+ snake: variants.snake,
2718
+ tableName,
2719
+ columns: renderKyselyColumns(cols),
2720
+ timestamp: formatTimestamp(new Date)
2721
+ });
2722
+ extension = "ts";
2723
+ } else if (isDrizzle) {
2724
+ if (!isValidDialect(dialect)) {
2725
+ logger.error(`Unsupported drizzle dialect: ${dialect}. Allowed: postgres, mysql, sqlite, bun-sqlite, d1.`);
2726
+ return 1;
2727
+ }
2728
+ code = renderDrizzleDialect(dialect);
2729
+ code = render(code, {
2730
+ name: variants.pascal,
2731
+ snake: variants.snake,
2732
+ tableName,
2733
+ columns: drizzleColumns,
2734
+ timestamp: formatTimestamp(new Date)
2735
+ });
2736
+ extension = "ts";
2737
+ } else if (useGenericSql) {
2738
+ const tpl = templates.migration.sql;
2739
+ code = render(tpl, {
2740
+ name: variants.pascal,
2741
+ snake: variants.snake,
2742
+ tableName,
2743
+ columns: sqlColumns,
2744
+ timestamp: formatTimestamp(new Date)
2745
+ });
2746
+ extension = "sql";
2747
+ } else {
2748
+ logger.error(`Unsupported ORM for migration: ${orm}. Allowed: drizzle, kysely, none.`);
2749
+ return 1;
2750
+ }
2751
+ const filename = `${formatTimestamp(new Date)}_${variants.snake}.${extension}`;
2752
+ const out = resolve12(ctx.cwd, ctx.config.paths.migrations, filename);
2753
+ writeFile(out, code);
2754
+ logger.success(`created ${out}`);
2755
+ if (isDrizzle || isKysely) {
2756
+ logger.finger(`run \`nx db:migrate\` to apply pending migrations.`);
2757
+ } else {
2758
+ logger.finger(`run \`bun nx db:migrate\` or your migration tool.`);
2759
+ }
2760
+ return 0;
2589
2761
  }
2590
- }
2591
- function formatTimestamp(d) {
2592
- const pad = (n) => String(n).padStart(2, "0");
2593
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}` + `_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
2762
+ };
2763
+ function parseColumns(input) {
2764
+ const list = Array.isArray(input) ? input : input.split(",");
2765
+ return list.map((s) => s.trim()).filter(Boolean).map((c2) => {
2766
+ const [name, type = "text"] = c2.split(":");
2767
+ return [name, type];
2768
+ });
2594
2769
  }
2595
2770
  var make_migration_default = makeMigrationCommand;
2596
2771
 
@@ -2640,7 +2815,7 @@ var makeModelCommand = {
2640
2815
  let code;
2641
2816
  if (orm === "drizzle") {
2642
2817
  const dialect = ctx.flags.dialect ?? ctx.config.dialect ?? "bun-sqlite";
2643
- if (!isValidDialect2(dialect)) {
2818
+ if (!isValidDialect(dialect)) {
2644
2819
  logger.error(`Unsupported drizzle dialect: ${dialect}. Allowed: postgres, mysql, sqlite, bun-sqlite, d1.`);
2645
2820
  return 1;
2646
2821
  }
@@ -2674,9 +2849,6 @@ var makeModelCommand = {
2674
2849
  return 0;
2675
2850
  }
2676
2851
  };
2677
- function isValidDialect2(d) {
2678
- return ["postgres", "mysql", "sqlite", "bun-sqlite", "d1"].includes(d);
2679
- }
2680
2852
  function renderColumns(cols, orm, dialect) {
2681
2853
  const flat = cols.flatMap((c2) => c2.split(",")).map((c2) => c2.trim()).filter(Boolean);
2682
2854
  return flat.map((col) => {
@@ -2749,81 +2921,6 @@ var make_module_default = makeModuleCommand;
2749
2921
 
2750
2922
  // packages/cli/src/commands/make-queue.ts
2751
2923
  import { resolve as resolve15 } from "path";
2752
- var WORKER_TEMPLATE = `
2753
- import { Inject, Injectable } from '@nexusts/core';
2754
- import { QueueService, OnQueueReady } from '@nexusts/queue';
2755
-
2756
- /**
2757
- * {{ name }} worker \u2014 generated by \`nx make:queue {{ name }}\`.
2758
- *
2759
- * Registers a handler for the \`{{ name }}\` job name on boot.
2760
- */
2761
- @Injectable()
2762
- export class {{ name }}Worker {
2763
- constructor(@Inject(QueueService.TOKEN) private readonly queue: QueueService) {}
2764
-
2765
- @OnQueueReady()
2766
- async register(): Promise<void> {
2767
- await this.queue.process('{{ name }}', async (data, ctx) => {
2768
- ctx.prefix; // \u2192 "[queue:{{ name }}]"
2769
- try {
2770
- await this.handle(data as {{ name }}Data);
2771
- return { status: 'completed' };
2772
- } catch (err) {
2773
- return {
2774
- status: 'failed',
2775
- error: err instanceof Error ? err : new Error(String(err)),
2776
- willRetry: ctx.attempts < 3,
2777
- };
2778
- }
2779
- });
2780
- }
2781
-
2782
- /**
2783
- * Replace this body with your actual worker logic.
2784
- */
2785
- async handle(data: {{ name }}Data): Promise<void> {
2786
- // TODO: implement
2787
- }
2788
- }
2789
-
2790
- /**
2791
- * Typed payload for the \`{{ name }}\` job.
2792
- */
2793
- export interface {{ name }}Data {
2794
- // TODO: define fields
2795
- [key: string]: unknown;
2796
- }
2797
- `.trimStart();
2798
- var JOB_HELPER_TEMPLATE = `
2799
- import { Inject, Injectable } from '@nexusts/core';
2800
- import { QueueService } from '@nexusts/queue';
2801
- import type { {{ name }}Data } from './{{ kebab }}.worker.js';
2802
-
2803
- /**
2804
- * Helper for enqueuing \`{{ name }}\` jobs from controllers / services.
2805
- * Generated by \`nx make:queue {{ name }}\`.
2806
- */
2807
- @Injectable()
2808
- export class {{ name }}Job {
2809
- constructor(@Inject(QueueService.TOKEN) private readonly queue: QueueService) {}
2810
-
2811
- /** Enqueue a single {{ name }} job. */
2812
- async enqueue(data: {{ name }}Data, options?: {
2813
- delaySeconds?: number;
2814
- attempts?: number;
2815
- }) {
2816
- return this.queue.add('{{ name }}', data, options);
2817
- }
2818
-
2819
- /** Enqueue many at once. */
2820
- async enqueueBatch(items: {{ name }}Data[]) {
2821
- return this.queue.addBatch(
2822
- items.map((data) => ({ name: '{{ name }}', data })),
2823
- );
2824
- }
2825
- }
2826
- `.trimStart();
2827
2924
  var WIRE_HINT = `
2828
2925
  import { QueueService } from '@nexusts/queue';
2829
2926
  import { %%NAME%%Job } from './queue/jobs/%%KEBAB%%.job.js';
@@ -2872,11 +2969,11 @@ var makeQueueCommand = {
2872
2969
  }
2873
2970
  logger.heading(`Scaffolding ${variants.pascal} (backend=${backend})`);
2874
2971
  if (!flagBool(ctx.flags, "no-worker", false)) {
2875
- const code = render(WORKER_TEMPLATE, {
2972
+ const code = render(templates.queue.worker, {
2876
2973
  name: variants.pascal,
2877
2974
  kebab: variants.kebab
2878
2975
  });
2879
- const out = resolve15(ctx.cwd, "app/queue/workers", `${variants.kebab}.worker.ts`);
2976
+ const out = resolve15(ctx.cwd, `${ctx.config.paths.app}/queue/workers`, `${variants.kebab}.worker.ts`);
2880
2977
  if (writeFile(out, code, { skipIfExists: true })) {
2881
2978
  logger.success(`created ${out}`);
2882
2979
  } else {
@@ -2884,11 +2981,11 @@ var makeQueueCommand = {
2884
2981
  }
2885
2982
  }
2886
2983
  if (!flagBool(ctx.flags, "no-job", false)) {
2887
- const code = render(JOB_HELPER_TEMPLATE, {
2984
+ const code = render(templates.queue.job, {
2888
2985
  name: variants.pascal,
2889
2986
  kebab: variants.kebab
2890
2987
  });
2891
- const out = resolve15(ctx.cwd, "app/queue/jobs", `${variants.kebab}.job.ts`);
2988
+ const out = resolve15(ctx.cwd, `${ctx.config.paths.app}/queue/jobs`, `${variants.kebab}.job.ts`);
2892
2989
  if (writeFile(out, code, { skipIfExists: true })) {
2893
2990
  logger.success(`created ${out}`);
2894
2991
  } else {
@@ -2957,32 +3054,6 @@ var make_repository_default = makeRepositoryCommand;
2957
3054
 
2958
3055
  // packages/cli/src/commands/make-schedule.ts
2959
3056
  import { resolve as resolve17 } from "path";
2960
- var TASK_TEMPLATE = `
2961
- import { Injectable } from '@nexusts/core';
2962
- import { Cron, Interval, Timeout } from '@nexusts/schedule';
2963
-
2964
- /**
2965
- * {{ name }} task \u2014 generated by \`nx make:schedule {{ name }}\`.
2966
- *
2967
- * Mark methods with \`@Cron\`, \`@Interval\`, or \`@Timeout\`.
2968
- * Auto-detected at boot \u2014 no manual registration needed.
2969
- */
2970
- @Injectable()
2971
- export class {{ name }}Task {
2972
- constructor() {}
2973
-
2974
- // TODO: add @Cron, @Interval, or @Timeout handlers below.
2975
-
2976
- // @Cron('0 * * * *') // every hour
2977
- // async hourly() { /* ... */ }
2978
-
2979
- // @Interval(60_000) // every minute
2980
- // async tick() { /* ... */ }
2981
-
2982
- // @Timeout(5_000) // 5s after boot
2983
- // async startup() { /* ... */ }
2984
- }
2985
- `.trimStart();
2986
3057
  var makeScheduleCommand = {
2987
3058
  name: "make:schedule",
2988
3059
  aliases: ["msk", "make-schedule"],
@@ -2996,11 +3067,11 @@ var makeScheduleCommand = {
2996
3067
  return 1;
2997
3068
  }
2998
3069
  const variants = nameVariants(name);
2999
- const code = render(TASK_TEMPLATE, {
3070
+ const code = render(templates.schedule, {
3000
3071
  name: variants.pascal,
3001
3072
  kebab: variants.kebab
3002
3073
  });
3003
- const out = resolve17(ctx.cwd, "app/schedule/tasks", `${variants.kebab}.task.ts`);
3074
+ const out = resolve17(ctx.cwd, `${ctx.config.paths.app}/schedule/tasks`, `${variants.kebab}.task.ts`);
3004
3075
  if (writeFile(out, code, { skipIfExists: true })) {
3005
3076
  logger.success(`created ${out}`);
3006
3077
  } else {
@@ -3060,50 +3131,6 @@ var make_service_default = makeServiceCommand;
3060
3131
 
3061
3132
  // packages/cli/src/commands/make-session.ts
3062
3133
  import { resolve as resolve19 } from "path";
3063
- var SESSION_TEMPLATE = `
3064
- import { Inject, Injectable } from '@nexusts/core';
3065
- import { SessionService } from '@nexusts/session';
3066
- import type { SessionRecord } from '@nexusts/session';
3067
-
3068
- /**
3069
- * {{ name }} session helper \u2014 generated by \`nx make:session {{ name }}\`.
3070
- *
3071
- * Wraps SessionService with typed accessors for the {{ name }} session's
3072
- * data. Use from controllers / services.
3073
- */
3074
- @Injectable()
3075
- export class {{ name }}Session {
3076
- constructor(@Inject(SessionService.TOKEN) private readonly sessions: SessionService) {}
3077
-
3078
- /** Read the current session record (or null). */
3079
- async getCurrent(sessionId: string | null | undefined) {
3080
- if (!sessionId) return null;
3081
- return this.sessions.read(sessionId);
3082
- }
3083
-
3084
- /** Patch the {{ name }} session's data. */
3085
- async update(sessionId: string, patch: {{ name }}DataPatch) {
3086
- return this.sessions.update(sessionId, { dataPatch: patch });
3087
- }
3088
-
3089
- /** Destroy the session (logout-everywhere equivalent). */
3090
- async destroy(sessionId: string) {
3091
- return this.sessions.destroy(sessionId, 'logout');
3092
- }
3093
- }
3094
-
3095
- /**
3096
- * Typed payload for the {{ name }} session.
3097
- *
3098
- * TODO: define the fields below to match your feature.
3099
- */
3100
- export interface {{ name }}Data {
3101
- // userId?: string;
3102
- // createdAt?: string;
3103
- }
3104
-
3105
- export type {{ name }}DataPatch = Partial<{{ name }}Data>;
3106
- `.trimStart();
3107
3134
  var makeSessionCommand = {
3108
3135
  name: "make:session",
3109
3136
  aliases: ["msess", "make-session"],
@@ -3117,11 +3144,11 @@ var makeSessionCommand = {
3117
3144
  return 1;
3118
3145
  }
3119
3146
  const variants = nameVariants(name);
3120
- const code = render(SESSION_TEMPLATE, {
3147
+ const code = render(templates.session, {
3121
3148
  name: variants.pascal,
3122
3149
  kebab: variants.kebab
3123
3150
  });
3124
- const out = resolve19(ctx.cwd, "app/session/services", `${variants.kebab}.session.ts`);
3151
+ const out = resolve19(ctx.cwd, `${ctx.config.paths.app}/session/services`, `${variants.kebab}.session.ts`);
3125
3152
  if (writeFile(out, code, { skipIfExists: true })) {
3126
3153
  logger.success(`created ${out}`);
3127
3154
  } else {
@@ -3468,14 +3495,14 @@ async function runKyselyTemplate(cwd, name, _dialect) {
3468
3495
  const migrationsDir = join(cwd, "app", "database", "migrations");
3469
3496
  mkdirSync5(migrationsDir, { recursive: true });
3470
3497
  const variants = nameVariants(name);
3471
- const timestamp = formatTimestamp2(new Date);
3498
+ const timestamp = formatTimestamp(new Date);
3472
3499
  const filename = `${timestamp}_${variants.snake}.ts`;
3473
3500
  const filepath = join(migrationsDir, filename);
3474
3501
  const tpl = templates.migration.kysely;
3475
3502
  const code = render(tpl, {
3476
3503
  name: variants.pascal,
3477
3504
  snake: variants.snake,
3478
- tableName: inferTableName2(name),
3505
+ tableName: inferTableName(name),
3479
3506
  columns: "",
3480
3507
  timestamp
3481
3508
  });
@@ -3489,7 +3516,7 @@ async function runSqlTemplate(cwd, name, dialect) {
3489
3516
  const { join } = await import("path");
3490
3517
  const migrationsDir = join(cwd, "app", "database", "migrations");
3491
3518
  mkdirSync5(migrationsDir, { recursive: true });
3492
- const timestamp = Date.now();
3519
+ const timestamp = formatTimestamp(new Date);
3493
3520
  const filename = `${timestamp}_${name.replace(/[^a-z0-9_]+/g, "_")}.sql`;
3494
3521
  const filepath = join(migrationsDir, filename);
3495
3522
  const header = dialect === "postgres" || dialect === "mysql" ? `-- Migration: ${name}
@@ -3506,19 +3533,6 @@ async function runSqlTemplate(cwd, name, dialect) {
3506
3533
  logger.info("Edit the SQL file, then run `nx db:migrate` to apply it.");
3507
3534
  return 0;
3508
3535
  }
3509
- function inferTableName2(input) {
3510
- const m = /^create_(\w+)_table$/.exec(input);
3511
- if (m)
3512
- return m[1] ?? "";
3513
- const m2 = /^(?:add|remove|drop|alter)_(\w+)_to_(\w+)$/.exec(input);
3514
- if (m2)
3515
- return m2[2] ?? "";
3516
- return `${input.toLowerCase().replace(/s$/, "")}s`;
3517
- }
3518
- function formatTimestamp2(d) {
3519
- const pad = (n) => String(n).padStart(2, "0");
3520
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
3521
- }
3522
3536
  var db_generate_default = dbGenerateCommand;
3523
3537
 
3524
3538
  // packages/cli/src/commands/db-seed.ts
@@ -3724,32 +3738,6 @@ var db_seed_default = dbSeedCommand;
3724
3738
  // packages/cli/src/commands/new.ts
3725
3739
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3726
3740
  import { resolve as resolve24 } from "path";
3727
- var VALID_OPTIONS2 = {
3728
- style: ["nest", "adonis", "functional"],
3729
- view: ["rendu", "edge", "eta", "inertia", "none"],
3730
- orm: ["drizzle", "kysely", "none"],
3731
- db: ["bun-sqlite", "node-sqlite", "libsql", "postgres", "mysql", "none"],
3732
- frontend: ["react", "vue", "svelte", "solid"]
3733
- };
3734
- async function resolveOpt2(flags, key, valid, defaultVal, interactive) {
3735
- const flagVal = flags[key];
3736
- if (flagVal) {
3737
- if (valid.includes(flagVal))
3738
- return flagVal;
3739
- if (!interactive) {
3740
- logger.error(`Invalid --${key} "${flagVal}". Valid values: ${valid.join(", ")}`);
3741
- process.exit(1);
3742
- }
3743
- logger.warn(`"${flagVal}" is not valid for --${key}. Please choose from the list.`);
3744
- }
3745
- const label = key === "style" ? "Routing style" : key === "view" ? "View engine" : key === "orm" ? "ORM driver" : key === "db" ? "Database driver" : "Inertia frontend";
3746
- for (;; ) {
3747
- const answer = await select(label, [...valid], { default: defaultVal });
3748
- if (valid.includes(answer))
3749
- return answer;
3750
- logger.warn(`"${answer}" is not valid. Please choose from: ${valid.join(", ")}`);
3751
- }
3752
- }
3753
3741
  var newCommand = {
3754
3742
  name: "new",
3755
3743
  aliases: ["n"],
@@ -3779,11 +3767,11 @@ var newCommand = {
3779
3767
  logger.error(`Directory "${name}" already exists.`);
3780
3768
  return 1;
3781
3769
  }
3782
- const routing = await resolveOpt2(ctx.flags, "style", VALID_OPTIONS2.style, "nest", interactive);
3783
- const view = await resolveOpt2(ctx.flags, "view", VALID_OPTIONS2.view, "rendu", interactive);
3784
- const orm = await resolveOpt2(ctx.flags, "orm", VALID_OPTIONS2.orm, "drizzle", interactive);
3785
- const db = await resolveOpt2(ctx.flags, "db", VALID_OPTIONS2.db, "bun-sqlite", interactive);
3786
- const frontend = await resolveOpt2(ctx.flags, "frontend", VALID_OPTIONS2.frontend, "react", interactive);
3770
+ const routing = await resolveProjectOption(ctx.flags, "style", VALID_PROJECT_OPTIONS.style, "nest", interactive);
3771
+ const view = await resolveProjectOption(ctx.flags, "view", VALID_PROJECT_OPTIONS.view, "rendu", interactive);
3772
+ const orm = await resolveProjectOption(ctx.flags, "orm", VALID_PROJECT_OPTIONS.orm, "drizzle", interactive);
3773
+ const db = await resolveProjectOption(ctx.flags, "db", VALID_PROJECT_OPTIONS.db, "bun-sqlite", interactive);
3774
+ const frontend = await resolveProjectOption(ctx.flags, "frontend", VALID_PROJECT_OPTIONS.frontend, "react", interactive);
3787
3775
  const ssr = !flagBool(ctx.flags, "no-ssr", false);
3788
3776
  mkdirSync5(target, { recursive: true });
3789
3777
  const dbUrl = db === "bun-sqlite" || db === "node-sqlite" ? "app.db" : "";
@@ -4586,7 +4574,7 @@ async function main() {
4586
4574
  const verbose = flagBool(parsed.flags, "verbose", false);
4587
4575
  logger.setVerbose(verbose);
4588
4576
  if (parsed.flags.version === true) {
4589
- console.log(PKG_VERSION);
4577
+ console.log(VERSION);
4590
4578
  return 0;
4591
4579
  }
4592
4580
  if (parsed.flags.help === true || parsed.command === "help") {
@@ -4680,7 +4668,6 @@ Examples`));
4680
4668
  }
4681
4669
  console.log();
4682
4670
  }
4683
- var PKG_VERSION = "0.1.0";
4684
4671
  main().then((code) => process.exit(code)).catch((err) => {
4685
4672
  logger.error(err?.message ?? String(err));
4686
4673
  if (process.env.NX_DEBUG === "1" && err?.stack) {
@@ -4689,5 +4676,5 @@ main().then((code) => process.exit(code)).catch((err) => {
4689
4676
  process.exit(1);
4690
4677
  });
4691
4678
 
4692
- //# debugId=B4CDEF95B1BE869264756E2164756E21
4679
+ //# debugId=0BB397411F7B54EA64756E2164756E21
4693
4680
  //# sourceMappingURL=index.js.map