Skip to content

Commit 8439cf4

Browse files
committed
feat: using stderr as process communication and clean export file
1 parent fe1bd18 commit 8439cf4

File tree

4 files changed

+135
-124
lines changed

4 files changed

+135
-124
lines changed

bin/outdoc.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'use strict';
44

55
const { Command } = require('commander');
6-
const { runner } = require('../lib/index');
6+
const { runner } = require('../lib/runner');
77

88
const main = async () => {
99
const program = new Command();
@@ -12,7 +12,7 @@ const main = async () => {
1212
.name('outdoc')
1313
.description('Generate OpenAPI document from local testing')
1414
.usage("[command running test] [options]")
15-
.option('-o, --output', 'file path of the generated doc, format supports json and yaml, default: api.yaml')
15+
.option('-o, --output <string>', 'file path of the generated doc, format supports json and yaml, default: api.yaml')
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')
@@ -21,7 +21,6 @@ const main = async () => {
2121

2222
const args = program.args;
2323
const opts = program.opts();
24-
2524
await runner(args, opts);
2625
};
2726

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.2.0",
44
"description": "Auto-generate OpenAPI document for Node.js service from the local testing",
55
"main": "lib/index.js",
6+
"types": "lib/index.d.ts",
67
"bin": {
78
"outdoc": "./bin/outdoc.js"
89
},

src/index.ts

+3-121
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import { spawn } from 'child_process';
2-
import fsPromises from 'fs/promises';
3-
import { existsSync } from 'fs';
41
import async_hooks from 'async_hooks';
52

63
import {
74
PREFIX_RESPONSE_BODY_DATA,
85
PREFIX_SERVER_RESPONSE
96
} from './constants';
10-
import RequestHook from './RequestHook';
11-
import APICollector from './APICollector';
12-
import APIGenerator from './APIGenerator';
137

148
import type {
159
ObjectForResBodyArg,
@@ -37,16 +31,6 @@ type serverResType = {
3731
}
3832
}
3933

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-
5034
export class OutDoc {
5135
public static init (): void {
5236
const asyncHook = async_hooks.createHook({
@@ -80,7 +64,7 @@ export class OutDoc {
8064
chunk
8165
}
8266
};
83-
console.log(PREFIX_RESPONSE_BODY_DATA + JSON.stringify(res));
67+
process.stderr.write(PREFIX_RESPONSE_BODY_DATA + JSON.stringify(res) + "\n");
8468
}
8569
}
8670
}
@@ -115,114 +99,12 @@ export class OutDoc {
11599
}
116100
};
117101
}
118-
console.log(PREFIX_SERVER_RESPONSE + JSON.stringify(res));
102+
103+
process.stderr.write(PREFIX_SERVER_RESPONSE + JSON.stringify(res) + "\n");
119104
}
120105
}
121106
}
122107
});
123108
asyncHook.enable();
124109
}
125110
}
126-
127-
export async function runner (
128-
args: Array<string>,
129-
options: Record<string, string>
130-
): Promise<void> {
131-
if (args.length === 0) {
132-
throw new Error('No arguments found');
133-
}
134-
135-
const apiCollector = new APICollector();
136-
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";
149-
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-
}
155-
156-
const childProcess = spawn(args[0], args.slice(1), {
157-
detached: true,
158-
stdio: ["inherit", "pipe", "inherit"]
159-
});
160-
161-
childProcess.stdout.on('data', (data) => {
162-
const dataStr = data.toString();
163-
164-
if (dataStr.startsWith(PREFIX_RESPONSE_BODY_DATA)) {
165-
try {
166-
const res = JSON.parse(dataStr.substr(PREFIX_RESPONSE_BODY_DATA.length));
167-
if (res.data?.encoding === 'buffer') {
168-
res.data.chunk = new Buffer(res.data.chunk);
169-
}
170-
requestHook.handleResponseBodyData(res);
171-
} catch (err) {
172-
if (err instanceof Error) {
173-
process.stderr.write(err.message);
174-
}
175-
}
176-
return;
177-
}
178-
179-
if (dataStr.startsWith(PREFIX_SERVER_RESPONSE)) {
180-
try {
181-
const res = JSON.parse(dataStr.substr(PREFIX_SERVER_RESPONSE.length));
182-
if (res.data?.req?._readableState) {
183-
const headData = res.data.req._readableState.buffer.head.data;
184-
res.data.req._readableState.buffer.head.data = new Buffer(headData);
185-
}
186-
requestHook.handleServerResponse(res);
187-
} catch (err) {
188-
if (err instanceof Error) {
189-
process.stderr.write(err.message);
190-
}
191-
}
192-
return;
193-
}
194-
195-
process.stdout.write(data.toString());
196-
});
197-
198-
childProcess.on('close', async (code) => {
199-
if (options.force) {
200-
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
201-
}
202-
203-
if (code === 0) {
204-
try {
205-
await APIGenerator.generate(
206-
apiCollector,
207-
{
208-
output: options.output,
209-
title: options.title,
210-
version: options.version,
211-
email: options.email
212-
}
213-
);
214-
console.log('Generate API document success');
215-
} catch (err) {
216-
let message = "";
217-
if (err instanceof Error) message = err.message;
218-
console.log('Generate API document failed: ', message);
219-
}
220-
}
221-
});
222-
223-
process.on('SIGINT', async () => {
224-
if (options.force) {
225-
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
226-
}
227-
});
228-
}

