@nexusts/cli 0.9.7 → 0.9.9

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" : "";
@@ -1610,8 +1877,7 @@ var initCommand = {
1610
1877
  if (entry.mode === "merge-tsconfig") {
1611
1878
  if (exists) {
1612
1879
  mergeTsconfig(abs, {
1613
- experimentalDecorators: true,
1614
- emitDecoratorMetadata: true
1880
+ experimentalDecorators: true
1615
1881
  });
1616
1882
  merged.push(entry.path);
1617
1883
  } else {
@@ -1737,7 +2003,6 @@ function defaultTsconfig() {
1737
2003
  "module": "ESNext",
1738
2004
  "moduleResolution": "bundler",
1739
2005
  "experimentalDecorators": true,
1740
- "emitDecoratorMetadata": true,
1741
2006
  "strict": true,
1742
2007
  "esModuleInterop": true,
1743
2008
  "skipLibCheck": true,
@@ -1751,51 +2016,6 @@ var init_default = initCommand;
1751
2016
 
1752
2017
  // packages/cli/src/commands/make-auth.ts
1753
2018
  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
2019
  var MODULE_UPDATE_HINT = `import { AuthModule } from '@nexusts/auth';
1800
2020
  // In your AppModule.imports:
1801
2021
  imports: [AuthModule.forRoot({ /* ... */ })],
@@ -1855,7 +2075,7 @@ var makeAuthCommand = {
1855
2075
  envVar: known?.env ?? p.toUpperCase()
1856
2076
  };
1857
2077
  });
1858
- const authCode = render(AUTH_INSTANCE_TEMPLATE, {
2078
+ const authCode = render(templates.auth.instance, {
1859
2079
  jwt: jwtEnabled,
1860
2080
  passkey: passkeyEnabled,
1861
2081
  providers: providers.length > 0,
@@ -1870,7 +2090,7 @@ var makeAuthCommand = {
1870
2090
  } else {
1871
2091
  logger.warn(`skipped (exists): ${authOut}`);
1872
2092
  }
1873
- const envCode = render(ENV_EXAMPLE_TEMPLATE, {
2093
+ const envCode = render(templates.auth["env.example"], {
1874
2094
  providers: providers.length > 0,
1875
2095
  entries
1876
2096
  });
@@ -1944,8 +2164,9 @@ var makeControllerCommand = {
1944
2164
  snake: variants.snake,
1945
2165
  pascal: variants.pascal,
1946
2166
  service: serviceName,
1947
- serviceCamel
1948
- }).replace(/import .*\n/g, skipService ? (m) => m.includes("services/") ? "" : m : (m) => m);
2167
+ serviceCamel,
2168
+ hasService: !skipService
2169
+ });
1949
2170
  const out = resolve8(ctx.cwd, ctx.config.paths.controllers, `${variants.kebab}.controller.ts`);
1950
2171
  const ok = writeFile(out, code, { skipIfExists: false });
1951
2172
  if (!ok) {
@@ -2078,6 +2299,96 @@ function mapDrizzleType(dialect, type) {
2078
2299
  }
2079
2300
  return "text";
2080
2301
  }
2302
+ function mapSqlType(t, dialect) {
2303
+ const type = t.toLowerCase();
2304
+ switch (type) {
2305
+ case "text":
2306
+ case "string":
2307
+ case "varchar":
2308
+ return dialect === "mysql" ? "VARCHAR(255)" : "TEXT";
2309
+ case "int":
2310
+ case "integer":
2311
+ return dialect === "postgres" ? "INTEGER" : dialect === "mysql" ? "INT" : "INTEGER";
2312
+ case "bigint":
2313
+ case "bigintunsigned":
2314
+ return dialect === "postgres" ? "BIGINT" : "BIGINT UNSIGNED";
2315
+ case "serial":
2316
+ return dialect === "postgres" ? "SERIAL" : "INTEGER AUTO_INCREMENT";
2317
+ case "bool":
2318
+ case "boolean":
2319
+ return "BOOLEAN";
2320
+ case "float":
2321
+ case "number":
2322
+ case "real":
2323
+ case "double":
2324
+ return dialect === "mysql" ? "DOUBLE" : "REAL";
2325
+ case "datetime":
2326
+ case "timestamp":
2327
+ return dialect === "postgres" ? "TIMESTAMP" : dialect === "mysql" ? "DATETIME" : "INTEGER";
2328
+ case "date":
2329
+ return dialect === "mysql" ? "DATE" : "TEXT";
2330
+ case "json":
2331
+ return dialect === "postgres" ? "JSONB" : dialect === "mysql" ? "JSON" : "TEXT";
2332
+ case "jsonb":
2333
+ return dialect === "postgres" ? "JSONB" : "TEXT";
2334
+ default:
2335
+ return "TEXT";
2336
+ }
2337
+ }
2338
+ function mapKyselyType(type) {
2339
+ switch (type.toLowerCase()) {
2340
+ case "text":
2341
+ case "string":
2342
+ case "varchar":
2343
+ return "text";
2344
+ case "int":
2345
+ case "integer":
2346
+ return "integer";
2347
+ case "bigint":
2348
+ return "bigint";
2349
+ case "bool":
2350
+ case "boolean":
2351
+ return "boolean";
2352
+ case "float":
2353
+ case "number":
2354
+ case "real":
2355
+ case "double":
2356
+ return "real";
2357
+ case "datetime":
2358
+ case "timestamp":
2359
+ case "date":
2360
+ case "json":
2361
+ case "jsonb":
2362
+ return "text";
2363
+ default:
2364
+ return "text";
2365
+ }
2366
+ }
2367
+ function renderSqlColumns(cols, dialect) {
2368
+ return cols.map(([name, type]) => {
2369
+ const sqlType = mapSqlType(type, dialect);
2370
+ const notNull = /NOT NULL/i.test(sqlType) ? "" : " NOT NULL";
2371
+ return ` ${name} ${sqlType}${notNull},`;
2372
+ }).join(`
2373
+ `);
2374
+ }
2375
+ function renderKyselyColumns(cols) {
2376
+ return cols.map(([name, type]) => {
2377
+ const kyselyType = mapKyselyType(type);
2378
+ return ` .addColumn('${name}', '${kyselyType}', (col) => col.notNull())`;
2379
+ }).join(`
2380
+ `);
2381
+ }
2382
+ function renderDrizzleColumns(cols, dialect) {
2383
+ return cols.map(([name, type]) => {
2384
+ const helper = mapDrizzleType(dialect, type);
2385
+ return ` ${name}: ${helper}('${name}'),`;
2386
+ }).join(`
2387
+ `);
2388
+ }
2389
+ function isValidDialect(d) {
2390
+ return ["postgres", "mysql", "sqlite", "bun-sqlite", "d1"].includes(d);
2391
+ }
2081
2392
 
2082
2393
  // packages/cli/src/commands/make-crud.ts
2083
2394
  var makeCrudCommand = {
@@ -2173,7 +2484,7 @@ var makeCrudCommand = {
2173
2484
  kebab: variants.kebab,
2174
2485
  snake: variants.snake,
2175
2486
  tableName,
2176
- columns: renderDrizzleColumns(dialect)
2487
+ columns: renderDrizzleColumns2(dialect)
2177
2488
  });
2178
2489
  } else {
2179
2490
  const tpl = templates.model[orm];
@@ -2281,7 +2592,7 @@ function renderDefaultColumns(orm) {
2281
2592
  }
2282
2593
  return " title text,";
2283
2594
  }
2284
- function renderDrizzleColumns(dialect) {
2595
+ function renderDrizzleColumns2(dialect) {
2285
2596
  const helper = mapDrizzleType(dialect, "text");
2286
2597
  return ` title: ${helper}('title').notNull(),`;
2287
2598
  }
@@ -2289,39 +2600,6 @@ var make_crud_default = makeCrudCommand;
2289
2600
 
2290
2601
  // packages/cli/src/commands/make-listener.ts
2291
2602
  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
2603
  var makeListenerCommand = {
2326
2604
  name: "make:listener",
2327
2605
  aliases: ["ml", "make-listener"],
@@ -2335,7 +2613,7 @@ var makeListenerCommand = {
2335
2613
  return 1;
2336
2614
  }
2337
2615
  const variants = nameVariants(name);
2338
- const code = render(LISTENER_TEMPLATE, {
2616
+ const code = render(templates.listener, {
2339
2617
  name: variants.pascal,
2340
2618
  kebab: variants.kebab
2341
2619
  });
@@ -2405,192 +2683,87 @@ var makeMigrationCommand = {
2405
2683
  {
2406
2684
  name: "orm",
2407
2685
  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";
2686
+ },
2687
+ {
2688
+ name: "dialect",
2689
+ description: "Drizzle dialect (postgres|mysql|sqlite|bun-sqlite|d1). Default: bun-sqlite"
2690
+ }
2691
+ ],
2692
+ async run(ctx) {
2693
+ const name = ctx.positional[0];
2694
+ if (!name) {
2695
+ logger.error("Usage: nx make:migration <Name> [--dialect ...]");
2696
+ return 1;
2697
+ }
2698
+ const orm = ctx.flags.orm ?? ctx.config.orm;
2699
+ const dialect = ctx.flags.dialect ?? ctx.config.dialect ?? "bun-sqlite";
2700
+ const isDrizzle = orm === "drizzle";
2701
+ const isKysely = orm === "kysely";
2702
+ const useGenericSql = orm === "none";
2703
+ const variants = nameVariants(name);
2704
+ const tableName = inferTableName(name);
2705
+ const colsFlag = ctx.flags.columns;
2706
+ const cols = parseColumns(colsFlag ?? "title:text");
2707
+ const drizzleColumns = renderDrizzleColumns(cols, dialect);
2708
+ const sqlColumns = renderSqlColumns(cols, dialect);
2709
+ let code;
2710
+ let extension;
2711
+ if (isKysely) {
2712
+ const tpl = templates.migration.kysely;
2713
+ code = render(tpl, {
2714
+ name: variants.pascal,
2715
+ snake: variants.snake,
2716
+ tableName,
2717
+ columns: renderKyselyColumns(cols),
2718
+ timestamp: formatTimestamp(new Date)
2719
+ });
2720
+ extension = "ts";
2721
+ } else if (isDrizzle) {
2722
+ if (!isValidDialect(dialect)) {
2723
+ logger.error(`Unsupported drizzle dialect: ${dialect}. Allowed: postgres, mysql, sqlite, bun-sqlite, d1.`);
2724
+ return 1;
2725
+ }
2726
+ code = renderDrizzleDialect(dialect);
2727
+ code = render(code, {
2728
+ name: variants.pascal,
2729
+ snake: variants.snake,
2730
+ tableName,
2731
+ columns: drizzleColumns,
2732
+ timestamp: formatTimestamp(new Date)
2733
+ });
2734
+ extension = "ts";
2735
+ } else if (useGenericSql) {
2736
+ const tpl = templates.migration.sql;
2737
+ code = render(tpl, {
2738
+ name: variants.pascal,
2739
+ snake: variants.snake,
2740
+ tableName,
2741
+ columns: sqlColumns,
2742
+ timestamp: formatTimestamp(new Date)
2743
+ });
2744
+ extension = "sql";
2745
+ } else {
2746
+ logger.error(`Unsupported ORM for migration: ${orm}. Allowed: drizzle, kysely, none.`);
2747
+ return 1;
2748
+ }
2749
+ const filename = `${formatTimestamp(new Date)}_${variants.snake}.${extension}`;
2750
+ const out = resolve12(ctx.cwd, ctx.config.paths.migrations, filename);
2751
+ writeFile(out, code);
2752
+ logger.success(`created ${out}`);
2753
+ if (isDrizzle || isKysely) {
2754
+ logger.finger(`run \`nx db:migrate\` to apply pending migrations.`);
2755
+ } else {
2756
+ logger.finger(`run \`bun nx db:migrate\` or your migration tool.`);
2757
+ }
2758
+ return 0;
2589
2759
  }
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())}`;
2760
+ };
2761
+ function parseColumns(input) {
2762
+ const list = Array.isArray(input) ? input : input.split(",");
2763
+ return list.map((s) => s.trim()).filter(Boolean).map((c2) => {
2764
+ const [name, type = "text"] = c2.split(":");
2765
+ return [name, type];
2766
+ });
2594
2767
  }
2595
2768
  var make_migration_default = makeMigrationCommand;
2596
2769
 
@@ -2640,7 +2813,7 @@ var makeModelCommand = {
2640
2813
  let code;
2641
2814
  if (orm === "drizzle") {
2642
2815
  const dialect = ctx.flags.dialect ?? ctx.config.dialect ?? "bun-sqlite";
2643
- if (!isValidDialect2(dialect)) {
2816
+ if (!isValidDialect(dialect)) {
2644
2817
  logger.error(`Unsupported drizzle dialect: ${dialect}. Allowed: postgres, mysql, sqlite, bun-sqlite, d1.`);
2645
2818
  return 1;
2646
2819
  }
@@ -2674,9 +2847,6 @@ var makeModelCommand = {
2674
2847
  return 0;
2675
2848
  }
2676
2849
  };
2677
- function isValidDialect2(d) {
2678
- return ["postgres", "mysql", "sqlite", "bun-sqlite", "d1"].includes(d);
2679
- }
2680
2850
  function renderColumns(cols, orm, dialect) {
2681
2851
  const flat = cols.flatMap((c2) => c2.split(",")).map((c2) => c2.trim()).filter(Boolean);
2682
2852
  return flat.map((col) => {
@@ -2749,81 +2919,6 @@ var make_module_default = makeModuleCommand;
2749
2919
 
2750
2920
  // packages/cli/src/commands/make-queue.ts
2751
2921
  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
2922
  var WIRE_HINT = `
