create-nexwire 4.1.2 → 4.2.0

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.
Files changed (52) hide show
  1. package/express/gitignore-stub +31 -24
  2. package/express/package.json +2 -2
  3. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +411 -411
  4. package/express/src/database/migrations/sqlite/meta/_journal.json +12 -12
  5. package/express/src/framework/maker-cli/db/index.mjs +1 -1
  6. package/express/src/framework/maker-cli/deploy/core.mjs +0 -1
  7. package/express/src/framework/maker-cli/index.mjs +13 -3
  8. package/express/src/framework/maker-cli/module/core.mjs +121 -43
  9. package/express/src/framework/maker-cli/runtime/core.mjs +26 -7
  10. package/express/src/framework/maker-cli/runtime/index.mjs +120 -104
  11. package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
  12. package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
  13. package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
  14. package/express/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
  15. package/express/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  16. package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
  17. package/express/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
  18. package/express/src/framework/maker-cli/utils/help.mjs +374 -50
  19. package/express/src/framework/maker-cli/utils/naming.mjs +83 -20
  20. package/express/src/framework/maker-cli/utils/project-commands.mjs +140 -0
  21. package/express/src/framework/queue/queue.ts +402 -342
  22. package/express/src/modules/auth/database/seeders/role.ts +18 -18
  23. package/express/src/modules/auth/database/seeders/user.ts +35 -35
  24. package/express/vitest.config.ts +38 -23
  25. package/hono/gitignore-stub +31 -24
  26. package/hono/package.json +2 -2
  27. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +411 -411
  28. package/hono/src/database/migrations/sqlite/meta/_journal.json +12 -12
  29. package/hono/src/framework/database/paginate.ts +253 -253
  30. package/hono/src/framework/maker-cli/db/index.mjs +1 -1
  31. package/hono/src/framework/maker-cli/deploy/core.mjs +0 -1
  32. package/hono/src/framework/maker-cli/index.mjs +13 -3
  33. package/hono/src/framework/maker-cli/module/core.mjs +116 -38
  34. package/hono/src/framework/maker-cli/runtime/core.mjs +24 -6
  35. package/hono/src/framework/maker-cli/runtime/index.mjs +120 -104
  36. package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
  37. package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
  38. package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
  39. package/hono/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
  40. package/hono/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  41. package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
  42. package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
  43. package/hono/src/framework/maker-cli/utils/help.mjs +374 -50
  44. package/hono/src/framework/maker-cli/utils/naming.mjs +83 -20
  45. package/hono/src/framework/maker-cli/utils/project-commands.mjs +140 -0
  46. package/hono/src/framework/queue/queue.ts +402 -342
  47. package/hono/src/modules/auth/database/seeders/role.ts +18 -18
  48. package/hono/src/modules/auth/database/seeders/user.ts +35 -35
  49. package/hono/vitest.config.ts +38 -23
  50. package/package.json +1 -1
  51. package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -73
  52. package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -69
@@ -1,13 +1,13 @@
1
- {
2
- "version": "7",
3
- "dialect": "sqlite",
4
- "entries": [
5
- {
6
- "idx": 0,
7
- "version": "6",
8
- "when": 1791141133457,
9
- "tag": "0000_init",
10
- "breakpoints": true
11
- }
12
- ]
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1791141133457,
9
+ "tag": "0000_init",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
13
  }
