Refactor strapi-generate-new to be async

This commit is contained in:
Alexandre Bodin 2019-06-19 19:02:36 +02:00
parent b1eccc05a7
commit 9fdf6be746
61 changed files with 933 additions and 648 deletions

View File

@ -1 +0,0 @@
package-lock=false

View File

@ -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
}
};
};

View File

@ -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];
}

View File

@ -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 }) {

View File

@ -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,
],
};

View File

@ -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 });
}

View File

@ -0,0 +1,3 @@
# Strapi application
A quick description of your strapi application

View File

@ -9,12 +9,10 @@
*/
module.exports = {
/**
* Simple example.
* Every monday at 1am.
*/
// '0 1 * * 1': () => {
//
// }

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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,
},
};
};

View File

@ -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
*/

View File

@ -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', () => {});
};

View File

@ -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,
};

View File

@ -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": {

View File

@ -1,3 +0,0 @@
# <%= name %>
A quick description of <%= name %>.

View File

@ -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/

View File

@ -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;

View File

@ -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);
};

View File

@ -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"
},

View File

@ -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);
});
};

View File

@ -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",