2828
2923
  import { QueueService } from '@nexusts/queue';
2829
2924
  import { %%NAME%%Job } from './queue/jobs/%%KEBAB%%.job.js';
@@ -2872,11 +2967,11 @@ var makeQueueCommand = {
2872
2967
  }
2873
2968
  logger.heading(`Scaffolding ${variants.pascal} (backend=${backend})`);
2874
2969
  if (!flagBool(ctx.flags, "no-worker", false)) {
2875
- const code = render(WORKER_TEMPLATE, {
2970
+ const code = render(templates.queue.worker, {
2876
2971
  name: variants.pascal,
2877
2972
  kebab: variants.kebab
2878
2973
  });
2879
- const out = resolve15(ctx.cwd, "app/queue/workers", `${variants.kebab}.worker.ts`);
2974
+ const out = resolve15(ctx.cwd, `${ctx.config.paths.app}/queue/workers`, `${variants.kebab}.worker.ts`);
2880
2975
  if (writeFile(out, code, { skipIfExists: true })) {
2881
2976
  logger.success(`created ${out}`);
2882
2977
  } else {
@@ -2884,11 +2979,11 @@ var makeQueueCommand = {
2884
2979
  }
2885
2980
  }
2886
2981
  if (!flagBool(ctx.flags, "no-job", false)) {
2887
- const code = render(JOB_HELPER_TEMPLATE, {
2982
+ const code = render(templates.queue.job, {
2888
2983
  name: variants.pascal,
2889
2984
  kebab: variants.kebab
2890
2985
  });
2891
- const out = resolve15(ctx.cwd, "app/queue/jobs", `${variants.kebab}.job.ts`);
2986
+ const out = resolve15(ctx.cwd, `${ctx.config.paths.app}/queue/jobs`, `${variants.kebab}.job.ts`);
2892
2987
  if (writeFile(out, code, { skipIfExists: true })) {
2893
2988
  logger.success(`created ${out}`);
2894
2989
  } else {
@@ -2957,32 +3052,6 @@ var make_repository_default = makeRepositoryCommand;
2957
3052
 
2958
3053
  // packages/cli/src/commands/make-schedule.ts
2959
3054
  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
3055
  var makeScheduleCommand = {
2987
3056
  name: "make:schedule",
2988
3057
  aliases: ["msk", "make-schedule"],
@@ -2996,11 +3065,11 @@ var makeScheduleCommand = {
2996
3065
  return 1;
2997
3066
  }
2998
3067
  const variants = nameVariants(name);
2999
- const code = render(TASK_TEMPLATE, {
3068
+ const code = render(templates.schedule, {
3000
3069
  name: variants.pascal,
3001
3070
  kebab: variants.kebab
3002
3071
  });
3003
- const out = resolve17(ctx.cwd, "app/schedule/tasks", `${variants.kebab}.task.ts`);
3072
+ const out = resolve17(ctx.cwd, `${ctx.config.paths.app}/schedule/tasks`, `${variants.kebab}.task.ts`);
3004
3073
  if (writeFile(out, code, { skipIfExists: true })) {
3005
3074
  logger.success(`created ${out}`);
3006
3075
  } else {
@@ -3060,50 +3129,6 @@ var make_service_default = makeServiceCommand;
3060
3129
 
3061
3130
  // packages/cli/src/commands/make-session.ts
3062
3131
  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
3132
  var makeSessionCommand = {
3108
3133
  name: "make:session",
3109
3134
  aliases: ["msess", "make-session"],
@@ -3117,11 +3142,11 @@ var makeSessionCommand = {
3117
3142
  return 1;
3118
3143
  }
3119
3144
  const variants = nameVariants(name);
3120
- const code = render(SESSION_TEMPLATE, {
3145
+ const code = render(templates.session, {
3121
3146
  name: variants.pascal,
3122
3147
  kebab: variants.kebab
3123
3148
  });
3124
- const out = resolve19(ctx.cwd, "app/session/services", `${variants.kebab}.session.ts`);
3149
+ const out = resolve19(ctx.cwd, `${ctx.config.paths.app}/session/services`, `${variants.kebab}.session.ts`);
3125
3150
  if (writeFile(out, code, { skipIfExists: true })) {
3126
3151
  logger.success(`created ${out}`);
3127
3152
  } else {
@@ -3468,14 +3493,14 @@ async function runKyselyTemplate(cwd, name, _dialect) {
3468
3493
  const migrationsDir = join(cwd, "app", "database", "migrations");
3469
3494
  mkdirSync5(migrationsDir, { recursive: true });
3470
3495
  const variants = nameVariants(name);
3471
- const timestamp = formatTimestamp2(new Date);
3496
+ const timestamp = formatTimestamp(new Date);
3472
3497
  const filename = `${timestamp}_${variants.snake}.ts`;
3473
3498
  const filepath = join(migrationsDir, filename);
3474
3499
  const tpl = templates.migration.kysely;
3475
3500
  const code = render(tpl, {
3476
3501
  name: variants.pascal,
3477
3502
  snake: variants.snake,
3478
- tableName: inferTableName2(name),
3503
+ tableName: inferTableName(name),
3479
3504
  columns: "",
3480
3505
  timestamp
3481
3506
  });
@@ -3489,7 +3514,7 @@ async function runSqlTemplate(cwd, name, dialect) {
3489
3514
  const { join } = await import("path");
3490
3515
  const migrationsDir = join(cwd, "app", "database", "migrations");
3491
3516
  mkdirSync5(migrationsDir, { recursive: true });
3492
- const timestamp = Date.now();
3517
+ const timestamp = formatTimestamp(new Date);
3493
3518
  const filename = `${timestamp}_${name.replace(/[^a-z0-9_]+/g, "_")}.sql`;
3494
3519
  const filepath = join(migrationsDir, filename);
3495
3520
  const header = dialect === "postgres" || dialect === "mysql" ? `-- Migration: ${name}
@@ -3506,19 +3531,6 @@ async function runSqlTemplate(cwd, name, dialect) {
3506
3531
  logger.info("Edit the SQL file, then run `nx db:migrate` to apply it.");
3507
3532
  return 0;
3508
3533
  }
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
3534
  var db_generate_default = dbGenerateCommand;
3523
3535
 
3524
3536
  // packages/cli/src/commands/db-seed.ts
@@ -3724,32 +3736,6 @@ var db_seed_default = dbSeedCommand;
3724
3736
  // packages/cli/src/commands/new.ts
3725
3737
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3726
3738
  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
3739
  var newCommand = {
3754
3740
  name: "new",
3755
3741
  aliases: ["n"],
@@ -3779,11 +3765,11 @@ var newCommand = {
3779
3765
  logger.error(`Directory "${name}" already exists.`);
3780
3766
  return 1;
3781
3767
  }
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);
3768
+ const routing = await resolveProjectOption(ctx.flags, "style", VALID_PROJECT_OPTIONS.style, "nest", interactive);
3769
+ const view = await resolveProjectOption(ctx.flags, "view", VALID_PROJECT_OPTIONS.view, "rendu", interactive);
3770
+ const orm = await resolveProjectOption(ctx.flags, "orm", VALID_PROJECT_OPTIONS.orm, "drizzle", interactive);
3771
+ const db = await resolveProjectOption(ctx.flags, "db", VALID_PROJECT_OPTIONS.db, "bun-sqlite", interactive);
3772
+ const frontend = await resolveProjectOption(ctx.flags, "frontend", VALID_PROJECT_OPTIONS.frontend, "react", interactive);
3787
3773
  const ssr = !flagBool(ctx.flags, "no-ssr", false);
3788
3774
  mkdirSync5(target, { recursive: true });
3789
3775
  const dbUrl = db === "bun-sqlite" || db === "node-sqlite" ? "app.db" : "";
@@ -3794,7 +3780,6 @@ var newCommand = {
3794
3780
  "module": "ESNext",
3795
3781
  "moduleResolution": "bundler",
3796
3782
  "experimentalDecorators": true,
3797
- "emitDecoratorMetadata": true,
3798
3783
  "strict": true,
3799
3784
  "esModuleInterop": true,
3800
3785
  "skipLibCheck": true,
@@ -4586,7 +4571,7 @@ async function main() {
4586
4571
  const verbose = flagBool(parsed.flags, "verbose", false);
4587
4572
  logger.setVerbose(verbose);
4588
4573
  if (parsed.flags.version === true) {
4589
- console.log(PKG_VERSION);
4574
+ console.log(VERSION);
4590
4575
  return 0;
4591
4576
  }
4592
4577
  if (parsed.flags.help === true || parsed.command === "help") {
@@ -4680,7 +4665,6 @@ Examples`));
4680
4665
  }
4681
4666
  console.log();
4682
4667
  }
4683
- var PKG_VERSION = "0.1.0";
4684
4668
  main().then((code) => process.exit(code)).catch((err) => {
4685
4669
  logger.error(err?.message ?? String(err));
4686
4670
  if (process.env.NX_DEBUG === "1" && err?.stack) {
@@ -4689,5 +4673,5 @@ main().then((code) => process.exit(code)).catch((err) => {
4689
4673
  process.exit(1);
4690
4674
  });
4691
4675
 
4692
- //# debugId=B4CDEF95B1BE869264756E2164756E21
4676
+ //# debugId=6C16C059AB4C7BF264756E2164756E21
4693
4677
  //# sourceMappingURL=index.js.map