Skip to content

Commit 83ee6c2

Browse files
committed
refactor: inject a proper logger
1 parent 0680b43 commit 83ee6c2

File tree

10 files changed

+782
-93
lines changed

10 files changed

+782
-93
lines changed

package-lock.json

Lines changed: 677 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"main": "dist/index.cjs",
2121
"module": "dist/index.js",
2222
"bin": {
23-
"yarpc": "./bin/yarpc-cli.mjs"
23+
"yarpc-cli": "./bin/yarpc-cli.mjs"
2424
},
2525
"exports": {
2626
"types": "./dist/index.d.ts",
@@ -33,6 +33,9 @@
3333
"scripts": {
3434
"build": "tsup-node src/index.ts",
3535
"build:check": "tsc --noEmit",
36+
"build:examples": "npm run build && npm run build:examples:yaml && npm run build:examples:json",
37+
"build:examples:yaml": "bin/yarpc-cli.mjs -i examples/widgets.rpc.yaml -o examples/widgets.oas.yaml -f yaml",
38+
"build:examples:json": "bin/yarpc-cli.mjs -i examples/widgets.rpc.yaml -o examples/widgets.oas.json -f json",
3639
"ci": "npm run build:check && npm run lint:check && npm run test:node && npm run format:check",
3740
"clean": "rm -rf dist",
3841
"format": "prettier --write .",
@@ -43,7 +46,7 @@
4346
"pretest": "npm run lint && tsc --noEmit",
4447
"test": "npm run build:check && npm run test:node",
4548
"test:node": "node --loader ts-node/esm --test src/*.test.ts",
46-
"posttest": "npm run format"
49+
"posttest": "npm run format && npm run build:examples"
4750
},
4851
"devDependencies": {
4952
"@sinclair/typebox": "^0.28.14",
@@ -68,6 +71,8 @@
6871
},
6972
"dependencies": {
7073
"just-intersect": "^4.3.0",
74+
"pino": "^8.14.1",
75+
"pino-pretty": "^10.0.0",
7176
"yaml": "^2.3.1"
7277
}
7378
}

packages/cli/src/cli.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { readFile, writeFile } from "node:fs/promises";
22
import { parseArgs } from "node:util";
3+
import pino from "pino";
34
import YAML from "yaml";
45
import { DocumentTransformer } from "./transformers";
5-
import { RPCDocument } from "./types";
6+
import { Logger, RPCDocument } from "./types";
67

78
const DEFAULT_OUT_PATH = "openapi.json";
89
const SUPPORTED_FORMATS = ["json", "yaml"] as const;
@@ -27,14 +28,15 @@ type EmitOpenAPIParams = {
2728
input: string;
2829
output: string;
2930
format: SupportedFormat;
31+
logger: Logger;
3032
};
3133

