techdocs: implement serve for the techdocs-cli (#1581)

* feat(techdocs-cli): point techdocs export to localhost:3000/api

* feat(techdocs-cli): add http-proxy package

* feat(techdocs-cli): add react-dev-utils package

* feat(techdocs-cli): implement serve and serve:mkdocs commands

* fix: use [module] prefix convention in console.log

* docs: added commands

* wip

* Fix type errors

Co-authored-by: Sebastian Qvarfordt <s.qvarfordt@gmail.com>
This commit is contained in:
Bilawal Hameed
2020-07-10 16:01:45 +02:00
committed by GitHub
parent 6d19a3a5ba
commit 353cf5a14f
6 changed files with 156 additions and 49 deletions
+81 -39
View File
@@ -13,17 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { spawn, ChildProcess } from 'child_process';
import program from 'commander';
import { version } from './lib/version';
// import chalk from 'chalk';
import { spawn } from 'child_process';
import path from 'path';
// import HTTPServer from './lib/httpServer';
import HTTPServer from './lib/httpServer';
import openBrowser from 'react-dev-utils/openBrowser';
const run = (workingDirectory: string, name: string, args: string[] = []) => {
const child = spawn(name, args, {
const run = (
workingDirectory: string,
name: string,
args: string[] = [],
): ChildProcess => {
const [stdin, stdout, stderr] = [
'inherit' as const,
'pipe' as const,
'inherit' as const,
];
const childProcess = spawn(name, args, {
cwd: workingDirectory,
stdio: ['inherit', 'inherit', 'inherit'],
stdio: [stdin, stdout, stderr],
shell: true,
env: {
...process.env,
@@ -31,56 +42,87 @@ const run = (workingDirectory: string, name: string, args: string[] = []) => {
},
});
child.once('error', error => {
childProcess.once('error', error => {
console.error(error);
childProcess.kill();
});
child.once('exit', code => {
console.log('exited!', code);
childProcess.once('exit', () => {
process.exit(0);
});
return childProcess;
};
const runMkdocsServer = (options?: {
devAddr: string;
}): Promise<ChildProcess> => {
const devAddr = options?.devAddr ?? '0.0.0.0:8000';
return new Promise(resolve => {
const childProcess = run(process.env.PWD!, 'docker', [
'run',
'-it',
'-w',
'/content',
'-v',
'$(pwd):/content',
'-p',
'8000:8000',
'mkdocs:local-dev',
'serve',
'-a',
devAddr,
]);
childProcess.stdout?.on('data', rawData => {
const data = rawData.toString().split('\n')[0];
console.log('[mkdocs] ', data);
if (data.includes(`Serving on http://${devAddr}`)) {
resolve(childProcess);
}
});
});
};
const main = (argv: string[]) => {
program.name('techdocs-cli').version(version);
program
.command('serve:mkdocs')
.description('Serve a documentation project locally')
.action(() => {
runMkdocsServer().then(() => {
openBrowser('http://localhost:8000');
});
});
program
.command('serve')
.description('Serve a documentation project locally')
.action(() => {
// const techdocsPreviewBundlePath = path.join(
// __dirname,
// '..',
// 'dist',
// 'techdocs-preview-bundle',
// );
// Mkdocs server
const mkdocsServer = runMkdocsServer();
// new HTTPServer(techdocsPreviewBundlePath, 3000).serve();
run(process.env.PWD!, 'docker', [
'run',
'-it',
'-w',
'/content',
'-v',
'$(pwd):/content',
'-p',
'8000:8000',
'mkdocs:local-dev',
'serve',
'-a',
'0.0.0.0:8000',
]);
const pluginPath = path.join(
require.resolve('@backstage/plugin-techdocs'),
'..',
// Local Backstage Preview
const techdocsPreviewBundlePath = path.join(
__dirname,
'..',
'dist',
'techdocs-preview-bundle',
);
run(
pluginPath,
path.join(require.resolve('@backstage/cli'), '../../bin/backstage-cli'),
['plugin:serve'],
);
const httpServer = new HTTPServer(techdocsPreviewBundlePath, 3000)
.serve()
.catch(err => {
console.error(err);
mkdocsServer.then(childProcess => childProcess.kill());
});
Promise.all([mkdocsServer, httpServer]).then(() => {
openBrowser('http://localhost:3000/docs/local-dev/');
});
});
program.parse(argv);
+53 -9
View File
@@ -16,20 +16,64 @@
import serveHandler from 'serve-handler';
import http from 'http';
import httpProxy from 'http-proxy';
export default class HTTPServer {
constructor(public dir: string, public port: number) {}
proxyEndpoint: string;
serve() {
const server = http.createServer((request, response) => {
return serveHandler(request, response, {
public: this.dir,
trailingSlash: true,
});
constructor(public dir: string, public port: number) {
this.proxyEndpoint = '/api/';
}
private createProxy() {
const proxy = httpProxy.createProxyServer({
target: 'http://localhost:8000',
});
server.listen(this.port, () => {
console.log('Running at http://localhost:3000');
return (request: http.IncomingMessage): [httpProxy, string] => {
const [, ...pathChunks] =
request.url?.substring(this.proxyEndpoint.length).split('/') ?? [];
const forwardPath = pathChunks.join('/');
return [proxy, forwardPath];
};
}
public async serve(): Promise<http.Server> {
return new Promise<http.Server>((resolve, reject) => {
const proxyHandler = this.createProxy();
const server = http.createServer(
(request: http.IncomingMessage, response: http.ServerResponse) => {
if (request.url?.startsWith(this.proxyEndpoint)) {
const [proxy, forwardPath] = proxyHandler(request);
proxy.on('error', (error: Error) => {
reject(error);
});
request.url = forwardPath;
return proxy.web(request, response);
}
return serveHandler(request, response, {
public: this.dir,
trailingSlash: true,
rewrites: [{ source: '**', destination: 'index.html' }],
});
},
);
server.listen(this.port, () => {
console.log(
'[techdocs-preview-bundle] Running local version of Backstage at http://localhost:3000',
);
resolve(server);
});
server.on('error', (error: Error) => {
reject(error);
});
});
}
}