UNPKG

@loopback/cli

Version:
349 lines (307 loc) 10.4 kB
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved. // Node module: @loopback/cli // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT // no translation: Datasource 'use strict'; const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('datasource-generator'); const chalk = require('chalk'); const path = require('path'); const utils = require('../../lib/utils'); const connectors = require('../../lib/connectors.json'); const g = require('../../lib/globalize'); /** * DataSource Generator -- CLI * * Prompts for a name, connector and connector options. Creates json file * for the DataSource as well as a Class for a user to modify. Also installs the * appropriate connector from npm. */ module.exports = class DataSourceGenerator extends ArtifactGenerator { constructor(args, opts) { super(args, opts); } _setupGenerator() { this.artifactInfo = { type: 'datasource', rootDir: 'src', }; // Datasources are stored in the datasources directory this.artifactInfo.outDir = path.resolve( this.artifactInfo.rootDir, 'datasources', ); const connectorChoices = []; /** * Creating a list of connectors -- and marking them as either supported by * StrongLoop or community. */ Object.values(connectors).forEach(connector => { const support = connector.supportedByStrongLoop ? '(supported by StrongLoop)' : '(provided by community)'; connectorChoices.push({ name: `${connector.description} ${chalk.gray(support)}`, value: connector.name, }); }); this.connectorChoices = connectorChoices; // Add `other` so users can add a connector that isn't part of the list // Though it can be added by creating a PR and adding it to // connectors.json this.connectorChoices.push('other'); return super._setupGenerator(); } setOptions() { // remove not needed .env property if (this.options.config) { delete this.options?.env; } this.artifactInfo.connector = this.options.connector; return super.setOptions(); } /** * Ensure CLI is being run in a LoopBack 4 project. */ checkLoopBackProject() { if (this.shouldExit()) return; return super.checkLoopBackProject(); } /** * Ask for DataSource Name -- Must be unique */ promptArtifactName() { debug('Prompting for artifact name'); if (this.shouldExit()) return false; if (this.artifactInfo.name) { return; } const prompts = [ { type: 'input', name: 'name', // capitalization message: g.f('%s name:', utils.toClassName(this.artifactInfo.type)), when: this.artifactInfo.name === undefined, validate: utils.validateClassName, }, ]; return this.prompt(prompts).then(props => { Object.assign(this.artifactInfo, props); return props; }); } /** * Ask the user to select the connector for the DataSource */ promptConnector() { debug('prompting for datasource connector'); if (this.shouldExit()) return; const prompts = [ { name: 'connector', message: g.f( 'Select the connector for %s: ', chalk.yellow(this.artifactInfo.name), ), type: 'list', default: 'memory', choices: this.connectorChoices, when: this.artifactInfo.connector === undefined, }, ]; return this.prompt(prompts).then(props => { Object.assign(this.artifactInfo, props); return props; }); } /** * If the user selected `other` for connector -- ask the user to provide * `npm` module name for the connector. */ promptCustomConnectorInfo() { if (this.shouldExit()) return; if (this.artifactInfo.connector !== 'other') { debug('custom connector option was not selected'); return; } else { debug('prompting for custom connector'); const prompts = [ { name: 'customConnector', message: g.f("Enter the connector's package name:"), validate: utils.validate, }, ]; return this.prompt(prompts).then(props => { this.artifactInfo.connector = props.customConnector; return props; }); } } /** * Prompt the user for connector specific settings -- only applies to * connectors in the connectors.json list */ promptConnectorConfig() { debug('prompting for connector config'); if (this.shouldExit()) return; // Check to make sure connector is from connectors list (not custom) const settings = (connectors[this.artifactInfo.connector] && connectors[this.artifactInfo.connector]['settings']) || {}; const prompts = []; // Create list of questions to prompt the user Object.entries(settings).forEach(([key, setting]) => { // Set defaults and merge with `setting` to override properties const question = Object.assign( {}, {name: key, message: key, suffix: ':', default: null}, setting, ); /** * Allowed Types: string, number, password, object, array, boolean * Must be converted to inquirer types -- input, confirm, password */ switch ((setting.type || '').toLowerCase()) { case 'string': case 'number': question.type = 'input'; break; case 'object': case 'array': question.type = 'input'; question.validate = utils.validateStringObject(setting.type); break; case 'boolean': question.type = 'confirm'; break; case 'password': break; default: console.warn( g.f( 'Using default input of type input for setting %s as %s is not supported', key, setting.type || undefined, ), ); // Default to input type question.type = 'input'; } prompts.push(question); }); debug(`connector setting questions - ${JSON.stringify(prompts)}`); // If no prompts, we need to return instead of attempting to ask prompts if (!prompts.length) return; debug('prompting the user - length > 0 for questions'); // Ask user for prompts return this.prompt(prompts).then(props => { // Convert user inputs to correct types Object.entries(settings).forEach(([key, setting]) => { switch ((setting.type || '').toLowerCase()) { case 'number': props[key] = Number(props[key]); break; case 'array': case 'object': if (props[key] == null || props[key] === '') { delete props[key]; } else { if (typeof props[key] === 'string') { props[key] = JSON.parse(props[key]); } } break; } }); this.artifactInfo = Object.assign(this.artifactInfo, {settings: props}); }); } /** * Scaffold DataSource related files * super.scaffold() doesn't provide a way to rename files -- don't call it */ scaffold() { // Exit if needed if (this.shouldExit()) return false; // Setting up data for templates this.artifactInfo.className = utils.toClassName(this.artifactInfo.name); this.artifactInfo.fileName = utils.toFileName(this.artifactInfo.name); // prettier-ignore this.artifactInfo.outFile = `${this.artifactInfo.fileName}.datasource.ts`; // Resolved Output Paths. const tsPath = this.destinationPath( this.artifactInfo.outDir, this.artifactInfo.outFile, ); // template path const classTemplatePath = this.templatePath('datasource.ts.ejs'); // Debug Info debug(`this.artifactInfo.name => ${this.artifactInfo.name}`); debug(`this.artifactInfo.className => ${this.artifactInfo.className}`); debug(`this.artifactInfo.fileName => ${this.artifactInfo.fileName}`); debug(`this.artifactInfo.outFile => ${this.artifactInfo.outFile}`); debug(`tsPath => ${tsPath}`); // Data to save to DataSource JSON file const dsConfig = Object.assign( {name: this.artifactInfo.name, connector: this.artifactInfo.connector}, this.artifactInfo.settings, ); // From LB3 if (dsConfig.connector === 'ibm-object-storage') { dsConfig.connector = 'loopback-component-storage'; dsConfig.provider = 'openstack'; dsConfig.useServiceCatalog = true; dsConfig.useInternal = false; dsConfig.keystoneAuthVersion = 'v3'; } this.artifactInfo.dsConfigString = utils.stringifyObject(dsConfig, { // Prevent inlining the config into a single line, e.g. // const config = {name: 'db', connector: 'memory'}; inlineCharacterLimit: 0, }); debug(`datasource configuration code: ${this.artifactInfo.dsConfigString}`); this.copyTemplatedFiles(classTemplatePath, tsPath, this.artifactInfo); } install() { if (this.shouldExit()) return false; debug('install npm dependencies'); const pkgJson = this.packageJson || {}; const deps = pkgJson.get('dependencies') || {}; const pkgs = []; // Connector package. const connector = connectors[this.artifactInfo.connector]; if (connector && connector.package) { if (!deps[connector.package.name]) { pkgs.push( connector.package.name + `${ connector.package.version ? '@' + connector.package.version : '' }`, ); } debug(`npmModule - ${pkgs[0]}`); } else { const connectorName = this.artifactInfo.connector; // Other connectors that are not listed in `connectors.json`. // No install is needed for those in connectors.json but without a // package name as they are built-in connectors if (!deps[connectorName] && !connector) pkgs.push(connectorName); } if (!deps['@loopback/repository']) { pkgs.push('@loopback/repository'); } if (pkgs.length === 0) return; this.pkgManagerInstall(pkgs, { npm: { save: true, }, }); } async end() { await super.end(); } };