-
Notifications
You must be signed in to change notification settings - Fork 940
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat, introduce an API Server to run commands through HTTP server (#7056
- Loading branch information
1 parent
a5a6aa7
commit 7ae80f6
Showing
9 changed files
with
140 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Aspect } from '@teambit/harmony'; | ||
|
||
export const ApiServerAspect = Aspect.create({ | ||
id: 'teambit.harmony/api-server', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { CLIAspect, CLIMain, MainRuntime } from '@teambit/cli'; | ||
import { ExpressAspect, ExpressMain } from '@teambit/express'; | ||
import { Logger, LoggerAspect, LoggerMain } from '@teambit/logger'; | ||
import WorkspaceAspect, { Workspace } from '@teambit/workspace'; | ||
import { ApiServerAspect } from './api-server.aspect'; | ||
import { CLIRoute } from './cli.route'; | ||
import { ServerCmd } from './server.cmd'; | ||
|
||
export class ApiServerMain { | ||
constructor(private workspace: Workspace, private logger: Logger, private express: ExpressMain) {} | ||
|
||
async runApiServer(options: { port: number }) { | ||
const port = options.port || 3000; | ||
await this.express.listen(port); | ||
|
||
this.workspace.watcher | ||
.watchAll({ | ||
preCompile: false, | ||
}) | ||
.catch((err) => { | ||
// don't throw an error, we don't want to break the "run" process | ||
this.logger.error('watcher found an error', err); | ||
}); | ||
|
||
// never ending promise to not exit the process (is there a better way?) | ||
return new Promise(() => { | ||
this.logger.consoleSuccess(`Bit Server is listening on port ${port}`); | ||
}); | ||
} | ||
|
||
static dependencies = [CLIAspect, WorkspaceAspect, LoggerAspect, ExpressAspect]; | ||
static runtime = MainRuntime; | ||
static async provider([cli, workspace, loggerMain, express]: [CLIMain, Workspace, LoggerMain, ExpressMain]) { | ||
const logger = loggerMain.createLogger(ApiServerAspect.id); | ||
const apiServer = new ApiServerMain(workspace, logger, express); | ||
cli.register(new ServerCmd(apiServer)); | ||
|
||
const cliRoute = new CLIRoute(logger, cli); | ||
// register only when the workspace is available. don't register this on a remote-scope, for security reasons. | ||
if (workspace) { | ||
express.register([cliRoute]); | ||
} | ||
|
||
return apiServer; | ||
} | ||
} | ||
|
||
ApiServerAspect.addRuntime(ApiServerMain); | ||
|
||
export default ApiServerMain; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { CLIMain } from '@teambit/cli'; | ||
import prettyTime from 'pretty-time'; | ||
import { Route, Request, Response } from '@teambit/express'; | ||
import { Logger } from '@teambit/logger'; | ||
|
||
/** | ||
* example usage: | ||
* post to http://localhost:3000/api/cli/list | ||
* with the following json as the body | ||
* | ||
{ | ||
"args": ["teambit.workspace"], | ||
"options": { | ||
"ids": true | ||
} | ||
} | ||
*/ | ||
export class CLIRoute implements Route { | ||
constructor(private logger: Logger, private cli: CLIMain) {} | ||
|
||
method = 'post'; | ||
route = '/cli/:cmd'; | ||
|
||
middlewares = [ | ||
async (req: Request, res: Response, next) => { | ||
this.logger.debug(`cli server: got request for ${req.params.cmd}`); | ||
try { | ||
const command = this.cli.getCommand(req.params.cmd); | ||
if (!command) throw new Error(`command "${req.params.cmd}" was not found`); | ||
if (!command.json) throw new Error(`command "${req.params.cmd}" does not have a json method`); | ||
const body = req.body; | ||
const { args, options } = body; | ||
const optsToString = Object.keys(options || {}) | ||
.map((key) => `--${key}`) | ||
.join(' '); | ||
this.logger.console(`started a new command: ${req.params.cmd} ${args.join(' ')} ${optsToString}`); | ||
const startTask = process.hrtime(); | ||
const result = await command?.json(args || [], options || {}); | ||
const duration = prettyTime(process.hrtime(startTask)); | ||
this.logger.consoleSuccess(`command "${req.params.cmd}" had been completed in ${duration}`); | ||
res.json(result); | ||
} catch (err) { | ||
this.logger.consoleFailure(`command "${req.params.cmd}" had failed`); | ||
next(err); | ||
} | ||
}, | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { ApiServerAspect } from './api-server.aspect'; | ||
|
||
export type { ApiServerMain } from './api-server.main.runtime'; | ||
export default ApiServerAspect; | ||
export { ApiServerAspect }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// eslint-disable-next-line max-classes-per-file | ||
import { Command, CommandOptions } from '@teambit/cli'; | ||
import { ApiServerMain } from './api-server.main.runtime'; | ||
|
||
export class ServerCmd implements Command { | ||
name = 'server'; | ||
description = 'EXPERIMENTAL. communicate with bit cli program via http requests'; | ||
alias = ''; | ||
commands: Command[] = []; | ||
group = 'general'; | ||
options = [['p', 'port [port]', 'port to run the server on']] as CommandOptions; | ||
|
||
constructor(private apiServer: ApiServerMain) {} | ||
|
||
async report(args, options: { port: number }): Promise<string> { | ||
await this.apiServer.runApiServer(options); | ||
return 'server is running successfully'; // should never get here, the previous line is blocking | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters