UNPKG

ryuu

Version:

Domo App Dev Studio CLI, The main tool used to create, edit, and publish app designs to Domo

201 lines 9.22 kB
import _ from 'lodash'; import isValidJSName from '../util/isValidJSName.js'; import slug from 'slug'; import chalk from 'chalk'; import shell from 'shelljs'; const { mkdir } = shell; import { reduce } from 'async'; import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import { log } from '../util/log.js'; import { TEMPLATES, TemplateName, getTemplateFiles, } from '../models/templates.js'; import { createConfirm, createInput, createSelect } from '../util/prompts.js'; import { Option } from 'commander'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export default (program) => { program .command('init') .option('-n, --design_name <value>', 'Name of the design') .addOption(new Option('-t, --template <value>', 'Name of the starter kit').choices([ TemplateName.HELLO_WORLD, TemplateName.MANIFEST_ONLY, TemplateName.BASIC_CHART, TemplateName.MAP_CHART, TemplateName.SUGARFORCE, ])) .option('--no-datasets', 'Skip dataset prompting (use with -i and -a to include specific datasets without prompting)') .option('-i, --dataset-id [value...]', 'Give dataset ids as multiple arguments after the flag instead of using the prompt') .option('-a, --dataset-alias [value...]', 'Give dataset aliases as multiple arguments after the flag instead of using the prompt') .description('initialize a new Custom App design') .action(async (options) => { // Handle prompts with enquirer let answers = {}; if (!options.design_name) { answers.name = await createInput('design name'); } else { answers.name = options.design_name; } if (!options.template) { answers.starter = await createSelect('select a starter', Object.values(TEMPLATES).map(template => template.name)); } else { answers.starter = options.template; } answers.datasources = options.datasetId && options.datasetAlias ? options.datasetId.map((item, i) => { return { id: item, alias: options.datasetAlias[i] }; }) : []; askDatasource(answers, options); }); }; // recursively ask user to enter all their datasources const askDatasource = async (currentAnswers, options) => { // Commander automatically sets 'datasets' to false when --no-datasets is provided if (options['datasets'] !== false) { const shouldAddDatasource = await createConfirm(currentAnswers.datasources.length > 0 ? 'add another dataset?' : 'would you like to connect to any datasets?', true); if (shouldAddDatasource) { try { const combinedAnswers = await addDatasource(_.merge(currentAnswers, { askDatasource: shouldAddDatasource })); // ask for more datasources (recursive call) void askDatasource(combinedAnswers, options); } catch (err) { console.log('why' + err); } } else { // all done with datasources, continue on. void initiate(_.merge(currentAnswers, { askDatasource: shouldAddDatasource })); } } else { const answers = { askDatasource: false, }; void initiate(_.merge(currentAnswers, answers)); } }; const addDatasource = async (currentAnswers) => { const id = await createInput('dataset id'); const alias = await createInput('dataset alias', '', (name) => { if (!isValidJSName(name)) return 'Alias must be a valid JavaScript property'; return true; }); const datasource = { id, alias }; currentAnswers.datasources.push(datasource); return currentAnswers; }; const writeFilesIfNonexistent = (files, dirs, allAnswers, callback) => { reduce(files, [], (filesThatExist, file, reduceCallback) => { fs.open(file, 'r', (err, fd) => { const fileExists = !err; if (fileExists) { fs.closeSync(fd); filesThatExist.push(file); } reduceCallback(null, filesThatExist); }); }, (err, filesThatExist) => { if (filesThatExist.length > 0) { callback(null, filesThatExist); } else { _.zip(files, dirs).forEach(writeTemplateFile.bind(this, allAnswers)); callback(null, files); } }); }; const writeTemplateFile = (allAnswers, filedir) => { const templateFileExt = ['.js', '.css', '.html', '.json']; const file = filedir[0]; const dir = filedir[1] || __dirname + '/../templates/'; const filePath = path.resolve(dir + file); const destPath = path.parse(path.resolve(process.cwd(), file)); let data; if (templateFileExt.some(ext => file.endsWith(ext))) { data = fs.readFileSync(filePath, 'utf8'); data = _.template(data)(allAnswers); } else { data = fs.readFileSync(filePath); } fs.writeFileSync(path.resolve(destPath.dir, destPath.base), data); }; const initiate = (allAnswers) => { let files; let dirs; let step = 0; const stepNumber = () => { step++; return `${step}.`; }; let nextStepsMessage = 'Next steps: \n'; const dirname = slug(allAnswers.name, { lower: false }); if (allAnswers.starter === 'manifest only') { files = ['manifest.json']; dirs = [null]; mkdir('-p', dirname); process.chdir(path.resolve(dirname)); } else if (allAnswers.starter === TemplateName.HELLO_WORLD) { files = getTemplateFiles(TemplateName.HELLO_WORLD); const templateDir = path.join(__dirname, '/../templates/', TemplateName.HELLO_WORLD + '/'); dirs = [...new Array(files.length)].map(_ => templateDir); // eslint-disable-line @typescript-eslint/no-unused-vars nextStepsMessage += `${stepNumber()} ${chalk.cyan('cd')} into the ${chalk.green(dirname)} directory\n`; mkdir('-p', dirname); process.chdir(path.resolve(dirname)); } else if (allAnswers.starter === TemplateName.BASIC_CHART) { files = getTemplateFiles(TemplateName.BASIC_CHART); const customChartTemplateDir = path.join(__dirname, '/../templates/', TemplateName.BASIC_CHART + '/'); dirs = [...new Array(files.length)].map(_ => customChartTemplateDir); // eslint-disable-line @typescript-eslint/no-unused-vars nextStepsMessage += `${stepNumber()} ${chalk.cyan('cd')} into the ${chalk.green(dirname)} directory\n`; mkdir('-p', dirname); process.chdir(path.resolve(dirname)); } else if (allAnswers.starter === TemplateName.MAP_CHART) { files = getTemplateFiles(TemplateName.MAP_CHART); const customChartTemplateDir = path.join(__dirname, '/../templates/', TemplateName.MAP_CHART + '/'); dirs = [...new Array(files.length)].map(_ => customChartTemplateDir); // eslint-disable-line @typescript-eslint/no-unused-vars nextStepsMessage += `${stepNumber()} ${chalk.cyan('cd')} into the ${chalk.green(dirname)} directory\n`; mkdir('-p', dirname); process.chdir(path.resolve(dirname)); } else if (allAnswers.starter === TemplateName.SUGARFORCE) { files = getTemplateFiles(TemplateName.SUGARFORCE); const customChartTemplateDir = path.join(__dirname, '/../templates/', TemplateName.SUGARFORCE + '/'); dirs = [...new Array(files.length)].map(_ => customChartTemplateDir); // eslint-disable-line @typescript-eslint/no-unused-vars nextStepsMessage += `${stepNumber()} ${chalk.cyan('cd')} into the ${chalk.green(dirname)} directory\n`; mkdir('-p', dirname); mkdir('-p', path.join(dirname, 'styles')); mkdir('-p', path.join(dirname, 'components')); mkdir('-p', path.join(dirname, 'js')); mkdir('-p', path.join(dirname, 'views')); process.chdir(path.resolve(dirname)); } // populate and write out the template files writeFilesIfNonexistent(files, dirs, allAnswers, (err) => { const dirname = slug(allAnswers.name, { lower: false }); if (err) { log.fail('Cannot initialize new design. Doing so would overwrite existing files:' + err); } else { nextStepsMessage += `${stepNumber()} Edit the files that were just generated. \n${stepNumber()} Run ${chalk.yellow('domo login')} if you haven't already. \n${stepNumber()} Run ${chalk.yellow('domo publish')} to finish initializiation and whenever you make changes. \n${stepNumber()} Run ${chalk.yellow('domo dev')} to see what your app will look like and locally develop\n${stepNumber()} Add a Custom App card from the design published to any page in Domo. `; log.ok(`New design initialized in the ${chalk.green(dirname || 'current')} directory`, nextStepsMessage); } if (allAnswers.starter === 'hello world') { fs.remove('node_modules'); } process.exit(); }); }; //# sourceMappingURL=init.js.map