forest-cli
Version:
The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.
178 lines • 16.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const abstract_authenticated_command_1 = __importDefault(require("./abstract-authenticated-command"));
const options_1 = require("./services/projects/create/options");
class AbstractProjectCreateCommand extends abstract_authenticated_command_1.default {
constructor(argv, config, plan) {
super(argv, config, plan);
// Whether the project is backed by a database. When false (e.g. the demo
// project on a dummy datasource), all DB steps (dialect requirement, connection
// test, introspection) are skipped.
this.requiresDatabase = true;
const { assertPresent, eventSender, optionParser, projectCreator, database, messages, spinner, databaseAnalyzer, } = this.context;
assertPresent({
eventSender,
optionParser,
projectCreator,
database,
messages,
spinner,
databaseAnalyzer,
});
this.eventSender = eventSender;
this.optionParser = optionParser;
this.projectCreator = projectCreator;
this.database = database;
this.messages = messages;
this.spinner = spinner;
this.databaseAnalyzer = databaseAnalyzer;
}
async runAuthenticated() {
try {
const { appConfig, dbConfig, language, meta, authenticationToken } = await this.getConfig();
this.spinner.start({ text: 'Creating your project on Forest Admin' });
const projectCreationPromise = this.projectCreator.create(authenticationToken, appConfig, meta);
const { id, envSecret, authSecret } = await this.spinner.attachToPromise(projectCreationPromise);
this.eventSender.meta.projectId = id;
if (this.requiresDatabase)
await this.testDatabaseConnection(dbConfig);
await this.generateProject({
dbConfig,
appConfig,
forestAuthSecret: authSecret,
forestEnvSecret: envSecret,
language,
});
await this.notifySuccess();
}
catch (error) {
// Display customized error for non-authentication errors.
if (error.status !== 401 && error.status !== 403) {
this.logger.error(['Cannot generate your project.', `${this.messages.ERROR_UNEXPECTED}`]);
this.logger.log(`${this.chalk.red(error)}`);
this.exit(1);
}
else {
throw error;
}
}
}
async generateProject(config) {
const schema = this.requiresDatabase ? await this.analyzeDatabase(config.dbConfig) : undefined;
this.spinner.start({ text: 'Creating your project files' });
const dumpPromise = this.dump(config, schema);
await this.spinner.attachToPromise(dumpPromise);
}
async analyzeDatabase(dbConfig) {
const connection = await this.database.connect(dbConfig);
this.spinner.start({ text: 'Analyzing the database' });
let schemaPromise;
if (dbConfig.dbDialect === 'mongodb') {
// the mongodb analyzer display a progress bar during the analysis
schemaPromise = this.databaseAnalyzer.analyzeMongoDb(connection, dbConfig, true);
}
else {
schemaPromise = this.databaseAnalyzer.analyze(connection, dbConfig, true);
}
const schema = await this.spinner.attachToPromise(schemaPromise);
this.logger.success('Database is analyzed', { lineColor: 'green' });
await this.database.disconnect(connection);
return schema;
}
async getConfig() {
const config = await this.getCommandOptions();
// FIXME: Works as only one instance at execution time. Not ideal.
this.eventSender.command = 'projects:create';
this.eventSender.applicationName = config.applicationName;
if (this.requiresDatabase && !config.databaseDialect && !config.databaseConnectionURL) {
this.logger.error('Missing database dialect option value');
this.exit(1);
}
const appConfig = {
appName: config.applicationName,
appHostname: config.applicationHost,
appPort: Number(config.applicationPort),
};
const dbConfig = {
dbConnectionUrl: config.databaseConnectionURL,
dbDialect: config.databaseDialect,
dbSchema: config.databaseSchema,
dbName: config.databaseName,
dbHostname: config.databaseHost,
dbPort: config.databasePort,
dbSsl: config.databaseSSL,
dbSslMode: config.databaseSslMode,
dbUser: config.databaseUser,
dbPassword: config.databasePassword,
mongodbSrv: config.mongoDBSRV,
};
const meta = {
// FIXME: Remove the condition when the agent v1 command is dropped
agent: this.agent || (dbConfig.dbDialect === 'mongodb' ? 'express-mongoose' : 'express-sequelize'),
dbDialect: dbConfig.dbDialect,
architecture: 'microservice',
isLocal: ['localhost', '127.0.0.1', '::1'].some(keyword => dbConfig.dbHostname
? dbConfig.dbHostname.includes(keyword)
: dbConfig.dbConnectionUrl?.includes(keyword)),
};
// This `await` seems to be useless but removing it breaks the tests.
// Leaving it here for now until we figure out why the mocks are not compatible with the implementation.
const authenticationToken = (await this.authenticator.getAuthToken()) || '';
this.eventSender.sessionToken = authenticationToken;
this.eventSender.meta = meta;
return {
appConfig,
dbConfig,
language: config.language,
meta,
authenticationToken,
};
}
async getCommandOptions() {
const options = await this.optionParser.getCommandLineOptions(this);
// Dialect must be set for the project creator to work even if the connection URL is provided
options.databaseDialect = (0, options_1.getDialect)(options);
// Support both `databaseSSL` and `databaseSslMode` options
if (options.databaseSSL !== undefined && options.databaseSslMode === undefined) {
options.databaseSslMode = options.databaseSSL ? 'preferred' : 'disabled';
}
else if (options.databaseSSL === undefined && options.databaseSslMode !== undefined) {
options.databaseSSL = options.databaseSslMode !== 'disabled';
}
else {
options.databaseSslMode = 'preferred'; // Default is preferred for agent-nodejs
options.databaseSSL = false; // Default is false for forest-express
}
return options;
}
async testDatabaseConnection(dbConfig) {
this.spinner.start({ text: 'Testing connection to your database' });
const connectionPromise = this.database
.connect(dbConfig)
.then(connection => this.database.disconnect(connection));
await this.spinner.attachToPromise(connectionPromise);
}
async notifySuccess() {
this.logger.info(`Hooray, ${this.chalk.green('installation success')}!`);
this.logNextSteps();
await this.eventSender.notifySuccess();
}
// Overridable no-op: subcommands print follow-up guidance here (the demo points to create:sql).
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function -- intentional no-op hook
logNextSteps() { }
}
exports.default = AbstractProjectCreateCommand;
AbstractProjectCreateCommand.args = {
applicationName: core_1.Args.string({
name: 'applicationName',
required: true,
description: 'Name of the project to create.',
}),
};
/** @see https://oclif.io/docs/commands */
AbstractProjectCreateCommand.description = 'Create a new Forest Admin project.';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWJzdHJhY3QtcHJvamVjdC1jcmVhdGUtY29tbWFuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hYnN0cmFjdC1wcm9qZWN0LWNyZWF0ZS1jb21tYW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBYUEsc0NBQW1DO0FBRW5DLHNHQUE0RTtBQUM1RSxnRUFBZ0U7QUFFaEUsTUFBOEIsNEJBQTZCLFNBQVEsd0NBQTRCO0lBaUM3RixZQUFZLElBQWMsRUFBRSxNQUFtQixFQUFFLElBQUk7UUFDbkQsS0FBSyxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFqQjVCLHlFQUF5RTtRQUN6RSxnRkFBZ0Y7UUFDaEYsb0NBQW9DO1FBQ2pCLHFCQUFnQixHQUFZLElBQUksQ0FBQztRQWdCbEQsTUFBTSxFQUNKLGFBQWEsRUFDYixXQUFXLEVBQ1gsWUFBWSxFQUNaLGNBQWMsRUFDZCxRQUFRLEVBQ1IsUUFBUSxFQUNSLE9BQU8sRUFDUCxnQkFBZ0IsR0FDakIsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBRWpCLGFBQWEsQ0FBQztZQUNaLFdBQVc7WUFDWCxZQUFZO1lBQ1osY0FBYztZQUNkLFFBQVE7WUFDUixRQUFRO1lBQ1IsT0FBTztZQUNQLGdCQUFnQjtTQUNqQixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsV0FBVyxHQUFHLFdBQVcsQ0FBQztRQUMvQixJQUFJLENBQUMsWUFBWSxHQUFHLFlBQVksQ0FBQztRQUNqQyxJQUFJLENBQUMsY0FBYyxHQUFHLGNBQWMsQ0FBQztRQUNyQyxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUM7SUFDM0MsQ0FBQztJQUVTLEtBQUssQ0FBQyxnQkFBZ0I7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