bazis 0.105.0 → 0.105.2

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/cli/main.ts CHANGED
@@ -6,7 +6,7 @@ import { parseCliArgs, type ParsedGenerateArgs } from "./parseCli";
6
6
  import { runCodegen, type CodegenOptions } from "./codegen";
7
7
  import { generateProject } from "./generateProject";
8
8
  import { runBuild, runDev, runTest } from "./build";
9
- import { commandFailed, commandHeader, createScreens, lineOutput, type CliOutput } from "./ui";
9
+ import { bazisVersion, commandFailed, commandHeader, createScreens, lineOutput, requiredBun, type CliOutput } from "./ui";
10
10
 
11
11
  export const USAGE = `Usage:
12
12
  bazis new <Name> [options] Create a new application project
@@ -21,6 +21,7 @@ export const USAGE = `Usage:
21
21
  bazis build --bin [--outfile <path>] Also compile a standalone executable (default bin/<name>)
22
22
  bazis build --bin --dotenv The executable also reads .env files from its working directory
23
23
  bazis --help
24
+ bazis --version Print the bazis version (also -v)
24
25
  g can also be written as generate.
25
26
  --lang <en|ru> sets the output language (also BAZIS_LANG, "language" in bazis.config.json).
26
27
 
@@ -72,6 +73,8 @@ export interface CliRuntime {
72
73
  readonly stderr?: ScreenOutput;
73
74
  /** Output language when `--lang` is not given; detected from the environment otherwise. */
74
75
  readonly language?: Language;
76
+ /** The running Bun: version and executable (default: this process). */
77
+ readonly bun?: { readonly version: string; readonly path: string };
75
78
  }
76
79
 
77
80
  const defaultRuntime: CliRuntime = {
@@ -107,6 +110,18 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
107
110
  runtime.log(USAGE.trim());
108
111
  return parsed.help ? 0 : 1;
109
112
  }
113
+ if (parsed.kind === "version") {
114
+ runtime.log(bazisVersion());
115
+ return 0;
116
+ }
117
+ // `#!/usr/bin/env bun` runs the CLI on the first bun in PATH, which may be
118
+ // older than the one the user called; stop before it fails somewhere later.
119
+ const bun = runtime.bun ?? { version: Bun.version, path: process.execPath };
120
+ const required = requiredBun();
121
+ if (required !== undefined && !Bun.semver.satisfies(bun.version, required)) {
122
+ const t = ui.err.t.errors;
123
+ return commandFailed(ui.err, ui.err.t.failure.command, [{ tag: "BUN", where: bun.path, cause: t.bunTooOld(required, bun.version), fixes: [t.bunWhich(bun.path), t.bunUpgrade] }], 1);
124
+ }
110
125
  if (parsed.kind === "error") {
111
126
  return commandFailed(ui.err, ui.err.t.failure.command, [{ tag: "CLI", where: "bazis", cause: parsed.message, fixes: [ui.err.t.errors.unknown] }], 1);
112
127
  }
package/cli/parseCli.ts CHANGED
@@ -20,6 +20,7 @@ export interface ParsedGenerateArgs {
20
20
 
21
21
  export type ParseCliResult =
22
22
  | { readonly kind: "help"; readonly help: boolean }
23
+ | { readonly kind: "version" }
23
24
  | { readonly kind: "new"; readonly name: string; readonly outputPath?: string; readonly frameworkPath?: string; readonly linkFramework: boolean; readonly vendor: boolean; readonly dryRun: boolean }
24
25
  | { readonly kind: "generate"; readonly args: ParsedGenerateArgs }
25
26
  | { readonly kind: "codegen"; readonly target?: string }
@@ -38,6 +39,7 @@ const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterpris
38
39
  /** Parsing is pure: help and invalid input can never start generation. */
39
40
  export function parseCliArgs(argv: readonly string[]): ParseCliResult {
40
41
  if (argv.some((arg) => arg === "--help" || arg === "-h")) return { kind: "help", help: true };
42
+ if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) return { kind: "version" };
41
43
  if (argv.length === 0) return { kind: "help", help: false };
42
44
  // Everything after `test` (or `test --`) belongs to `bun test`.
43
45
  if (argv[0] === "test") return { kind: "test", args: argv[1] === "--" ? argv.slice(2) : argv.slice(1) };
package/cli/ui.ts CHANGED
@@ -37,18 +37,28 @@ export function projectInfo(cwd: string): { name?: string; version?: string } {
37
37
  }
38
38
  }
39
39
 