3234
async function emitOpenAPI(params: EmitOpenAPIParams) {
33-
const { input, output, format } = params;
35+
const { input, output, format, logger } = params;
3436

3537
const source = YAML.parse(await readFile(input, "utf8")) as RPCDocument;
3638

37-
const transformed = await DocumentTransformer.transform(source);
39+
const transformed = await DocumentTransformer.transform(source, { logger });
3840
let contents;
3941

4042
if (format === "yaml") {
@@ -51,12 +53,19 @@ async function emitOpenAPI(params: EmitOpenAPIParams) {
5153
export async function main() {
5254
const args = parseArgs({
5355
options: {
54-
help: { type: "boolean", alias: "h" },
55-
version: { type: "boolean", alias: "v" },
56-
verbose: { type: "boolean", alias: "V" },
57-
input: { type: "string", alias: "i" },
58-
output: { type: "string", alias: "o", default: "openapi.json" },
59-
format: { type: "string", alias: "f", default: "json" },
56+
help: { type: "boolean", short: "h" },
57+
version: { type: "boolean", short: "v" },
58+
verbose: { type: "boolean", short: "V" },
59+
input: { type: "string", short: "i" },
60+
output: { type: "string", short: "o", default: "openapi.json" },
61+
format: { type: "string", short: "f", default: "json" },
62+
},
63+
});
64+
65+
const logger = pino({
66+
level: args.values.verbose ? "debug" : "info",
67+
transport: {
68+
target: "pino-pretty",
6069
},
6170
});
6271

@@ -82,5 +91,10 @@ export async function main() {
8291
return;
8392
}
8493

85-
await emitOpenAPI({ format: format as SupportedFormat, input, output });
94+
await emitOpenAPI({
95+
input,
96+
output,
97+
format: format as SupportedFormat,
98+
logger,
99+
});
86100
}

packages/cli/src/resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import safeGet from "just-safe-get";
22
import { RPCDocument, ReferenceObject, SchemaObject } from "./types/index.js";
33

4-
export type IResolver = {
4+
export type IRefResolver = {
55
resolve(ref: ReferenceObject): SchemaObject | PromiseLike<SchemaObject>;
66
};
77

8-
export const getDefaultResolver = (doc: RPCDocument): IResolver => ({
8+
export const getDefaultRefResolver = (doc: RPCDocument): IRefResolver => ({
99
resolve: (ref: ReferenceObject) => {
1010
if (!ref.$ref.startsWith("#/components/")) {
1111
throw new Error(`Unsupported reference ${ref.$ref}`);

packages/cli/src/transformers/document-transformer.test.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from "node:assert";
22
import test from "node:test";
3+
import pino from "pino";
34
import {
45
components,
56
mutations,
@@ -9,6 +10,8 @@ import {
910
import { OASDocument, RPCDocument } from "../types/index.js";
1011
import { DocumentTransformer } from "./document-transformer.js";
1112

13+
const logger = pino({ level: "silent" });
14+
1215
const healthCheck = {
1316
"/health": {
1417
get: {
@@ -66,20 +69,17 @@ const oasDocument: OASDocument = {
6669
components: rpcDocument.components,
6770
};
6871

69-
const transformer = new DocumentTransformer(rpcDocument);
70-
7172
test("DocumentTransformer#transform", async (t) => {
7273
await t.test("transforms an RPC document into an OAS document", async () => {
73-
const actual = await transformer.transform();
74+
const actual = await DocumentTransformer.transform(rpcDocument, { logger });
7475
assert.deepStrictEqual(actual, oasDocument);
7576
});
7677

7778
await t.test("throws an error if the YARPC version is missing", async () => {
7879
const doc = { ...rpcDocument };
7980
// @ts-expect-error Missing required yarpc version
8081
delete doc.yarpc;
81-
const transformer = new DocumentTransformer(doc);
82-
await assert.rejects(transformer.transform(), {
82+
await assert.rejects(DocumentTransformer.transform(doc, { logger }), {
8383
message: "Missing required yarpc version",
8484
});
8585
});
@@ -90,8 +90,7 @@ test("DocumentTransformer#transform", async (t) => {
9090
const doc = { ...rpcDocument };
9191
// @ts-expect-error incorrect yarpc version
9292
doc.yarpc = "2.0.0";
93-
const transformer = new DocumentTransformer(doc);
94-
await assert.rejects(transformer.transform(), {
93+
await assert.rejects(DocumentTransformer.transform(doc, { logger }), {
9594
message: "Unsupported YARPC version: 2.0.0",
9695
});
9796
}
@@ -120,8 +119,7 @@ test("DocumentTransformer#transform", async (t) => {
120119
},
121120
};
122121

123-
const transformer = new DocumentTransformer(doc);
124-
const actual = await transformer.transform();
122+
const actual = await DocumentTransformer.transform(doc, { logger });
125123
assert.deepStrictEqual(actual, expected);
126124
});
127125

@@ -136,8 +134,7 @@ test("DocumentTransformer#transform", async (t) => {
136134
},
137135
};
138136

139-
const transformer = new DocumentTransformer(doc);
140-
const actual = await transformer.transform();
137+
const actual = await DocumentTransformer.transform(doc, { logger });
141138
assert.deepStrictEqual(actual, oasDocument);
142139
});
143140

@@ -174,8 +171,7 @@ test("DocumentTransformer#transform", async (t) => {
174171
},
175172
};
176173

177-
const transformer = new DocumentTransformer(doc);
178-
const actual = await transformer.transform();
174+
const actual = await DocumentTransformer.transform(doc, { logger });
179175
assert.deepStrictEqual(actual, expected);
180176
});
181177
});

packages/cli/src/transformers/document-transformer.ts

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import intersect from "just-intersect";
2-
import { getDefaultResolver } from "../resolver.js";
2+
import { IRefResolver, getDefaultRefResolver } from "../resolver.js";
33
import {
44
Logger,
55
OASDocument,
@@ -8,26 +8,39 @@ import {
88
} from "../types/index.js";
99
import { OperationTransformer } from "./operation-transformer.js";
1010

11+
type TransformerOptions = {
12+
logger?: Logger;
13+
/** specify a different schema $ref resolver */
14+
refResolver?: IRefResolver;
15+
/** use this operation transformer instead */
16+
operationTransformer?: OperationTransformer;
17+
};
18+
1119
/**
1220
* Transforms an RPC document into an OAS document
1321
*/
1422
export class DocumentTransformer {
15-
static transform(doc: RPCDocument): Promise<OASDocument> {
16-
return new DocumentTransformer(doc).transform();
23+
static transform(
24+
doc: RPCDocument,
25+
opts: TransformerOptions = {}
26+
): Promise<OASDocument> {
27+
return new DocumentTransformer(doc, opts).transform();
1728
}
1829

1930
private doc: RPCDocument;
20-
private transformer: OperationTransformer;
21-
private logger: Logger;
31+
private operationTransformer: OperationTransformer;
32+
private logger?: Logger;
2233

23-
constructor(doc: RPCDocument) {
34+
constructor(doc: RPCDocument, opts: TransformerOptions = {}) {
2435
this.doc = doc;
25-
this.logger = console;
26-
27-
this.transformer = new OperationTransformer({
28-
logger: this.logger,
29-
resolver: getDefaultResolver(doc),
30-
});
36+
this.logger = opts.logger;
37+
38+
this.operationTransformer =
39+
opts.operationTransformer ??
40+
new OperationTransformer({
41+
logger: this.logger ?? console,
42+
resolver: opts.refResolver ?? getDefaultRefResolver(doc),
43+
});
3144
}
3245

3346
async transform(): Promise<OASDocument> {
@@ -53,14 +66,21 @@ export class DocumentTransformer {
5366
};
5467
}
5568

56-
async rpcToPaths(rpc: RPCDocument["operations"]): Promise<PathsObject> {
69+
private async rpcToPaths(
70+
rpc: RPCDocument["operations"]
71+
): Promise<PathsObject> {
5772
const paths: PathsObject = {};
5873

5974
for (const [operationId, operation] of Object.entries(rpc.queries ?? {})) {
6075
const httpMethod = operation.method ?? "get";
76+
const path = operation.path ?? `/queries/${operationId}`;
77+
this.logger?.debug(
78+
{ operationId, httpMethod, operation, path },
79+
"Transforming query"
80+
);
6181

62-
paths[operation.path ?? `/queries/${operationId}`] = {
63-
[httpMethod]: await this.transformer.transformQueryOperation(
82+
paths[path] = {
83+
[httpMethod]: await this.operationTransformer.transformQueryOperation(
6484
operationId,
6585
operation
6686
),
@@ -71,19 +91,30 @@ export class DocumentTransformer {
7191
rpc.mutations ?? {}
7292
)) {
7393
const httpMethod = operation.method ?? "post";
94+
const path = operation.path ?? `/mutations/${operationId}`;
95+
this.logger?.debug(
96+
{ operationId, httpMethod, operation, path },
97+
"Transforming mutation"
98+
);
7499

75-
paths[operation.path ?? `/mutations/${operationId}`] = {
76-
[httpMethod]: await this.transformer.transformMutationOperation(
77-
operationId,
78-
operation
79-
),
100+
paths[path] = {
101+
[httpMethod]:
102+
await this.operationTransformer.transformMutationOperation(
103+
operationId,
104+
operation
105+
),
80106
};
81107
}
82108

83109
return paths;
84110
}
85111

86-
deepMergePaths(paths: PathsObject, otherPaths: PathsObject): PathsObject {
112+
private deepMergePaths(
113+
paths: PathsObject,
114+
otherPaths: PathsObject
115+
): PathsObject {
116+
this.logger?.debug({ paths, otherPaths }, "Merging paths");
117+
87118
const mergedPaths: PathsObject = {
88119
...paths,
89120
};
@@ -101,7 +132,7 @@ export class DocumentTransformer {
101132
Object.keys(mergedPaths[path])
102133
);
103134
if (duplicate.length > 0) {
104-
this.logger.warn(
135+
this.logger?.warn(
105136
{ path, duplicate },
106137
"Duplicate operation(s) will be overwritten"
107138
);

packages/cli/src/transformers/operation-transformer.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert";
2-
import { IResolver } from "../resolver.js";
2+
import { IRefResolver } from "../resolver.js";
33
import {
44
Logger,
55
MutationOperationObject,
@@ -23,13 +23,13 @@ const DEFAULT_RESPONSE: ResponseObject = {
2323
};
2424

2525
type TransformerDeps = {
26-
logger: Logger;
27-
resolver: IResolver;
26+
resolver: IRefResolver;
27+
logger?: Logger;
2828
};
2929

3030
export class OperationTransformer {
31-
resolver: IResolver;
32-
logger: Logger;
31+
resolver: IRefResolver;
32+
logger?: Logger;
3333

3434
constructor({ logger, resolver }: TransformerDeps) {
3535
this.logger = logger;
@@ -96,7 +96,7 @@ export class OperationTransformer {
9696
operation: RPCOperationObject,
9797
parameterDefaults: ParameterDefaults
9898
): Promise<OperationObject> {
99-
this.logger.debug(
99+
this.logger?.debug(
100100
{ operationId, operation, parameterDefaults },
101101
`Transforming operation "${operationId}"`
102102
);
@@ -138,7 +138,7 @@ export class OperationTransformer {
138138
input: RPCInputObject,
139139
defaults: ParameterDefaults
140140
): Promise<TransformOutput> {
141-
this.logger.debug({ input, defaults }, "Transforming input");
141+
this.logger?.debug({ input, defaults }, "Transforming input");
142142

143143
const { schema: schemaOrRef, parameters: parameterOverrides = {} } = input;
144144
assert(

packages/cli/src/types/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
export * from "./oas.js";
23
export * from "./operations.js";
34
export * from "./service.js";
45

56
export type Logger = {
6-
debug: (...args: unknown[]) => void;
7-
warn: (...args: unknown[]) => void;
7+
debug: (...args: any[]) => void;
8+
warn: (...args: any[]) => void;
89
};

0 commit comments

Comments
 (0)