@basaltkit/cli 1.2.5 → 1.3.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
@@ -94,9 +94,15 @@ Without registering anything, `runCli` always provides:
94
94
  | `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`) |
95
95
  | `basalt schedule:list` | Lists scheduled tasks and their cron expressions (read from the `schedule:entries` bucket, populated by `@basaltkit/scheduler`) |
96
96
  | `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`. |
97
- | `basalt upgrade [--dry] [--only=<id>]` | Applies framework upgrade codemods (ships the `@machize/*` → `@basaltkit/*` scope rename; `--dry` previews) |
97
+ | `basalt upgrade [--dry] [--only=<id>] [--dir=<path>]` | Applies framework upgrade codemods (ships the `@machize/*` → `@basaltkit/*` scope rename; `--dry` previews). `create-basalt update` runs them for you after an install. `nodeUpgradeFs(baseDir)` resolves relative paths against the directory being upgraded, so `--dir=` reads the tree it lists |
98
98
  | `basalt publish [<id>] [--force]` | Copies a bundled stub group into the app — `dockerfile` (plus a `.dockerignore` keeping `.env` and keys out of the image), `ci`, `editorconfig` (run with no id to list) |
99
99
 
100
+ Not served by `runCli`: the **project commands** `update`, `add`, `doctor` and
101
+ `info` belong to `create-basalt` — the scaffolded `bin/basalt.ts` forwards them
102
+ before booting the app (so `pnpm basalt update` works even when the app does
103
+ not boot). On an older `bin/basalt.ts` they hit `Unknown command`, followed by a
104
+ hint to run `npx create-basalt@latest <command>`.
105
+
100
106
  If you also install `@basaltkit/generator`, you gain the `make:*` commands. Feature
101
107
  plugins register their own: `queue:work|stats|retry|jobs` (`@basaltkit/queue`),
102
108
  `tenant:list|create|migrate|seed|run` (`@basaltkit/tenancy`), `generate:docs`
@@ -243,7 +249,7 @@ The definitions of the built-in `routes` and `schedule:list` commands. *(Advance
243
249
  ## Common errors and solutions (FAQ)
244
250
 
245
251
  **`Unknown command "x". Run "basalt list" to see what is available.`**
246
- The command isn't registered. Confirm you passed it inside `commandsPlugin([...])` and that plugin is in the `plugins` list of `createApp`. Run `basalt list` to see what's available.
252
+ The command isn't registered. Confirm you passed it inside `commandsPlugin([...])` and that plugin is in the `plugins` list of `createApp`. Run `basalt list` to see what's available. For `update`, `add`, `doctor` or `info`, see the second line of the error: those are create-basalt's project commands, and `npx create-basalt@latest update` teaches an old `bin/basalt.ts` to forward them.
247
253
 
248
254
  **My command runs but the `--step 2` flag doesn't work.**
249
255
  The parser only recognizes the equals-sign form: `--step=2`. Written with a space, `2` is treated as a positional argument (it shows up in `args`).
@@ -263,4 +269,4 @@ Return a number from `handle` (for example `return 3`). `runCli` propagates it;
263
269
  - **`@basaltkit/generator`** — provides `generatorCommands()`, a list of `make:*` commands (code generators) ready to pass to `commandsPlugin`. That's how `basalt make:resource` shows up.
264
270
  - **`@basaltkit/fastify`** — writes routes into the `http:routes` bucket, which the built-in `routes` command reads.
265
271
  - **`@basaltkit/scheduler`** — writes tasks into the `schedule:entries` bucket, which `schedule:list` reads.
266
- - **`create-basalt`** — with the `--cli` flag, the project generator creates `bin/basalt.ts`, the `pnpm basalt` script, and registers `commandsPlugin(generatorCommands())` for you.
272
+ - **`create-basalt`** — with the `--cli` flag (or later, `create-basalt add cli`), the project generator creates `bin/basalt.ts`, the `pnpm basalt` script, and registers `commandsPlugin(generatorCommands())` for you. It also provides the project commands `bin/basalt.ts` forwards: `update`, `add`, `doctor`, `info`.
package/dist/runner.js CHANGED
@@ -2,6 +2,8 @@ import { ensureMetadata } from '@basaltkit/core';
2
2
  import { builtinCommands } from './builtins.js';
3
3
  import { consoleIo } from './io.js';
4
4
  import { parseArgv } from './parse.js';
5
+ /** Commands create-basalt serves (bin/basalt.ts forwards them before booting the app). */
6
+ const PROJECT_COMMANDS = ['update', 'add', 'doctor', 'info'];
5
7
  /**
6
8
  * Boots the app, resolves the command (built-ins + everything registered in
7
9
  * the 'commands' metadata bucket), runs it and shuts the app down.
@@ -25,6 +27,11 @@ export async function runCli(options) {
25
27
  const found = commands.find((candidate) => candidate.name === command);
26
28
  if (!found) {
27
29
  io.error(`Unknown command "${command}". Run "basalt list" to see what is available.`);
30
+ if (PROJECT_COMMANDS.includes(command)) {
31
+ // A bin/basalt.ts generated before the project commands existed: they
32
+ // live in create-basalt, which also patches this file.
33
+ io.error(`"${command}" is a project command of create-basalt: run \`npx create-basalt@latest ${command}\` (its \`update\` also teaches this bin/basalt.ts to forward it).`);
34
+ }
28
35
  return 1;
29
36
  }
30
37
  const code = await found.handle({ app, container: app.container, io, args, flags });
package/dist/upgrade.d.ts CHANGED
@@ -42,7 +42,15 @@ export declare function runUpgrade(migrations: Migration[], fs: UpgradeFs, optio
42
42
  dry?: boolean;
43
43
  only?: string;
44
44
  }): Promise<UpgradeReport[]>;
45
- /** Node-backed {@link UpgradeFs}. Recursive, skipping build/vcs directories. */
46
- export declare function nodeUpgradeFs(): UpgradeFs;
45
+ /**
46
+ * Node-backed {@link UpgradeFs}. Recursive, skipping build/vcs directories.
47
+ *
48
+ * `list(dir)` returns paths relative to `dir`; `read`/`write` resolve a relative
49
+ * path against `baseDir` — pass the same directory you upgrade (default: the
50
+ * current working directory). Before `baseDir` existed, relative paths always
51
+ * resolved against `process.cwd()`, so `basalt upgrade --dir=<other>` listed
52
+ * one tree and read another.
53
+ */
54
+ export declare function nodeUpgradeFs(baseDir?: string): UpgradeFs;
47
55
  /** `basalt upgrade [--dir=<path>] [--dry] [--only=<migration-id>]` */
48
56
  export declare const upgradeCommand: CommandDefinition;
package/dist/upgrade.js CHANGED
@@ -40,13 +40,26 @@ export async function runUpgrade(migrations, fs, options) {
40
40
  }
41
41
  return reports;
42
42
  }
