@basaltkit/cli 1.1.0 → 1.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.
package/README.md CHANGED
@@ -87,7 +87,7 @@ Without registering anything, `runCli` always provides:
87
87
  | `basalt list` (or `basalt` with no arguments) | Lists all available commands, with their descriptions |
88
88
  | `basalt routes` | Lists the HTTP routes registered by the application (read from the `http:routes` metadata bucket, populated by HTTP adapters such as `@basaltkit/fastify`) |
89
89
  | `basalt schedule:list` | Lists scheduled tasks and their cron expressions (read from the `schedule:entries` bucket, populated by `@basaltkit/scheduler`) |
90
- | `basalt dev [--entry=<file>]` | Runs the app with file watching + auto-restart (delegates to `tsx watch` when available, otherwise `node --watch`) |
90
+ | `basalt dev [--entry] [--worker] [--queue] [--no-routes]` | Runs the app with watch + restart, **prints the route table on boot**, and with `--worker` also starts a watched `queue:work` alongside it (server + worker in one command). Delegates watching to `tsx watch` / `node --watch`. |
91
91
  | `basalt upgrade [--dry] [--only=<id>]` | Applies framework upgrade codemods (ships the `@machize/*` → `@basaltkit/*` scope rename; `--dry` previews) |
92
92
  | `basalt publish [<id>] [--force]` | Copies a bundled stub group into the app — `dockerfile`, `ci`, `editorconfig` (run with no id to list) |
93
93
 
