Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
.DS_Store
package-lock.json
package-lock.json
./templates/projectTemplate
84 changes: 40 additions & 44 deletions bin/init.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,49 @@
#!/usr/bin/env node

const program = require('commander');
const handlebars = require('handlebars');

handlebars.registerHelper('toLowerCase', str => str.toLowerCase());
handlebars.registerHelper('toUpperCase', str => str.toUpperCase());
handlebars.registerHelper(
'capitalize',
str => str.charAt(0).toUpperCase() + str.slice(1),
);
handlebars.registerHelper('ifEquals', function(arg1, arg2, options) {
return arg1 == arg2 ? options.fn(this) : options.inverse(this);
});
handlebars.registerHelper('setVar', function(varName, varValue, options) {
options.data.root[varName] = varValue;
});
const registerHandlebarHelpers = require('../helpers/registerHandlebarHelpers');
const cloneProjectTemplate = require('../helpers/cloneProjectTemplate');

program
.version(require('../package').version, '-v, --version')
.command('generate <type> <name> ')
.option('-f, --functional', 'functional component')
.alias('g')
.description('Generate new file or store')
.action(() => {
require('../commands/generate');
});
(async () => {
registerHandlebarHelpers();

program
.command('init <name>')
.alias('i')
.description('Create new boilerplate project')
.action(() => {
require('../commands/createNewProject');
await cloneProjectTemplate();

program
.version(require('../package').version, '-v, --version')
.command('generate <type> <name> ')
.option('-f, --functional', 'functional component')
.alias('g')
.description('Generate new file or store')
.action(() => {
require('../commands/generate');
});

program
.command('init <name>')
.alias('i')
.description('Create new boilerplate project')
.action(() => {
require('../commands/createNewProject');
});

program.on('command:*', () => {
console.log(
console.error(
'Invalid command \nSee --help for a list of available commands.',
),
);
process.exit(1);
});

program.on('command:*', () => {
console.log(

if (process.argv.length > 6) {
console.error(
'Invalid command \nSee --help for a list of available commands.',
),
);
process.exit(1);
});
'Invalid command!\nSee --help for a list of available commands.',
);
process.exit(1);
return;
}

if (process.argv.length > 6) {
console.error(
'Invalid command!\nSee --help for a list of available commands.',
);
process.exit(1);
return;
}
program.parse(process.argv);
program.parse(process.argv);
})();
131 changes: 97 additions & 34 deletions commands/createNewProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ const render = require('consolidate').handlebars.render;

const config = require('../config');
const filesManager = require('../helpers/filesManager');
const manageProjectConfig = require('../helpers/manageProjectConfig');
const prettify = require('../helpers/prettify');

const spinnerInstance = new Spinner('Installing dependencies... %s');
spinnerInstance.setSpinnerString('|/-\\');

const outputPath = program.args[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing semicolon

const packageSrc = `${outputPath}/package.json`;

const copyAssetsContent = async (includeCypress, middleware) => {
const projectTemplate = path.join(__dirname, '../packageTemplate');
const projectTemplate = path.join(__dirname, '..', config.projectTemplateDir);

try {
console.info('Copying CEA files...');

await fs.copy(projectTemplate, filesManager.getOutputFile(''), {
await fs.copy(projectTemplate, filesManager.getOutputFile(`/${outputPath}`), {
filter: path => (includeCypress ? true : !path.includes('cypress')),
});

Expand All @@ -37,31 +42,45 @@ const copyAssetsContent = async (includeCypress, middleware) => {

const setMiddleware = async middleware => {
try {
const packagePath = filesManager.getOutputFile(`${outputPath}/package.json`);
const packageJson = JSON.parse(
fs.readFileSync(filesManager.getOutputFile('package.json')),
fs.readFileSync(packagePath),
);

const middleWareDeps = {
const middlewareToRemove = {
package: middleware === config.supportedMiddlewares.reduxSaga ? 'redux-observable' : 'redux-saga',
file: middleware === config.supportedMiddlewares.reduxSaga ? config.projectFilesToOverride.rootEpic
: config.projectFilesToOverride.rootSaga,
files: middleware === config.supportedMiddlewares.reduxSaga ?
[
config.projectFilesToOverride.rootEpic,
config.projectFilesToOverride.articlesEpic
] :
[
config.projectFilesToOverride.rootSaga,
config.projectFilesToOverride.articlesSaga
],
};

delete packageJson.dependencies[middleWareDeps.package];

await exec(
`
rm '${filesManager.getOutputFile(middleWareDeps.file)}'
`,
(err, stdout) => {
console.log(stdout);
},
);
delete packageJson.dependencies[middlewareToRemove.package];

try {
for (let fileToRemove of middlewareToRemove.files) {
await exec(
`
rm '${filesManager.getOutputFile(`/${outputPath}/${fileToRemove}`)}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you're removing all middleware related files, shouldn't we also remove epic / saga folders which you can in MainArticles/store path?

`,
(err, stdout) => {
console.log(stdout);
},
);
}
} catch({ stderr }) {
console.error(stderr);
}

await fillStoreConfig(middleware)

fs.writeFileSync(
filesManager.getOutputFile('package.json'),
packagePath,
JSON.stringify(packageJson, null, 2),
);
} catch (err) {
Expand All @@ -74,7 +93,7 @@ const setMiddleware = async middleware => {
const removeCypressFromPackage = async () => {
try {
const packageJson = JSON.parse(
fs.readFileSync(filesManager.getOutputFile('package.json')),
fs.readFileSync(filesManager.getOutputFile(packageSrc)),
);

const scripts = ['cy:ci', 'cy:open', 'cy:run'];
Expand All @@ -86,7 +105,7 @@ const removeCypressFromPackage = async () => {
delete packageJson.devDependencies['cypress'];

fs.writeFileSync(
filesManager.getOutputFile('package.json'),
filesManager.getOutputFile(packageSrc),
JSON.stringify(packageJson, null, 2),
);
} catch (err) {
Expand All @@ -98,18 +117,20 @@ const removeCypressFromPackage = async () => {
};

const fillStoreConfig = async middleware => {
const storeConfigSrc = filesManager.getOutputFile(config.projectFilesToOverride.storeConfig);
const storeConfigSrc = filesManager.getOutputFile(`/${outputPath}/${config.projectFilesToOverride.storeConfig}`);
const storeConfigTemplateFile = fs.readFileSync(filesManager.getTemplateFile('/store/storeConfig.ts'));
if (!storeConfigTemplateFile) {
console.error('Could not find store config template!');
return;
}

try {
const res = await render(storeConfigTemplateFile.toString(), {
middleware,
supportedMiddlewares: config.supportedMiddlewares
});
const res = prettify(
await render(storeConfigTemplateFile.toString(), {
middleware,
supportedMiddlewares: config.supportedMiddlewares
})
);
fs.writeFileSync(storeConfigSrc, res);
console.info('Successfuly updated store config file: ' + storeConfigSrc);
} catch(e) {
Expand All @@ -121,16 +142,58 @@ const fillStoreConfig = async middleware => {

const init = async (includeCypress, packageManager, middleware) => {
await copyAssetsContent(includeCypress, middleware);
spinnerInstance.start();
await exec(
`cd ${program.args[0]} && ${packageManager.toLowerCase()} install`,
(err, stdout) => {
console.log(stdout);

console.info('Setup finished!');
},
);
spinnerInstance.stop();

await manageProjectConfig.generateConfig({
selectedMiddleware: middleware
}, outputPath);

try {
await exec(
`cd ./${program.args[0]} && git init`,
(err, stdout) => {
console.log(stdout);
},
);

spinnerInstance.start();
await exec(
`cd ${program.args[0]} && ${packageManager.toLowerCase()} install`,
(err, stdout) => {
console.log(err);
console.log(stdout);
},
);
spinnerInstance.stop();
console.log('');
console.info('Setup finished!');

await exec(
`cd ./${program.args[0]} && git add --ignore-errors .`,
(err, stdout) => {
console.log(err);
console.log(stdout);
},
);
} catch({ stdout, stderr }) {
stdout && stdout !== '' && console.error(stdout);
stderr && stderr !== '' && console.error(stderr);
}

try {
await exec(
`cd ./${program.args[0]} && git commit -m "Initial commit from Create Espeo React App CLI"`,
(err, stdout) => {
console.log(err);
console.log(stdout);
},
);
} catch({ stdout, stderr }) {
stdout && stdout !== '' && console.error(stdout);
stderr && stderr !== '' && console.error(stderr);
}

console.info('Latest changes committed!');

};

inquirer.prompt(config.questions).then(async answers => {
Expand Down
7 changes: 6 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ module.exports = {
projectFilesToOverride: {
rootEpic: 'src/app/store/rootEpic.ts',
rootSaga: 'src/app/store/rootSaga.ts',
articlesEpic: 'src/app/pages/MainArticles/store/epics/index.ts',
articlesSaga: 'src/app/pages/MainArticles/store/saga/index.ts',
storeConfig: 'src/app/store/index.ts',
package: 'package.json',
},
templatesFolder: '/templates'
templatesFolder: '/templates',
projectTemplateRepositoryUrl: 'https://github.com/espeo/espeo-create-react-app.git',
projectTemplateVersion: '1.0.0',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to have this value read dynamically from package.json file from cloned repository, instead of keeping it hardcoded?

projectTemplateDir: './templates/projectTemplate',
}
38 changes: 38 additions & 0 deletions helpers/cloneProjectTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const path = require('path');
const fs = require('fs');
const exec = require('await-exec');

const config = require('../config');

const projectTemplateSrc = path.join(__dirname, '..', config.projectTemplateDir);

const cloneProjectTemplate = async () => {
if (!fs.existsSync(projectTemplateSrc)) {
try {
await exec(
`
git clone ${config.projectTemplateRepositoryUrl} --branch ${config.projectTemplateVersion} ${projectTemplateSrc}
`,
(err, stdout) => {
console.log(stdout);
},
);

await exec(
`
rm -R ${projectTemplateSrc}/.git
`,
(err, stdout) => {
console.log(stdout);
},
);
} catch(err) {
console.error(err);
}

console.log('Successfully cloned template repository');
return
}
}

module.exports=cloneProjectTemplate;
17 changes: 15 additions & 2 deletions helpers/filesManager.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
const path = require('path');
const program = require('commander');
const find = require('find');

const config = require('../config');
const maxDirectoryDepth = 10;

let dirDepth = 0;
const findFile = (fileName, root) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this function of yours doing exactly the same what find-up package do? The one we were using in updateRootStore.js

let files = find.fileSync(fileName, root || process.cwd());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be const?

if (dirDepth < maxDirectoryDepth && files.length === 0) {
dirDepth++;
return findFile(fileName, '../'.repeat(dirDepth));
}
dirDepth = 0;
return files[0];
}

module.exports = {
getTemplateFile: templateSrc => path.join(__dirname, '..', config.templatesFolder, templateSrc),
getOutputFile: outputSrc => path.join(process.cwd(), program.args[0], outputSrc)
getOutputFile: outputSrc => path.join(process.cwd(), outputSrc),
findFile
}
Loading