Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC 638] Interactive way to create new Ember apps and addons #9824

Merged
merged 1 commit into from Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 31 additions & 4 deletions lib/commands/new.js
Expand Up @@ -38,9 +38,15 @@ module.exports = Command.extend({
{
name: 'ci-provider',
type: ['travis', 'github'],
default: 'github',
description: 'Installs the default CI blueprint. Either Travis or Github Actions is supported.',
},
{
name: 'interactive',
type: Boolean,
default: false,
aliases: ['i'],
description: 'Create a new Ember app/addon in an interactive way.',
},
],

anonymousOptions: ['<app-name>'],
Expand All @@ -53,10 +59,31 @@ module.exports = Command.extend({

commandOptions.name = rawArgs.shift();

if (!projectName) {
message = `The \`ember ${this.name}\` command requires a name to be specified. For more details, use \`ember help\`.`;
if (!projectName || commandOptions.interactive) {
let answers = await this.runTask('InteractiveNew', commandOptions);

return Promise.reject(new SilentError(message));
commandOptions.blueprint = answers.blueprint;

if (answers.name) {
projectName = answers.name;
commandOptions.name = answers.name;
}

if (answers.lang) {
commandOptions.lang = answers.lang;
}

if (answers.packageManager) {
commandOptions.yarn = answers.packageManager === 'yarn';
}

if (answers.ciProvider) {
commandOptions.ciProvider = answers.ciProvider;
}
}

if (!commandOptions.ciProvider) {
commandOptions.ciProvider = 'github';
}

if (commandOptions.dryRun) {
Expand Down
153 changes: 153 additions & 0 deletions lib/tasks/interactive-new.js
@@ -0,0 +1,153 @@
'use strict';

const inquirer = require('inquirer');
const { isLangCode } = require('is-language-code');
const osLocale = require('os-locale');

const Task = require('../models/task');
const isValidProjectName = require('../utilities/valid-project-name');

const DEFAULT_LOCALE = 'en-US';

class InteractiveNewTask extends Task {
async run(newCommandOptions, _testAnswers) {
let prompt = inquirer.createPromptModule();
let questions = await this.getQuestions(newCommandOptions);
let answers = await prompt(questions, _testAnswers);

answers.lang = answers.langSelection || answers.langDifferent;

delete answers.langSelection;
delete answers.langDifferent;

return answers;
}

async getQuestions(newCommandOptions = {}) {
return [
{
name: 'blueprint',
type: 'list',
message: 'Is this an app or an addon?',
choices: [
{
name: 'App',
value: 'app',
},
{
name: 'Addon',
value: 'addon',
},
],
},
{
name: 'name',
type: 'input',
message: ({ blueprint }) => `Please provide the name of your ${blueprint}:`,
when: !newCommandOptions.name,
validate: (name) => {
if (name) {
if (isValidProjectName(name)) {
return true;
}

return `We currently do not support \`${name}\` as a name.`;
}

return 'Please provide a name.';
},
},
{
name: 'langSelection',
type: 'list',
message: ({ blueprint }) => `Please provide the spoken/content language of your ${blueprint}:`,
when: !newCommandOptions.lang,
choices: await this.getLangChoices(),
},
{
name: 'langDifferent',
type: 'input',
message: 'Please provide the different language:',
when: ({ langSelection } = {}) => !newCommandOptions.lang && !langSelection,
validate: (lang) => {
if (isLangCode(lang).res) {
return true;
}

return 'Please provide a valid locale code.';
},
},
{
name: 'packageManager',
type: 'list',
message: 'Pick the package manager to use when installing dependencies:',
when: !newCommandOptions.yarn,
choices: [
{
name: 'NPM',
value: 'npm',
},
{
name: 'Yarn',
value: 'yarn',
},
{
name: 'Ignore/Skip',
value: null,
},
],
},
{
name: 'ciProvider',
type: 'list',
message: 'Which CI provider do you want to use?',
when: !newCommandOptions.ciProvider,
choices: [
{
name: 'GitHub Actions',
value: 'github',
},
{
name: 'Travis CI',
value: 'travis',
},
{
name: 'Ignore/Skip',
value: null,
},
],
},
];
}

async getLangChoices() {
let userLocale = await this.getUserLocale();
let langChoices = [
{
name: DEFAULT_LOCALE,
value: DEFAULT_LOCALE,
},
];

if (userLocale !== DEFAULT_LOCALE) {
langChoices.push({
name: userLocale,
value: userLocale,
});
}

langChoices.push({
name: 'I want to manually provide a different language',
value: null,
});

return langChoices;
}

getUserLocale() {
return osLocale();
}
}

module.exports = InteractiveNewTask;
module.exports.DEFAULT_LOCALE = DEFAULT_LOCALE;
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -94,6 +94,7 @@
"heimdalljs-logger": "^0.1.10",
"http-proxy": "^1.18.1",
"inflection": "^1.13.1",
"inquirer": "^8.2.1",
rwjblue marked this conversation as resolved.
Show resolved Hide resolved
"is-git-url": "^1.0.0",
"is-language-code": "^3.1.0",
"isbinaryfile": "^5.0.0",
Expand All @@ -106,6 +107,7 @@
"morgan": "^1.10.0",
"nopt": "^3.0.6",
"npm-package-arg": "^9.1.0",
"os-locale": "^5.0.0",
"p-defer": "^3.0.0",
"portfinder": "^1.0.29",
"promise-map-series": "^0.3.0",
Expand Down
14 changes: 0 additions & 14 deletions tests/acceptance/new-test.js
Expand Up @@ -170,20 +170,6 @@ describe('Acceptance: ember new', function () {
expect(blueprintTargets).to.have.same.deep.members(defaultTargets);
});

it('ember new with empty app name fails with a warning', async function () {
let err = await expect(ember(['new', ''])).to.be.rejected;

expect(err.name).to.equal('SilentError');
expect(err.message).to.contain('The `ember new` command requires a name to be specified.');
});

it('ember new without app name fails with a warning', async function () {
let err = await expect(ember(['new'])).to.be.rejected;

expect(err.name).to.equal('SilentError');
expect(err.message).to.contain('The `ember new` command requires a name to be specified.');
});

it('ember new with app name creates new directory and has a dasherized package name', async function () {
await ember(['new', 'FooApp', '--skip-npm', '--skip-bower', '--skip-git']);

Expand Down
4 changes: 3 additions & 1 deletion tests/fixtures/help/help-with-addon.txt
Expand Up @@ -139,7 +139,9 @@ ember new <app-name> <options...>
aliases: -dir <value>
--lang (String) Sets the base human language of the application via index.html
--embroider (Boolean) (Default: false) Enables the build system to use Embroider
--ci-provider (travis, github) (Default: github) Installs the default CI blueprint. Either Travis or Github Actions is supported.
--ci-provider (travis, github) Installs the default CI blueprint. Either Travis or Github Actions is supported.
--interactive (Boolean) (Default: false) Create a new Ember app/addon in an interactive way.
aliases: -i

ember serve <options...>
Builds and serves your app, rebuilding on file changes.
Expand Down
9 changes: 8 additions & 1 deletion tests/fixtures/help/help.js
Expand Up @@ -602,10 +602,17 @@ module.exports = {
name: 'ci-provider',
key: 'ciProvider',
type: ['travis', 'github'],
default: 'github',
description: 'Installs the default CI blueprint. Either Travis or Github Actions is supported.',
required: false,
},
{
name: 'interactive',
default: false,
aliases: ['i'],
description: 'Create a new Ember app/addon in an interactive way.',
key: 'interactive',
required: false,
},
],
anonymousOptions: ['<app-name>']
},
Expand Down
4 changes: 3 additions & 1 deletion tests/fixtures/help/help.txt
Expand Up @@ -139,7 +139,9 @@ ember new <app-name> <options...>
aliases: -dir <value>
--lang (String) Sets the base human language of the application via index.html
--embroider (Boolean) (Default: false) Enables the build system to use Embroider
--ci-provider (travis, github) (Default: github) Installs the default CI blueprint. Either Travis or Github Actions is supported.
--ci-provider (travis, github) Installs the default CI blueprint. Either Travis or Github Actions is supported.
--interactive (Boolean) (Default: false) Create a new Ember app/addon in an interactive way.
aliases: -i

ember serve <options...>
Builds and serves your app, rebuilding on file changes.
Expand Down
9 changes: 8 additions & 1 deletion tests/fixtures/help/with-addon-blueprints.js
Expand Up @@ -634,10 +634,17 @@ module.exports = {
name: 'ci-provider',
key: 'ciProvider',
type: ['travis', 'github'],
default: 'github',
description: 'Installs the default CI blueprint. Either Travis or Github Actions is supported.',
required: false,
},
{
name: 'interactive',
default: false,
aliases: ['i'],
description: 'Create a new Ember app/addon in an interactive way.',
key: 'interactive',
required: false,
},
],
anonymousOptions: ['<app-name>']
},
Expand Down
9 changes: 8 additions & 1 deletion tests/fixtures/help/with-addon-commands.js
Expand Up @@ -602,10 +602,17 @@ module.exports = {
name: 'ci-provider',
key: 'ciProvider',
type: ['travis', 'github'],
default: 'github',
description: 'Installs the default CI blueprint. Either Travis or Github Actions is supported.',
required: false,
},
{
name: 'interactive',
default: false,
aliases: ['i'],
description: 'Create a new Ember app/addon in an interactive way.',
key: 'interactive',
required: false,
},
],
anonymousOptions: ['<app-name>']
},
Expand Down