@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/core/index.d.ts +1 -0
- package/dist/core/naming.d.ts +20 -0
- package/dist/core/prompts.d.ts +14 -0
- package/dist/index.js +619 -635
- package/dist/index.js.map +32 -24
- package/dist/templates/auth/auth-instance.d.ts +14 -0
- package/dist/templates/auth/env-example.d.ts +9 -0
- package/dist/templates/index.d.ts +11 -0
- package/dist/templates/listener/listener.d.ts +9 -0
- package/dist/templates/model/drizzle-dialect.d.ts +12 -0
- package/dist/templates/queue/job.d.ts +9 -0
- package/dist/templates/queue/worker.d.ts +9 -0
- package/dist/templates/schedule/task.d.ts +9 -0
- package/dist/templates/session/session.d.ts +9 -0
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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/
|
|
997
|
-
var
|
|
998
|
-
|
|
999
|
-
|
|
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
|
|
1565
|
-
const view = await
|
|
1566
|
-
const orm = await
|
|
1567
|
-
const db = await
|
|
1568
|
-
const frontend = await
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
2592
|
-
const
|
|
2593
|
-
return
|
|
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 (!
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
3068
|
+
const code = render(templates.schedule, {
|
|
3000
3069
|
name: variants.pascal,
|
|
3001
3070
|
kebab: variants.kebab
|
|
3002
3071
|
});
|
|
3003
|
-
const out = resolve17(ctx.cwd,
|
|
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(
|
|
3145
|
+
const code = render(templates.session, {
|
|
3121
3146
|
name: variants.pascal,
|
|
3122
3147
|
kebab: variants.kebab
|
|
3123
3148
|
});
|
|
3124
|
-
const out = resolve19(ctx.cwd,
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
3783
|
-
const view = await
|
|
3784
|
-
const orm = await
|
|
3785
|
-
const db = await
|
|
3786
|
-
const frontend = await
|
|
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(
|
|
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=
|
|
4676
|
+
//# debugId=6C16C059AB4C7BF264756E2164756E21
|
|
4693
4677
|
//# sourceMappingURL=index.js.map
|