11import { spawn } from 'child_process' ;
22import fsPromises from 'fs/promises' ;
33import { existsSync } from 'fs' ;
4+ import async_hooks from 'async_hooks' ;
45
56import {
67 PREFIX_RESPONSE_BODY_DATA ,
@@ -10,14 +11,119 @@ import RequestHook from './RequestHook';
1011import APICollector from './APICollector' ;
1112import 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+
21127export 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- } ;
0 commit comments