@@ -42,7 +42,7 @@ function assertName(value, label) {
42
42
 
43
43
  function unsupported() {
44
44
  console.error(
45
- 'Drizzle Kit does not support Laravel-style rollback/refresh migrations. Use "npm run maker db:fresh -- --seed" (or "bun maker db:fresh --seed") for a SQLite development reset, or manage rollback SQL manually for production databases.'
45
+ 'Drizzle Kit does not support rollback/refresh migrations. Use "npm run maker db:fresh -- --seed" (or "bun maker db:fresh --seed") for a SQLite development reset, or manage rollback SQL manually for production databases.'
46
46
  );
47
47
  process.exit(1);
48
48
  }
@@ -69,7 +69,6 @@ const STUBS = {
69
69
  },
70
70
  controller: {
71
71
  openapi: "controller/openapi.ts.stub",
72
- openapiWithModel: "controller/openapi.with-model.ts.stub",
73
72
  plain: "controller/plain.ts.stub",
74
73
  schema: {
75
74
  openapi: "controller/schema.ts.stub",
@@ -6,7 +6,8 @@ import { registerDbCommands } from "./db/index.mjs";
6
6
  import { registerMiddlewareCommands } from "./middleware/index.mjs";
7
7
  import { registerModuleCommands } from "./module/index.mjs";
8
8
  import { registerRuntimeCommands } from "./runtime/index.mjs";
9
- import { makerCommandPrefix, showHelp } from "./utils/help.mjs";
9
+ import { configureGroupedHelp, makerCommandPrefix, showHelp } from "./utils/help.mjs";
10
+ import { registerProjectCommands } from "./utils/project-commands.mjs";
10
11
 
11
12
  const args = process.argv.slice(2);
12
13
  const [command] = args;
@@ -33,8 +34,11 @@ try {
33
34
  .name(namePrefix ? `${namePrefix} maker` : "maker")
34
35
  .allowUnknownOption(true)
35
36
  .allowExcessArguments(true)
36
- .helpOption("-h, --help")
37
- .addHelpText("beforeAll", "nexwire maker\n");
37
+ .helpOption("-h, --help");
38
+
39
+ // Grouped command list. Must be set before anything prints help. Per-command
40
+ // help is untouched, so `maker <command> --help` still uses commander's layout.
41
+ configureGroupedHelp(program);
38
42
 
39
43
  registerMiddlewareCommands(program, args);
40
44
  registerModuleCommands(program, args);
@@ -42,6 +46,12 @@ try {
42
46
  registerDbCommands(program, args);
43
47
  registerRuntimeCommands(program, args);
44
48
 
49
+ // The project's own commands come last, so a built-in always wins a name
50
+ // clash. Absent file is silent and free; a file that is present but broken
51
+ // throws, and the catch below reports it and exits rather than leaving the
52
+ // developer with commands that silently do not exist.
53
+ await registerProjectCommands(program, args);
54
+
45
55
  if (!command) {
46
56
  showHelp(program);
47
57
  } else {
@@ -5,7 +5,7 @@ import { drizzleGenerateArgs, ensureDatabaseDirectory, ensureMigrationMeta, sync
5
5
  import { detectDialect, openApiEnabled } from "../utils/env-db.mjs";
6
6
  import { writeFiles } from "../utils/file-ops.mjs";
7
7
  import { hasFlag } from "../utils/flags.mjs";
8
- import { assertName, camelCase, pascal } from "../utils/naming.mjs";
8
+ import { assertName, camelCase, normalizeName, pascal, plural, snakeCase } from "../utils/naming.mjs";
9
9
  import { packageScript, runNodeScript } from "../utils/process.mjs";
10
10
 
11
11
  const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
@@ -18,7 +18,6 @@ const STUBS = {
18
18
  },
19
19
  controller: {
20
20
  openapi: "controller/openapi.ts.stub",
21
- openapiWithModel: "controller/openapi.with-model.ts.stub",
22
21
  plain: "controller/plain.ts.stub",
23
22
  standalone: "controller/standalone.ts.stub"
24
23
  },
@@ -36,7 +35,8 @@ const STUBS = {
36
35
  }
37
36
  },
38
37
  seeder: {
39
- named: "seeder/name.ts.stub"
38
+ named: "seeder/name.ts.stub",
39
+ standalone: "seeder/standalone.ts.stub"
40
40
  },
41
41
  service: {
42
42
  named: "service/name.ts.stub",
@@ -185,6 +185,35 @@ function resolveModuleRef(rawModule, ...flagGroups) {
185
185
  return { moduleName, leaf: leafName(moduleName) };
186
186
  }
187
187
 
188
+ /**
189
+ * Find an existing module directory whose folder name spells the requested
190
+ * module differently, only in the separators used (`my_module` vs `my-module`).
191
+ *
192
+ * Names are canonicalised to kebab-case, so a module scaffolded before this
193
+ * happened can live in a folder the canonical name no longer resolves to.
194
+ * Without this lookup, normalising the name would lock a developer out of their
195
+ * own module. Matching is segment by segment so a panel path such as
196
+ * `admin/reporting` keeps working, and each candidate is a real directory
197
+ * inside src/modules, so the result cannot escape it.
198
+ */
199
+ async function resolveExistingModuleDir(moduleName) {
200
+ let current = path.resolve(process.cwd(), "src/modules");
201
+ for (const segment of moduleName.split("/").filter(Boolean)) {
202
+ let entries;
203
+ try {
204
+ entries = await fs.readdir(current, { withFileTypes: true });
205
+ } catch {
206
+ return "";
207
+ }
208
+ const directories = entries.filter((entry) => entry.isDirectory());
209
+ const exact = directories.find((entry) => entry.name === segment);
210
+ const loose = exact ?? directories.find((entry) => normalizeName(entry.name) === normalizeName(segment));
211
+ if (!loose) return "";
212
+ current = path.join(current, loose.name);
213
+ }
214
+ return current;
215
+ }
216
+
188
217
  /** Ensure a module directory exists, throwing a helpful error if not. */
189
218
  async function assertModuleExists(moduleName) {
190
219
  const root = moduleRoot(moduleName);
@@ -192,6 +221,8 @@ async function assertModuleExists(moduleName) {
192
221
  try {
193
222
  stats = await fs.stat(root);
194
223
  } catch {
224
+ const legacy = await resolveExistingModuleDir(moduleName);
225
+ if (legacy) return legacy;
195
226
  throw new Error(`Module does not exist: ${moduleName}. Create it first with: bun maker module:make ${moduleName}`);
196
227
  }
197
228
  if (!stats.isDirectory()) {
@@ -200,16 +231,30 @@ async function assertModuleExists(moduleName) {
200
231
  return root;
201
232
  }
202
233
 
203
- /** Build the placeholder substitution map shared by module file generators. */
234
+ /**
235
+ * Build the placeholder substitution map shared by module file generators.
236
+ *
237
+ * The same developer-supplied name reaches four different places in a stub, and
238
+ * each needs a different shape: import paths use the kebab file name, the
239
+ * service identifier is camelCase, the table identifier is camelCase plural and
240
+ * the SQL table name is snake_case plural. Deriving all of them from one
241
+ * canonical name is what keeps a multi-word name from landing in an identifier
242
+ * position as `game-configService`, which parses as subtraction.
243
+ */
204
244
  function moduleFileVars(moduleName, controller) {
205
- return {
206
- module: moduleName,
207
- controller,
208
- ClassName: pascal(controller),
209
- name: leafName(moduleName),
210
- tableVariable: `${controller}s`
211
- };
212
- }
245
+ const base = normalizeName(controller);
246
+ return {
247
+ module: moduleName,
248
+ // File name on disk and therefore every import path that points at it.
249
+ controller: base,
250
+ // Identifier the stubs export and call.
251
+ ServiceName: `${camelCase(base)}Service`,
252
+ ClassName: pascal(base),
253
+ name: leafName(moduleName),
254
+ tableName: plural(snakeCase(base)),
255
+ tableVariable: plural(camelCase(base))
256
+ };
257
+ }
213
258
 
214
259
  /**
215
260
  * Generate a controller file for a module.
@@ -244,8 +289,7 @@ async function serviceFile(moduleName, controllerName, withModel) {
244
289
  async function moduleFiles(moduleName, openApi) {
245
290
  const name = leafName(moduleName);
246
291
  const vars = moduleFileVars(moduleName, name);
247
- // The with-model controller variant only exists for the openapi style.
248
- const controllerStub = openApi ? STUBS.controller.openapiWithModel : STUBS.controller.plain;
292
+ const controllerStub = openApi ? STUBS.controller.openapi : STUBS.controller.plain;
249
293
  return {
250
294
  "facade.ts": await stub(STUBS.facade.named, vars),
251
295
  [`controllers/${name}.ts`]: await stub(controllerStub, vars),
@@ -286,25 +330,28 @@ return await stub(routeStub, {
286
330
 
287
331
  /** Generate a named model file for a module. */
288
332
  async function namedModelTemplate(moduleName, name, dialect) {
333
+ const base = normalizeName(name);
289
334
  return await stub(STUBS.model.named[dialect], {
290
335
  module: moduleName,
291
- name,
292
- controller: name,
293
- ClassName: pascal(name),
294
- tableName: `${name}s`,
295
- tableVariable: `${name}s`
336
+ name: base,
337
+ controller: base,
338
+ ClassName: pascal(base),
339
+ tableName: plural(snakeCase(base)),
340
+ tableVariable: plural(camelCase(base))
296
341
  });
297
342
  }
298
343
 
299
344
  /** Generate a seeder file for a module model. */
300
345
  async function namedSeederTemplate(moduleName, modelName, className) {
346
+ const base = normalizeName(modelName);
301
347
  return await stub(STUBS.seeder.named, {
302
348
  module: moduleName,
303
- name: modelName,
349
+ // Import path: the model file is named after the model, in kebab-case.
350
+ name: base,
304
351
  controller: className,
305
352
  ClassName: pascal(className),
306
- tableName: `${modelName}s`,
307
- tableVariable: `${modelName}s`
353
+ tableName: plural(snakeCase(base)),
354
+ tableVariable: plural(camelCase(base))
308
355
  });
309
356
  }
310
357
 
@@ -717,7 +764,16 @@ export async function makeModel(rawModule, rawNameOrFlag, extraFlags = []) {
717
764
  await writeFileSafe(modelPath, await namedModelTemplate(moduleName, name, dialect), flags, "Model file");
718
765
  }
719
766
 
720
- /** Generate a seeder file for an existing module model. */
767
+ /**
768
+ * Generate a seeder file.
769
+ *
770
+ * A seeder does not have to own the table it writes to, and does not have to
771
+ * write to only one table, so a missing model is not fatal. When the model is
772
+ * there we import it and pre-wire the insert; when it is not we emit the
773
+ * standalone shape and let the developer choose what to write. That is the
774
+ * same trade the controller makes when there is no service, and the service
775
+ * makes when there is no model. Seeder was the only generator that refused.
776
+ */
721
777
  export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
722
778
  const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
723
779
  const flags = [];
@@ -728,36 +784,58 @@ export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
728
784
  }
729
785
  flags.push(...extraFlags);
730
786
  const root = await assertModuleExists(moduleName);
731
- let modelName = name;
732
- let modelPath = path.join(root, "database", "models", `${modelName}.ts`);
733
-
734
- try {
735
- await fs.access(modelPath);
736
- } catch {
737
- // Fall back to the module's own model. This must use the leaf, not
738
- // moduleName: for a panel module moduleName is "admin/testme", so using it
739
- // would probe database/models/admin/testme.ts and never match.
740
- modelName = leaf;
741
- modelPath = path.join(root, "database", "models", `${modelName}.ts`);
742
787
 
743
- try {
744
- await fs.access(modelPath);
788
+ // The fallback probes the leaf, not moduleName: for a panel module moduleName
789
+ // is "admin/testme", so using it would look for database/models/admin/testme.ts
790
+ // and never match.
791
+ const modelName = await existingModelName(root, name, leaf);
792
+ let seeder;
793
+ if (modelName) {
794
+ seeder = await namedSeederTemplate(moduleName, modelName, name);
795
+ if (modelName !== name) {
745
796
  console.log(`Model '${name}' not found, using module model '${modelName}' for seeder '${name}'.`);
746
- } catch {
747
- const panel = panelOf(moduleName);
748
- const suggest = panel ? `${leaf} ${name} --path=${panel}` : `${leaf} ${name}`;
749
- throw new Error(
750
- `Model not found for seeder: src/modules/${moduleName}/database/models/${name}.ts. Create it first with: bun maker module:make-model ${suggest}`
751
- );
752
797
  }
798
+ } else {
799
+ seeder = await stub(STUBS.seeder.standalone, {
800
+ module: moduleName,
801
+ name,
802
+ ClassName: pascal(name),
803
+ // The stub shows this as the conventional table name, so derive it the
804
+ // same way the bound stub does - it is what a model of this name exports.
805
+ tableVariable: plural(camelCase(name))
806
+ });
807
+ const available = await listModels(root);
808
+ console.log(`No model '${name}' in module ${moduleName}, so this is a standalone seeder: it imports no model and writes nothing until you fill it in.`);
809
+ console.log(
810
+ available.length
811
+ ? ` Models in this module: ${available.join(", ")}. Run module:make-seeder ${moduleName} <one of these> for a seeder with the model already imported.`
812
+ : ` This module has no models yet. Create one with: bun maker module:make-model ${moduleName} ${name}`
813
+ );
753
814
  }
754
815
 
755
- const seeder = await namedSeederTemplate(moduleName, modelName, name);
756
816
  await writeFileSafe(path.join(root, `database/seeders/${name}.ts`), seeder, flags, "Seeder file");
757
817
 
758
818
  console.log(`Seeder ready: ${moduleName}/${name}`);
759
819
  }
760
820
 
821
+ /** The model a seeder binds to: the requested one, else the module's own. Null when neither exists. */
822
+ async function existingModelName(root, name, leaf) {
823
+ for (const candidate of [name, leaf]) {
824
+ if (await pathExists(path.join(root, "database", "models", `${candidate}.ts`))) return candidate;
825
+ }
826
+ return null;
827
+ }
828
+
829
+ /** Model file names in a module, without the extension. Empty when there are none. */
830
+ async function listModels(root) {
831
+ try {
832
+ const entries = await fs.readdir(path.join(root, "database", "models"));
833
+ return entries.filter((f) => f.endsWith(".ts")).map((f) => f.slice(0, -3));
834
+ } catch {
835
+ return [];
836
+ }
837
+ }
838
+
761
839
  /** Generate a job file for an existing module. */
762
840
  export async function makeJob(rawModule, rawNameOrFlag, extraFlags = []) {
763
841
  const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
@@ -120,7 +120,7 @@ function serveRedisUnavailablePage(port, endpoint) {
120
120
  </html>
121
121
  `;
122
122
 
123
- return new Promise((_resolve, reject) => {
123
+ return new Promise((resolve, reject) => {
124
124
  const server = http.createServer((_req, res) => {
125
125
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
126
126
  res.end(body);
@@ -498,24 +498,42 @@ export async function runVite(configPath) {
498
498
  await runCommand(localBin("vite"), ["--config", configPath]);
499
499
  }
500
500
 
501
- /** Run Vitest tests (backend). */
501
+ /**
502
+ * Strip the argument separator npm inserts.
503
+ *
504
+ * `npm run maker test -- posts` reaches us as `test posts`, but
505
+ * `npm run maker test -- -- posts` reaches us as `test -- posts`, because npm
506
+ * eats one separator and forwards the other. Vitest reads a bare `--` as a
507
+ * positional filter that matches no test file, which silently runs the whole
508
+ * suite instead of the one you asked for - so the command appears to work while
509
+ * doing something else. Dropping a leading `--` makes both spellings behave the
510
+ * same, and makes the passthrough behave identically across package managers.
511
+ */
512
+ function vitestArgs(rawArgs) {
513
+ return rawArgs[0] === "--" ? rawArgs.slice(1) : rawArgs;
514
+ }
515
+
516
+ /** Run Vitest tests once (backend). */
502
517
  export async function runTest(rawArgs = []) {
503
- await runCommand(localBin("vitest"), rawArgs);
518
+ // "run" is required. Bare `vitest` is watch mode, which never exits - so
519
+ // without this, `maker test` hung in any interactive terminal while CI and
520
+ // piped runs exited normally, and the two looked identical from a log.
521
+ await runCommand(localBin("vitest"), ["run", ...vitestArgs(rawArgs)]);
504
522
  }
505
523
 
506
524
  /** Run Vitest in watch mode (backend). */
507
525
  export async function runTestWatch(rawArgs = []) {
508
- await runCommand(localBin("vitest"), rawArgs);
526
+ await runCommand(localBin("vitest"), vitestArgs(rawArgs));
509
527
  }
510
528
 
511
- /** Run Vitest with coverage (backend). */
529
+ /** Run Vitest once with coverage (backend). */
512
530
  export async function runTestCoverage(rawArgs = []) {
513
- await runCommand(localBin("vitest"), ["--coverage", ...rawArgs]);
531
+ await runCommand(localBin("vitest"), ["run", "--coverage", ...vitestArgs(rawArgs)]);
514
532
  }
515
533
 
516
534
  /** Run Vitest in UI mode (backend). */
517
535
  export async function runTestUI(rawArgs = []) {
518
- await runCommand(localBin("vitest"), ["--ui", ...rawArgs]);
536
+ await runCommand(localBin("vitest"), ["--ui", ...vitestArgs(rawArgs)]);
519
537
  }
520
538
 
521
539
  /** Clear Vite cache directories. */
@@ -540,3 +558,4 @@ export async function clearViteCache() {
540
558
 
541
559
  console.log("Vite cache cleared.");
542
560
  }
561
+
@@ -1,104 +1,120 @@
1
- import { clearViteCache, runRuntime, runTest, runTestCoverage, runTestUI, runTestWatch, runUi, runVite } from "./core.mjs";
2
-
3
- /** Register runtime commands (dev, serve, queue, schedule, UI tools) on the CLI program. */
4
- export function registerRuntimeCommands(program, rawArgs) {
5
- program
6
- .command("dev")
7
- .description("Start API + Vue 3 UI plus optional workers/tools")
8
- .option("--view <tools>", "Comma-separated UI tools to launch (redis,maildev,studio)")
9
- .option("--viewer <tools>", "Alias for --view (npm expands --view to --viewer)")
10
- .option("--with <tools>", "Alias for --view")
11
- .option("--with-redis-view", "Launch Redis Commander")
12
- .option("--with-maildev", "Launch MailDev")
13
- .option("--with-db-studio", "Launch Drizzle Studio")
14
- .allowUnknownOption(true)
15
- .action(async () => runRuntime("dev", rawArgs));
16
- program
17
- .command("serve")
18
- .description("Start HTTP server (dist first if built, else src)")
19
- .option("--prod", "Run production build")
20
- .option("--runtime <name>", "Runtime: node or bun", "node")
21
- .option("--watch", "Watch for file changes (src mode)")
22
- .option("--src", "Force source mode even if dist exists")
23
- .allowUnknownOption(true)
24
- .action(async () => runRuntime("serve", rawArgs));
25
- program
26
- .command("queue:work")
27
- .description("Start queue worker (dist first if built, else src)")
28
- .option("--queue <name>", "Queue name", "default")
29
- .option("--prod", "Run production build")
30
- .option("--runtime <name>", "Runtime: node or bun", "node")
31
- .option("--src", "Force source mode even if dist exists")
32
- .allowUnknownOption(true)
33
- .action(async () => runRuntime("queue:work", rawArgs));
34
- program
35
- .command("queue:clear")
36
- .description("Clear all queue keys")
37
- .allowUnknownOption(true)
38
- .action(async () => runRuntime("queue:clear", rawArgs));
39
- program
40
- .command("schedule:work")
41
- .description("Start scheduler worker (dist first if built, else src)")
42
- .option("--prod", "Run production build")
43
- .option("--runtime <name>", "Runtime: node or bun", "node")
44
- .option("--src", "Force source mode even if dist exists")
45
- .allowUnknownOption(true)
46
- .action(async () => runRuntime("schedule:work", rawArgs));
47
- program
48
- .command("maildev:view")
49
- .description("Start MailDev SMTP and web UI")
50
- .allowUnknownOption(true)
51
- .action(async () => runUi("maildev:view"));
52
- program
53
- .command("redis:view")
54
- .description("Start Redis Commander UI")
55
- .allowUnknownOption(true)
56
- .action(async () => runUi("redis:view"));
57
- program
58
- .command("ui:dev")
59
- .description("Start Vue 3 UI from src/resources")
60
- .allowUnknownOption(true)
61
- .action(async () => runVite("src/resources/vite.config.ts"));
62
- program
63
- .command("admin:dev")
64
- .description("Start admin UI dev server")
65
- .allowUnknownOption(true)
66
- .action(async () => runVite("src/resources/vite.config.ts"));
67
- program
68
- .command("vite:cache:clear")
69
- .description("Clear Vite cache folders (cross-platform)")
70
- .allowUnknownOption(true)
71
- .action(async () => clearViteCache());
72
- program
73
- .command("test")
74
- .description("Run Vitest backend tests")
75
- .allowUnknownOption(true)
76
- .action(async () => {
77
- const args = process.argv.slice(3);
78
- await runTest(args);
79
- });
80
- program
81
- .command("test:watch")
82
- .description("Run Vitest backend tests in watch mode")
83
- .allowUnknownOption(true)
84
- .action(async () => {
85
- const args = process.argv.slice(3);
86
- await runTestWatch(args);
87
- });
88
- program
89
- .command("test:coverage")
90
- .description("Run Vitest backend tests with code coverage")
91
- .allowUnknownOption(true)
92
- .action(async () => {
93
- const args = process.argv.slice(3);
94
- await runTestCoverage(args);
95
- });
96
- program
97
- .command("test:ui")
98
- .description("Run Vitest backend tests in UI mode")
99
- .allowUnknownOption(true)
100
- .action(async () => {
101
- const args = process.argv.slice(3);
102
- await runTestUI(args);
103
- });
104
- }
1
+ import { clearViteCache, runRuntime, runTest, runTestCoverage, runTestUI, runTestWatch, runUi, runVite } from "./core.mjs";
2
+ import { initProjectCommands, PROJECT_COMMANDS_PATH } from "../utils/project-commands.mjs";
3
+
4
+ /** Register runtime commands (dev, serve, queue, schedule, UI tools) on the CLI program. */
5
+ export function registerRuntimeCommands(program, rawArgs) {
6
+ program
7
+ .command("dev")
8
+ .description("Start API + Vue 3 UI plus optional workers/tools")
9
+ .option("--view <tools>", "Comma-separated UI tools to launch (redis,maildev,studio)")
10
+ .option("--viewer <tools>", "Alias for --view (npm expands --view to --viewer)")
11
+ .option("--with <tools>", "Alias for --view")
12
+ .option("--with-redis-view", "Launch Redis Commander")
13
+ .option("--with-maildev", "Launch MailDev")
14
+ .option("--with-db-studio", "Launch Drizzle Studio")
15
+ .allowUnknownOption(true)
16
+ .action(async () => runRuntime("dev", rawArgs));
17
+ program
18
+ .command("serve")
19
+ .description("Start HTTP server (dist first if built, else src)")
20
+ .option("--prod", "Run production build")
21
+ .option("--runtime <name>", "Runtime: node or bun", "node")
22
+ .option("--watch", "Watch for file changes (src mode)")
23
+ .option("--src", "Force source mode even if dist exists")
24
+ .allowUnknownOption(true)
25
+ .action(async () => runRuntime("serve", rawArgs));
26
+ program
27
+ .command("queue:work")
28
+ .description("Start queue worker (dist first if built, else src)")
29
+ .option("--queue <name>", "Queue name", "default")
30
+ .option("--prod", "Run production build")
31
+ .option("--runtime <name>", "Runtime: node or bun", "node")
32
+ .option("--src", "Force source mode even if dist exists")
33
+ .allowUnknownOption(true)
34
+ .action(async () => runRuntime("queue:work", rawArgs));
35
+ program
36
+ .command("queue:clear")
37
+ .description("Clear all queue keys")
38
+ .allowUnknownOption(true)
39
+ .action(async () => runRuntime("queue:clear", rawArgs));
40
+ program
41
+ .command("schedule:work")
42
+ .description("Start scheduler worker (dist first if built, else src)")
43
+ .option("--prod", "Run production build")
44
+ .option("--runtime <name>", "Runtime: node or bun", "node")
45
+ .option("--src", "Force source mode even if dist exists")
46
+ .allowUnknownOption(true)
47
+ .action(async () => runRuntime("schedule:work", rawArgs));
48
+ program
49
+ .command("maildev:view")
50
+ .description("Start MailDev SMTP and web UI")
51
+ .allowUnknownOption(true)
52
+ .action(async () => runUi("maildev:view"));
53
+ program
54
+ .command("redis:view")
55
+ .description("Start Redis Commander UI")
56
+ .allowUnknownOption(true)
57
+ .action(async () => runUi("redis:view"));
58
+ program
59
+ .command("ui:dev")
60
+ .description("Start Vue 3 UI from src/resources")
61
+ .allowUnknownOption(true)
62
+ .action(async () => runVite("src/resources/vite.config.ts"));
63
+ program
64
+ .command("admin:dev")
65
+ .description("Start admin UI dev server")
66
+ .allowUnknownOption(true)
67
+ .action(async () => runVite("src/resources/vite.config.ts"));
68
+ program
69
+ .command("vite:cache:clear")
70
+ .description("Clear Vite cache folders (cross-platform)")
71
+ .allowUnknownOption(true)
72
+ .action(async () => clearViteCache());
73
+ program
74
+ .command("maker:init")
75
+ .description(`Create ${PROJECT_COMMANDS_PATH} for your own commands`)
76
+ .option("--force", "Overwrite an existing file")
77
+ .allowUnknownOption(true)
78
+ .action(async (options) => {
79
+ const { created, overwritten } = await initProjectCommands(process.cwd(), { force: Boolean(options?.force) });
80
+ // created = written into a new file; overwritten = replaced under --force.
81
+ // Neither means the file was already there and was left alone.
82
+ console.log(
83
+ created || overwritten
84
+ ? `Add your commands to ${PROJECT_COMMANDS_PATH}, then run: maker --help`
85
+ : `${PROJECT_COMMANDS_PATH} already exists and was left alone. Edit it to add commands, or re-run with --force to replace it.`
86
+ );
87
+ });
88
+ program
89
+ .command("test")
90
+ .description("Run Vitest backend tests")
91
+ .allowUnknownOption(true)
92
+ .action(async () => {
93
+ const args = process.argv.slice(3);
94
+ await runTest(args);
95
+ });
96
+ program
97
+ .command("test:watch")
98
+ .description("Run Vitest backend tests in watch mode")
99
+ .allowUnknownOption(true)
100
+ .action(async () => {
101
+ const args = process.argv.slice(3);
102
+ await runTestWatch(args);
103
+ });
104
+ program
105
+ .command("test:coverage")
106
+ .description("Run Vitest backend tests with code coverage")
107
+ .allowUnknownOption(true)
108
+ .action(async () => {
109
+ const args = process.argv.slice(3);
110
+ await runTestCoverage(args);
111
+ });
112
+ program
113
+ .command("test:ui")
114
+ .description("Run Vitest backend tests in UI mode")
115
+ .allowUnknownOption(true)
116
+ .action(async () => {
117
+ const args = process.argv.slice(3);
118
+ await runTestUI(args);
119
+ });
120
+ }