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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user