Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1bbb06f
new logger with suppression functionality
MikhailArtemyev Feb 18, 2026
eff5908
fix: Use picocolors instead of hardcoded ANSI escapes in logger
ryukzak Mar 5, 2026
8630d21
Consolidate mkLogFn and mkDryLogFn into single factory with dedupe flag
ryukzak Mar 5, 2026
ae32557
fix: fork() inherits runtime suppress() calls and simplifies type cast
ryukzak Mar 5, 2026
842d067
Add silent log level to suppress all console output
ryukzak Mar 5, 2026
7f2d407
Fix Logger type mismatch in profile-extensions.ts
ryukzak Mar 6, 2026
e1acddd
Fix file rewrite warnings test to use snapshot
ryukzak Mar 6, 2026
4d54ae4
Remove codegen-logger.ts, migrate CLI to Logger + cli-fmt.ts
ryukzak Mar 6, 2026
ab8ee02
Add uppercase LogLevel variants, normalize internally to uppercase
ryukzak Mar 6, 2026
65c1ce1
Simplify logger: tuple rest params for TaggedLogFn, Record for tagCounts
ryukzak Mar 6, 2026
247e851
Rename makeLogger to mkLogger for consistency with codebase convention
ryukzak Mar 6, 2026
2296316
Remove lowercase LogLevel variants and UpperLogLevel duplication
ryukzak Mar 6, 2026
c1d75ed
Split Logger into Log and LogManager, rename logger.ts to log.ts
ryukzak Mar 6, 2026
d08d04e
Unexport TaggedLogFn and LoggerOptions, reorder types-first layout
ryukzak Mar 6, 2026
b75aad2
Add CodegenTag union and CodegenLog type alias, migrate consumers fro…
ryukzak Mar 9, 2026
94f447d
fix: Use string literal for LogLevel in local-package-folder example
ryukzak Mar 9, 2026
bb9d514
Use CodegenLogManager/CodegenTag consistently across builder and tests
ryukzak Mar 9, 2026
3c207e0
fix: Use mkLogger<CodegenTag> in python example to satisfy type const…
ryukzak Mar 9, 2026
a9d8550
Improve log formatting: hashtag tags, dimmed suffix, slash prefixes, …
ryukzak Mar 9, 2026
daf84b5
Rename tag strings to hashtag camelCase across typeschema consumers
ryukzak Mar 9, 2026
8ec7feb
Fork logger for register and typeschema stages, print tag summary bef…
ryukzak Mar 9, 2026
4da8f75
Suppress #fieldTypeNotFound in r4 examples
ryukzak Mar 9, 2026
6b51a63
Remove setLogLevel from APIBuilder, pass pre-configured loggers instead
ryukzak Mar 9, 2026
302572f
Remove generic fork<C> overload, keep only fork preserving parent tag…
ryukzak Mar 9, 2026
97606a2
Remove suppress() and setLevel() from LogManager, configure via mkLog…
ryukzak Mar 9, 2026
374f6ca
Export logger from package API, add mkCodegenLogger for examples
ryukzak Mar 9, 2026
6847111
Clean up package exports: hide Log/LogEntry/LogManager/mkLogger, reus…
ryukzak Mar 9, 2026
4151cf7
Fix stale logger references in CLAUDE.md and writer-generator guide
ryukzak Mar 9, 2026
1ed16b9
Rename utils/log.ts → utils/common-log.ts, extract codegen logging to…
ryukzak Mar 9, 2026
30d03c6
Replace shared test loggers with per-test factory functions
ryukzak Mar 9, 2026
f0f5c46
Fix logger type comment in writer-generator guide
ryukzak Mar 9, 2026
096d0c9
Fix typo in duplicate schema warning, pass logger to enrichResolver
ryukzak Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ assets/api/writer-generator/
1. Use `builder.introspection({ typeSchemas: "./debug-schemas" })` to inspect intermediate output
2. Check `src/typeschema/types.ts` for TypeSchema structure
3. Review `src/typeschema/core/transformer.ts` for transformation logic
4. Enable verbose logging with `builder.setLogLevel("DEBUG")`
4. Enable verbose logging by passing `mkCodegenLogger({ level: "DEBUG" })` to the builder