src/runner.ts

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { spawn } from 'child_process';
2+
import fsPromises from 'fs/promises';
3+
import { existsSync } from 'fs';
4+
5+
import {
6+
PREFIX_RESPONSE_BODY_DATA,
7+
PREFIX_SERVER_RESPONSE
8+
} from './constants';
9+
10+
import RequestHook from './RequestHook';
11+
import APICollector from './APICollector';
12+
import APIGenerator from './APIGenerator';
13+
14+
const rmTmpFileAndGetOriginalBack = async (
15+
tmpFilePath: string,
16+
mainFilePath: string
17+
) => {
18+
if (existsSync(tmpFilePath)) {
19+
await fsPromises.copyFile(tmpFilePath, mainFilePath);
20+
await fsPromises.rm(tmpFilePath);
21+
}
22+
};
23+
24+
export async function runner (
25+
args: Array<string>,
26+
options: Record<string, string>
27+
): Promise<void> {
28+
if (args.length === 0) {
29+
throw new Error('No arguments found');
30+
}
31+
32+
const apiCollector = new APICollector();
33+
const requestHook = new RequestHook(apiCollector);
34+
let mainFileAbsolutePath: string;
35+
let tmpFileAbsoluteath: string;
36+
37+
if (options.force) {
38+
const projectCWD = process.cwd();
39+
const packageJSONStr = await fsPromises.readFile(projectCWD + '/package.json', 'utf8');
40+
const packageJSON = JSON.parse(packageJSONStr);
41+
const mainFilePath = packageJSON?.outdoc?.main || packageJSON?.main;
42+
if (!mainFilePath) throw new Error('Please define main or outdoc.main in package.json');
43+
44+
mainFileAbsolutePath = projectCWD + "/" + mainFilePath;
45+
tmpFileAbsoluteath = projectCWD + "/outdoc_tmp_file";
46+
47+
const injectedCodes = RequestHook.getInjectedCodes();
48+
await fsPromises.copyFile(mainFileAbsolutePath, projectCWD + "/outdoc_tmp_file");
49+
await fsPromises.writeFile(mainFileAbsolutePath, injectedCodes, { flag: "a" });
50+
await fsPromises.appendFile(mainFileAbsolutePath, "// @ts-nocheck");
51+
}
52+
53+
const childProcess = spawn(args[0], args.slice(1), {
54+
detached: true,
55+
stdio: ["inherit", "inherit", "pipe"]
56+
});
57+
58+
childProcess.stderr.on('data', (data) => {
59+
data
60+
.toString()
61+
.split("\n")
62+
.filter((dataStr: string) => dataStr.trim())
63+
.forEach((dataStr: string) => {
64+
if (dataStr.startsWith(PREFIX_RESPONSE_BODY_DATA)) {
65+
try {
66+
const res = JSON.parse(dataStr.substr(PREFIX_RESPONSE_BODY_DATA.length));
67+
if (res.data?.encoding === 'buffer') {
68+
res.data.chunk = new Buffer(res.data.chunk);
69+
}
70+
requestHook.handleResponseBodyData(res);
71+
} catch (err) {
72+
if (err instanceof Error) {
73+
process.stdout.write(err.message)
74+
}
75+
}
76+
return;
77+
}
78+
79+
if (dataStr.startsWith(PREFIX_SERVER_RESPONSE)) {
80+
try {
81+
const res = JSON.parse(dataStr.substr(PREFIX_SERVER_RESPONSE.length));
82+
if (res.data?.req?._readableState) {
83+
const headData = res.data.req._readableState.buffer.head.data;
84+
res.data.req._readableState.buffer.head.data = new Buffer(headData);
85+
}
86+
requestHook.handleServerResponse(res);
87+
} catch (err) {
88+
if (err instanceof Error) {
89+
process.stdout.write(err.message);
90+
}
91+
}
92+
return;
93+
}
94+
95+
process.stderr.write(dataStr + "\n");
96+
})
97+
});
98+
99+
childProcess.on('close', async (code) => {
100+
if (options.force) {
101+
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
102+
}
103+
104+
if (code === 0) {
105+
try {
106+
await APIGenerator.generate(
107+
apiCollector,
108+
{
109+
output: options.output,
110+
title: options.title,
111+
version: options.version,
112+
email: options.email
113+
}
114+
);
115+
console.log('Generate API document success');
116+
} catch (err) {
117+
let message = "";
118+
if (err instanceof Error) message = err.message;
119+
console.log('Generate API document failed: ', message);
120+
}
121+
}
122+
});
123+
124+
process.on('SIGINT', async () => {
125+
if (options.force) {
126+
await rmTmpFileAndGetOriginalBack(tmpFileAbsoluteath, mainFileAbsolutePath);
127+
}
128+
});
129+
}

0 commit comments

Comments
 (0)