generator-fedhipster
Version:
Spring Boot + Angular/React in one handy generator
754 lines (670 loc) • 33.3 kB
JavaScript
/**
* Copyright 2013-2019 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const os = require('os');
const exec = require('child_process').exec;
const spawn = require('child_process').spawn;
const execSync = require('child_process').execSync;
const chalk = require('chalk');
const _ = require('lodash');
const BaseGenerator = require('../generator-base');
const statistics = require('../statistics');
const constants = require('../generator-constants');
module.exports = class extends BaseGenerator {
constructor(args, opts) {
super(args, opts);
this.registerPrettierTransform();
}
get initializing() {
return {
sayHello() {
this.log(chalk.bold('Welcome to Google App Engine Generator (Beta)'));
this.warning(chalk.bold('This sub-generator is still in development, please report bugs on Github'));
},
checkInstallation() {
if (this.abort) return;
const done = this.async();
exec('gcloud version', err => {
if (err) {
this.log.error(
"You don't have the Cloud SDK (gcloud) installed. \nDownload it from https://cloud.google.com/sdk/install"
);
this.abort = true;
}
done();
});
},
checkAppEngineJavaComponent() {
if (this.abort) return;
const done = this.async();
const component = 'app-engine-java';
exec('gcloud components list --quiet --filter="Status=Installed" --format="value(id)"', (err, stdout, srderr) => {
if (_.includes(stdout, component)) {
done();
} else {
this.log(chalk.bold('\nInstalling App Engine Java SDK'));
this.log(`... Running: gcloud components install ${component} --quiet`);
const child = spawn('gcloud', ['components', 'install', component, '--quiet'], {
stdio: [process.stdin, process.stdout, process.stderr]
});
child.on('exit', code => {
if (code !== 0) {
this.abort = true;
}
done();
});
}
});
},
loadConfig() {
this.env.options.appPath = this.config.get('appPath') || constants.CLIENT_MAIN_SRC_DIR;
this.baseName = this.config.get('baseName');
this.packageName = this.config.get('packageName');
this.packageFolder = this.config.get('packageFolder');
this.cacheProvider = this.config.get('cacheProvider') || this.config.get('hibernateCache') || 'no';
this.enableHibernateCache =
this.config.get('enableHibernateCache') ||
(this.config.get('hibernateCache') !== undefined && this.config.get('hibernateCache') !== 'no');
this.databaseType = this.config.get('databaseType');
this.prodDatabaseType = this.config.get('prodDatabaseType');
this.searchEngine = this.config.get('searchEngine');
this.angularAppName = this.getAngularAppName();
this.buildTool = this.config.get('buildTool');
this.applicationType = this.config.get('applicationType');
this.serviceDiscoveryType = this.config.get('serviceDiscoveryType');
this.gcpProjectId = this.config.get('gcpProjectId');
this.gcpCloudSqlInstanceName = this.config.get('gcpCloudSqlInstanceName');
this.gcpCloudSqlUserName = this.config.get('gcpCloudSqlUserName');
this.gcpCloudSqlDatabaseName = this.config.get('gcpCloudSqlDatabaseName');
this.gaeServiceName = this.config.get('gaeServiceName');
this.gaeLocation = this.config.get('gaeLocation');
this.gaeInstanceClass = this.config.get('gaeInstanceClass');
this.gaeScalingType = this.config.get('gaeScalingType');
this.gaeInstances = this.config.get('gaeInstances');
this.gaeMaxInstances = this.config.get('gaeMaxInstances');
this.gaeMinInstances = this.config.get('gaeMinInstances');
}
};
}
defaultProjectId() {
if (this.gcpProjectId) {
return this.gcpProjectId;
}
try {
const projectId = execSync('gcloud config get-value core/project --quiet', { encoding: 'utf8' });
return projectId.trim();
} catch (ex) {
this.log.error('Unable to determine the default Google Cloud Project ID');
return undefined;
}
}
defaultServiceNameChoices(defaultServiceExists) {
if (this.applicationType === 'monolith') {
return defaultServiceExists ? ['default', _.kebabCase(this.baseName)] : ['default'];
}
if (this.applicationType === 'gateway') {
return ['default'];
}
return [_.kebabCase(this.baseName)];
}
get prompting() {
return {
askForProjectId() {
if (this.abort) return;
const done = this.async();
const prompts = [
{
type: 'input',
name: 'gcpProjectId',
message: 'Google Cloud Project ID',
default: this.defaultProjectId(),
validate: input => {
if (input.length === 0) {
return 'Project ID cannot empty';
}
try {
execSync(`gcloud projects describe ${input}`);
this.gcpProjectIdExists = true;
} catch (ex) {
this.gcpProjectIdExists = false;
return `Project ID "${chalk.cyan(input)}" does not exist, please create one first!`;
}
return true;
}
}
];
this.prompt(prompts).then(props => {
this.gcpProjectId = props.gcpProjectId;
done();
});
},
askForLocation() {
if (this.abort) return;
const done = this.async();
exec(`gcloud app describe --format="value(locationId)" --project="${this.gcpProjectId}"`, (err, stdout) => {
if (err) {
const prompts = [
{
type: 'list',
name: 'gaeLocation',
message: 'In which Google App Engine location do you want to deploy ?',
choices: [
{ value: 'northamerica-northeast1', name: 'northamerica-northeast1 - Montréal' },
{ value: 'us-central', name: 'us-central - Iowa' },
{ value: 'us-east1', name: 'us-east1 - South Carolina' },
{ value: 'us-east4', name: 'us-east4 - Northern Virginia' },
{ value: 'southamerica-east1', name: 'southamerica-east1 - São Paulo' },
{ value: 'europe-west', name: 'europe-west - Belgium' },
{ value: 'europe-west2', name: 'europe-west2 - London' },
{ value: 'europe-west3', name: 'europe-west3 - Frankfurt' },
{ value: 'asia-northeast1', name: 'asia-northeast1 - Tokyo' },
{ value: 'asia-south1', name: 'asia-south1 - Mumbai' },
{ value: 'australia-southeast1', name: 'australia-southeast1 - Sydney' }
],
default: this.gaeLocation ? this.gaeLocation : 0
}
];
this.prompt(prompts).then(props => {
this.gaeLocation = props.gaeLocation;
this.gaeLocationExists = false;
done();
});
} else {
this.gaeLocationExists = true;
this.gaeLocation = stdout.trim();
this.log(`This project already has an App Engine location set, using location "${chalk.cyan(this.gaeLocation)}"`);
done();
}
});
},
askForServiceName() {
if (this.abort) return;
const done = this.async();
try {
execSync(`gcloud app services describe default --project="${this.gcpProjectId}"`, { encoding: 'utf8' });
this.defaultServiceExists = true;
} catch (ex) {
this.defaultServiceExists = false;
}
const prompts = [
{
type: 'list',
name: 'gaeServiceName',
message: 'Google App Engine Service Name',
choices: this.defaultServiceNameChoices(this.defaultServiceExists),
default: this.gaeServiceName ? this.gaeServiceName : 0
}
];
this.prompt(prompts).then(props => {
this.gaeServiceName = props.gaeServiceName;
done();
});
},
askForInstanceClass() {
if (this.abort) return;
const done = this.async();
const prompts = [
{
type: 'list',
name: 'gaeInstanceClass',
message: 'Google App Engine Instance Class',
choices: [
{ value: 'F1', name: 'F1 - 600MHz, 128MB, Automatic Scaling' },
{ value: 'F2', name: 'F2 - 1.2GHz, 256MB, Automatic Scaling' },
{ value: 'F4', name: 'F4 - 2.4GHz, 512MB, Automatic Scaling' },
{ value: 'F4_1G', name: 'F4_1G - 2.4GHz, 1GB, Automatic' },
{ value: 'B1', name: 'B1 - 600MHz, 128MB, Basic or Manual Scaling' },
{ value: 'B2', name: 'B2 - 1.2GHz, 256MB, Basic or Manual Scaling' },
{ value: 'B4', name: 'B4 - 2.4GHz, 512MB, Basic or Manual Scaling' },
{ value: 'B4_1G', name: 'B4_1G - 2.4GHz, 1GB, Basic or Manual Scaling' },
{ value: 'B8', name: 'B8 - 4.8GHz, 1GB, Basic or Manual Scaling' }
],
default: this.gaeInstanceClass ? this.gaeInstanceClass : 0
}
];
this.prompt(prompts).then(props => {
this.gaeInstanceClass = props.gaeInstanceClass;
done();
});
},
askForScalingType() {
if (this.abort) return;
const done = this.async();
if (this.gaeInstanceClass.startsWith('F')) {
this.log(
`Instance Class "${chalk.cyan(
this.gaeInstanceClass
)}" can only be automatically scaled. Setting scaling type to automatic.`
);
this.gaeScalingType = 'automatic';
done();
} else {
const prompts = [
{
type: 'list',
name: 'gaeScalingType',
message: 'Basic or Manual Scaling',
choices: ['basic', 'manual'],
default: this.gaeScalingType ? this.gaeScalingType : 0
}
];
this.prompt(prompts).then(props => {
this.gaeScalingType = props.gaeScalingType;
done();
});
}
},
askForInstances() {
if (this.abort) return;
const done = this.async();
const prompts = [];
if (this.gaeScalingType === 'manual') {
prompts.push({
type: 'input',
name: 'gaeInstances',
message: 'How many instances to run ?',
default: this.gaeInstances ? this.gaeInstances : '1',
validate: input => {
if (input.length === 0) {
return 'Instances cannot be empty';
}
const n = Math.floor(Number(input));
if (n === Infinity || String(n) !== input || n <= 0) {
return 'Please enter an integer greater than 0';
}
return true;
}
});
}
if (this.gaeScalingType === 'automatic') {
prompts.push({
type: 'input',
name: 'gaeMinInstances',
message: 'How many instances minimum ?',
default: this.gaeMinInstances ? this.gaeMinInstances : '0',
validate: input => {
if (input.length === 0) {
return 'Minimum Instances cannot be empty';
}
const n = Math.floor(Number(input));
if (n === Infinity || String(n) !== input || n < 0) {
return 'Please enter an integer >= 0';
}
return true;
}
});
}
if (this.gaeScalingType === 'automatic' || this.gaeScalingType === 'basic') {
prompts.push({
type: 'input',
name: 'gaeMaxInstances',
message: 'How many instances max (0 for unlimited) ?',
default: this.gaeMaxInstances ? this.gaeMaxInstances : '0',
validate: input => {
if (input.length === 0) {
return 'Max Instances cannot be empty';
}
const n = Math.floor(Number(input));
if (n === Infinity || String(n) !== input || n < 0) {
return 'Please enter an integer >= 0';
}
return true;
}
});
}
this.prompt(prompts).then(props => {
this.gaeInstances = props.gaeInstances;
this.gaeMaxInstances = props.gaeMaxInstances;
this.gaeMinInstances = props.gaeMinInstances;
done();
});
},
askForCloudSqlInstance() {
if (this.abort) return;
if (this.prodDatabaseType !== 'mysql' && this.prodDatabaseType !== 'mariadb' && this.prodDatabaseType !== 'postgresql')
return;
const done = this.async();
const cloudSqlInstances = [{ value: '', name: 'New Cloud SQL Instance' }];
exec(
`gcloud sql instances list --format='value[separator=":"](project,region,name)' --project="${this.gcpProjectId}"`,
(err, stdout, stderr) => {
if (err) {
this.log.error(err);
} else {
_.forEach(stdout.toString().split(os.EOL), instance => {
if (!instance) return;
cloudSqlInstances.push({ value: instance, name: instance });
});
}
const prompts = [
{
type: 'list',
name: 'gcpCloudSqlInstanceName',
message: 'Google Cloud SQL Instance Name',
choices: cloudSqlInstances,
default: this.gcpCloudSqlInstanceName ? this.gcpCloudSqlInstanceName : 0
}
];
this.prompt(prompts).then(props => {
this.gcpCloudSqlInstanceName = props.gcpCloudSqlInstanceName;
this.gcpCloudSqlInstanceNameExists = true;
done();
});
}
);
},
promptForCloudSqlInstanceNameIfNeeded() {
if (this.abort) return;
if (this.gcpCloudSqlInstanceName) return;
const done = this.async();
const prompts = [
{
type: 'input',
name: 'gcpCloudSqlInstanceName',
message: 'Google Cloud SQL Instance Name',
default: this.gcpCloudSqlInstanceName ? this.gcpCloudSqlInstanceName : this.baseName
}
];
this.prompt(prompts).then(props => {
this.gcpCloudSqlInstanceName = props.gcpCloudSqlInstanceName;
this.gcpCloudSqlInstanceNameExists = false;
done();
});
},
askForCloudSqlLogin() {
if (this.abort) return;
if (!this.gcpCloudSqlInstanceName) return;
const done = this.async();
const prompts = [
{
type: 'input',
name: 'gcpCloudSqlUserName',
message: 'Google Cloud SQL User Name',
default: this.gcpCloudSqlUserName ? this.gcpCloudSqlUserName : 'root',
validate: input => {
if (input.length === 0) {
return 'User Name cannot empty';
}
return true;
}
},
{
type: 'password',
name: 'gcpCloudSqlPassword',
message: 'Google Cloud SQL Password',
default: this.gcpCloudSqlPassword ? this.gcpCloudSqlPassword : ''
}
];
this.prompt(prompts).then(props => {
this.gcpCloudSqlUserName = props.gcpCloudSqlUserName;
this.gcpCloudSqlPassword = props.gcpCloudSqlPassword;
done();
});
},
askForCloudSqlDatabaseName() {
if (this.abort) return;
if (!this.gcpCloudSqlInstanceNameExists) return;
const done = this.async();
const cloudSqlDatabases = [{ value: '', name: 'New Database' }];
const name = this.gcpCloudSqlInstanceName.split(':')[2];
exec(
`gcloud sql databases list -i ${name} --format='value(name)' --project="${this.gcpProjectId}"`,
(err, stdout, stderr) => {
if (err) {
this.log.error(err);
} else {
_.forEach(stdout.toString().split(os.EOL), database => {
if (!database) return;
cloudSqlDatabases.push({ value: database, name: database });
});
}
const prompts = [
{
type: 'list',
name: 'gcpCloudSqlDatabaseName',
message: 'Google Cloud SQL Database Name',
choices: cloudSqlDatabases,
default: this.gcpCloudSqlDatabaseName ? this.gcpCloudSqlDatabaseName : 0
}
];
this.prompt(prompts).then(props => {
this.gcpCloudSqlDatabaseName = props.gcpCloudSqlDatabaseName;
this.gcpCloudSqlDatabaseNameExists = true;
done();
});
}
);
},
promptForCloudSqlDatabaseNameIfNeeded() {
if (this.abort) return;
if (this.gcpCloudSqlInstanceName !== 'new' && this.gcpCloudSqlDatabaseName) return;
const done = this.async();
const prompts = [
{
type: 'input',
name: 'gcpCloudSqlDatabaseName',
message: 'Google Cloud SQL Database Name',
default: this.gcpCloudSqlDatabaseName ? this.gcpCloudSqlDatabaseName : this.baseName
}
];
this.prompt(prompts).then(props => {
this.gcpCloudSqlDatabaseName = props.gcpCloudSqlDatabaseName;
this.gcpCloudSqlDatabaseNameExists = false;
done();
});
}
};
}
get default() {
return {};
}
get configuring() {
return {
insight() {
statistics.sendSubGenEvent('generator', 'gae');
},
configureProject() {
if (this.abort) return;
const done = this.async();
if (!this.gaeLocationExists) {
this.log(chalk.bold(`Configuring Google App Engine Location "${chalk.cyan(this.gaeLocation)}"`));
exec(`gcloud app create --region="${this.gaeLocation}" --project="${this.gcpProjectId}"`, (err, stdout) => {
if (err) {
this.log.error(err);
this.abort = true;
}
done();
});
} else {
done();
}
},
createCloudSqlInstance() {
if (this.abort) return;
if (!this.gcpCloudSqlInstanceName) return;
if (this.gcpCloudSqlInstanceNameExists) return;
const done = this.async();
this.log(chalk.bold('\nCreating New Cloud SQL Instance'));
const name = this.gcpCloudSqlInstanceName;
// for mysql keep default options, set specific option for pg
const dbVersionFlag =
this.prodDatabaseType === 'postgresql' ? ' --database-version="POSTGRES_9_6" --tier="db-g1-small"' : '';
const cmd = `gcloud sql instances create "${name}" --region='${this.gaeLocation}' --project=${
this.gcpProjectId
}${dbVersionFlag}`;
this.log(chalk.bold(`\n... Running: ${cmd}`));
exec(cmd, (err, stdout, stderr) => {
if (err) {
this.abort = true;
this.log.error(err);
}
this.gcpCloudSqlInstanceName = execSync(
`gcloud sql instances describe ${name} --format="value(connectionName)" --project="${this.gcpProjectId}"`,
{ encoding: 'utf8' }
);
done();
});
},
createCloudSqlLogin() {
if (this.abort) return;
if (!this.gcpCloudSqlInstanceName) return;
const done = this.async();
this.log(chalk.bold('\nConfiguring Cloud SQL Login'));
const name = this.gcpCloudSqlInstanceName.split(':')[2];
exec(`gcloud sql users list -i jhipster --format='value(name)' --project="${this.gcpProjectId}"`, (err, stdout) => {
if (_.includes(stdout, this.gcpCloudSqlUserName)) {
this.log(chalk.bold(`... User "${chalk.cyan(this.gcpCloudSqlUserName)}" already exists`));
const cmd = `gcloud sql users set-password "${this.gcpCloudSqlUserName}" -i "${name}" --host="%" --project="${
this.gcpProjectId
}" --password="..."`;
this.log(chalk.bold(`... To set its password, run: ${cmd}`));
done();
} else {
const cmd = `gcloud sql users create "${this.gcpCloudSqlUserName}" -i "${name}" --host="%" --password="${
this.gcpCloudSqlPassword
}" --project="${this.gcpProjectId}"`;
this.log(chalk.bold(`... Running: ${cmd}`));
exec(cmd, (err, stdout, stderr) => {
if (err) {
this.log.error(err);
}
done();
});
}
});
},
createCloudSqlDatabase() {
if (this.abort) return;
if (!this.gcpCloudSqlInstanceName) return;
if (this.gcpCloudSqlDatabaseNameExists) return;
const done = this.async();
const name = this.gcpCloudSqlInstanceName.split(':')[2];
this.log(chalk.bold(`\nCreating Database ${chalk.cyan(this.gcpCloudSqlDatabaseName)}`));
const cmd = `gcloud sql databases create "${this.gcpCloudSqlDatabaseName}" --charset=utf8 -i "${name}" --project="${
this.gcpProjectId
}"`;
this.log(chalk.bold(`... Running: ${cmd}`));
exec(cmd, (err, stdout, stderr) => {
if (err) {
this.log.error(err);
}
done();
});
},
saveConfig() {
this.config.set({
gcpProjectId: this.gcpProjectId,
gcpCloudSqlInstanceName: this.gcpCloudSqlInstanceName,
gcpCloudSqlUserName: this.gcpCloudSqlUserName,
gcpCloudSqlDatabaseName: this.gcpCloudSqlDatabaseName,
gaeServiceName: this.gaeServiceName,
gaeLocation: this.gaeLocation,
gaeInstanceClass: this.gaeInstanceClass,
gaeScalingType: this.gaeScalingType,
gaeInstances: this.gaeInstances,
gaeMinInstances: this.gaeMinInstances,
gaeMaxInstances: this.gaeMaxInstances
});
}
};
}
get writing() {
return {
copyFiles() {
if (this.abort) return;
const done = this.async();
this.log(chalk.bold('\nCreating Google App Engine deployment files'));
this.template('web.xml.ejs', `${constants.CLIENT_MAIN_SRC_DIR}/WEB-INF/web.xml`);
this.template('appengine-web.xml.ejs', `${constants.CLIENT_MAIN_SRC_DIR}/WEB-INF/appengine-web.xml`);
this.template('logging.properties.ejs', `${constants.CLIENT_MAIN_SRC_DIR}/WEB-INF/logging.properties`);
this.template('application-prod-gae.yml.ejs', `${constants.SERVER_MAIN_RES_DIR}/config/application-prod-gae.yml`);
if (this.buildTool === 'gradle') {
this.template('gae.gradle.ejs', 'gradle/gae.gradle');
}
this.conflicter.resolve(err => {
done();
});
},
addDependencies() {
if (this.prodDatabaseType === 'mysql' || this.prodDatabaseType === 'mariadb') {
if (this.buildTool === 'maven') {
this.addMavenDependency('com.google.cloud.sql', 'mysql-socket-factory', '1.0.8');
} else if (this.buildTool === 'gradle') {
this.addGradleDependency('compile', 'com.google.cloud.sql', 'mysql-socket-factory', '1.0.8');
}
}
if (this.prodDatabaseType === 'postgresql') {
if (this.buildTool === 'maven') {
this.addMavenDependency('com.google.cloud.sql', 'postgres-socket-factory', '1.0.12');
} else if (this.buildTool === 'gradle') {
this.addGradleDependency('compile', 'com.google.cloud.sql', 'postgres-socket-factory', '1.0.12');
}
}
},
addGradlePlugin() {
if (this.buildTool === 'gradle') {
this.addGradlePlugin('com.google.cloud.tools', 'appengine-gradle-plugin', '1.3.3');
this.applyFromGradleScript('gradle/gae');
}
},
addMavenPlugin() {
if (this.buildTool === 'maven') {
this.render('pom-plugin.xml.ejs', rendered => {
this.addMavenPlugin('com.google.cloud.tools', 'appengine-maven-plugin', '1.3.2', rendered.trim());
});
this.render('pom-profile.xml.ejs', rendered => {
this.addMavenProfile('prod-gae', ` ${rendered.trim()}`);
});
}
}
};
}
get end() {
return {
productionBuild() {
if (this.abort) return;
if (this.buildTool === 'maven') {
this.log(chalk.bold('\nRun App Engine DevServer Locally: ./mvnw appengine:run -DskipTests'));
this.log(chalk.bold('Deploy to App Engine: ./mvnw appengine:deploy -DskipTests -Pprod,prod-gae'));
} else if (this.buildTool === 'gradle') {
this.log(chalk.bold('\nRun App Engine DevServer Locally: ./gradlew appengineRun'));
this.log(chalk.bold('Deploy to App Engine: ./gradlew appengineDeploy -Pprod -Pprod-gae'));
}
/*
if (this.gcpSkipBuild || this.gcpDeployType === 'git') {
this.log(chalk.bold('\nSkipping build'));
return;
}
const done = this.async();
this.log(chalk.bold('\nBuilding application'));
const child = this.buildApplication(this.buildTool, 'prod', (err) => {
if (err) {
this.abort = true;
this.log.error(err);
}
done();
});
this.buildCmd = child.buildCmd;
child.stdout.on('data', (data) => {
process.stdout.write(data.toString());
});
*/
}
};
}
};