43
- /** Node-backed {@link UpgradeFs}. Recursive, skipping build/vcs directories. */
44
- export function nodeUpgradeFs() {
43
+ /**
44
+ * Node-backed {@link UpgradeFs}. Recursive, skipping build/vcs directories.
45
+ *
46
+ * `list(dir)` returns paths relative to `dir`; `read`/`write` resolve a relative
47
+ * path against `baseDir` — pass the same directory you upgrade (default: the
48
+ * current working directory). Before `baseDir` existed, relative paths always
49
+ * resolved against `process.cwd()`, so `basalt upgrade --dir=<other>` listed
50
+ * one tree and read another.
51
+ */
52
+ export function nodeUpgradeFs(baseDir) {
45
53
  const SKIP = new Set(['node_modules', 'dist', '.git', '.next', 'build', 'coverage']);
54
+ const at = async (path) => {
55
+ const { isAbsolute, resolve } = await import('node:path');
56
+ return isAbsolute(path) ? path : resolve(baseDir ?? process.cwd(), path);
57
+ };
46
58
  return {
47
59
  async list(dir) {
48
60
  const { readdir } = await import('node:fs/promises');
49
61
  const { join, relative } = await import('node:path');
62
+ const root = await at(dir);
50
63
  const out = [];
51
64
  const walk = async (current) => {
52
65
  const entries = await readdir(current, { withFileTypes: true });
@@ -56,22 +69,20 @@ export function nodeUpgradeFs() {
56
69
  await walk(join(current, entry.name));
57
70
  }
58
71
  else {
59
- out.push(relative(dir, join(current, entry.name)));
72
+ out.push(relative(root, join(current, entry.name)));
60
73
  }
61
74
  }
62
75
  };
63
- await walk(dir);
76
+ await walk(root);
64
77
  return out;
65
78
  },
66
79
  async read(path) {
67
80
  const { readFile } = await import('node:fs/promises');
68
- const { isAbsolute, join } = await import('node:path');
69
- return readFile(isAbsolute(path) ? path : join(process.cwd(), path), 'utf8');
81
+ return readFile(await at(path), 'utf8');
70
82
  },
71
83
  async write(path, content) {
72
84
  const { writeFile } = await import('node:fs/promises');
73
- const { isAbsolute, join } = await import('node:path');
74
- await writeFile(isAbsolute(path) ? path : join(process.cwd(), path), content, 'utf8');
85
+ await writeFile(await at(path), content, 'utf8');
75
86
  },
76
87
  };
77
88
  }
@@ -80,14 +91,15 @@ export const upgradeCommand = defineCommand({
80
91
  name: 'upgrade',
81
92
  description: 'Apply framework upgrade codemods (--dry to preview, --only=<id>)',
82
93
  async handle({ io, flags }) {
83
- const dir = typeof flags['dir'] === 'string' ? flags['dir'] : process.cwd();
94
+ const { resolve } = await import('node:path');
95
+ const dir = resolve(typeof flags['dir'] === 'string' ? flags['dir'] : process.cwd());
84
96
  const dry = flags['dry'] === true;
85
97
  const only = typeof flags['only'] === 'string' ? flags['only'] : undefined;
86
98
  if (only && !MIGRATIONS.some((m) => m.id === only)) {
87
99
  io.error(`Unknown migration "${only}". Available: ${MIGRATIONS.map((m) => m.id).join(', ')}.`);
88
100
  return 1;
89
101
  }
90
- const reports = await runUpgrade(MIGRATIONS, nodeUpgradeFs(), {
102
+ const reports = await runUpgrade(MIGRATIONS, nodeUpgradeFs(dir), {
91
103
  dir,
92
104
  dry,
93
105
  ...(only ? { only } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/cli",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "engines": {
5
5
  "node": ">=22.5.0"
6
6
  },
@@ -18,12 +18,12 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
- "@basaltkit/core": "^1.3.1"
21
+ "@basaltkit/core": "^1.5.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/node": "^26.5.1",
24
+ "@types/node": "^26.6.1",
25
25
  "typescript": "^7.0.2",
26
- "vitest": "^5.0.0",
26
+ "vitest": "^5.0.1",
27
27
  "@basaltkit/tsconfig": "^0.24.0"
28
28
  },
29
29
  "publishConfig": {