Skip to content

Commit c147890

Browse files
committed
feat: locate and read metadata from package.json
1 parent 171597a commit c147890

File tree

3 files changed

+95
-24
lines changed

3 files changed

+95
-24
lines changed

packages/openapi-generator/README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ of the Typescript file passed as an input parameter. The OpenAPI specification w
1616
written to stdout.
1717

1818
```
19+
ARGUMENTS:
20+
<file> - API route definition file
21+
1922
OPTIONS:
20-
--name, -n <str> - API name [optional]
21-
--version, -v <str> - API version [optional]
23+
--name, -n <str> - API name [optional]
24+
--version, -v <str> - API version [optional]
25+
--codec-file, -c <str> - Custom codec definition file [optional]
2226
2327
FLAGS:
2428
--internal, -i - include routes marked private
@@ -30,3 +34,34 @@ For example:
3034
```shell
3135
npx openapi-generator src/index.ts
3236
```
37+
38+
## Custom codec file
39+
40+
`openapi-generator` only reads files in the specified package, and stops at the module
41+
boundary. This allows it to work even without `node_modules` installed. It has built-in
42+
support for `io-ts`, `io-ts-types`, and `@api-ts/io-ts-http` imports. If your package
43+
imports codecs from another external library, then you will have to define them in a
44+
custom configuration file so that `openapi-generator` will understand them. To do so,
45+
create a JS file with the following format:
46+
47+
```typescript
48+
module.exports = (E) => {
49+
return {
50+
'io-ts-bigint': {
51+
BigIntFromString: () => E.right({ type: 'primitive', value: 'string' }),
52+
NonZeroBigInt: () => E.right({ type: 'primitive', value: 'number' }),
53+
NonZeroBigIntFromString: () => E.right({ type: 'primitive', value: 'string' }),
54+
NegativeBigIntFromString: () => E.right({ type: 'primitive', value: 'string' }),
55+
NonNegativeBigIntFromString: () =>
56+
E.right({ type: 'primitive', value: 'string' }),
57+
PositiveBigIntFromString: () => E.right({ type: 'primitive', value: 'string' }),
58+
},
59+
// ... and so on for other packages
60+
};
61+
};
62+
```
63+
64+
The input parameter `E` is the namespace import of `fp-ts/Either` (so that trying to
65+
`require` it from the config file isn't an issue), and the return type is a `Record`
66+
containing AST definitions for external libraries.
67+
[Refer to KNOWN_IMPORTS here for info on the structure](./src/knownImports.ts)

packages/openapi-generator/src/cli.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { getRefs } from './ref';
1919
import { convertRoutesToOpenAPI } from './openapi';
2020
import type { Route } from './route';
2121
import type { Schema } from './ir';
22+
import { getPackageJsonPath } from './packageInfo';
2223
import { Project } from './project';
2324
import { KNOWN_IMPORTS } from './knownImports';
2425
import { findSymbolInitializer } from './resolveInit';
@@ -33,34 +34,16 @@ const app = command({
3334
displayName: 'file',
3435
}),
3536
name: option({
36-
type: string,
37+
type: optional(string),
3738
description: 'API name',
3839
long: 'name',
3940
short: 'n',
40-
defaultValue: () => {
41-
const pkgFile = p.join(process.cwd(), 'package.json');
42-
try {
43-
const pkgJson = fs.readFileSync(pkgFile, 'utf-8');
44-
return JSON.parse(pkgJson)['name'] ?? 'openapi-generator';
45-
} catch (err) {
46-
return 'openapi-generator';
47-
}
48-
},
4941
}),
5042
version: option({
51-
type: string,
43+
type: optional(string),
5244
description: 'API version',
5345
long: 'version',
5446
short: 'v',
55-
defaultValue: () => {
56-
const pkgFile = p.join(process.cwd(), 'package.json');
57-
try {
58-
const pkgJson = fs.readFileSync(pkgFile, 'utf-8');
59-
return JSON.parse(pkgJson)['version'] ?? '0.0.1';
60-
} catch (err) {
61-
return '0.0.1';
62-
}
63-
},
6447
}),
6548
includeInternal: flag({
6649
type: boolean,
@@ -74,12 +57,35 @@ const app = command({
7457
description: 'Custom codec definition file',
7558
long: 'codec-file',
7659
short: 'c',
77-
defaultValue: () => undefined,
7860
}),
7961
},
80-
handler: async ({ input, name, version, codecFile }) => {
62+
handler: async ({
63+
input,
64+
name: nameParam,
65+
version: versionParam,
66+
codecFile: codecFileParam,
67+
}) => {
8168
const filePath = p.resolve(input);
8269

70+
const packageJsonPath = await getPackageJsonPath(filePath);
71+
let packageJson: Record<string, any> = {};
72+
if (packageJsonPath !== undefined) {
73+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
74+
}
75+
76+
const name = nameParam ?? packageJson['name'] ?? 'openapi-generator';
77+
const version = versionParam ?? packageJson['version'] ?? '0.0.1';
78+
79+
let codecFile: string | undefined = codecFileParam;
80+
if (
81+
codecFileParam === undefined &&
82+
packageJsonPath !== undefined &&
83+
packageJson['openapi-generator']?.['codec-file'] !== undefined
84+
) {
85+
const relativeCodecFilePath = packageJson['openapi-generator']['codec-file'];
86+
codecFile = p.join(p.dirname(packageJsonPath), relativeCodecFilePath);
87+
}
88+
8389
let knownImports = KNOWN_IMPORTS;
8490
if (codecFile !== undefined) {
8591
const codecFilePath = p.resolve(codecFile);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as fs from 'fs/promises';
2+
import * as p from 'path';
3+
4+
export async function getPackageJsonPath(
5+
entryPoint: string,
6+
): Promise<string | undefined> {
7+
let dir = p.dirname(entryPoint);
8+
while (dir !== '/') {
9+
const pkgJsonPath = p.join(dir, 'package.json');
10+
try {
11+
const pkgJson = await fs.stat(pkgJsonPath);
12+
if (pkgJson !== undefined) {
13+
return pkgJsonPath;
14+
}
15+
} catch (e: any) {
16+
if (e.code === 'ENOENT') {
17+
const parentDir = p.dirname(dir);
18+
if (parentDir === dir) {
19+
// This is the root directory
20+
break;
21+
}
22+
dir = parentDir;
23+
continue;
24+
} else {
25+
throw e;
26+
}
27+
}
28+
}
29+
return undefined;
30+
}

0 commit comments

Comments
 (0)