40
- /** Version of the bazis package this CLI belongs to. */
41
- export function bazisVersion(): string {
40
+ function bazisPackage(): { version?: unknown; engines?: { bun?: unknown } } | undefined {
42
41
  try {
43
42
  const file = fileURLToPath(new URL("../package.json", import.meta.url));
44
- if (!existsSync(file)) return "?";
45
- const version = (JSON.parse(readFileSync(file, "utf8")) as { version?: unknown }).version;
46
- return typeof version === "string" ? version : "?";
43
+ if (!existsSync(file)) return undefined;
44
+ return JSON.parse(readFileSync(file, "utf8")) as { version?: unknown; engines?: { bun?: unknown } };
47
45
  } catch {
48
- return "?";
46
+ return undefined;
49
47
  }
50
48
  }
51
49
 
50
+ /** Version of the bazis package this CLI belongs to. */
51
+ export function bazisVersion(): string {
52
+ const version = bazisPackage()?.version;
53
+ return typeof version === "string" ? version : "?";
54
+ }
55
+
56
+ /** The Bun range the package declares in `engines` (`>=1.4.0`). */
57
+ export function requiredBun(): string | undefined {
58
+ const range = bazisPackage()?.engines?.bun;
59
+ return typeof range === "string" ? range : undefined;
60
+ }
61
+
52
62
  /** The logo with the command badge: project, versions, and the command's own facts. */
53
63
  export function commandHeader(screen: Screen, mode: keyof typeof MODE, cwd: string, facts: readonly string[], project = projectInfo(cwd)): void {
54
64
  screen.header(mode, MODE[mode], [
@@ -10,9 +10,18 @@ export function registerNamedDependencyHint(hint: NamedDependencyHint): void {
10
10
  hints.push(hint);
11
11
  }
12
12
 
13
+ function hintFor(name: string): string {
14
+ const hint = hints.map((item) => item(name)).find((text) => text !== undefined);
15
+ return hint ? ` ${hint}` : "";
16
+ }
17
+
18
+ /** The graph-validation issue for a named dependency nobody registers, naming the service that needs it. */
19
+ export function missingNamedDependencyIssue(name: string, requester: string): string {
20
+ return `Missing dependency "${name}" for "${requester}".${hintFor(name)}`;
21
+ }
22
+
13
23
  export class NamedDependencyNotFoundError extends DiError {
14
24
  public constructor(name: string) {
15
- const hint = hints.map((item) => item(name)).find((text) => text !== undefined);
16
- super(`No provider token found for named dependency "${name}".${hint ? ` ${hint}` : ""}`);
25
+ super(`No provider token found for named dependency "${name}".${hintFor(name)}`);
17
26
  }
18
27
  }
@@ -1,4 +1,5 @@
1
1
  import { ServiceValidationError } from "../errors";
2
+ import { missingNamedDependencyIssue } from "../errors/NamedDependencyNotFoundError";
2
3
  import { isLazyDependency, isNamedDependency, isOptionalDependency } from "../provider";
3
4
  import type {
4
5
  KeyedDependency,
@@ -97,7 +98,8 @@ export class GraphValidator {
97
98
  const isOptional = isOptionalDependency(dependency);
98
99
  const inner = isLazy || isOptional ? dependency.inner : dependency;
99
100
  // An optional dependency on a name nothing registered is simply absent.
100
- if (isOptional && isNamedDependency(inner) && !this.context.hasName(inner.name)) {
101
+ if (isNamedDependency(inner) && !this.context.hasName(inner.name)) {
102
+ if (!isOptional) issues.add(missingNamedDependencyIssue(inner.name, tokenToDebugName(registration.token)));
101
103
  continue;
102
104
  }
103
105
  const descriptor = this.context.describeDependency(inner);
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.105.0" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.105.2" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -7,6 +7,16 @@ import { ENV_PREFIX } from "./sources";
7
7
  import { isSensitiveKey, redactSensitiveText } from "../../../library/redaction";
8
8
  import { ConfigValidationError, type ConfigIssue, type ConfigIssueKind } from "./ConfigValidationError";
9
9
  import { callerFile } from "./callerFile";
10
+ import { registerNamedDependencyHint } from "../../di/errors/NamedDependencyNotFoundError";
11
+
12
+ // `ConfigView<T>` is injected by the name of its config; a config that no
13
+ // module declares has no provider, and the start says where to declare it.
14
+ registerNamedDependencyHint((name) => {
15
+ const match = /^Config:(.+)$/.exec(name);
16
+ if (!match) return undefined;
17
+ const declaration = match[1] === "default" ? "defineConfig({ … })" : `defineConfig("${match[1]}", …)`;
18
+ return `The config ${declaration} is not declared in the application: add it to \`config\` of the module that uses it, or to runApp({ config }).`;
19
+ });
10
20
 
11
21
  type Stand = "development" | "test" | "production";
12
22
  type Primitive = string | number | boolean;
@@ -238,6 +238,9 @@ const en = {
238
238
  },
239
239
  errors: {
240
240
  unknown: "Use bazis --help to list commands and options.",
241
+ bunTooOld: (required: string, actual: string) => `bazis needs Bun ${required.startsWith(">=") ? `${required.slice(2).trim()} or newer` : required}; this is Bun ${actual}.`,
242
+ bunWhich: (file: string) => `This Bun is ${file}. bunx bazis and bun run scripts start bazis with the first bun in PATH, not with the Bun that called them.`,
243
+ bunUpgrade: "Update that Bun: bun upgrade. Or put the newer Bun first in PATH.",
241
244
  },
242
245
  };
243
246
 
@@ -397,6 +400,9 @@ const ru: Messages = {
397
400
  },
398
401
  errors: {
399
402
  unknown: "Список команд и опций: bazis --help.",
403
+ bunTooOld: (required, actual) => `bazis нужен Bun ${required.startsWith(">=") ? `${required.slice(2).trim()} или новее` : required}, а запущен Bun ${actual}.`,
404
+ bunWhich: (file) => `Запущен ${file}. bunx bazis и скрипты bun run запускают bazis первым bun из PATH, а не тем Bun, который их вызвал.`,
405
+ bunUpgrade: "Обновите этот Bun: bun upgrade. Или поставьте новый Bun первым в PATH.",
400
406
  },
401
407
  };
402
408
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.105.0",
3
+ "version": "0.105.2",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",