bazis 0.105.1 → 0.105.3
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 +16 -1
- package/cli/parseCli.ts +2 -0
- package/cli/ui.ts +16 -6
- package/core/app/runApp.ts +2 -0
- package/core/di/internal/GraphValidator.ts +19 -5
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/codex/contracts.ts +20 -4
- package/library/terminal/messages.ts +6 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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], [
|
package/core/app/runApp.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../../library/ui";
|
|
22
22
|
import { modelValidatorAdapter } from "../../library/validation";
|
|
23
23
|
import { createLocalize, type AppLanguage, type AppMessages } from "./messages";
|
|
24
|
+
import { useCodexLanguage } from "../infra/connectors/codex/contracts";
|
|
24
25
|
import {
|
|
25
26
|
type RunAppUiOptions,
|
|
26
27
|
type RunAppUiSurfaceOptions,
|
|
@@ -290,6 +291,7 @@ export async function runApp(root: BazisModuleRef, options: RunAppOptions = {}):
|
|
|
290
291
|
export function composeApp(root: BazisModuleRef, options: RunAppOptions = {}): BazisModuleRef {
|
|
291
292
|
// Checked even without HTTP: an unknown language is a startup error.
|
|
292
293
|
const localize = createLocalize(options.language, options.messages);
|
|
294
|
+
useCodexLanguage(options.language ?? "en");
|
|
293
295
|
// Infrastructure and cache are global modules; add them to the graph next to the feature root.
|
|
294
296
|
const globals: BazisModuleRef[] = [];
|
|
295
297
|
if (options.infra !== undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ServiceValidationError } from "../errors";
|
|
2
2
|
import { missingNamedDependencyIssue } from "../errors/NamedDependencyNotFoundError";
|
|
3
|
-
import { isLazyDependency, isNamedDependency, isOptionalDependency } from "../provider";
|
|
3
|
+
import { isClassProvider, isLazyDependency, isNamedDependency, isOptionalDependency } from "../provider";
|
|
4
4
|
import type {
|
|
5
5
|
KeyedDependency,
|
|
6
6
|
NamedDependency,
|
|
@@ -99,7 +99,7 @@ export class GraphValidator {
|
|
|
99
99
|
const inner = isLazy || isOptional ? dependency.inner : dependency;
|
|
100
100
|
// An optional dependency on a name nothing registered is simply absent.
|
|
101
101
|
if (isNamedDependency(inner) && !this.context.hasName(inner.name)) {
|
|
102
|
-
if (!isOptional) issues.add(missingNamedDependencyIssue(inner.name,
|
|
102
|
+
if (!isOptional) issues.add(missingNamedDependencyIssue(inner.name, serviceName(registration)));
|
|
103
103
|
continue;
|
|
104
104
|
}
|
|
105
105
|
const descriptor = this.context.describeDependency(inner);
|
|
@@ -109,15 +109,15 @@ export class GraphValidator {
|
|
|
109
109
|
}
|
|
110
110
|
if (!depRegistration) {
|
|
111
111
|
issues.add(
|
|
112
|
-
`Missing dependency "${tokenToDebugName(descriptor.token)}" for "${
|
|
112
|
+
`Missing dependency "${tokenToDebugName(descriptor.token)}" for "${serviceName(registration)}"`,
|
|
113
113
|
);
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (this.context.validateScopes && rootLifetime === "singleton" && depRegistration.lifetime === "scoped") {
|
|
118
118
|
issues.add(
|
|
119
|
-
`Singleton "${
|
|
120
|
-
+ `A scoped service lives for one request or scope: make "${tokenToDebugName(registration.token)}" scoped too, `
|
|
119
|
+
`Singleton "${serviceName(registration)}" depends on scoped "${tokenToDebugName(descriptor.token)}". `
|
|
120
|
+
+ `A scoped service lives for one request or scope: make "${className(registration) ?? tokenToDebugName(registration.token)}" scoped too, `
|
|
121
121
|
+ `or inject ServiceProvider and resolve "${tokenToDebugName(descriptor.token)}" in a scope you create (provider.createScope()).`,
|
|
122
122
|
);
|
|
123
123
|
}
|
|
@@ -136,3 +136,17 @@ export class GraphValidator {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
/** The class behind a token registration, when it is not the token itself: `singleton(LIFECYCLE_HOOK, WarmUp)`. */
|
|
141
|
+
function className(registration: ServiceRegistration): string | undefined {
|
|
142
|
+
if (!isClassProvider(registration.provider)) return undefined;
|
|
143
|
+
const name = registration.provider.useClass.name;
|
|
144
|
+
return name && name !== tokenToDebugName(registration.token) ? name : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** `WarmUp (as BazisLifecycleHook)` for a class under a token, else the token. */
|
|
148
|
+
function serviceName(registration: ServiceRegistration): string {
|
|
149
|
+
const token = tokenToDebugName(registration.token);
|
|
150
|
+
const name = className(registration);
|
|
151
|
+
return name ? `${name} (as ${token})` : token;
|
|
152
|
+
}
|
|
@@ -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.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.105.3" }, 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");
|
|
@@ -52,7 +52,7 @@ const EN_CODEX_MESSAGES: Readonly<Record<CodexErrorCode, string>> = Object.freez
|
|
|
52
52
|
LOGIN_EXPIRED: "The sign-in window expired. Start signing in again.",
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
/** Russian texts for every code
|
|
55
|
+
/** Russian texts for every code; `runApp({ language: "ru" })` turns them on. */
|
|
56
56
|
export const RU_CODEX_MESSAGES: Readonly<Record<CodexErrorCode, string>> = Object.freeze({
|
|
57
57
|
NOT_CONFIGURED: "Подключение ChatGPT не настроено на сервере.",
|
|
58
58
|
UNAVAILABLE: "Codex App Server недоступен. Проверьте его установку и настройки.",
|
|
@@ -67,15 +67,31 @@ export const RU_CODEX_MESSAGES: Readonly<Record<CodexErrorCode, string>> = Objec
|
|
|
67
67
|
LOGIN_EXPIRED: "Время для входа истекло. Начните вход заново.",
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
const CODEX_LANGUAGES = { en: EN_CODEX_MESSAGES, ru: RU_CODEX_MESSAGES } as const;
|
|
71
|
+
let language: Readonly<Record<CodexErrorCode, string>> = EN_CODEX_MESSAGES;
|
|
72
|
+
let own: Partial<Record<CodexErrorCode, string>> = {};
|
|
70
73
|
let messages: Readonly<Record<CodexErrorCode, string>> = EN_CODEX_MESSAGES;
|
|
71
74
|
|
|
72
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* @internal `runApp({ language })` sets the language of the texts. It is
|
|
77
|
+
* process-wide, like the texts themselves: the last started application wins.
|
|
78
|
+
*/
|
|
79
|
+
export function useCodexLanguage(name: keyof typeof CODEX_LANGUAGES): void {
|
|
80
|
+
language = CODEX_LANGUAGES[name];
|
|
81
|
+
messages = Object.freeze({ ...language, ...own });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `message` is user-facing text for `code`, in the language of `runApp({ language })`. */
|
|
73
85
|
export class CodexError extends Error {
|
|
74
86
|
constructor(readonly code: CodexErrorCode) { super(messages[code]); this.name = "CodexError"; }
|
|
75
87
|
|
|
76
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Own texts for some or all codes (process-wide), on top of the application
|
|
90
|
+
* language; missing codes keep it. `useMessages({})` drops own texts.
|
|
91
|
+
*/
|
|
77
92
|
static useMessages(overrides: Partial<Record<CodexErrorCode, string>>): void {
|
|
78
|
-
|
|
93
|
+
own = { ...overrides };
|
|
94
|
+
messages = Object.freeze({ ...language, ...own });
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
export const CODEX_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,120}$/;
|
|
@@ -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.
|
|
3
|
+
"version": "0.105.3",
|
|
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",
|