Skip to content

Commit fe1bd18

Browse files
committed
feat: explicit init outdoc as default
1 parent 481ed9e commit fe1bd18

File tree

5 files changed

+144
-105
lines changed

5 files changed

+144
-105
lines changed

bin/outdoc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const main = async () => {
1616
.option('-t, --title <string>', 'title of the api document, default: API Document')
1717
.option('-v, --version <string>', 'version of the api document, default: 1.0.0')
1818
.option('-e, --email <string>', 'contact information')
19+
.option('-f, --force', 'without importing the library in your application')
1920
.parse();
2021

2122
const args = program.args;

src/APIGenerator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export default class APIGenerator {
4848
pathItem: OpenAPIV3_1.PathItemObject
4949
): void {
5050
Object.keys(pathItem).forEach(key => {
51-
pathItem[key as OpenAPIV3_1.HttpMethods]!.tags = [tag]
52-
})
51+
pathItem[key as OpenAPIV3_1.HttpMethods]!.tags = [tag];
52+
});
5353
}
5454

5555
public static async generate (

src/RequestHook.ts

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import {
2-
PREFIX_RESPONSE_BODY_DATA,
3-
PREFIX_SERVER_RESPONSE
4-
} from './constants';
51
import type {
62
ObjectForResBodyBufferItem,
73
ServerResponseArg
@@ -30,72 +26,8 @@ export default class RequestHook {
3026

3127
public static getInjectedCodes (): string {
3228
return `
33-
const async_hooks = require('async_hooks')
34-
const asyncHook = async_hooks.createHook({
35-
init: (asyncId, type, triggerAsyncId, resource) => {
36-
if (type === "TickObject" && resource.args) {
37-
const className = resource.args?.[0]?.constructor.name;
38-
39-
// Response body data
40-
if (className === "Object") {
41-
const arg = resource.args[0]
42-
if (arg?.stream?.server && arg?.state?.buffered) {
43-
const dataItem = arg.state.buffered.find(item => {
44-
if (!item) return false
45-
return ['buffer', 'utf-8'].includes(item.encoding)
46-
})
47-
if (dataItem) {
48-
const chunk = dataItem.encoding === 'buffer'
49-
? dataItem.chunk.toString()
50-
: dataItem.chunk
51-
const res = {
52-
asyncId,
53-
data: {
54-
encoding: dataItem.encoding,
55-
chunk
56-
}
57-
}
58-
console.log("${PREFIX_RESPONSE_BODY_DATA}" + JSON.stringify(res))
59-
}
60-
}
61-
}
62-
63-
// Server response
64-
if (className === "ServerResponse") {
65-
const arg = resource.args[0];
66-
const res = {
67-
triggerAsyncId,
68-
data: {
69-
_header: arg._header,
70-
statusCode: arg.statusCode,
71-
statusMessage: arg.statusMessage,
72-
req: {
73-
rawHeaders: arg.req.rawHeaders,
74-
url: arg.req.url,
75-
method: arg.req.method,
76-
params: arg.req.params,
77-
query: arg.req.query,
78-
baseUrl: arg.req.baseUrl,
79-
originalUrl: arg.req.originalUrl,
80-
body: arg.req.body
81-
}
82-
}
83-
}
84-
if (arg.req._readableState?.buffer?.head?.data) {
85-
res.data.req._readableState = {
86-
buffer: {
87-
head: {
88-
data: arg.req._readableState.buffer.head.data.toString()
89-
}
90-
}
91-
}
92-
}
93-
console.log("${PREFIX_SERVER_RESPONSE}" + JSON.stringify(res))
94-
}
95-
}
96-
}
97-
});
98-
asyncHook.enable();
29+
const { OutDoc } = require('outdoc');
30+
OutDoc.init();
9931
`;
10032
}
10133

src/index.ts

Lines changed: 138 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawn } from 'child_process';
22
import fsPromises from 'fs/promises';
33
import { existsSync } from 'fs';
4+
import async_hooks from 'async_hooks';
45

56
import {
67
PREFIX_RESPONSE_BODY_DATA,
@@ -10,14 +11,119 @@ import RequestHook from './RequestHook';
1011
import APICollector from './APICollector';
1112
import APIGenerator from './APIGenerator';
1213

13-
/**
14-
* 1. Copy origin server file to be a temporary file
15-
* 2. Inject async_hook to the new server file
16-
* 3. Spawn a child process to run the test
17-
* 4. In child process.stdout to get the req/res data
18-
* 5. Save these req/res data into APICollector
19-
* 6. In child process close, get original server file back and generate api doc
20-
*/
14+
import type {
15+
ObjectForResBodyArg,
16+
ServerResponseArg,
17+
ObjectForResBodyBufferItem
18+
} from './types/asyncEventTypes';
19+
20+
type resBodyType = {
21+
asyncId: number,
22+
data: ObjectForResBodyBufferItem
23+
}
24+
25+
type serverResType = {
26+
triggerAsyncId: number,
27+
data: ServerResponseArg | {
28+
req: {
29+
_readableState?: {
30+
buffer: {
31+
head: {
32+
data: string
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
const rmTmpFileAndGetOriginalBack = async (
41+
tmpFilePath: string,
42+
mainFilePath: string
43+
) => {
44+
if (existsSync(tmpFilePath)) {
45+
await fsPromises.copyFile(tmpFilePath, mainFilePath);
46+
await fsPromises.rm(tmpFilePath);
47+
}
48+
};
49+
50+
export class OutDoc {
51+
public static init (): void {
52+
const asyncHook = async_hooks.createHook({
53+
init: (
54+
asyncId: number,
55+
type: string,
56+
triggerAsyncId: number,
57+
resource: {
58+
args: Array<ObjectForResBodyArg | ServerResponseArg>
59+
}
60+
) => {
61+
if (type === "TickObject" && resource.args) {
62+
const className = resource.args?.[0]?.constructor.name;
63+
64+
// Response body data
65+
if (className === "Object") {
66+
const arg = resource.args[0] as ObjectForResBodyArg;
67+
if (arg?.stream?.server && arg?.state?.buffered) {
68+
const dataItem = arg.state.buffered.find(item => {
69+
if (!item) return false;
70+
return ['buffer', 'utf-8'].includes(item.encoding);
71+
});
72+
if (dataItem) {
73+
const chunk = dataItem.encoding === 'buffer'
74+
? dataItem.chunk.toString()
75+
: dataItem.chunk;
76+
const res: resBodyType = {
77+
asyncId,
78+
data: {
79+
encoding: dataItem.encoding,
80+
chunk
81+
}
82+
};
83+
console.log(PREFIX_RESPONSE_BODY_DATA + JSON.stringify(res));
84+
}
85+
}
86+
}
87+
88+
// Server response
89+
if (className === "ServerResponse") {
90+
const arg = resource.args[0] as ServerResponseArg;
91+
const res: serverResType = {
92+
triggerAsyncId,
93+
data: {
94+
_header: arg._header,
95+
statusCode: arg.statusCode,
96+
statusMessage: arg.statusMessage,
97+
req: {
98+
rawHeaders: arg.req.rawHeaders,
99+
url: arg.req.url,
100+
method: arg.req.method,
101+
params: arg.req.params,
102+
query: arg.req.query,
103+
baseUrl: arg.req.baseUrl,
104+
originalUrl: arg.req.originalUrl,
105+
body: arg.req.body
106+
}
107+
}
108+
};
109+
if (arg.req._readableState?.buffer?.head?.data) {
110+
res.data.req._readableState = {
111+
buffer: {
112+
head: {
113+
data: arg.req._readableState.buffer.head.data.toString()
114+
}
115+
}
116+
};
117+
}
118+
console.log(PREFIX_SERVER_RESPONSE + JSON.stringify(res));
119+
}
120+
}
121+
}
122+
});
123+
asyncHook.enable();
124+
}
125+
}
126+
21127
export async function runner (
22128
args: Array<string>,
23129
options: Record<string, string>
@@ -26,21 +132,26 @@ export async function runner (
26132
throw new Error('No arguments found');
27133
}
28134

29-
const projectCWD = process.cwd();
30-
const packageJSONStr = await fsPromises.readFile(projectCWD + '/package.json', 'utf8');
31-
const packageJSON = JSON.parse(packageJSONStr);
32-
const mainFilePath = packageJSON?.outdoc?.main || packageJSON?.main;
33-
if (!mainFilePath) throw new Error('Please define main or outdoc.main in package.json');
34-
35-
const mainFileAbsolutePath = projectCWD + "/" + mainFilePath;
36-
const tmpFileAbsoluteath = projectCWD + "/outdoc_tmp_file";
37135
const apiCollector = new APICollector();
38136
const requestHook = new RequestHook(apiCollector);
137+
let mainFileAbsolutePath: string;
138+
let tmpFileAbsoluteath: string;
139+
140+
if (options.force) {
141+
const projectCWD = process.cwd();
142+
const packageJSONStr = await fsPromises.readFile(projectCWD + '/package.json', 'utf8');
143+
const packageJSON = JSON.parse(packageJSONStr);
144+
const mainFilePath = packageJSON?.outdoc?.main || packageJSON?.main;
145+
if (!mainFilePath) throw new Error('Please define main or outdoc.main in package.json');
146+
147+
mainFileAbsolutePath = projectCWD + "/" + mainFilePath;
148+
tmpFileAbsoluteath = projectCWD + "/outdoc_tmp_file";
39149

40-
const injectedCodes = RequestHook.getInjectedCodes();
41-
await fsPromises.copyFile(mainFileAbsolutePath, projectCWD + "/outdoc_tmp_file");
42-
await fsPromises.writeFile(mainFileAbsolutePath, injectedCodes, { flag: "a" });
43-
await fsPromises.appendFile(mainFileAbsolutePath, "// @ts-nocheck");
150+
const injectedCodes = RequestHook.getInjectedCodes();
151+
await fsPromises.copyFile(mainFileAbsolutePath, projectCWD + "/outdoc_tmp_file");
152+
await fsPromises.writeFile(mainFileAbsolutePath, injectedCodes, { flag: "a" });
153+
await fsPromises.appendFile(mainFileAbsolutePath, "// @ts-nocheck");
154+
}
44155

45156
const childProcess = spawn(args[0], args.slice(1), {
46157
detached: true,
@@ -85,7 +196,10 @@ export async function runner (
85196
});
86197

87198
childProcess.on('close', async (code) => {
88-
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
199+
if (options.force) {
200+
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
201+
}
202+
89203
if (code === 0) {
90204
try {
91205
await APIGenerator.generate(
@@ -107,16 +221,8 @@ export async function runner (
107221
});
108222

109223
process.on('SIGINT', async () => {
110-
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
224+
if (options.force) {
225+
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
226+
}
111227
});
112228
}
113-
114-
const rmTmpFileAndGetOriginalBack = async (
115-
tmpFilePath: string,
116-
mainFilePath: string
117-
) => {
118-
if (existsSync(tmpFilePath)) {
119-
await fsPromises.copyFile(tmpFilePath, mainFilePath);
120-
await fsPromises.rm(tmpFilePath);
121-
}
122-
};

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"target": "ES5",
55
"sourceMap": false,
66
"outDir": "lib",
7-
"declaration": false,
7+
"declaration": true,
88
"moduleResolution": "node",
99
"removeComments": true,
1010
"esModuleInterop": true,

0 commit comments

Comments
 (0)