bazis 0.97.7 → 0.97.9
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/generateModule.ts +48 -8
- package/cli/generateProject.ts +1 -1
- package/cli/main.ts +4 -1
- package/cli/parseCli.ts +7 -2
- package/cli/templates/module.ts +4 -3
- package/cli/templates/pack.ts +9 -4
- package/cli/templates/passport.ts +12 -3
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/kernel/config/defineConfig.ts +15 -1
- package/package.json +1 -1
package/cli/generateModule.ts
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { lstat, mkdir, readFile, realpath, rename, rm, rmdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { buildModuleTemplates, type ModuleTemplateFiles, type ModuleTemplateProfile } from "./templates/module";
|
|
5
|
-
import { buildPackTemplates } from "./templates/pack";
|
|
5
|
+
import { buildPackTemplates, packPartFolder } from "./templates/pack";
|
|
6
6
|
import { parseModuleName, type ModuleNaming } from "./naming";
|
|
7
7
|
import { moduleImportPath, registerModuleInSource } from "./moduleRegistration";
|
|
8
8
|
|
|
@@ -16,11 +16,16 @@ export interface GenerateModuleOptions {
|
|
|
16
16
|
readonly register?: boolean;
|
|
17
17
|
readonly force?: boolean;
|
|
18
18
|
readonly profile?: ModuleTemplateProfile;
|
|
19
|
+
/**
|
|
20
|
+
* Composite module to add this module to as a part: `Catalog` puts it into
|
|
21
|
+
* `{modulesRoot}/catalog_modules/<name>_module` and connects it in the pack root.
|
|
22
|
+
*/
|
|
23
|
+
readonly pack?: string;
|
|
19
24
|
/** Validate and return the plan without writing files. */
|
|
20
25
|
readonly dryRun?: boolean;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
export interface GenerateModulePackOptions extends Omit<GenerateModuleOptions, "profile"> {
|
|
28
|
+
export interface GenerateModulePackOptions extends Omit<GenerateModuleOptions, "profile" | "pack"> {
|
|
24
29
|
readonly parts: readonly string[];
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -44,7 +49,14 @@ export async function generateModule(options: GenerateModuleOptions): Promise<Ge
|
|
|
44
49
|
const naming = parseModuleName(options.name);
|
|
45
50
|
const profile = options.profile ?? "minimal";
|
|
46
51
|
if (!["empty", "minimal", "full"].includes(profile)) throw new Error(`Unknown profile: ${profile}`);
|
|
52
|
+
const command = creationCommand("module", options, [`--${profile}`]);
|
|
47
53
|
const modulesRoot = await canonicalDirectory(path.resolve(options.modulesRoot ?? "src/app/modules"));
|
|
54
|
+
// A part of a composite module lives in its pack and is connected in the pack root.
|
|
55
|
+
const pack = options.pack === undefined ? undefined : await findPack(modulesRoot, options.pack);
|
|
56
|
+
if (pack && options.appModulePath !== undefined) throw new Error("--pack connects the part in the pack root; do not combine it with --app-module.");
|
|
57
|
+
const targetRoot = pack?.dir ?? modulesRoot;
|
|
58
|
+
const folder = pack ? packPartFolder(naming) : naming.folder;
|
|
59
|
+
const target: GenerateModuleOptions = pack ? { ...options, modulesRoot: targetRoot, appModulePath: pack.entry } : options;
|
|
48
60
|
let authImportPath: string | null | undefined;
|
|
49
61
|
if (profile === "full") {
|
|
50
62
|
// The host's auth helpers are optional: without them the controller is
|
|
@@ -52,18 +64,46 @@ export async function generateModule(options: GenerateModuleOptions): Promise<Ge
|
|
|
52
64
|
const authDir = path.resolve("src/app/modules/auth");
|
|
53
65
|
const helpers = await Promise.all(["tokenKinds.ts", "jwtAuth.ts"].map(async (helper) => (await fileInfo(path.join(authDir, helper)))?.isFile() === true));
|
|
54
66
|
authImportPath = helpers.every(Boolean)
|
|
55
|
-
? moduleImportPath(path.join(
|
|
67
|
+
? moduleImportPath(path.join(targetRoot, folder, "http", `${naming.module}.controller.ts`), await canonicalDirectory(authDir))
|
|
56
68
|
: null;
|
|
57
69
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
const templates = buildModuleTemplates(naming, profile, authImportPath, { command, pack: pack?.moduleClass });
|
|
71
|
+
const result = await generateFiles(target, naming, folder, templates);
|
|
72
|
+
const warnings = [...result.warnings];
|
|
73
|
+
if (authImportPath === null) warnings.push("No auth helpers in src/app/modules/auth: the full profile controller has no @Authorize. Protect its routes before exposing them.");
|
|
74
|
+
if (pack) warnings.push(`Add ${naming.moduleClass} to the parts table in ${path.relative(process.cwd(), path.join(pack.dir, "MODULE.md"))}.`);
|
|
75
|
+
return { ...result, warnings };
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
export async function generateModulePack(options: GenerateModulePackOptions): Promise<GenerateModuleResult> {
|
|
65
79
|
const naming = parseModuleName(options.name);
|
|
66
|
-
|
|
80
|
+
const command = creationCommand("pack", options, ["--parts", options.parts.join(",")]);
|
|
81
|
+
return generateFiles(options, naming, `${naming.folder}_modules`, buildPackTemplates(naming, options.parts, command));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The existing composite module `name` under `modulesRoot`: `Catalog` → `catalog_modules/Catalog.module.ts`. */
|
|
85
|
+
async function findPack(modulesRoot: string, name: string): Promise<{ readonly dir: string; readonly entry: string; readonly moduleClass: string }> {
|
|
86
|
+
const naming = parseModuleName(name);
|
|
87
|
+
const dir = path.join(modulesRoot, `${naming.folder}_modules`);
|
|
88
|
+
const entry = path.join(dir, `${naming.module}.module.ts`);
|
|
89
|
+
if ((await fileInfo(entry))?.isFile() !== true) {
|
|
90
|
+
throw new Error(`Pack ${naming.moduleClass} not found: ${path.relative(process.cwd(), entry)}. Create it with bazis g pack ${naming.module} --parts <a,b>.`);
|
|
91
|
+
}
|
|
92
|
+
return { dir, entry, moduleClass: naming.moduleClass };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The command recorded in the passport, as the architecture rules require. It is
|
|
97
|
+
* rebuilt from the options in a canonical form, so the passport shows the same
|
|
98
|
+
* text however the flags were ordered or aliased.
|
|
99
|
+
*/
|
|
100
|
+
function creationCommand(generator: "module" | "pack", options: GenerateModuleOptions, extra: readonly string[]): string {
|
|
101
|
+
const parts = ["bunx bazis g", generator, options.name, ...extra];
|
|
102
|
+
if (options.pack !== undefined) parts.push("--pack", options.pack);
|
|
103
|
+
if (options.modulesRoot !== undefined) parts.push("--modules-root", options.modulesRoot);
|
|
104
|
+
if (options.appModulePath !== undefined) parts.push("--app-module", options.appModulePath);
|
|
105
|
+
if (options.register === false) parts.push("--no-register");
|
|
106
|
+
return parts.join(" ");
|
|
67
107
|
}
|
|
68
108
|
|
|
69
109
|
async function generateFiles(options: GenerateModuleOptions, naming: ModuleNaming, folder: string, templates: readonly ModuleTemplateFiles[]): Promise<GenerateModuleResult> {
|
package/cli/generateProject.ts
CHANGED
|
@@ -168,7 +168,7 @@ function buildProjectFiles(name: string, dependency: string, frameworkMode: Fram
|
|
|
168
168
|
[".env.example", "# Copy to .env (Bun loads it automatically).\nBAZIS_ENV=development\nHOST=127.0.0.1\nPORT=3000\n"],
|
|
169
169
|
["src/app/test/health.test.ts", HEALTH_TEST],
|
|
170
170
|
["AGENTS.md", "# Working on this bazis project\n\nBefore changing the application, read the [module architecture](docs/architecture/MODULE_ARCHITECTURE.md). Create new modules only with `bunx bazis g module` or `bunx bazis g pack`; fill in the generated `MODULE.md` afterwards. Only codegen updates `src/generated/`.\n"],
|
|
171
|
-
["docs/architecture/MODULE_ARCHITECTURE.md", "# Application module architecture\n\n`src/index.ts` calls `runApp`; `src/app/modules/App.module.ts` composes feature modules through `imports`. The application root owns no domain logic.\n\nOne self-contained function is an atomic module. It owns its data, services, HTTP and background handlers. A composite module is only for several independent functions; its root does composition. Layers and file counts alone do not create submodules.\n\nCreate new modules only with `bunx bazis g module <Name> --empty|--minimal|--full` or `bunx bazis g pack <Name> --parts <a,b>`. Before implementing, define the responsibility and public entries, then fill in the generated `MODULE.md`: fields, errors, dependencies, exports and checks. Use the public APIs of the `bazis` package and its DI and ORM. Do not edit `src/generated/` by hand; run `bunx bazis codegen`.\n\n`--minimal` creates a sample CRUD with the ORM. To run it the application needs a database provider and a ready schema. For a first function without a database use `--empty`. Check types and the binary build after changes that affect startup.\n"],
|
|
171
|
+
["docs/architecture/MODULE_ARCHITECTURE.md", "# Application module architecture\n\n`src/index.ts` calls `runApp`; `src/app/modules/App.module.ts` composes feature modules through `imports`. The application root owns no domain logic.\n\nOne self-contained function is an atomic module. It owns its data, services, HTTP and background handlers. A composite module is only for several independent functions; its root does composition. Layers and file counts alone do not create submodules.\n\nCreate new modules only with `bunx bazis g module <Name> --empty|--minimal|--full` or `bunx bazis g pack <Name> --parts <a,b>`; add a part to an existing composite with `bunx bazis g module <Name> --pack <Pack>`. The generated `MODULE.md` records the creation command. Before implementing, define the responsibility and public entries, then fill in the generated `MODULE.md`: fields, errors, dependencies, exports and checks. Use the public APIs of the `bazis` package and its DI and ORM. Do not edit `src/generated/` by hand; run `bunx bazis codegen`.\n\n`--minimal` creates a sample CRUD with the ORM. To run it the application needs a database provider and a ready schema. For a first function without a database use `--empty`. Check types and the binary build after changes that affect startup.\n"],
|
|
172
172
|
["src/app/modules/App.module.ts", 'import { Module } from "bazis/core/di";\n\n@Module({ imports: [], exports: [] })\nexport class AppModule {}\n'],
|
|
173
173
|
["src/index.ts", 'import { runApp } from "bazis/core/app";\nimport { AppModule } from "./app/modules/App.module";\nimport { registerBazisGeneratedRuntime } from "./generated/bazis/runtime";\n\nawait registerBazisGeneratedRuntime();\nawait runApp(AppModule, { http: { hostname: process.env.HOST ?? "127.0.0.1", port: Number(process.env.PORT ?? 3000), health: true } });\n'],
|
|
174
174
|
["README.md", `# ${name}\n\nA [bazis](https://www.npmjs.com/package/bazis) application. ${frameworkMode === "registry"
|
package/cli/main.ts
CHANGED
|
@@ -26,6 +26,8 @@ Generation options:
|
|
|
26
26
|
--link-framework Depend on that local checkout through a file: link (new only)
|
|
27
27
|
--modules-root <path> Modules root (default: src/app/modules)
|
|
28
28
|
--app-module <path> Host module (default: {modules-root}/App.module.ts)
|
|
29
|
+
--pack <Name> Module only: add it as a part of the composite <Name>
|
|
30
|
+
({modules-root}/<name>_modules/<module>_module)
|
|
29
31
|
--empty Module entry and MODULE.md only
|
|
30
32
|
--minimal Compact example CRUD and MODULE.md (default)
|
|
31
33
|
--full Example CRUD/list/cache/auth/background/AI and MODULE.md;
|
|
@@ -47,6 +49,7 @@ Examples (from the project root):
|
|
|
47
49
|
bunx bazis g m Guest --no-codegen
|
|
48
50
|
bunx bazis g module Mailer --empty --no-register
|
|
49
51
|
bunx bazis g pack DataManager --parts tables,fields,validators,records
|
|
52
|
+
bunx bazis g module Prices --empty --pack Catalog
|
|
50
53
|
bunx bazis codegen --target production
|
|
51
54
|
bunx bazis dev --watch
|
|
52
55
|
bunx bazis test
|
|
@@ -106,7 +109,7 @@ export async function runCli(argv: readonly string[], runtime: CliRuntime = defa
|
|
|
106
109
|
const { generateModule, generateModulePack } = await import("./generateModule");
|
|
107
110
|
const result = args.generator === "pack"
|
|
108
111
|
? await generateModulePack({ ...options, parts: args.parts })
|
|
109
|
-
: await generateModule({ ...options, profile: args.profile });
|
|
112
|
+
: await generateModule({ ...options, profile: args.profile, pack: args.pack });
|
|
110
113
|
runtime.log(`[bazis] ${result.dryRun ? "planned" : "generated"} ${args.generator}: ${result.moduleDir}`);
|
|
111
114
|
for (const change of result.changes) runtime.log(` ${change.action === "create" ? "+" : "~"} ${change.path}`);
|
|
112
115
|
for (const warning of result.warnings) runtime.log(`[bazis] ${warning}`);
|
package/cli/parseCli.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface ParsedGenerateArgs {
|
|
|
8
8
|
readonly parts: readonly string[];
|
|
9
9
|
readonly modulesRoot?: string;
|
|
10
10
|
readonly appModule?: string;
|
|
11
|
+
/** Composite module to add the new module to as a part (module generator only). */
|
|
12
|
+
readonly pack?: string;
|
|
11
13
|
readonly register: boolean;
|
|
12
14
|
readonly force: boolean;
|
|
13
15
|
readonly profile: GenerateProfile;
|
|
@@ -30,7 +32,7 @@ const GENERATOR_ALIASES = new Map<string, "module" | "pack">([
|
|
|
30
32
|
["module", "module"], ["m", "module"],
|
|
31
33
|
["pack", "pack"], ["p", "pack"], ["module-pack", "pack"],
|
|
32
34
|
]);
|
|
33
|
-
const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--parts", "--target", "--path", "--framework", "--outfile"]);
|
|
35
|
+
const VALUE_OPTIONS = new Set(["--modules-root", "--app-module", "--pack", "--parts", "--target", "--path", "--framework", "--outfile"]);
|
|
34
36
|
const FLAG_OPTIONS = new Set(["--no-register", "--force", "--full", "--enterprise", "--minimal", "--empty", "--dry-run", "--no-codegen", "--link-framework", "--vendor", "--bin", "--watch"]);
|
|
35
37
|
|
|
36
38
|
/** Parsing is pure: help and invalid input can never start generation. */
|
|
@@ -119,12 +121,15 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
|
|
|
119
121
|
if (profiles.length > 1) return error("Choose only one profile: --empty, --minimal or --full.");
|
|
120
122
|
if (generator === "pack" && profiles.length > 0) return error("Pack parts start empty; module profile flags are not supported by pack.");
|
|
121
123
|
if (generator === "module" && options.has("--parts")) return error("--parts is only supported by pack.");
|
|
124
|
+
if (generator === "pack" && options.has("--pack")) return error("--pack adds a part with g module; a pack is created with --parts.");
|
|
125
|
+
if (options.has("--pack") && options.has("--app-module")) return error("--pack connects the part in the pack root; do not combine it with --app-module.");
|
|
122
126
|
if (options.has("--no-codegen") && target !== undefined) return error("--target cannot be used with --no-codegen.");
|
|
123
127
|
if (options.has("--no-register") && target !== undefined) return error("Use bazis codegen --target separately after connecting the module.");
|
|
124
128
|
|
|
125
129
|
const parts = options.get("--parts")?.split(",").map((part) => part.trim()) ?? [];
|
|
126
130
|
try {
|
|
127
131
|
parseModuleName(name);
|
|
132
|
+
if (options.has("--pack")) parseModuleName(options.get("--pack")!);
|
|
128
133
|
if (generator === "pack") validatePackParts(parts);
|
|
129
134
|
} catch (cause) {
|
|
130
135
|
return error(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -133,7 +138,7 @@ export function parseCliArgs(argv: readonly string[]): ParseCliResult {
|
|
|
133
138
|
kind: "generate",
|
|
134
139
|
args: {
|
|
135
140
|
generator, name, parts,
|
|
136
|
-
modulesRoot: options.get("--modules-root"), appModule: options.get("--app-module"),
|
|
141
|
+
modulesRoot: options.get("--modules-root"), appModule: options.get("--app-module"), pack: options.get("--pack"),
|
|
137
142
|
register: !options.has("--no-register"), force: options.has("--force"),
|
|
138
143
|
profile: profiles[0] ?? "minimal", dryRun: options.has("--dry-run"),
|
|
139
144
|
codegen: !options.has("--no-codegen"), target,
|
package/cli/templates/module.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModuleNaming } from "../naming";
|
|
2
|
-
import { modulePassport } from "./passport";
|
|
2
|
+
import { modulePassport, type PassportOrigin } from "./passport";
|
|
3
3
|
|
|
4
4
|
export type ModuleTemplateProfile = "empty" | "minimal" | "full";
|
|
5
5
|
|
|
@@ -20,11 +20,12 @@ export function buildModuleTemplates(
|
|
|
20
20
|
profile: ModuleTemplateProfile = "minimal",
|
|
21
21
|
/** null: the host has no auth helpers, so `full` emits no @Authorize. */
|
|
22
22
|
authImportPath: string | null = "../../auth",
|
|
23
|
+
origin: PassportOrigin = {},
|
|
23
24
|
): readonly ModuleTemplateFiles[] {
|
|
24
25
|
if (profile === "empty") {
|
|
25
26
|
return [
|
|
26
27
|
{ relativePath: `${naming.module}.module.ts`, content: `import { Module } from "bazis/core/di";\n\n@Module({ imports: [], exports: [] })\nexport class ${naming.moduleClass} {}\n` },
|
|
27
|
-
{ relativePath: "MODULE.md", content: modulePassport(naming, profile) },
|
|
28
|
+
{ relativePath: "MODULE.md", content: modulePassport(naming, profile, origin) },
|
|
28
29
|
];
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -38,7 +39,7 @@ export function buildModuleTemplates(
|
|
|
38
39
|
{ relativePath: `http/contracts/${naming.module}.requests.ts`, content: requestFile(naming) },
|
|
39
40
|
{ relativePath: `http/contracts/${naming.module}.responses.ts`, content: responseFile(naming) },
|
|
40
41
|
{ relativePath: `http/contracts/${naming.module}List.query.ts`, content: listQueryFile(naming) },
|
|
41
|
-
{ relativePath: "MODULE.md", content: modulePassport(naming, profile) },
|
|
42
|
+
{ relativePath: "MODULE.md", content: modulePassport(naming, profile, origin) },
|
|
42
43
|
];
|
|
43
44
|
|
|
44
45
|
if (profile === "minimal") {
|
package/cli/templates/pack.ts
CHANGED
|
@@ -2,12 +2,17 @@ import { parseModuleName, type ModuleNaming } from "../naming";
|
|
|
2
2
|
import { validatePackParts } from "../parseCli";
|
|
3
3
|
import { buildModuleTemplates, type ModuleTemplateFiles } from "./module";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/** The folder of a pack part: `prices` → `prices_module`. */
|
|
6
|
+
export function packPartFolder(part: ModuleNaming): string {
|
|
7
|
+
return `${part.folder}_module`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildPackTemplates(naming: ModuleNaming, partNames: readonly string[], command?: string): readonly ModuleTemplateFiles[] {
|
|
6
11
|
validatePackParts(partNames);
|
|
7
12
|
const parts = partNames.map((name) => {
|
|
8
13
|
const part = parseModuleName(name);
|
|
9
14
|
// A part names a responsibility (Tables), not a CRUD entity (Table).
|
|
10
|
-
return { ...part, entity: part.module, folder:
|
|
15
|
+
return { ...part, entity: part.module, folder: packPartFolder(part) };
|
|
11
16
|
});
|
|
12
17
|
if (parts.some((part) => part.moduleClass === naming.moduleClass)) {
|
|
13
18
|
throw new Error("A pack and its parts must have distinct module class names.");
|
|
@@ -32,7 +37,7 @@ export class ${naming.moduleClass} {}
|
|
|
32
37
|
Passport version: 1.0. Type: composite.
|
|
33
38
|
Status: composition scaffold generated; the parts are not implemented yet.
|
|
34
39
|
Entry: [${naming.module}.module.ts](${naming.module}.module.ts).
|
|
35
|
-
Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
|
|
40
|
+
${command === undefined ? "" : `Created with: \`${command}\`.\n`}Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
|
|
36
41
|
|
|
37
42
|
## Responsibility and parts
|
|
38
43
|
|
|
@@ -69,7 +74,7 @@ exports, no cycles or duplicate registrations, the parts' tests.
|
|
|
69
74
|
A generated scaffold does not prove the pack is ready.
|
|
70
75
|
`,
|
|
71
76
|
},
|
|
72
|
-
...parts.flatMap((part) => buildModuleTemplates(part, "empty").map((file) => ({
|
|
77
|
+
...parts.flatMap((part) => buildModuleTemplates(part, "empty", undefined, { command, pack: naming.moduleClass }).map((file) => ({
|
|
73
78
|
relativePath: `${part.folder}/${file.relativePath}`, content: file.content,
|
|
74
79
|
}))),
|
|
75
80
|
];
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import type { ModuleNaming } from "../naming";
|
|
2
2
|
import type { ModuleTemplateProfile } from "./module";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/** Facts the generator knows about how the module was created. */
|
|
5
|
+
export interface PassportOrigin {
|
|
6
|
+
/** The CLI command that created the module, recorded as the architecture rules require. */
|
|
7
|
+
readonly command?: string;
|
|
8
|
+
/** Module class of the composite module this one is a part of. */
|
|
9
|
+
readonly pack?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function modulePassport(n: ModuleNaming, profile: ModuleTemplateProfile, origin: PassportOrigin = {}): string {
|
|
13
|
+
const type = origin.pack === undefined ? "atomic" : `atomic, a part of the composite ${origin.pack} ([passport](../MODULE.md))`;
|
|
5
14
|
const header = `# ${n.moduleClass}
|
|
6
15
|
|
|
7
|
-
Passport version: 1.0. Type:
|
|
16
|
+
Passport version: 1.0. Type: ${type}. CLI profile: ${profile}.
|
|
8
17
|
Status: scaffold generated; the domain implementation and checks are not done.
|
|
9
18
|
Entry: [${n.module}.module.ts](${n.module}.module.ts), class ${n.moduleClass}, no arguments.
|
|
10
|
-
|
|
19
|
+
${origin.command === undefined ? "" : `Created with: \`${origin.command}\`.\n`}
|
|
11
20
|
Before changing it, read AGENTS.md and docs/architecture/MODULE_ARCHITECTURE.md.
|
|
12
21
|
This passport covers the scaffold files. The author defines the responsibility
|
|
13
22
|
and fields before implementing the domain function and updates this passport
|
|
@@ -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.97.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.97.9" }, 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");
|
|
@@ -90,6 +90,19 @@ export function processConfiguration(): Configuration {
|
|
|
90
90
|
}
|
|
91
91
|
return new Configuration(values, origins);
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Why an environment variable name cannot be used for a key, or undefined.
|
|
95
|
+
* Aliases are read from the same BAZIS_* variables as the generated names,
|
|
96
|
+
* so they share the prefix.
|
|
97
|
+
*/
|
|
98
|
+
function environmentNameProblem(name: string, claimed: ReadonlySet<string>): string | undefined {
|
|
99
|
+
if (!name.startsWith(ENV_PREFIX)) return `must start with ${ENV_PREFIX}: configuration reads only ${ENV_PREFIX}* variables (for example ${ENV_PREFIX}${name.replace(/^[^A-Za-z0-9]+/, "").toUpperCase() || "NAME"})`;
|
|
100
|
+
if (name.length === ENV_PREFIX.length) return `needs a name after ${ENV_PREFIX}`;
|
|
101
|
+
if (!/^[A-Z0-9_-]+$/.test(name)) return "may contain only A-Z, 0-9, _ and -";
|
|
102
|
+
if (claimed.has(name)) return "is already used by another key of this configuration";
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
function isSecret(cell: Cell): cell is SecretSpec { return typeof cell === "object" && cell !== null && "__secret" in cell; }
|
|
94
107
|
function isEnum(cell: Cell): cell is ConfigEnum { return typeof cell === "object" && cell !== null && "__enum" in cell; }
|
|
95
108
|
function copyCell(cell: Cell): Cell {
|
|
@@ -127,7 +140,8 @@ export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, ma
|
|
|
127
140
|
const alias = input.env?.[key];
|
|
128
141
|
const env = [...new Set([`${ENV_PREFIX}${fullKey(key).replaceAll(".", "__").toUpperCase()}`, ...(typeof alias === "string" ? [alias] : alias ?? [])])];
|
|
129
142
|
for (const name of env) {
|
|
130
|
-
|
|
143
|
+
const problem = environmentNameProblem(name, claimed);
|
|
144
|
+
if (problem) throw new KernelError(`Configuration key "${fullKey(key)}": environment variable "${name}" ${problem}.`);
|
|
131
145
|
claimed.add(name);
|
|
132
146
|
}
|
|
133
147
|
names.set(key, Object.freeze(env));
|
package/package.json
CHANGED