diff --git a/packages/strapi-generate-new/files/.npmrc b/packages/strapi-generate-new/files/.npmrc deleted file mode 100644 index 43c97e719a..0000000000 --- a/packages/strapi-generate-new/files/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/packages/strapi-generate-new/json/database.json.js b/packages/strapi-generate-new/json/database.json.js deleted file mode 100644 index 3906fc0a37..0000000000 --- a/packages/strapi-generate-new/json/database.json.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -module.exports = scope => { - // Production/Staging Template - if (['production', 'staging'].includes(scope.keyPath.split('/')[2])) { - // All available settings (bookshelf and mongoose) - const settingsBase = { - client: scope.client.database, - host: '${process.env.DATABASE_HOST || \'127.0.0.1\'}', - port: '${process.env.DATABASE_PORT || 27017}', - srv: '${process.env.DATABASE_SRV || false}', - database: '${process.env.DATABASE_NAME || \'strapi\'}', - username: '${process.env.DATABASE_USERNAME || \'\'}', - password: '${process.env.DATABASE_PASSWORD || \'\'}', - ssl: '${process.env.DATABASE_SSL || false}' - }; - - // Apply only settings set during the configuration - Object.keys(scope.database.settings).forEach((key) => { - scope.database.settings[key] = settingsBase[key]; - }); - - // All available options (bookshelf and mongoose) - const optionsBase = { - ssl: '${process.env.DATABASE_SSL || false}', - authenticationDatabase: '${process.env.DATABASE_AUTHENTICATION_DATABASE || \'\'}' - }; - - // Apply only options set during the configuration - Object.keys(scope.database.options).forEach((key) => { - scope.database.options[key] = optionsBase[key]; - }); - - return { - defaultConnection: 'default', - connections: { - default: { - connector: scope.client.connector, - settings: scope.database.settings, - options: scope.database.options - } - } - }; - } - - return { - defaultConnection: 'default', - connections: { - default: scope.database - } - }; -}; diff --git a/packages/strapi-generate-new/json/package.json.js b/packages/strapi-generate-new/json/package.json.js deleted file mode 100644 index 3cc44f27a3..0000000000 --- a/packages/strapi-generate-new/json/package.json.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -// Public node modules. -const _ = require('lodash'); - -/** - * Expose main package JSON of the application - * with basic info, dependencies, etc. - */ - -module.exports = scope => { - const cliPkg = scope.strapiPackageJSON || {}; - - // Let us install additional dependencies on a specific version. - // Ex: it allows us to install the right version of knex. - const additionalsDependencies = _.isArray(scope.additionalsDependencies) ? - scope.additionalsDependencies.reduce((acc, current) => { - const pkg = current.split('@'); - const name = pkg[0]; - const version = pkg[1] || 'latest'; - - acc[name] = name.indexOf('strapi') !== -1 ? getDependencyVersion(cliPkg, 'strapi') : version; - - return acc; - }, {}) : {}; - - // Finally, return the JSON. - return _.merge(scope.appPackageJSON || {}, { - 'name': scope.name, - 'private': true, - 'version': '0.1.0', - 'description': 'A Strapi application.', - 'scripts': { - 'develop': 'strapi develop', - 'start': 'strapi start', - 'build': 'strapi build', - 'strapi': 'strapi', // Allow to use `npm run strapi` CLI, - 'lint': 'eslint api/**/*.js config/**/*.js plugins/**/*.js' - }, - 'devDependencies': { - 'babel-eslint': '^7.1.1', - 'eslint': '^4.19.1', - 'eslint-config-airbnb': '^13.0.0', - 'eslint-plugin-import': '^2.11.0', - 'eslint-plugin-react': '^7.7.0' - }, - 'dependencies': Object.assign({}, { - 'lodash': '^4.17.5', - 'strapi': getDependencyVersion(cliPkg, 'strapi'), - 'strapi-admin': getDependencyVersion(cliPkg, 'strapi'), - 'strapi-utils': getDependencyVersion(cliPkg, 'strapi'), - [scope.client.connector]: getDependencyVersion(cliPkg, 'strapi'), - }, additionalsDependencies, { - [scope.client.module]: scope.client.version - }), - 'author': { - 'name': scope.author || 'A Strapi developer', - 'email': scope.email || '', - 'url': scope.website || '' - }, - 'maintainers': [{ - 'name': scope.author || 'A Strapi developer', - 'email': scope.email || '', - 'url': scope.website || '' - }], - 'strapi': { - 'uuid': scope.uuid - }, - 'engines': { - "node": "^10.0.0", - "npm": ">= 6.0.0" - }, - 'license': scope.license || 'MIT' - }); -}; - -/** - * Get dependencies version - */ - -function getDependencyVersion(packageJSON, module) { - return module === packageJSON.name ? packageJSON.version : packageJSON.dependencies && packageJSON.dependencies[module]; -} diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index 1256a09c77..961db88d84 100644 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -15,11 +15,9 @@ const { cyan, green } = require('chalk'); const fs = require('fs-extra'); const inquirer = require('inquirer'); const execa = require('execa'); -const uuid = require('uuid/v4'); -const rimraf = require('rimraf'); // Logger. -const trackSuccess = require('./success'); +const recordUsage = require('./success'); function hasYarn() { try { @@ -31,6 +29,40 @@ function hasYarn() { } } +const databaseChoices = [ + { + name: 'SQLite', + value: { + database: 'sqlite', + connector: 'strapi-hook-bookshelf', + module: 'sqlite3', + }, + }, + { + name: 'MongoDB', + value: { + database: 'mongo', + connector: 'strapi-hook-mongoose', + }, + }, + { + name: 'MySQL', + value: { + database: 'mysql', + connector: 'strapi-hook-bookshelf', + module: 'mysql', + }, + }, + { + name: 'Postgres', + value: { + database: 'postgres', + connector: 'strapi-hook-bookshelf', + module: 'pg', + }, + }, +]; + /** * This `before` function is run before generating targets. * Validate, configure defaults, get extra dependencies, etc. @@ -40,7 +72,7 @@ function hasYarn() { */ /* eslint-disable no-useless-escape */ -module.exports = (scope, cb) => { +module.exports = async scope => { // App info. const hasDatabaseConfig = !!scope.database; @@ -71,20 +103,30 @@ module.exports = (scope, cb) => { os.tmpdir(), `strapi${crypto.randomBytes(6).toString('hex')}` ); - scope.uuid = uuid(); - trackSuccess('willCreateProject', scope); + await recordUsage('willCreateProject', scope); - // Ensure we aren't going to inadvertently delete any files. - try { - const files = fs.readdirSync(scope.rootPath); - if (files.length > 1) { - return console.log( - `⛔️ ${cyan('strapi new')} can only be called in an empty directory.` + // Ensure we aren't going to inadvertently delete any files + if (await fs.exists(scope.rootPath)) { + const stat = await fs.stat(scope.rootPath); + + if (!stat.isDirectory()) { + console.log( + `⛔️ ${ + scope.rootPath + } is not a directory. Make sure to create a Strapi application in an empty directory.` ); + throw new Error('Path is not a directory'); + } + + const files = await fs.readdir(scope.rootPath); + if (files.length > 1) { + console.log( + `⛔️ You can only create a Strapi app in an empty directory.` + ); + + throw new Error('Directory is not empty'); } - } catch (err) { - // ... } if (hasDatabaseConfig) { @@ -94,40 +136,6 @@ module.exports = (scope, cb) => { } const connectionValidation = async () => { - const databaseChoices = [ - { - name: 'SQLite', - value: { - database: 'sqlite', - connector: 'strapi-hook-bookshelf', - module: 'sqlite3', - }, - }, - { - name: 'MongoDB', - value: { - database: 'mongo', - connector: 'strapi-hook-mongoose', - }, - }, - { - name: 'MySQL', - value: { - database: 'mysql', - connector: 'strapi-hook-bookshelf', - module: 'mysql', - }, - }, - { - name: 'Postgres', - value: { - database: 'postgres', - connector: 'strapi-hook-bookshelf', - module: 'pg', - }, - }, - ]; - const answers = await inquirer.prompt([ { when: !scope.quick && !hasDatabaseConfig, @@ -191,7 +199,7 @@ module.exports = (scope, cb) => { scope.client = answers.client; - const connectedToTheDatabase = (withMessage = true) => { + const connectedToTheDatabase = async (withMessage = true) => { if (withMessage) { console.log(); console.log( @@ -200,55 +208,48 @@ module.exports = (scope, cb) => { } if (isQuick) { - trackSuccess('didChooseQuickstart', scope); + await recordUsage('didChooseQuickstart', scope); } else { - trackSuccess('didChooseCustomDatabase', scope); + await recordUsage('didChooseCustomDatabase', scope); } - trackSuccess('didConnectDatabase', scope); - - cb.success(); + await recordUsage('didConnectDatabase', scope); }; - Promise.all([ + await Promise.all([ handleCustomDatabase({ scope, isQuick, hasDatabaseConfig }), installDatabaseTestingDep({ scope, isQuick }), - ]) - .then(() => { - // Bypass real connection test. - if (isQuick) { - return connectedToTheDatabase(false); - } + ]); - try { - const connectivityFile = path.join( - scope.tmpPath, - 'node_modules', - scope.client.connector, - 'lib', - 'utils', - 'connectivity.js' - ); + // Bypass real connection test. + if (isQuick) { + return connectedToTheDatabase(false); + } - require(connectivityFile)( - scope, - connectedToTheDatabase, - connectionValidation - ); - } catch (err) { - trackSuccess('didNotConnectDatabase', scope, err); - console.log(err); - rimraf.sync(scope.tmpPath); - cb.error(); - } - }) - .catch(err => { - console.log(err); - cb.error(err); - }); + try { + const connectivityFile = path.join( + scope.tmpPath, + 'node_modules', + scope.client.connector, + 'lib', + 'utils', + 'connectivity.js' + ); + + return require(connectivityFile)( + scope, + connectedToTheDatabase, + connectionValidation + ); + } catch (err) { + await recordUsage('didNotConnectDatabase', scope, err); + console.log(err); + fs.removeSync(scope.tmpPath); + throw err; + } }; - connectionValidation(); + return connectionValidation(); }; async function handleCustomDatabase({ scope, hasDatabaseConfig, isQuick }) { diff --git a/packages/strapi-generate-new/lib/db-questions.js b/packages/strapi-generate-new/lib/db-questions.js new file mode 100644 index 0000000000..0e08f834d9 --- /dev/null +++ b/packages/strapi-generate-new/lib/db-questions.js @@ -0,0 +1,86 @@ +const database = ({ scope }) => ({ + type: 'input', + name: 'database', + message: 'Database name:', + default: scope.name, +}); + +const host = () => ({ + type: 'input', + name: 'host', + message: 'Host:', + default: '127.0.0.1', +}); + +const srv = () => ({ + type: 'boolean', + name: 'srv', + message: '+srv connection:', + default: false, +}); + +const port = ({ client }) => ({ + type: 'input', + name: 'port', + message: `Port${ + client === 'mongo' ? ' (It will be ignored if you enable +srv)' : '' + }:`, + default: () => { + const ports = { + mongo: 27017, + postgres: 5432, + mysql: 3306, + }; + + return ports[client]; + }, +}); + +const username = () => ({ + type: 'input', + name: 'username', + message: 'Username:', +}); + +const password = () => ({ + type: 'password', + name: 'password', + message: 'Password:', + mask: '*', +}); + +const authenticationDatabase = () => ({ + type: 'input', + name: 'authenticationDatabase', + message: 'Authentication database (Maybe "admin" or blank):', +}); + +const ssl = () => ({ + type: 'confirm', + name: 'ssl', + message: 'Enable SSL connection:', + default: false, +}); + +const filename = () => ({ + type: 'input', + name: 'filename', + message: 'Filename:', + default: '.tmp/data.db', +}); + +module.exports = { + sqlite: [filename], + postgres: [database, host, port, username, password, ssl], + mysql: [database, host, port, username, password, ssl], + mongo: [ + database, + host, + srv, + port, + username, + password, + authenticationDatabase, + ssl, + ], +}; diff --git a/packages/strapi-generate-new/lib/index.js b/packages/strapi-generate-new/lib/index.js index 510998b63f..6cfa7e042c 100644 --- a/packages/strapi-generate-new/lib/index.js +++ b/packages/strapi-generate-new/lib/index.js @@ -5,90 +5,525 @@ */ // Node.js core. -const path = require('path'); +const { join, resolve, basename } = require('path'); +const { merge, pick } = require('lodash'); +const os = require('os'); +const crypto = require('crypto'); +const { machineIdSync } = require('node-machine-id'); +const uuid = require('uuid/v4'); +const inquirer = require('inquirer'); +const execa = require('execa'); // Local dependencies. -const packageJSON = require('../json/package.json.js'); -const database = require('../json/database.json.js'); +const packageJSON = require('./resources/json/package.json'); +const databaseJSON = require('./resources/json/database.json.js'); + +const { trackError, trackUsage } = require('./usage'); +const dbQuestions = require('./db-questions'); +const fse = require('fs-extra'); /** * Copy required files for the generated application */ -module.exports = { - moduleDir: path.resolve(__dirname, '..'), - templatesDirectory: path.resolve(__dirname, '..', 'templates'), - before: require('./before'), - after: require('./after'), - targets: { +const defaultConfigs = { + sqlite: { + connector: 'strapi-hook-bookshelf', + settings: { + client: 'sqlite', + filename: '.tmp/data.db', + }, + options: { + useNullAsDefault: true, + }, + }, + postgres: { + connector: 'strapi-hook-bookshelf', + settings: { + client: 'postgres', + }, + }, + mysql: { + connector: 'strapi-hook-bookshelf', + settings: { + client: 'mysql', + }, + }, + mongo: { + connector: 'strapi-hook-mongoose', + }, +}; - // Call the `admin` generator. - // '.': ['admin'], +const sqlClientModule = { + sqlite: 'sqlite3', + postgres: 'pg', + mysql: 'mysql', +}; - // Main package. - 'package.json': { - jsonfile: packageJSON - }, - - 'config/environments/development/database.json': { - jsonfile: database - }, - - 'config/environments/production/database.json': { - jsonfile: database - }, - - 'config/environments/staging/database.json': { - jsonfile: database - }, - - // Copy dot files. - '.editorconfig': { - copy: 'editorconfig' - }, - '.gitignore': { - copy: 'gitignore' - }, - - // Copy Markdown files with some information. - 'README.md': { - template: 'README.md' - }, - - // Empty API directory. - 'api': { - folder: {} - }, - - 'api/.gitkeep': { - copy: 'gitkeep' - }, - - // Empty plugins directory. - 'extensions': { - folder: {} - }, - - 'extensions/.gitkeep': { - copy: 'gitkeep' - }, - - // Empty public directory. - 'public': { - folder: {} - }, - - // Empty public directory. - 'public/uploads': { - folder: {} - }, - // Copy gitkeep into uploads directory. - 'public/uploads/.gitkeep': { - copy: 'gitkeep' - }, - // Empty node_modules directory. - 'node_modules': { - folder: {} - } +const clientDependencies = ({ scope, client }) => { + switch (client) { + case 'sqlite': + case 'postgres': + case 'mysql': + return { + 'strapi-hook-bookshelf': scope.strapiVersion, + 'strapi-hook-knex': scope.strapiVersion, + knex: 'latest', + [sqlClientModule[client]]: 'latest', + }; + case 'mongo': + return { + 'strapi-hook-mongoose': scope.strapiVersion, + }; + default: + throw new Error(`Invalid client ${client}`); } }; + +module.exports = async (location, cliArguments = {}) => { + console.log('🚀 Creating your Strapi application.\n'); + + const { debug = false, quickstart = false } = cliArguments; + + // Build scope. + const rootPath = resolve(location); + + const tmpPath = join( + os.tmpdir(), + `strapi${crypto.randomBytes(6).toString('hex')}` + ); + + const scope = { + rootPath, + name: basename(rootPath), + // use pacakge version as strapiVersion (all packages have the same version); + strapiVersion: require('../package.json').version, + debug: debug !== false, + quick: quickstart !== false, + uuid: 'testing', //uuid(), + deviceId: machineIdSync(), + tmpPath, + hasYarn: hasYarn(), + strapiDependencies: [ + 'strapi', + 'strapi-admin', + 'strapi-utils', + 'strapi-plugin-settings-manager', + 'strapi-plugin-content-type-builder', + 'strapi-plugin-content-manager', + 'strapi-plugin-users-permissions', + 'strapi-plugin-email', + 'strapi-plugin-upload', + ], + additionalsDependencies: {}, + }; + + parseDatabaseArguments({ scope, args: cliArguments }); + initCancelCatcher(); + + await trackUsage({ event: 'willCreateProject', scope }); + + const hasDatabaseConfig = Boolean(scope.database); + + // check rootPath is empty + if (await fse.exists(scope.rootPath)) { + const stat = await fse.stat(scope.rootPath); + + if (!stat.isDirectory()) { + await trackError({ scope, error: 'Path is not a directory' }); + + stopProcess( + `⛔️ ${ + scope.rootPath + } is not a directory. Make sure to create a Strapi application in an empty directory.` + ); + } + + const files = await fse.readdir(scope.rootPath); + if (files.length > 1) { + await trackError({ scope, error: 'Directory is not empty' }); + stopProcess( + `⛔️ You can only create a Strapi app in an empty directory.` + ); + } + } + + // if database config is provided don't test the connection and create the project directly + if (hasDatabaseConfig) { + await trackUsage({ event: 'didChooseCustomDatabase', scope }); + + const client = scope.database.settings.client; + const configuration = { + client, + connection: merge(defaultConfigs[client] || {}, scope.database), + dependencies: clientDependencies({ scope, client: client }), + }; + return createProject(scope, configuration); + } + + // if cli quickstart create project with default sqlite options + if (scope.quick === true) { + return createQuickStartProject(scope); + } + + const useQuickStart = await askShouldUseQuickstart(); + + // else if question response is quickstart create project + if (useQuickStart) { + return createQuickStartProject(scope); + } + + await trackUsage({ event: 'didChooseCustomDatabase', scope }); + + const configuration = await askDbInfosAndTest(scope).catch(error => { + return trackUsage({ event: 'didNotConnectDatabase', scope, error }).then( + () => { + throw error; + } + ); + }); + + await trackUsage({ event: 'didConnectDatabase', scope }); + return createProject(scope, configuration); +}; + +function stopProcess(message) { + console.error(message); + process.exit(1); +} + +const MAX_RETRIES = 5; +async function askDbInfosAndTest(scope) { + let retries = 0; + + async function loop() { + // else ask for the client name + const { client, connection } = await askDatabaseInfos(scope); + + const configuration = { + client, + connection, + dependencies: clientDependencies({ scope, client: client }), + }; + + await testDatabaseConnection({ + scope, + configuration, + }) + .then( + () => fse.remove(scope.tmpPath), + err => { + return fse.remove(scope.tmpPath).then(() => { + throw err; + }); + } + ) + .catch(err => { + console.log(`⛔️ Connection test failed: ${err.message}`); + + if (scope.debug) { + console.log('Full error log:'); + console.log(err); + } + + if (retries < MAX_RETRIES) { + console.log('Retrying...'); + retries++; + return loop(); + } + + throw new Error( + `Could not connect to your database after ${MAX_RETRIES} tries` + ); + }); + + return configuration; + } + + return loop(); +} + +async function testDatabaseConnection({ scope, configuration }) { + const { client } = configuration; + + if (client === 'sqlite') return; + + await installDatabaseTestingDep({ + scope, + configuration, + }); + + // const connectivityFile = join( + // scope.tmpPath, + // 'node_modules', + // configuration.connection.connector, + // 'lib', + // 'utils', + // 'connectivity.js' + // ); + + // const tester = require(connectivityFile); + const tester = require(`${ + configuration.connection.connector + }/lib/utils/connectivity.js`); + return tester({ scope, connection: configuration.connection }); +} + +async function createProject(scope, { client, connection, dependencies }) { + try { + const { rootPath } = scope; + const resources = join(__dirname, 'resources'); + + // copy files + await fse.copy(join(resources, 'files'), rootPath); + + // copy templates + await fse.writeJSON( + join(rootPath, 'package.json'), + packageJSON({ + strapiDependencies: scope.strapiDependencies, + additionalsDependencies: dependencies, + strapiVersion: scope.strapiVersion, + projectName: scope.name, + uuid: scope.uuid, + }), + { + spaces: 2, + } + ); + + // ensure node_modules is created + await fse.ensureDir(join(rootPath, 'node_modules')); + + await Promise.all( + ['development', 'staging', 'production'].map(env => { + return fse.writeJSON( + join(rootPath, `config/environments/${env}/database.json`), + databaseJSON({ + connection, + env, + }), + { spaces: 2 } + ); + }) + ); + } catch (err) { + await fse.remove(scope.rootPath); + throw err; + } + + try { + await runInstall(scope); + } catch (error) { + await trackUsage({ + event: 'didNotInstallProjectDependencies', + scope, + error, + }); + throw error; + } + + await trackUsage({ event: 'didCreateProject', scope }); +} + +async function createQuickStartProject(scope) { + await trackUsage({ event: 'didChooseQuickstart', scope }); + + // get default sqlite config + const client = 'sqlite'; + const configuration = { + client, + connection: defaultConfigs[client], + dependencies: clientDependencies({ scope, client: client }), + }; + + await createProject(scope, configuration); + + await execa('npm', ['run', 'develop'], { + stdio: 'inherit', + cwd: scope.rootPath, + env: { + FORCE_COLOR: 1, + }, + }); +} + +function hasYarn() { + try { + const { code } = execa.shellSync('yarnpkg --version'); + if (code === 0) return true; + return false; + } catch (err) { + return false; + } +} + +async function askShouldUseQuickstart() { + const answer = await inquirer.prompt([ + { + type: 'list', + name: 'type', + message: 'Choose your installation type', + choices: [ + { + name: 'Quickstart (recommended)', + value: 'quick', + }, + { + name: 'Custom (manual settings)', + value: 'custom', + }, + ], + }, + ]); + + return answer.type === 'quick'; +} + +const SETTINGS_FIELDS = [ + 'database', + 'host', + 'srv', + 'port', + 'username', + 'password', + 'filename', +]; + +const OPTIONS_FIELDS = ['authenticationDatabase']; + +async function askDatabaseInfos(scope) { + const { client } = await inquirer.prompt([ + { + type: 'list', + name: 'client', + message: 'Choose your default database client', + choices: ['sqlite', 'postgres', 'mysql', 'mongo'], + default: 'sqlite', + }, + ]); + + const responses = await inquirer.prompt( + dbQuestions[client].map(q => q({ scope, client })) + ); + + const connection = merge({}, defaultConfigs[client] || {}, { + settings: pick(responses, SETTINGS_FIELDS), + options: pick(responses, OPTIONS_FIELDS), + }); + + if (responses.ssl === true) { + if (client === 'mongo') { + connection.options.ssl = true; + } else { + connection.settings.ssl = true; + } + } + + return { + client, + connection, + }; +} + +async function installDatabaseTestingDep({ scope, configuration }) { + let packageCmd = scope.hasYarn + ? `yarnpkg --cwd ${scope.tmpPath} add` + : `npm install --prefix ${scope.tmpPath}`; + + // Manually create the temp directory for yarn + if (scope.hasYarn) { + await fse.ensureDir(scope.tmpPath); + } + + const depArgs = Object.keys(configuration.dependencies).map(dep => { + return `${dep}@${configuration.dependencies[dep]}`; + }); + + const cmd = `${packageCmd} ${depArgs.join(' ')}`; + await execa.shell(cmd); +} + +const dbArguments = [ + 'dbclient', + 'dbhost', + 'dbport', + 'dbname', + 'dbusername', + 'dbpassword', +]; + +function parseDatabaseArguments({ scope, args }) { + const argKeys = Object.keys(args); + const matchingDbArguments = dbArguments.filter(key => argKeys.includes(key)); + + if (matchingDbArguments.length === 0) return; + + if ( + matchingDbArguments.length !== dbArguments.length && + args.dbclient !== 'sqlite' + ) { + return stopProcess( + `⛔️ Some database arguments are missing. Required arguments list: ${dbArguments}` + ); + } + + scope.dbforce = args.dbforce !== undefined; + + const database = { + settings: { + client: args.dbclient, + host: args.dbhost, + srv: args.dbsrv, + port: args.dbport, + database: args.dbname, + username: args.dbusername, + password: args.dbpassword, + filename: args.dbfile, + }, + options: {}, + }; + + if (args.dbauth !== undefined) { + database.options.authenticationDatabase = args.dbauth; + } + + if (args.dbssl !== undefined) { + if (args.dbclient === 'mongo') { + database.options.ssl = args.dbssl === 'true'; + } else { + database.settings.ssl = args.dbssl === 'true'; + } + } + + scope.database = database; +} + +function initCancelCatcher(scope) { + // Create interface for windows user to let them quit the program. + if (process.platform === 'win32') { + const rl = require('readline').createInterface({ + input: process.stdin, + output: process.stdout, + }); + + rl.on('SIGINT', function() { + process.emit('SIGINT'); + }); + } + + process.on('SIGINT', () => { + console.log('Cancelling...'); + process.exit(); + // trackUsage({ event: 'didStopCreateProject', scope }).then(() => { + // }); + }); +} + +const installArguments = ['install', '--production', '--no-optional']; +function runInstall({ rootPath, hasYarn }) { + if (hasYarn) { + return execa('yarnpkg', installArguments, { cwd: rootPath }); + } + return execa('npm', installArguments, { cwd: rootPath }); +} diff --git a/packages/strapi-generate-new/templates/editorconfig b/packages/strapi-generate-new/lib/resources/files/.editorconfig similarity index 100% rename from packages/strapi-generate-new/templates/editorconfig rename to packages/strapi-generate-new/lib/resources/files/.editorconfig diff --git a/packages/strapi-generate-new/files/.eslintignore b/packages/strapi-generate-new/lib/resources/files/.eslintignore similarity index 100% rename from packages/strapi-generate-new/files/.eslintignore rename to packages/strapi-generate-new/lib/resources/files/.eslintignore diff --git a/packages/strapi-generate-new/files/.eslintrc b/packages/strapi-generate-new/lib/resources/files/.eslintrc similarity index 100% rename from packages/strapi-generate-new/files/.eslintrc rename to packages/strapi-generate-new/lib/resources/files/.eslintrc diff --git a/packages/strapi-generate-new/templates/gitignore b/packages/strapi-generate-new/lib/resources/files/.gitignore similarity index 100% rename from packages/strapi-generate-new/templates/gitignore rename to packages/strapi-generate-new/lib/resources/files/.gitignore diff --git a/packages/strapi-generate-new/lib/resources/files/README.md b/packages/strapi-generate-new/lib/resources/files/README.md new file mode 100644 index 0000000000..4d901a095d --- /dev/null +++ b/packages/strapi-generate-new/lib/resources/files/README.md @@ -0,0 +1,3 @@ +# Strapi application + +A quick description of your strapi application diff --git a/packages/strapi-generate-new/templates/gitkeep b/packages/strapi-generate-new/lib/resources/files/api/.gitkeep similarity index 100% rename from packages/strapi-generate-new/templates/gitkeep rename to packages/strapi-generate-new/lib/resources/files/api/.gitkeep diff --git a/packages/strapi-generate-new/files/config/.init.json b/packages/strapi-generate-new/lib/resources/files/config/.init.json similarity index 100% rename from packages/strapi-generate-new/files/config/.init.json rename to packages/strapi-generate-new/lib/resources/files/config/.init.json diff --git a/packages/strapi-generate-new/files/config/application.json b/packages/strapi-generate-new/lib/resources/files/config/application.json similarity index 100% rename from packages/strapi-generate-new/files/config/application.json rename to packages/strapi-generate-new/lib/resources/files/config/application.json diff --git a/packages/strapi-generate-new/files/config/custom.json b/packages/strapi-generate-new/lib/resources/files/config/custom.json similarity index 100% rename from packages/strapi-generate-new/files/config/custom.json rename to packages/strapi-generate-new/lib/resources/files/config/custom.json diff --git a/packages/strapi-generate-new/files/config/environments/development/custom.json b/packages/strapi-generate-new/lib/resources/files/config/environments/development/custom.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/development/custom.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/development/custom.json diff --git a/packages/strapi-generate-new/files/config/environments/development/request.json b/packages/strapi-generate-new/lib/resources/files/config/environments/development/request.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/development/request.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/development/request.json diff --git a/packages/strapi-generate-new/files/config/environments/development/response.json b/packages/strapi-generate-new/lib/resources/files/config/environments/development/response.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/development/response.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/development/response.json diff --git a/packages/strapi-generate-new/files/config/environments/development/security.json b/packages/strapi-generate-new/lib/resources/files/config/environments/development/security.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/development/security.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/development/security.json diff --git a/packages/strapi-generate-new/files/config/environments/development/server.json b/packages/strapi-generate-new/lib/resources/files/config/environments/development/server.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/development/server.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/development/server.json diff --git a/packages/strapi-generate-new/files/config/environments/production/custom.json b/packages/strapi-generate-new/lib/resources/files/config/environments/production/custom.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/production/custom.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/production/custom.json diff --git a/packages/strapi-generate-new/files/config/environments/production/request.json b/packages/strapi-generate-new/lib/resources/files/config/environments/production/request.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/production/request.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/production/request.json diff --git a/packages/strapi-generate-new/files/config/environments/production/response.json b/packages/strapi-generate-new/lib/resources/files/config/environments/production/response.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/production/response.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/production/response.json diff --git a/packages/strapi-generate-new/files/config/environments/production/security.json b/packages/strapi-generate-new/lib/resources/files/config/environments/production/security.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/production/security.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/production/security.json diff --git a/packages/strapi-generate-new/files/config/environments/production/server.json b/packages/strapi-generate-new/lib/resources/files/config/environments/production/server.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/production/server.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/production/server.json diff --git a/packages/strapi-generate-new/files/config/environments/staging/custom.json b/packages/strapi-generate-new/lib/resources/files/config/environments/staging/custom.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/staging/custom.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/staging/custom.json diff --git a/packages/strapi-generate-new/files/config/environments/staging/request.json b/packages/strapi-generate-new/lib/resources/files/config/environments/staging/request.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/staging/request.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/staging/request.json diff --git a/packages/strapi-generate-new/files/config/environments/staging/response.json b/packages/strapi-generate-new/lib/resources/files/config/environments/staging/response.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/staging/response.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/staging/response.json diff --git a/packages/strapi-generate-new/files/config/environments/staging/security.json b/packages/strapi-generate-new/lib/resources/files/config/environments/staging/security.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/staging/security.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/staging/security.json diff --git a/packages/strapi-generate-new/files/config/environments/staging/server.json b/packages/strapi-generate-new/lib/resources/files/config/environments/staging/server.json similarity index 100% rename from packages/strapi-generate-new/files/config/environments/staging/server.json rename to packages/strapi-generate-new/lib/resources/files/config/environments/staging/server.json diff --git a/packages/strapi-generate-new/files/config/functions/bootstrap.js b/packages/strapi-generate-new/lib/resources/files/config/functions/bootstrap.js similarity index 100% rename from packages/strapi-generate-new/files/config/functions/bootstrap.js rename to packages/strapi-generate-new/lib/resources/files/config/functions/bootstrap.js diff --git a/packages/strapi-generate-new/files/config/functions/cron.js b/packages/strapi-generate-new/lib/resources/files/config/functions/cron.js similarity index 99% rename from packages/strapi-generate-new/files/config/functions/cron.js rename to packages/strapi-generate-new/lib/resources/files/config/functions/cron.js index f604bf3ebe..eca41b22ed 100644 --- a/packages/strapi-generate-new/files/config/functions/cron.js +++ b/packages/strapi-generate-new/lib/resources/files/config/functions/cron.js @@ -9,12 +9,10 @@ */ module.exports = { - /** * Simple example. * Every monday at 1am. */ - // '0 1 * * 1': () => { // // } diff --git a/packages/strapi-generate-new/files/config/functions/responses/404.js b/packages/strapi-generate-new/lib/resources/files/config/functions/responses/404.js similarity index 100% rename from packages/strapi-generate-new/files/config/functions/responses/404.js rename to packages/strapi-generate-new/lib/resources/files/config/functions/responses/404.js diff --git a/packages/strapi-generate-new/files/config/hook.json b/packages/strapi-generate-new/lib/resources/files/config/hook.json similarity index 100% rename from packages/strapi-generate-new/files/config/hook.json rename to packages/strapi-generate-new/lib/resources/files/config/hook.json diff --git a/packages/strapi-generate-new/files/config/language.json b/packages/strapi-generate-new/lib/resources/files/config/language.json similarity index 100% rename from packages/strapi-generate-new/files/config/language.json rename to packages/strapi-generate-new/lib/resources/files/config/language.json diff --git a/packages/strapi-generate-new/files/config/locales/de_de.json b/packages/strapi-generate-new/lib/resources/files/config/locales/de_de.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/de_de.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/de_de.json diff --git a/packages/strapi-generate-new/files/config/locales/en_us.json b/packages/strapi-generate-new/lib/resources/files/config/locales/en_us.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/en_us.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/en_us.json diff --git a/packages/strapi-generate-new/files/config/locales/es_es.json b/packages/strapi-generate-new/lib/resources/files/config/locales/es_es.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/es_es.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/es_es.json diff --git a/packages/strapi-generate-new/files/config/locales/fr_fr.json b/packages/strapi-generate-new/lib/resources/files/config/locales/fr_fr.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/fr_fr.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/fr_fr.json diff --git a/packages/strapi-generate-new/files/config/locales/it_it.json b/packages/strapi-generate-new/lib/resources/files/config/locales/it_it.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/it_it.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/it_it.json diff --git a/packages/strapi-generate-new/files/config/locales/ja_jp.json b/packages/strapi-generate-new/lib/resources/files/config/locales/ja_jp.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/ja_jp.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/ja_jp.json diff --git a/packages/strapi-generate-new/files/config/locales/ru_ru.json b/packages/strapi-generate-new/lib/resources/files/config/locales/ru_ru.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/ru_ru.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/ru_ru.json diff --git a/packages/strapi-generate-new/files/config/locales/tr_tr.json b/packages/strapi-generate-new/lib/resources/files/config/locales/tr_tr.json similarity index 100% rename from packages/strapi-generate-new/files/config/locales/tr_tr.json rename to packages/strapi-generate-new/lib/resources/files/config/locales/tr_tr.json diff --git a/packages/strapi-generate-new/files/config/middleware.json b/packages/strapi-generate-new/lib/resources/files/config/middleware.json similarity index 100% rename from packages/strapi-generate-new/files/config/middleware.json rename to packages/strapi-generate-new/lib/resources/files/config/middleware.json diff --git a/packages/strapi-generate-new/lib/resources/files/extensions/.gitkeep b/packages/strapi-generate-new/lib/resources/files/extensions/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/strapi-generate-new/files/favicon.ico b/packages/strapi-generate-new/lib/resources/files/favicon.ico similarity index 100% rename from packages/strapi-generate-new/files/favicon.ico rename to packages/strapi-generate-new/lib/resources/files/favicon.ico diff --git a/packages/strapi-generate-new/files/public/index.html b/packages/strapi-generate-new/lib/resources/files/public/index.html similarity index 100% rename from packages/strapi-generate-new/files/public/index.html rename to packages/strapi-generate-new/lib/resources/files/public/index.html diff --git a/packages/strapi-generate-new/files/public/robots.txt b/packages/strapi-generate-new/lib/resources/files/public/robots.txt similarity index 100% rename from packages/strapi-generate-new/files/public/robots.txt rename to packages/strapi-generate-new/lib/resources/files/public/robots.txt diff --git a/packages/strapi-generate-new/lib/resources/files/public/uploads/.gitkeep b/packages/strapi-generate-new/lib/resources/files/public/uploads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/strapi-generate-new/lib/resources/json/database.json.js b/packages/strapi-generate-new/lib/resources/json/database.json.js new file mode 100644 index 0000000000..55d927c34a --- /dev/null +++ b/packages/strapi-generate-new/lib/resources/json/database.json.js @@ -0,0 +1,43 @@ +'use strict'; +const { merge } = require('lodash'); + +module.exports = ({ connection, env }) => { + // Production/Staging Template + if (['production', 'staging'].includes(env)) { + // All available settings (bookshelf and mongoose) + const settingsBase = { + client: connection.settings.client, + host: "${process.env.DATABASE_HOST || '127.0.0.1'}", + port: '${process.env.DATABASE_PORT || 27017}', + srv: '${process.env.DATABASE_SRV || false}', + database: "${process.env.DATABASE_NAME || 'strapi'}", + username: "${process.env.DATABASE_USERNAME || ''}", + password: "${process.env.DATABASE_PASSWORD || ''}", + ssl: '${process.env.DATABASE_SSL || false}', + }; + + // All available options (bookshelf and mongoose) + const optionsBase = { + ssl: '${process.env.DATABASE_SSL || false}', + authenticationDatabase: + "${process.env.DATABASE_AUTHENTICATION_DATABASE || ''}", + }; + + return { + defaultConnection: 'default', + connections: { + default: merge({ + settings: settingsBase, + options: optionsBase, + }), + }, + }; + } + + return { + defaultConnection: 'default', + connections: { + default: connection, + }, + }; +}; diff --git a/packages/strapi-generate-new/lib/resources/json/package.json.js b/packages/strapi-generate-new/lib/resources/json/package.json.js new file mode 100644 index 0000000000..11f0c18c75 --- /dev/null +++ b/packages/strapi-generate-new/lib/resources/json/package.json.js @@ -0,0 +1,61 @@ +'use strict'; + +/** + * Expose main package JSON of the application + * with basic info, dependencies, etc. + */ + +module.exports = opts => { + const { + strapiDependencies, + additionalsDependencies, + strapiVersion, + projectName, + uuid, + } = opts; + + // Finally, return the JSON. + return { + name: projectName, + private: true, + version: '0.1.0', + description: 'A Strapi application', + scripts: { + develop: 'strapi develop', + start: 'strapi start', + build: 'strapi build', + strapi: 'strapi', // Allow to use `npm run strapi` CLI, + lint: 'eslint api/**/*.js config/**/*.js plugins/**/*.js', + }, + devDependencies: { + 'babel-eslint': '^7.1.1', + eslint: '^4.19.1', + 'eslint-config-airbnb': '^13.0.0', + 'eslint-plugin-import': '^2.11.0', + 'eslint-plugin-react': '^7.7.0', + }, + dependencies: Object.assign( + { lodash: '^4.17.5' }, + strapiDependencies.reduce((acc, key) => { + acc[key] = strapiVersion; + return acc; + }, {}), + additionalsDependencies + ), + author: { + name: 'A Strapi developer', + }, + strapi: { + uuid: uuid, + }, + engines: { + node: '^10.0.0', + npm: '>= 6.0.0', + }, + license: 'MIT', + }; +}; + +/** + * Get dependencies version + */ diff --git a/packages/strapi-generate-new/lib/success.js b/packages/strapi-generate-new/lib/success.js index 9b36afce6e..8f12f62f03 100644 --- a/packages/strapi-generate-new/lib/success.js +++ b/packages/strapi-generate-new/lib/success.js @@ -6,20 +6,35 @@ // Node.js core. const os = require('os'); -const request = require('request'); -const { machineIdSync } = require('node-machine-id'); +const fetch = require('node-fetch'); -module.exports = function trackSuccess(event, scope, error) { - request - .post('https://analytics.strapi.io/track') - .form({ +module.exports = function recordUsage(event, scope, error) { + return fetch('https://analytics.strapi.io/track', { + method: 'POST', + body: JSON.stringify({ event, uuid: scope.uuid, - deviceId: machineIdSync(), + deviceId: scope.deviceId, properties: { - error, - os: os.type() - } - }) - .on('error', () => {}); + error: typeof error == 'string' ? error : error && error.message, + os: os.type(), + version: scope.strapiPackageJSON.version, + }, + }), + timeout: 1000, + headers: { 'Content-Type': 'application/json' }, + }).catch(() => {}); + + // request + // .post('https://analytics.strapi.io/track') + // .form({ + // event, + // uuid: scope.uuid, + // deviceId: machineIdSync(), + // properties: { + // error, + // os: os.type() + // } + // }) + // .on('error', () => {}); }; diff --git a/packages/strapi-generate-new/lib/usage.js b/packages/strapi-generate-new/lib/usage.js new file mode 100644 index 0000000000..439f340e35 --- /dev/null +++ b/packages/strapi-generate-new/lib/usage.js @@ -0,0 +1,44 @@ +'use strict'; +const os = require('os'); +const fetch = require('node-fetch'); + +function trackEvent(event, body) { + return fetch('https://analytics.strapi.io/track', { + method: 'POST', + body: JSON.stringify({ + event, + ...body, + }), + timeout: 1000, + headers: { 'Content-Type': 'application/json' }, + }).catch(() => {}); +} + +function trackError({ scope, error }) { + return trackEvent('didNotCreateProject', { + uuid: scope.uuid, + deviceId: scope.deviceId, + properties: { + error: typeof error == 'string' ? error : error && error.message, + os: os.type(), + version: scope.strapiVersion, + }, + }); +} + +function trackUsage({ event, scope, error }) { + return trackEvent(event, { + uuid: scope.uuid, + deviceId: scope.deviceId, + properties: { + error: typeof error == 'string' ? error : error && error.message, + os: os.type(), + version: scope.strapiVersion, + }, + }); +} + +module.exports = { + trackError, + trackUsage, +}; diff --git a/packages/strapi-generate-new/package.json b/packages/strapi-generate-new/package.json index 7f1802d96c..046a325adc 100644 --- a/packages/strapi-generate-new/package.json +++ b/packages/strapi-generate-new/package.json @@ -16,12 +16,10 @@ "execa": "^1.0.0", "fs-extra": "^8.0.1", "inquirer": "^6.3.1", - "listr": "^0.14.3", "lodash": "^4.17.11", - "node-machine-id": "^1.1.10", "ora": "^3.4.0", - "request": "^2.88.0", - "rimraf": "^2.6.3", + "node-fetch": "^1.7.3", + "node-machine-id": "^1.1.10", "uuid": "^3.3.2" }, "scripts": { diff --git a/packages/strapi-generate-new/templates/README.md b/packages/strapi-generate-new/templates/README.md deleted file mode 100644 index aa7cea3d95..0000000000 --- a/packages/strapi-generate-new/templates/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# <%= name %> - -A quick description of <%= name %>. diff --git a/packages/strapi-generate-new/templates/npmignore b/packages/strapi-generate-new/templates/npmignore deleted file mode 100644 index 792527cf8f..0000000000 --- a/packages/strapi-generate-new/templates/npmignore +++ /dev/null @@ -1,118 +0,0 @@ -############################ -# OS X -############################ - -.DS_Store -.AppleDouble -.LSOverride -Icon -.Spotlight-V100 -.Trashes -._* - - -############################ -# Linux -############################ - -*~ - - -############################ -# Windows -############################ - -Thumbs.db -ehthumbs.db -Desktop.ini -$RECYCLE.BIN/ -*.cab -*.msi -*.msm -*.msp - - -############################ -# Packages -############################ - -*.7z -*.csv -*.dat -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip -*.com -*.class -*.dll -*.exe -*.o -*.seed -*.so -*.swo -*.swp -*.swn -*.swm -*.out -*.pid - - -############################ -# Logs and databases -############################ - -.tmp -*.log -*.sql -*.sqlite -*.sqlite3 - - -############################ -# Misc. -############################ - -*# -ssl -.idea -nbproject - - -############################ -# Node.js -############################ - -lib-cov -lcov.info -pids -logs -results -build -node_modules -.node_history - - -############################ -# Tests -############################ - -testApp -coverage - - -############################ -# Front-end workflow -############################ - -bower_components - - -############################ -# Production config -############################ - -**/production/ diff --git a/packages/strapi-generate/lib/index.js b/packages/strapi-generate/lib/index.js index 0e5322b5ff..e64f39b01e 100644 --- a/packages/strapi-generate/lib/index.js +++ b/packages/strapi-generate/lib/index.js @@ -12,6 +12,7 @@ const logger = require('strapi-utils').logger; // Local dependencies. const generate = require('./generate'); +const generateTarget = require('./target'); /* eslint-disable prefer-template */ /** @@ -31,7 +32,7 @@ module.exports = (scope, cb) => { notStrapiApp: () => {}, alreadyExists: () => { return cb.error(); - } + }, }); // Use configured module name for this `generatorType` if applicable. @@ -39,7 +40,10 @@ module.exports = (scope, cb) => { let generator; function throwIfModuleNotFoundError(error, module) { - const isModuleNotFoundError = error && error.code === 'MODULE_NOT_FOUND' && error.message.match(new RegExp(module)); + const isModuleNotFoundError = + error && + error.code === 'MODULE_NOT_FOUND' && + error.message.match(new RegExp(module)); if (!isModuleNotFoundError) { logger.error('Invalid `' + scope.generatorType + '` generator.'); throw error; @@ -56,8 +60,12 @@ module.exports = (scope, cb) => { } if (!generator) { - return logger.error('No generator called `' + scope.generatorType + '` found.'); + return logger.error( + 'No generator called `' + scope.generatorType + '` found.' + ); } generate(generator, scope, cb); }; + +module.exports.generateTarget = generateTarget; diff --git a/packages/strapi-hook-bookshelf/lib/utils/connectivity.js b/packages/strapi-hook-bookshelf/lib/utils/connectivity.js index b2b5be2e93..00e949a6c1 100644 --- a/packages/strapi-hook-bookshelf/lib/utils/connectivity.js +++ b/packages/strapi-hook-bookshelf/lib/utils/connectivity.js @@ -1,94 +1,71 @@ 'use strict'; -// Node.js core. -const path = require('path'); - // Public node modules const inquirer = require('inquirer'); -const rimraf = require('rimraf'); -module.exports = (scope, success, error) => { - if (scope.client.database === 'sqlite') { - return success(); - } +const selectQueries = { + postgres: "SELECT tablename FROM pg_tables WHERE schemaname='public'", + mysql: 'SELECT * FROM information_schema.tables', + sqlite: 'select * from sqlite_master', +}; - let knex; +module.exports = async ({ scope, connection }) => { + const knex = require('knex'); - try { - // eslint-disable-next-line import/no-unresolved - knex = require('knex'); - } catch (err) { - // eslint-disable-next-line import/no-unresolved - knex = require(path.resolve(scope.tmpPath, 'node_modules', 'knex')); - } - - // eslint-disable-next-line import/no-unresolved + const { settings } = connection; const client = knex({ - client: scope.client.module, - connection: Object.assign({}, scope.database.settings, { - user: scope.database.settings.username + client: settings.client, + connection: Object.assign({}, settings, { + user: settings.username, }), - useNullAsDefault: true + useNullAsDefault: true, }); - client.raw('select 1+1 as result').then(() => { - const selectQueries = { - postgres: 'SELECT tablename FROM pg_tables WHERE schemaname=\'public\'', - mysql: 'SELECT * FROM information_schema.tables', - sqlite: 'select * from sqlite_master' - }; + const destroyClientAndThrow = err => { + return client.destroy().then( + () => { + throw err; + }, + () => { + throw err; + } + ); + }; - client.raw(selectQueries[scope.client.database]).then((tables) => { - client.destroy(); + await client.raw('select 1+1 as result').catch(destroyClientAndThrow); - const next = () => { - rimraf(scope.tmpPath, (err) => { - if (err) { - console.log(`Error removing connection test folder: ${scope.tmpPath}`); - } + return client + .raw(selectQueries[settings.client]) + .then(tables => { + if (tables.rows && tables.rows.length === 0) { + return; + } - success(); - }); - }; + if (scope.dbforce) { + return; + } - if (tables.rows && tables.rows.length !== 0) { - if (scope.dbforce) { - next(); - } else { - console.log('🤔 It seems that your database is not empty. Be aware that Strapi is going to automatically creates tables & columns, and might update columns which can corrupt data or cause data loss.'); + console.log( + '🤔 It seems that your database is not empty. Be aware that Strapi is going to automatically creates tables & columns, and might update columns which can corrupt data or cause data loss.' + ); - inquirer.prompt([{ + return inquirer + .prompt([ + { type: 'confirm', name: 'confirm', - message: `Are you sure you want to continue with the ${scope.database.settings.database} database:`, - }]) - .then(({ confirm }) => { - if (confirm) { - next(); - } else { - error(); - } - }); - } - } else { - next(); - } - }); - }) - .catch((err) => { - if (scope.debug) { - console.log('🐛 Full error log:'); - console.log(err); - return error(); - } - - if (err.sql) { - console.log('⚠️ Server connection has failed! Make sure your database server is running.'); - } else { - console.log(`⚠️ Database connection has failed! Make sure your "${scope.database.settings.database}" database exist.`); - } - console.log(err.message); - - error(); - }); + message: `Are you sure you want to continue with the ${ + settings.database + } database:`, + }, + ]) + .then(({ confirm }) => { + if (!confirm) { + // TODO: cancel somehow + throw new Error('Not confirmed'); + } + }); + }) + .then(() => client.destroy()) + .catch(destroyClientAndThrow); }; diff --git a/packages/strapi-hook-bookshelf/package.json b/packages/strapi-hook-bookshelf/package.json index 631f699825..490205070f 100644 --- a/packages/strapi-hook-bookshelf/package.json +++ b/packages/strapi-hook-bookshelf/package.json @@ -20,7 +20,6 @@ "inquirer": "^6.3.1", "lodash": "^4.17.11", "pluralize": "^7.0.0", - "rimraf": "^2.6.3", "strapi-hook-knex": "3.0.0-beta.6", "strapi-utils": "3.0.0-beta.6" }, diff --git a/packages/strapi/lib/commands/new.js b/packages/strapi/lib/commands/new.js index 011db5e78e..4de99a4c40 100644 --- a/packages/strapi/lib/commands/new.js +++ b/packages/strapi/lib/commands/new.js @@ -2,138 +2,15 @@ 'use strict'; -/** - * Module dependencies - */ - -// Node.js core. -const os = require('os'); -const path = require('path'); - -// Public node modules. -const _ = require('lodash'); -const fetch = require('node-fetch'); -const { machineIdSync } = require('node-machine-id'); -const execa = require('execa'); - -// Master of ceremonies for generators. -const generate = require('strapi-generate'); - -// Local Strapi dependencies. -const packageJSON = require('../../package.json'); - /** * `$ strapi new` * * Generate a new Strapi application. */ -const logError = error => { - fetch('https://analytics.strapi.io/track', { - method: 'POST', - body: JSON.stringify({ - event: 'didNotStartAutomatically', - deviceId: machineIdSync(), - properties: { - error, - os: os.type(), - }, - }), - headers: { 'Content-Type': 'application/json' }, - }).catch(() => {}); -}; - -module.exports = function(name, cliArguments) { - console.log('🚀 Creating your Strapi application.\n'); - - // Build initial scope. - const scope = { - rootPath: process.cwd(), - strapiRoot: path.resolve(__dirname, '..'), - generatorType: 'new', - name, - strapiPackageJSON: packageJSON, - debug: cliArguments.debug !== undefined, - quick: cliArguments.quickstart !== undefined, - }; - - const dbArguments = [ - 'dbclient', - 'dbhost', - 'dbport', - 'dbname', - 'dbusername', - 'dbpassword', - ]; - const matchingDbArguments = _.intersection(_.keys(cliArguments), dbArguments); - - if (matchingDbArguments.length) { - if ( - matchingDbArguments.length !== dbArguments.length && - cliArguments.dbclient !== 'sqlite' - ) { - console.log( - `⛔️ Some database arguments are missing. Required arguments list: ${dbArguments}` - ); - return process.exit(1); - } - - scope.dbforce = cliArguments.dbforce !== undefined; - - scope.database = { - settings: { - client: cliArguments.dbclient, - host: cliArguments.dbhost, - srv: cliArguments.dbsrv, - port: cliArguments.dbport, - database: cliArguments.dbname, - username: cliArguments.dbusername, - password: cliArguments.dbpassword, - filename: cliArguments.dbfile, - }, - options: { - authenticationDatabase: cliArguments.dbauth, - ssl: cliArguments.dbssl, - }, - }; - } - - // Return the scope and the response (`error` or `success`). - return generate(scope, { - // Log and exit the REPL in case there is an error - // while we were trying to generate the new app. - error(err) { - logError(err); - console.log(err); - process.exit(1); - }, - - success: async () => { - if (scope.quick) { - // Create interface for windows user to let them quit the program. - if (process.platform === 'win32') { - const rl = require('readline').createInterface({ - input: process.stdin, - output: process.stdout, - }); - - rl.on('SIGINT', function() { - process.emit('SIGINT'); - }); - } - // Listen Ctrl+C / SIGINT event to close the process. - process.on('SIGINT', function() { - process.exit(); - }); - - await execa('npm', ['run', 'develop'], { - stdio: 'inherit', - cwd: scope.rootPath, - env: { - FORCE_COLOR: 1, - }, - }); - } - }, +module.exports = function(...args) { + return require('strapi-generate-new')(...args).catch(err => { + console.error(err); + process.exit(1); }); }; diff --git a/packages/strapi/package.json b/packages/strapi/package.json index 73759adea6..030e430f70 100644 --- a/packages/strapi/package.json +++ b/packages/strapi/package.json @@ -41,6 +41,7 @@ "minimatch": "^3.0.4", "node-fetch": "^1.7.3", "node-machine-id": "^1.1.10", + "uuid": "^3.3.2", "node-schedule": "^1.2.0", "opn": "^5.3.0", "ora": "^3.0.0",