package/dist/index.d.ts CHANGED
@@ -104,7 +104,31 @@ interface DevRunner {
104
104
  declare function resolveDevRunner(entry: string, options?: {
105
105
  tsx?: boolean;
106
106
  }): DevRunner;
107
- /** `basalt dev [--entry=<file>]` — run the app with watch + restart. */
107
+ /** A route as published into the `http:routes` metadata bucket by an adapter. */
108
+ interface DevRoute {
109
+ method: string;
110
+ url: string;
111
+ meta?: Record<string, unknown>;
112
+ }
113
+ /**
114
+ * Formats the registered routes into printable rows (method, url, and a compact
115
+ * flags column for auth/rate-limit) — sorted by url then method. Pure: this is
116
+ * what `basalt dev` prints on boot, and `pnpm dev` (plain tsx watch) can't.
117
+ */
118
+ declare function devRouteRows(routes: DevRoute[]): {
119
+ method: string;
120
+ url: string;
121
+ flags: string;
122
+ }[];
123
+ /**
124
+ * `basalt dev [--entry=<file>] [--worker] [--queue=<name>] [--no-routes]`
125
+ *
126
+ * A richer dev loop than a bare `tsx watch`: prints the app's **route table** on
127
+ * boot (the app is already booted by the CLI runner, so routes are known), then
128
+ * runs the server with watch/restart — and, with `--worker`, also starts a
129
+ * watched queue worker alongside it (server + worker in one command, the real
130
+ * producer/worker topology).
131
+ */
108
132
  declare const devCommand: CommandDefinition;
109
133
 
110
134
  /**
@@ -190,4 +214,4 @@ declare function nodePublishFs(dir: string): PublishFs;
190
214
  /** `basalt publish [<id>] [--dir=<path>] [--force]` — list, or copy a stub group. */
191
215
  declare const publishCommand: CommandDefinition;
192
216
 
193
- export { type CommandContext, type CommandDefinition, type CommandIo, DEV_ENTRY_CANDIDATES, type DevRunner, type Edit, MIGRATIONS, type Migration, PUBLISHABLES, type ParsedArgv, type PublishFs, type PublishResult, type Publishable, type PublishableFile, type RouteMetadata, type RunCliOptions, type ScheduleMetadata, type UpgradeFs, type UpgradeReport, builtinCommands, commandsPlugin, consoleIo, defineCommand, devCommand, memoryIo, nodePublishFs, nodeUpgradeFs, parseArgv, publishCommand, renameMachizeScope, renderTable, resolveDevEntry, resolveDevRunner, routesCommand, runCli, runPublish, runUpgrade, scheduleListCommand, upgradeCommand };
217
+ export { type CommandContext, type CommandDefinition, type CommandIo, DEV_ENTRY_CANDIDATES, type DevRoute, type DevRunner, type Edit, MIGRATIONS, type Migration, PUBLISHABLES, type ParsedArgv, type PublishFs, type PublishResult, type Publishable, type PublishableFile, type RouteMetadata, type RunCliOptions, type ScheduleMetadata, type UpgradeFs, type UpgradeReport, builtinCommands, commandsPlugin, consoleIo, defineCommand, devCommand, devRouteRows, memoryIo, nodePublishFs, nodeUpgradeFs, parseArgv, publishCommand, renameMachizeScope, renderTable, resolveDevEntry, resolveDevRunner, routesCommand, runCli, runPublish, runUpgrade, scheduleListCommand, upgradeCommand };
package/dist/index.js CHANGED
@@ -4,12 +4,13 @@ function defineCommand(command) {
4
4
  }
5
5
 
6
6
  // src/runner.ts
7
- import { ensureMetadata } from "@basaltkit/core";
7
+ import { ensureMetadata as ensureMetadata2 } from "@basaltkit/core";
8
8
 
9
9
  // src/builtins.ts
10
10
  import { METADATA } from "@basaltkit/core";
11
11
 
12
12
  // src/dev.ts
13
+ import { ensureMetadata } from "@basaltkit/core";
13
14
  var DEV_ENTRY_CANDIDATES = ["src/main.ts", "src/server.ts", "src/index.ts", "src/main.js", "src/index.js"];
14
15
  function resolveDevEntry(candidates, exists) {
15
16
  return candidates.find(exists);
@@ -20,10 +21,20 @@ function resolveDevRunner(entry, options = {}) {
20
21
  if (isTs) return { command: "node", args: ["--watch", "--experimental-strip-types", entry] };
21
22
  return { command: "node", args: ["--watch", entry] };
22
23
  }
24
+ function devRouteRows(routes) {
25
+ return [...routes].sort((a, b) => a.url.localeCompare(b.url) || a.method.localeCompare(b.method)).map((r) => {
26
+ const flags = [];
27
+ if (r.meta?.["auth"] === true) flags.push("auth");
28
+ if (r.meta?.["rateLimit"]) flags.push("rate-limit");
29
+ const tags = r.meta?.["tags"];
30
+ if (Array.isArray(tags) && tags.length) flags.push(...tags.map(String));
31
+ return { method: r.method.toUpperCase(), url: r.url, flags: flags.join(", ") };
32
+ });
33
+ }
23
34
  var devCommand = defineCommand({
24
35
  name: "dev",
25
- description: "Run the app with file watching and auto-restart",
26
- async handle({ io, flags }) {
36
+ description: "Run the app with watch + restart, print the route table, and (--worker) an embedded queue worker",
37
+ async handle({ container, io, flags }) {
27
38
  const { existsSync } = await import("fs");
28
39
  const entry = typeof flags["entry"] === "string" ? flags["entry"] : resolveDevEntry(DEV_ENTRY_CANDIDATES, existsSync);
29
40
  if (!entry) {
@@ -32,22 +43,45 @@ var devCommand = defineCommand({
32
43
  );
33
44
  return 1;
34
45
  }
46
+ if (flags["routes"] !== false) {
47
+ const routes = ensureMetadata(container).get("http:routes");
48
+ if (routes.length > 0) {
49
+ io.log(`Routes (${routes.length}):`);
50
+ io.table(devRouteRows(routes));
51
+ }
52
+ }
35
53
  const tsx = await canResolve("tsx");
36
- const runner = resolveDevRunner(entry, { tsx });
37
- io.log(`Starting: ${runner.command} ${runner.args.join(" ")}`);
54
+ const server = resolveDevRunner(entry, { tsx });
55
+ const runners = [{ label: "server", runner: server }];
56
+ if (flags["worker"] === true || flags["w"] === true) {
57
+ const bin = process.argv[1] ?? entry;
58
+ const workerBase = resolveDevRunner(bin, { tsx });
59
+ const queue = typeof flags["queue"] === "string" ? [`--queue=${flags["queue"]}`] : [];
60
+ runners.push({ label: "worker", runner: { command: workerBase.command, args: [...workerBase.args, "queue:work", ...queue] } });
61
+ }
62
+ for (const { label, runner } of runners) {
63
+ io.log(`\u25B6 ${label}: ${runner.command} ${runner.args.join(" ")}`);
64
+ }
38
65
  const { spawn } = await import("child_process");
39
66
  return await new Promise((resolve) => {
40
- const child = spawn(runner.command, runner.args, { stdio: "inherit" });
41
- const stop = () => child.kill("SIGINT");
67
+ const children = runners.map(({ runner }) => spawn(runner.command, runner.args, { stdio: "inherit" }));
68
+ const stop = () => children.forEach((c) => c.kill("SIGINT"));
42
69
  process.once("SIGINT", stop);
43
- child.on("exit", (code) => {
70
+ let done = false;
71
+ const finish = (code) => {
72
+ if (done) return;
73
+ done = true;
44
74
  process.removeListener("SIGINT", stop);
45
- resolve(code ?? 0);
46
- });
47
- child.on("error", (error) => {
48
- io.error(`Failed to start dev runner: ${error.message}`);
49
- resolve(1);
50
- });
75
+ children.forEach((c) => c.kill("SIGINT"));
76
+ resolve(code);
77
+ };
78
+ children[0]?.on("exit", (code) => finish(code ?? 0));
79
+ children.forEach(
80
+ (child, i) => child.on("error", (error) => {
81
+ io.error(`Failed to start ${runners[i]?.label}: ${error.message}`);
82
+ finish(1);
83
+ })
84
+ );
51
85
  });
52
86
  }
53
87
  });
@@ -361,7 +395,7 @@ async function runCli(options) {
361
395
  const { command, args, flags } = parseArgv(options.argv ?? process.argv.slice(2));
362
396
  if (app.phase === "created") await app.boot();
363
397
  try {
364
- const registered = ensureMetadata(app.container).get("commands");
398
+ const registered = ensureMetadata2(app.container).get("commands");
365
399
  const commands = [...builtinCommands(), ...registered];
366
400
  if (command === void 0 || command === "list") {
367
401
  io.log("Available commands:");
@@ -383,12 +417,12 @@ async function runCli(options) {
383
417
  }
384
418
 
385
419
  // src/plugin.ts
386
- import { definePlugin, ensureMetadata as ensureMetadata2 } from "@basaltkit/core";
420
+ import { definePlugin, ensureMetadata as ensureMetadata3 } from "@basaltkit/core";
387
421
  function commandsPlugin(commands) {
388
422
  return definePlugin({
389
423
  name: "basalt:commands",
390
424
  register({ container }) {
391
- const metadata = ensureMetadata2(container);
425
+ const metadata = ensureMetadata3(container);
392
426
  for (const command of commands) metadata.add("commands", command);
393
427
  }
394
428
  });
@@ -402,6 +436,7 @@ export {
402
436
  consoleIo,
403
437
  defineCommand,
404
438
  devCommand,
439
+ devRouteRows,
405
440
  memoryIo,
406
441
  nodePublishFs,
407
442
  nodeUpgradeFs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "The 'basalt' command framework for Basalt apps: define commands, run them against a booted app, and inspect routes and schedules.",
5
5
  "license": "MIT",
6
6
  "type": "module",