Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat, introduce an API Server to run commands through HTTP server #7056

Merged
merged 20 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e165e04
(wip) introduce bit server to interact with Bit commands via http req…
davidfirst Jan 14, 2023
67fdce0
Merge branch 'master' into bit-server
davidfirst Jan 17, 2023
a8e71de
Merge branch 'master' into bit-server
davidfirst Jan 18, 2023
0290be2
rename bit-server to api-server
davidfirst Jan 19, 2023
3132574
add missing scopes/harmony/api-server dir
davidfirst Jan 19, 2023
543e542
Merge branch 'bit-server' of https://github.com/teambit/bit into bit-…
davidfirst Jan 20, 2023
2242941
fix conflicts
davidfirst Jan 20, 2023
6a5423b
Merge branch 'master' into bit-server
davidfirst Jan 23, 2023
e37a8ca
change to api-server
davidfirst Jan 23, 2023
89ada53
add missing scopes/harmony/api-server
davidfirst Jan 30, 2023
7797d9a
fix conflicts
davidfirst Jan 30, 2023
a9b052b
fix conflicts
davidfirst Feb 17, 2023
3ef753d
Merge branch 'bit-server' of https://github.com/teambit/bit into bit-…
davidfirst Feb 17, 2023
1f12e3a
change to use express-aspect instead of calling express() pkg directly
davidfirst Feb 17, 2023
db1328a
fix loader, run only on workspace
davidfirst Feb 17, 2023
e3d242e
change to post method, add duration, support args and flags
davidfirst Feb 17, 2023
dc1454d
Merge branch 'master' into bit-server
davidfirst Feb 17, 2023
96d79f1
remove unused parts from the cli aspect
davidfirst Feb 17, 2023
c2505b5
Merge branch 'bit-server' of https://github.com/teambit/bit into bit-…
davidfirst Feb 17, 2023
ae8c9d4
Merge branch 'master' into bit-server
davidfirst Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .bitmap
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@
"mainFile": "index.ts",
"rootDir": "scopes/harmony/bit-error"
},
"api-server": {
"scope": "",
"version": "",
"defaultScope": "teambit.harmony",
"mainFile": "index.ts",
"rootDir": "scopes/harmony/api-server"
},
"builder": {
"scope": "teambit.pipelines",
"version": "0.0.988",
Expand Down Expand Up @@ -2440,4 +2447,4 @@
"rootDir": "scopes/dependencies/yarn"
},
"$schema-version": "15.0.0"
}
}
5 changes: 5 additions & 0 deletions scopes/harmony/api-server/api-server.aspect.ts
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',
});
50 changes: 50 additions & 0 deletions scopes/harmony/api-server/api-server.main.runtime.ts
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;
48 changes: 48 additions & 0 deletions scopes/harmony/api-server/cli.route.ts
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);
}
},
];
}
5 changes: 5 additions & 0 deletions scopes/harmony/api-server/index.ts
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 };
19 changes: 19 additions & 0 deletions scopes/harmony/api-server/server.cmd.ts
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
}
}
2 changes: 2 additions & 0 deletions scopes/harmony/bit/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import { RemoveAspect } from '@teambit/remove';
import { MergeLanesAspect } from '@teambit/merge-lanes';
import { CheckoutAspect } from '@teambit/checkout';
import { APIReferenceAspect } from '@teambit/api-reference';
import { ApiServerAspect } from '@teambit/api-server';
import { ComponentWriterAspect } from '@teambit/component-writer';
import { TrackerAspect } from '@teambit/tracker';
import { MoverAspect } from '@teambit/mover';
Expand Down Expand Up @@ -196,6 +197,7 @@ export const manifestsMap = {
[CheckoutAspect.id]: CheckoutAspect,
[ComponentWriterAspect.id]: ComponentWriterAspect,
[APIReferenceAspect.id]: APIReferenceAspect,
[ApiServerAspect.id]: ApiServerAspect,
[TrackerAspect.id]: TrackerAspect,
[MoverAspect.id]: MoverAspect,
};
Expand Down
4 changes: 2 additions & 2 deletions scopes/harmony/cli/cli.cmd.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line max-classes-per-file
import { Command, CommandOptions } from '@teambit/cli';
import logger from '@teambit/legacy/dist/logger/logger';
import legacyLogger from '@teambit/legacy/dist/logger/logger';
import { handleErrorAndExit } from '@teambit/legacy/dist/cli/handle-errors';
import { loadConsumerIfExist } from '@teambit/legacy/dist/consumer';
import readline from 'readline';
Expand Down Expand Up @@ -46,7 +46,7 @@ export class CliCmd implements Command {
constructor(private cliMain: CLIMain, private docsDomain: string) {}

async report(): Promise<string> {
logger.isDaemon = true;
legacyLogger.isDaemon = true;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
Expand Down
1 change: 1 addition & 0 deletions scopes/workspace/workspace/watch/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export class Watcher {
}
watcher.on('ready', () => {
msgs?.onReady(this.workspace, this.trackDirs, this.verbose);
loader.stop();
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises
watcher.on('change', async (filePath) => {
Expand Down