### Testing Generated Code
1. Use `builder.build()` instead of `generate()` to avoid file I/O
Expand Down
8 changes: 3 additions & 5 deletions docs/guides/testing-generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ All generator tests follow this basic structure:
```typescript
import { describe, expect, it } from "bun:test";
import { APIBuilder } from "@root/api/builder";
import { r4Manager } from "@typeschema-test/utils";
import { silentLogger, r4Manager } from "@typeschema-test/utils";

describe("TypeScript Writer Generator", async () => {
const result = await new APIBuilder({ manager: r4Manager })
.setLogLevel("SILENT")
const result = await new APIBuilder({ manager: r4Manager, logger: silentLogger })
.typescript({
inMemoryOnly: true,
})
Expand All @@ -79,8 +78,7 @@ describe("TypeScript Writer Generator", async () => {
### Key Components

**APIBuilder Setup:**
- Initialize with test manager: `new APIBuilder({ manager: r4Manager })`
- Set log level to SILENT to reduce noise: `.setLogLevel("SILENT")`
- Initialize with test manager and silent logger: `new APIBuilder({ manager: r4Manager, logger: silentLogger })`
- Choose generator method: `.typescript()`, `.python()`, `.csharp()`, `.mustache()`
- Enable in-memory mode: `inMemoryOnly: true` (no file I/O)

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/writer-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The `FileSystemWriter` class handles file I/O and directory management. It requi
export type FileSystemWriterOptions = {
outputDir: string; // Where to write files
inMemoryOnly?: boolean; // Don't write to disk (for testing)
logger?: CodegenLogger; // Optional logging
logger?: CodegenLogManager; // Created automatically if not provided
resolveAssets?: (fn: string) => string; // Asset resolution function
};
```
Expand Down
4 changes: 2 additions & 2 deletions examples/local-package-folder/generate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as Path from "node:path";
import { fileURLToPath } from "node:url";
import { APIBuilder, LogLevel, prettyReport } from "../../src/api";
import { APIBuilder, prettyReport } from "../../src/api";

const __dirname = Path.dirname(fileURLToPath(import.meta.url));

async function generateFromLocalPackageFolder() {
const builder = new APIBuilder({
logLevel: LogLevel.INFO,
logLevel: "INFO",
});

const report = await builder
Expand Down
5 changes: 2 additions & 3 deletions examples/mustache/mustache-java-r4-gen.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { APIBuilder, prettyReport } from "../../src/api/builder";
import { APIBuilder, mkCodegenLogger, prettyReport } from "../../src";

if (require.main === module) {
console.log("📦 Generating FHIR R4 Core Types...");

const builder = new APIBuilder()
.setLogLevel("DEBUG")
const builder = new APIBuilder({ logger: mkCodegenLogger({ level: "DEBUG" }) })
.throwException()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.outputTo("./examples/mustache/mustache-java-r4-output")
Expand Down
9 changes: 7 additions & 2 deletions examples/python/generate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { APIBuilder, prettyReport } from "../../src";
import { APIBuilder, mkCodegenLogger, prettyReport } from "../../src";

console.log("📦 Generating FHIR R4 Core Types...");

const builder = new APIBuilder()
const logger = mkCodegenLogger({
prefix: "API",
suppressTags: ["#fieldTypeNotFound", "#largeValueSet"],
});

const builder = new APIBuilder({ logger })
.throwException()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.python({
Expand Down
4 changes: 2 additions & 2 deletions examples/typescript-r4/generate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Run this script using Bun CLI with:
// bun run scripts/generate-fhir-types.ts

import { APIBuilder, prettyReport } from "../../src/api/builder";
import { APIBuilder, mkCodegenLogger, prettyReport } from "../../src";

if (require.main === module) {
console.log("📦 Generating FHIR R4 Core Types...");

const builder = new APIBuilder()
const builder = new APIBuilder({ logger: mkCodegenLogger({ suppressTags: ["#fieldTypeNotFound"] }) })
.throwException()
.fromPackage("hl7.fhir.r4.core", "4.0.1")
.typescript({
Expand Down
37 changes: 14 additions & 23 deletions src/api/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,8 @@ import type { IrConf, LogicalPromotionConf, TreeShakeConf } from "@root/typesche
import { type Register, registerFromManager } from "@root/typeschema/register";
import { type PackageMeta, packageMetaToNpm } from "@root/typeschema/types";
import { mkTypeSchemaIndex, type TypeSchemaIndex } from "@root/typeschema/utils";
import {
type CodegenLogger,
createLogger,
type LogLevel,
type LogLevelString,
parseLogLevel,
} from "@root/utils/codegen-logger";
import type { CodegenLogManager } from "@root/utils/log";
import { mkLogger } from "@root/utils/log";
import { IntrospectionWriter, type IntrospectionWriterOptions } from "./writer-generator/introspection";
import { IrReportWriterWriter, type IrReportWriterWriterOptions } from "./writer-generator/ir-report";
import type { FileBasedMustacheGeneratorOptions } from "./writer-generator/mustache";
Expand All @@ -47,8 +42,6 @@ export interface APIBuilderOptions {
treeShake: TreeShakeConf | undefined;
promoteLogical: LogicalPromotionConf | undefined;

/** Log level for the logger. Default: INFO */
logLevel: LogLevel;
/** Custom FHIR package registry URL (default: https://fs.get-ig.org/pkgs/) */
registry: string | undefined;
/** Drop the canonical manager cache */
Expand Down Expand Up @@ -100,7 +93,7 @@ export interface LocalStructureDefinitionConfig {
dependencies?: PackageMeta[];
}

const cleanup = async (opts: APIBuilderOptions, logger: CodegenLogger): Promise<void> => {
const cleanup = async (opts: APIBuilderOptions, logger: CodegenLogManager): Promise<void> => {
logger.info(`Cleaning outputs...`);
try {
logger.info(`Clean ${opts.outputDir}`);
Expand All @@ -125,15 +118,15 @@ export class APIBuilder {
localSDs: LocalPackageConfig[];
localTgzPackages: TgzPackageConfig[];
};
private logger: CodegenLogger;
private logger: CodegenLogManager;
private generators: { name: string; writer: FileSystemWriter }[] = [];

constructor(
userOpts: Partial<APIBuilderOptions> & {
manager?: ReturnType<typeof CanonicalManager>;
register?: Register;
preprocessPackage?: (context: PreprocessContext) => PreprocessContext;
logger?: CodegenLogger;
logger?: CodegenLogManager;
} = {},
) {
const defaultOpts: APIBuilderOptions = {
Expand All @@ -143,7 +136,6 @@ export class APIBuilder {
treeShake: undefined,
promoteLogical: undefined,
registry: undefined,
logLevel: parseLogLevel("INFO"),
dropCanonicalManagerCache: false,
};
const opts: APIBuilderOptions = {
Expand Down Expand Up @@ -179,7 +171,7 @@ export class APIBuilder {
dropCache: userOpts.dropCanonicalManagerCache,
preprocessPackage: userOpts.preprocessPackage,
});
this.logger = userOpts.logger ?? createLogger({ prefix: "API", level: opts.logLevel });
this.logger = userOpts.logger ?? mkLogger({ prefix: "api" });
this.options = opts;
}

Expand Down Expand Up @@ -340,11 +332,6 @@ export class APIBuilder {
return this;
}

setLogLevel(level: LogLevel | LogLevelString): APIBuilder {
this.logger?.setLevel(typeof level === "string" ? parseLogLevel(level) : level);
return this;
}

throwException(enabled = true): APIBuilder {
this.options.throwException = enabled;
return this;
Expand Down Expand Up @@ -423,22 +410,26 @@ export class APIBuilder {

const packageMetas = Object.values(ref2meta);
register = await registerFromManager(this.manager, {
logger: this.logger,
logger: this.logger.fork("reg"),
focusedPackages: packageMetas,
});
}

const { schemas: typeSchemas, collisions } = await generateTypeSchemas(register, this.logger);
const tsLogger = this.logger.fork("ts");

const { schemas: typeSchemas, collisions } = await generateTypeSchemas(register, tsLogger);

const tsIndexOpts = {
register,
logger: this.logger,
logger: tsLogger,
irReport: Object.keys(collisions).length > 0 ? { collisions } : {},
};
let tsIndex = mkTypeSchemaIndex(typeSchemas, tsIndexOpts);
if (this.options.treeShake) tsIndex = treeShake(tsIndex, this.options.treeShake);
if (this.options.promoteLogical) tsIndex = promoteLogical(tsIndex, this.options.promoteLogical);

tsLogger.printTagSummary();

this.logger.debug(`Executing ${this.generators.length} generators`);

await this.executeGenerators(result, tsIndex);
Expand All @@ -449,7 +440,7 @@ export class APIBuilder {

this.logger.debug(`Generation completed: ${result.filesGenerated.length} files`);
} catch (error) {
this.logger.error("Code generation failed", error instanceof Error ? error : new Error(String(error)));
this.logger.error(`Code generation failed: ${error instanceof Error ? error.message : String(error)}`);
result.errors.push(error instanceof Error ? error.message : String(error));
if (this.options.throwException) throw error;
}
Expand Down
3 changes: 2 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
*/

export type { IrConf, LogicalPromotionConf, TreeShakeConf } from "../typeschema/ir/types";
export { LogLevel } from "../utils/codegen-logger";
export type { CodegenLog, CodegenLogManager, CodegenTag, LogLevel } from "../utils/log";
export { mkCodegenLogger } from "../utils/log";
export type { APIBuilderOptions, LocalStructureDefinitionConfig } from "./builder";
export { APIBuilder, prettyReport } from "./builder";
export type { CSharpGeneratorOptions } from "./writer-generator/csharp/csharp";
Expand Down
4 changes: 2 additions & 2 deletions src/api/writer-generator/mustache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type {
ViewModel,
} from "@mustache/types";
import type { TypeSchemaIndex } from "@root/typeschema/utils";
import type { CodegenLogger } from "@root/utils/codegen-logger";
import type { CodegenLog } from "@root/utils/log";
import { default as Mustache } from "mustache";
import { FileSystemWriter, type FileSystemWriterOptions } from "./writer";

Expand Down Expand Up @@ -57,7 +57,7 @@ export type MustacheGeneratorOptions = FileSystemWriterOptions &

export function loadMustacheGeneratorConfig(
templatePath: string,
logger?: CodegenLogger,
logger?: CodegenLog,
): Partial<FileBasedMustacheGeneratorOptions> {
const filePath = Path.resolve(templatePath, "config.json");
try {
Expand Down
6 changes: 3 additions & 3 deletions src/api/writer-generator/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import * as fs from "node:fs";
import * as fsPromises from "node:fs/promises";
import * as Path from "node:path";
import type { TypeSchemaIndex } from "@root/typeschema/utils";
import type { CodegenLogger } from "@root/utils/codegen-logger";
import type { CodegenLog } from "@root/utils/log";

export type FileSystemWriterOptions = {
outputDir: string;
inMemoryOnly?: boolean;
logger?: CodegenLogger;
logger?: CodegenLog;
resolveAssets?: (fn: string) => string;
};

Expand Down Expand Up @@ -36,7 +36,7 @@ export abstract class FileSystemWriter<T extends FileSystemWriterOptions = FileS
this.opts.outputDir = path;
}

logger(): CodegenLogger | undefined {
logger(): CodegenLog | undefined {
return this.opts.logger;
}

Expand Down
60 changes: 13 additions & 47 deletions src/cli/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* Modern CLI with subcommands for typeschema and code generation
*/

import { configure, error, header, LogLevel } from "@root/utils/codegen-logger";
import { header } from "@root/utils/cli-fmt";
import type { LogLevel } from "@root/utils/log";
import { mkLogger } from "@root/utils/log";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { typeschemaCommand } from "./typeschema";
Expand All @@ -17,45 +19,14 @@ import { typeschemaCommand } from "./typeschema";
export interface CLIArgv {
verbose?: boolean;
debug?: boolean;
logLevel?: "debug" | "info" | "warn" | "error" | "silent";
logLevel?: LogLevel;
}

/**
* Map string log level to LogLevel enum
*/
function parseLogLevel(level: string | undefined): LogLevel | undefined {
if (!level) return undefined;
const levelMap: Record<string, LogLevel> = {
debug: LogLevel.DEBUG,
info: LogLevel.INFO,
warn: LogLevel.WARN,
error: LogLevel.ERROR,
silent: LogLevel.SILENT,
};
return levelMap[level.toLowerCase()];
}
let cliLogger = mkLogger({ prefix: "cli" });

/**
* Middleware to setup logging
*/
async function setupLoggingMiddleware(argv: any) {
// Determine log level: explicit --log-level takes precedence over --verbose/--debug
let level = parseLogLevel(argv.logLevel);

// If no explicit log level, use --verbose or --debug as shortcuts
if (level === undefined) {
if (argv.debug || argv.verbose) {
level = LogLevel.DEBUG;
} else {
level = LogLevel.INFO;
}
}

// Configure the CliLogger with user preferences
configure({
timestamp: argv.debug,
level,
});
const level: LogLevel = argv.logLevel ?? (argv.debug || argv.verbose ? "DEBUG" : "INFO");
cliLogger = mkLogger({ prefix: "cli", level });
}

/**
Expand Down Expand Up @@ -84,8 +55,8 @@ export function createCLI() {
.option("log-level", {
alias: "l",
type: "string",
choices: ["debug", "info", "warn", "error", "silent"] as const,
description: "Set the log level (default: info)",
choices: ["DEBUG", "INFO", "WARN", "ERROR", "SILENT"] as const,
description: "Set the log level (default: INFO)",
global: true,
})
.demandCommand(0) // Allow 0 commands so we can handle it ourselves
Expand All @@ -110,13 +81,8 @@ export function createCLI() {
"Generate TypeSchemas from FHIR package",
)
.fail((msg, err, _yargs) => {
if (err) {
error(err.message, err);
} else {
error(msg);
}

error("\nUse --help for usage information");
cliLogger.error(err ? err.message : msg);
cliLogger.error("Use --help for usage information");
process.exit(1);
})
.wrap(Math.min(120, process.stdout.columns || 80));
Expand All @@ -132,8 +98,8 @@ export async function runCLI() {

// Run CLI if this file is executed directly
if (import.meta.main) {
runCLI().catch((error) => {
error("Unexpected error:", error);
runCLI().catch((err) => {
cliLogger.error(String(err));
process.exit(1);
});
}
Loading