diff --git a/build/tasks/generate-projects.js b/build/tasks/generate-projects.js new file mode 100644 index 000000000..1445b4422 --- /dev/null +++ b/build/tasks/generate-projects.js @@ -0,0 +1,142 @@ +require('aurelia-polyfills'); +const gulp = require('gulp'); +const Container = require('aurelia-dependency-injection').Container; +const definition = require('../../lib/commands/new/new-application.json'); +const WorkflowEngine = require('../../lib/workflow/workflow-engine').WorkflowEngine; +const ProjectCreate = require('../../lib/workflow/activities/project-create'); +const UI = require('../../lib/ui').UI; +const ConsoleUI = require('../../lib/ui').ConsoleUI; +const fs = require('fs'); +const LogManager = require('aurelia-logging'); +const Logger = require('../../lib/logger').Logger; + +gulp.task('generate-projects', function(done) { + LogManager.addAppender(new Logger(new ConsoleUI())); + LogManager.setLevel('debug'); + + const ui = new ConsoleUI(); + + ui.question('Where would you like to create the projects?') + .then(dir => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + process.chdir(dir); + + return createProjectsInSeries(require('./skeletons.json')) + .then(() => { + console.log('Created all projects'); + done(); + }) + .catch(e => { + console.log(e); + done(); + }); + }); +}); + +function createProjectsInSeries(instructions) { + let i = -1; + function createP() { + i++; + + if (i < instructions.length) { + return createProject(instructions[i]).then(createP); + } + + return Promise.resolve(); + } + + return createP(); +} + +function createProject(answers) { + let container = new Container(); + let engine = new WorkflowEngine( + definition, + container + ); + + container.registerInstance(UI, new ConsoleUI()); + container.unregister('project-install'); + container.registerInstance('project-install', new AutoProjectInstall()); + container.unregister('project-create'); + container.registerSingleton('project-create', AutoProjectCreate); + container.unregister('input-text'); + container.registerInstance('input-text', new AutoInputText(answers)); + container.unregister('input-select'); + container.registerInstance('input-select', new AutoInputSelect(answers)); + container.unregister('input-multiselect'); + container.registerInstance('input-multiselect', new AutoInputMultiSelect(answers)); + + return engine.start() + .then(() => console.log('Finished creating project')) + .catch(e => { + console.log('error while creating project'); + console.log(e); + throw e; + }); +} + +class AutoInputSelect { + constructor(answers) { + this._answers = answers; + } + + execute(context) { + let answer = this._answers[this.id]; + + if (!answer) { + answer = this.options[0].value; + } + + context.state[this.stateProperty] = answer; + context.next(this.nextActivity); + } +} + +class AutoInputMultiSelect { + constructor(answers) { + this._answers = answers; + } + + execute(context) { + let answer = this._answers[this.id]; + + if (!answer) { + answer = [this.options[0].value]; + } + + context.state[this.stateProperty] = answer; + context.next(this.nextActivity); + } +} + +class AutoInputText { + constructor(answers) { + this._answers = answers; + } + + execute(context) { + let answer = this._answers[this.id]; + + if (!answer) { + throw new Error('AutoInputText has no answer for activityId ' + this.id); + } + + context.state[this.stateProperty] = answer; + context.next(this.nextActivity); + } +} + +class AutoProjectCreate extends ProjectCreate { + projectConfirmation() { + return Promise.resolve({ value: 'yes' }); + } +} + +class AutoProjectInstall { + execute(context) { + context.next(this.nextActivity); + } +} diff --git a/build/tasks/skeletons.json b/build/tasks/skeletons.json new file mode 100644 index 000000000..46d6510a7 --- /dev/null +++ b/build/tasks/skeletons.json @@ -0,0 +1,47 @@ +[{ + "200": "skeleton-requirejs-esnext", + "300": "default-esnext" +}, { + "200": "skeleton-requirejs-typescript", + "300": "default-typescript" +},{ + "200": "skeleton-systemjs-esnext", + "300": "custom", + "600": "systemjs" +}, { + "200": "skeleton-systemjs-typescript", + "300": "custom", + "600": "systemjs", + "630": { + "id": "typescript", + "displayName": "TypeScript", + "fileExtension": ".ts" + } +}, { + "200": "skeleton-webpack-esnext", + "300": "custom", + "600": "webpack", + "680": [{ + "id": "karma", + "displayName": "Karma" + }, { + "id": "jest", + "displayName": "Jest" + }] +}, { + "200": "skeleton-webpack-typescript", + "300": "custom", + "600": "webpack", + "630": { + "id": "typescript", + "displayName": "TypeScript", + "fileExtension": ".ts" + }, + "680": [{ + "id": "karma", + "displayName": "Karma" + }, { + "id": "jest", + "displayName": "Jest" + }] +}] \ No newline at end of file diff --git a/lib/build/webpack-reporter.js b/lib/build/webpack-reporter.js new file mode 100644 index 000000000..e867fc947 --- /dev/null +++ b/lib/build/webpack-reporter.js @@ -0,0 +1,54 @@ +'use strict'; + +module.exports = function reportReadiness(options) { + const uri = createDomain(options); + const yargs = require('yargs'); + const argv = yargs.argv; + argv.color = require('supports-color'); + const open = require('opn'); + const useColor = argv.color; + + let startSentence = `Project is running at ${colorInfo(useColor, uri)}`; + + if (options.socket) { + startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`; + } + console.log((argv.progress ? '\n' : '') + startSentence); + + console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`); + const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') : options.contentBase; + + if (contentBase) { + console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`); + } + + if (options.historyApiFallback) { + console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || '/index.html')}`); + } + + if (options.open) { + open(uri).catch(function() { + console.log('Unable to open browser. If you are running in a headless environment, please do not use the open flag.'); + }); + } +}; + +function createDomain(opts) { + const protocol = opts.https ? 'https' : 'http'; + const url = require('url'); + + // the formatted domain (url without path) of the webpack server + return opts.public ? `${protocol}://${opts.public}` : url.format({ + protocol: protocol, + hostname: opts.host, + port: opts.socket ? 0 : opts.port.toString() + }); +} + +function colorInfo(useColor, msg) { + if (useColor) { + // Make text blue and bold, so it *pops* + return `\u001b[1m\u001b[33m${msg}\u001b[39m\u001b[22m`; + } + return msg; +} diff --git a/lib/commands/help/command.js b/lib/commands/help/command.js index 9e1aac15b..996d78619 100644 --- a/lib/commands/help/command.js +++ b/lib/commands/help/command.js @@ -35,11 +35,17 @@ module.exports = class { getLocalCommandText() { let commands = [ require('../generate/command.json'), - require('../import/command.json'), - require('../install/command.json'), require('./command.json') ]; + let bundler = this.project.model.bundler; + if (!bundler || bundler.id === 'cli') { + commands = commands.concat([ + require('../import/command.json'), + require('../install/command.json') + ]); + } + return this.project.getTaskMetadata().then(metadata => { return string.buildFromMetadata(metadata.concat(commands), this.ui.getWidth()); }); diff --git a/lib/commands/new/css-processors/less.js b/lib/commands/new/buildsystems/cli/css-processors/less.js similarity index 75% rename from lib/commands/new/css-processors/less.js rename to lib/commands/new/buildsystems/cli/css-processors/less.js index a296a7af2..6a90a6c2b 100644 --- a/lib/commands/new/css-processors/less.js +++ b/lib/commands/new/buildsystems/cli/css-processors/less.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/css-processors/none.js b/lib/commands/new/buildsystems/cli/css-processors/none.js similarity index 71% rename from lib/commands/new/css-processors/none.js rename to lib/commands/new/buildsystems/cli/css-processors/none.js index 91b0a5508..b0f443ee8 100644 --- a/lib/commands/new/css-processors/none.js +++ b/lib/commands/new/buildsystems/cli/css-processors/none.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/css-processors/postcss.js b/lib/commands/new/buildsystems/cli/css-processors/postcss.js similarity index 77% rename from lib/commands/new/css-processors/postcss.js rename to lib/commands/new/buildsystems/cli/css-processors/postcss.js index e2912df17..9334e3947 100644 --- a/lib/commands/new/css-processors/postcss.js +++ b/lib/commands/new/buildsystems/cli/css-processors/postcss.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/css-processors/sass.js b/lib/commands/new/buildsystems/cli/css-processors/sass.js similarity index 75% rename from lib/commands/new/css-processors/sass.js rename to lib/commands/new/buildsystems/cli/css-processors/sass.js index 9cc7d56c9..c29240fb8 100644 --- a/lib/commands/new/css-processors/sass.js +++ b/lib/commands/new/buildsystems/cli/css-processors/sass.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/css-processors/stylus.js b/lib/commands/new/buildsystems/cli/css-processors/stylus.js similarity index 75% rename from lib/commands/new/css-processors/stylus.js rename to lib/commands/new/buildsystems/cli/css-processors/stylus.js index 5650e9adf..48676123d 100644 --- a/lib/commands/new/css-processors/stylus.js +++ b/lib/commands/new/buildsystems/cli/css-processors/stylus.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/editors/atom.js b/lib/commands/new/buildsystems/cli/editors/atom.js similarity index 100% rename from lib/commands/new/editors/atom.js rename to lib/commands/new/buildsystems/cli/editors/atom.js diff --git a/lib/commands/new/editors/none.js b/lib/commands/new/buildsystems/cli/editors/none.js similarity index 100% rename from lib/commands/new/editors/none.js rename to lib/commands/new/buildsystems/cli/editors/none.js diff --git a/lib/commands/new/editors/sublime.js b/lib/commands/new/buildsystems/cli/editors/sublime.js similarity index 100% rename from lib/commands/new/editors/sublime.js rename to lib/commands/new/buildsystems/cli/editors/sublime.js diff --git a/lib/commands/new/editors/visualstudio.js b/lib/commands/new/buildsystems/cli/editors/visualstudio.js similarity index 74% rename from lib/commands/new/editors/visualstudio.js rename to lib/commands/new/buildsystems/cli/editors/visualstudio.js index 65af43255..f7989a5c4 100644 --- a/lib/commands/new/editors/visualstudio.js +++ b/lib/commands/new/buildsystems/cli/editors/visualstudio.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { if (project.model.transpiler.id !== 'typescript') { diff --git a/lib/commands/new/editors/vscode.js b/lib/commands/new/buildsystems/cli/editors/vscode.js similarity index 84% rename from lib/commands/new/editors/vscode.js rename to lib/commands/new/buildsystems/cli/editors/vscode.js index 01d79e06d..2ac538d24 100644 --- a/lib/commands/new/editors/vscode.js +++ b/lib/commands/new/buildsystems/cli/editors/vscode.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { if (project.model.transpiler.id !== 'typescript') { diff --git a/lib/commands/new/editors/webstorm.js b/lib/commands/new/buildsystems/cli/editors/webstorm.js similarity index 100% rename from lib/commands/new/editors/webstorm.js rename to lib/commands/new/buildsystems/cli/editors/webstorm.js diff --git a/lib/commands/new/buildsystems/cli/index.js b/lib/commands/new/buildsystems/cli/index.js new file mode 100644 index 000000000..3fb19fe1a --- /dev/null +++ b/lib/commands/new/buildsystems/cli/index.js @@ -0,0 +1,136 @@ +'use strict'; + +const ProjectItem = require('../../../../project-item').ProjectItem; +const path = require('path'); + +module.exports = function(project, options) { + let model = project.model; + + let configurePlatform = require(`./platforms/${model.platform.id}`); + configurePlatform(project, options); + + let configureTranspiler = require(`./transpilers/${model.transpiler.id}`); + configureTranspiler(project, options); + + let configureMarkupProcessor = require(`./markup-processors/${model.markupProcessor.id}`); + configureMarkupProcessor(project, options); + + let configureCSSProcessor = require(`./css-processors/${model.cssProcessor.id}`); + configureCSSProcessor(project, options); + + let configureUnitTestRunner = require(`./unit-test-runners/${model.unitTestRunner.id}`); + configureUnitTestRunner(project, options); + + let configureEditor = require(`./editors/${model.editor.id}`); + configureEditor(project, options); + + model.build = { + targets: [ + model.platform + ], + options: { + minify: 'stage & prod', + sourcemaps: 'dev & stage' + }, + bundles: [ + { + name: 'app-bundle.js', + source: [ + '[**/*.js]', + '**/*.{css,html}' + ] + }, + { + name: 'vendor-bundle.js', + prepend: [ + 'node_modules/bluebird/js/browser/bluebird.core.js', + 'node_modules/aurelia-cli/lib/resources/scripts/configure-bluebird.js' + ], + dependencies: [ + 'aurelia-binding', + 'aurelia-bootstrapper', + 'aurelia-dependency-injection', + 'aurelia-event-aggregator', + 'aurelia-framework', + 'aurelia-history', + 'aurelia-history-browser', + 'aurelia-loader', + 'aurelia-loader-default', + 'aurelia-logging', + 'aurelia-logging-console', + 'aurelia-metadata', + 'aurelia-pal', + 'aurelia-pal-browser', + 'aurelia-path', + 'aurelia-polyfills', + 'aurelia-route-recognizer', + 'aurelia-router', + 'aurelia-task-queue', + 'aurelia-templating', + 'aurelia-templating-binding', + { + 'name': 'aurelia-templating-resources', + 'path': '../node_modules/aurelia-templating-resources/dist/amd', + 'main': 'aurelia-templating-resources' + }, + { + 'name': 'aurelia-templating-router', + 'path': '../node_modules/aurelia-templating-router/dist/amd', + 'main': 'aurelia-templating-router' + }, + { + 'name': 'aurelia-testing', + 'path': '../node_modules/aurelia-testing/dist/amd', + 'main': 'aurelia-testing', + 'env': 'dev' + } + ] + } + ] + }; + + let appRoot = project.projectOutput.calculateRelativePath(project.root); + let srcRoot = project.src.calculateRelativePath(project.root); + + model.platform.index = path.posix.join(appRoot, 'index.html'); + model.platform.baseDir = '.'; + model.transpiler.source = path.posix.join(srcRoot, '**/*' + model.transpiler.fileExtension); + model.markupProcessor.source = path.posix.join(srcRoot, '**/*' + model.markupProcessor.fileExtension); + model.cssProcessor.source = path.posix.join(srcRoot, '**/*' + model.cssProcessor.fileExtension); + + if (model.platform.id === 'aspnetcore') { + model.platform.baseUrl = project.dist.calculateRelativePath(project.projectOutput); + model.platform.baseDir = './wwwroot'; + } + + if (project.unitTests.parent) { + model.unitTestRunner.source = path.posix.join( + project.unitTests.calculateRelativePath(project.root), + '**/*' + model.transpiler.fileExtension + ); + } + + project.addToSource( + ProjectItem.resource('main.ext', 'src/main-cli.ext', model.transpiler) + ).addToTasks( + ProjectItem.resource('build.ext', 'tasks/build.ext', model.transpiler), + ProjectItem.resource('build.json', 'tasks/build.json'), + ProjectItem.resource('copy-files.ext', 'tasks/copy-files.ext', model.transpiler), + ProjectItem.resource('run.ext', 'tasks/run.ext', model.transpiler), + ProjectItem.resource('run.json', 'tasks/run.json'), + ProjectItem.resource('watch.ext', 'tasks/watch.ext', model.transpiler) + ).addToDevDependencies( + 'browser-sync', + 'connect-history-api-fallback', + 'debounce', + 'gulp-changed-in-place', + 'gulp-plumber', + 'gulp-rename', + 'gulp-sourcemaps', + 'gulp-notify', + 'gulp-watch' + ); + + let configureLoader = require(`./loaders/${model.loader.id}`); + configureLoader(project, options); +}; diff --git a/lib/commands/new/buildsystems/cli/loaders/require.js b/lib/commands/new/buildsystems/cli/loaders/require.js new file mode 100644 index 000000000..742ae9c98 --- /dev/null +++ b/lib/commands/new/buildsystems/cli/loaders/require.js @@ -0,0 +1,30 @@ +'use strict'; + +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project, options) { + let model = project.model; + + project.loader = model.loader.id; + delete model.loader; + + model.build.loader = { + type: 'require', + configTarget: 'vendor-bundle.js', + includeBundleMetadataInConfig: 'auto', + plugins: [ + { name: 'text', extensions: ['.html', '.css'], stub: true } + ] + }; + + let vendorBundle = model.build.bundles.find(x => x.name === 'vendor-bundle.js'); + vendorBundle.dependencies.push('text'); + vendorBundle.prepend.push('node_modules/requirejs/require.js'); + + project + .addToClientDependencies( + 'requirejs', + 'text' + ) + .addToContent(ProjectItem.resource('index.html', 'content/require.index.html')); +}; diff --git a/lib/commands/new/buildsystems/cli/loaders/system.js b/lib/commands/new/buildsystems/cli/loaders/system.js new file mode 100644 index 000000000..e5947dbaf --- /dev/null +++ b/lib/commands/new/buildsystems/cli/loaders/system.js @@ -0,0 +1,34 @@ +'use strict'; + +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project, options) { + let model = project.model; + + project.loader = model.loader.id; + delete model.loader; + + model.build.loader = { + type: 'system', + configTarget: 'vendor-bundle.js', + includeBundleMetadataInConfig: 'auto', + plugins: [ + { name: 'text', extensions: ['.html', '.css'], stub: true } + ] + }; + + let vendorBundle = model.build.bundles.find(x => x.name === 'vendor-bundle.js'); + vendorBundle.dependencies.push({ + name: 'text', + path: '../node_modules/systemjs-plugin-text', + main: 'text' + }); + vendorBundle.prepend.push('node_modules/systemjs/dist/system.js'); + + project + .addToClientDependencies( + 'systemjs', + 'systemjs-plugin-text' + ) + .addToContent(ProjectItem.resource('index.html', 'content/system.index.html')); +}; diff --git a/lib/commands/new/markup-processors/maximum.js b/lib/commands/new/buildsystems/cli/markup-processors/maximum.js similarity index 78% rename from lib/commands/new/markup-processors/maximum.js rename to lib/commands/new/buildsystems/cli/markup-processors/maximum.js index 9b7ffb974..c27da00a8 100644 --- a/lib/commands/new/markup-processors/maximum.js +++ b/lib/commands/new/buildsystems/cli/markup-processors/maximum.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/markup-processors/minimum.js b/lib/commands/new/buildsystems/cli/markup-processors/minimum.js similarity index 78% rename from lib/commands/new/markup-processors/minimum.js rename to lib/commands/new/buildsystems/cli/markup-processors/minimum.js index 3418d269d..8bdb4f3f1 100644 --- a/lib/commands/new/markup-processors/minimum.js +++ b/lib/commands/new/buildsystems/cli/markup-processors/minimum.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/markup-processors/none.js b/lib/commands/new/buildsystems/cli/markup-processors/none.js similarity index 71% rename from lib/commands/new/markup-processors/none.js rename to lib/commands/new/buildsystems/cli/markup-processors/none.js index 22789077b..c5fd66fd6 100644 --- a/lib/commands/new/markup-processors/none.js +++ b/lib/commands/new/buildsystems/cli/markup-processors/none.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/markup-processors/pug.js b/lib/commands/new/buildsystems/cli/markup-processors/pug.js similarity index 75% rename from lib/commands/new/markup-processors/pug.js rename to lib/commands/new/buildsystems/cli/markup-processors/pug.js index 2657e9a33..ca6a5be44 100644 --- a/lib/commands/new/markup-processors/pug.js +++ b/lib/commands/new/buildsystems/cli/markup-processors/pug.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.addToTasks( diff --git a/lib/commands/new/buildsystems/cli/platforms/aspnetcore.js b/lib/commands/new/buildsystems/cli/platforms/aspnetcore.js new file mode 100644 index 000000000..ddf9a8980 --- /dev/null +++ b/lib/commands/new/buildsystems/cli/platforms/aspnetcore.js @@ -0,0 +1,12 @@ +'use strict'; + +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.configureVisualStudioStructure(); + project.configureDist(ProjectItem.directory('scripts')); + project.projectOutput.add( + ProjectItem.resource('index.html', `content/${project.model.loader.id}.index.html`).askUserIfExists() + ); + project.configureDefaultSetup(); +}; diff --git a/lib/commands/new/buildsystems/cli/platforms/web.js b/lib/commands/new/buildsystems/cli/platforms/web.js new file mode 100644 index 000000000..0eddb0d79 --- /dev/null +++ b/lib/commands/new/buildsystems/cli/platforms/web.js @@ -0,0 +1,9 @@ +'use strict'; + +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.configureDefaultStructure(); + project.configureDist(ProjectItem.directory('scripts')); + project.configureDefaultSetup(); +}; diff --git a/lib/commands/new/transpilers/babel.js b/lib/commands/new/buildsystems/cli/transpilers/babel.js similarity index 91% rename from lib/commands/new/transpilers/babel.js rename to lib/commands/new/buildsystems/cli/transpilers/babel.js index 09cd59e22..f95688573 100644 --- a/lib/commands/new/transpilers/babel.js +++ b/lib/commands/new/buildsystems/cli/transpilers/babel.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.model.transpiler.options = { diff --git a/lib/commands/new/transpilers/typescript.js b/lib/commands/new/buildsystems/cli/transpilers/typescript.js similarity index 88% rename from lib/commands/new/transpilers/typescript.js rename to lib/commands/new/buildsystems/cli/transpilers/typescript.js index 512f62146..3d7cbc32e 100644 --- a/lib/commands/new/transpilers/typescript.js +++ b/lib/commands/new/buildsystems/cli/transpilers/typescript.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.model.transpiler.dtsSource = [ diff --git a/lib/commands/new/unit-test-runners/karma.js b/lib/commands/new/buildsystems/cli/unit-test-runners/karma.js similarity index 85% rename from lib/commands/new/unit-test-runners/karma.js rename to lib/commands/new/buildsystems/cli/unit-test-runners/karma.js index 7aa0b7a33..e9ef19a34 100644 --- a/lib/commands/new/unit-test-runners/karma.js +++ b/lib/commands/new/buildsystems/cli/unit-test-runners/karma.js @@ -1,5 +1,5 @@ 'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; +const ProjectItem = require('../../../../../project-item').ProjectItem; module.exports = function(project) { project.model.testFramework = { @@ -17,7 +17,7 @@ module.exports = function(project) { ProjectItem.resource('setup.ext', 'test/setup.js', project.model.transpiler), ProjectItem.resource('app.spec.ext', 'test/app.spec.js', project.model.transpiler) ), - ProjectItem.resource('aurelia-karma.js', `test/${project.loader}.aurelia-karma.js`) + ProjectItem.resource('aurelia-karma.js', `test/${project.model.loader.id}.aurelia-karma.js`) ) ).addToDevDependencies( 'jasmine-core', diff --git a/lib/commands/new/unit-test-runners/none.js b/lib/commands/new/buildsystems/cli/unit-test-runners/none.js similarity index 100% rename from lib/commands/new/unit-test-runners/none.js rename to lib/commands/new/buildsystems/cli/unit-test-runners/none.js diff --git a/lib/commands/new/buildsystems/webpack/css-processors/less.js b/lib/commands/new/buildsystems/webpack/css-processors/less.js new file mode 100644 index 000000000..e65c052c9 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/css-processors/less.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(project) { + project.addToDevDependencies( + 'less-loader', + 'less' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/css-processors/none.js b/lib/commands/new/buildsystems/webpack/css-processors/none.js new file mode 100644 index 000000000..24a3553b7 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/css-processors/none.js @@ -0,0 +1,4 @@ +'use strict'; + +module.exports = function(project) { +}; diff --git a/lib/commands/new/buildsystems/webpack/css-processors/postcss.js b/lib/commands/new/buildsystems/webpack/css-processors/postcss.js new file mode 100644 index 000000000..e87770197 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/css-processors/postcss.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(project) { + project.addToDevDependencies( + 'postcss-loader', + 'autoprefixer' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/css-processors/sass.js b/lib/commands/new/buildsystems/webpack/css-processors/sass.js new file mode 100644 index 000000000..60d0c53ab --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/css-processors/sass.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(project) { + project.addToDevDependencies( + 'sass-loader', + 'node-sass' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/css-processors/stylus.js b/lib/commands/new/buildsystems/webpack/css-processors/stylus.js new file mode 100644 index 000000000..a8bd9cb7a --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/css-processors/stylus.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(project) { + project.addToDevDependencies( + 'stylus-loader', + 'stylus' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/editors/atom.js b/lib/commands/new/buildsystems/webpack/editors/atom.js new file mode 100644 index 000000000..c7c42ab85 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/editors/atom.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(project) { + +}; diff --git a/lib/commands/new/buildsystems/webpack/editors/none.js b/lib/commands/new/buildsystems/webpack/editors/none.js new file mode 100644 index 000000000..d5c6bc1ae --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/editors/none.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = function() {}; diff --git a/lib/commands/new/buildsystems/webpack/editors/sublime.js b/lib/commands/new/buildsystems/webpack/editors/sublime.js new file mode 100644 index 000000000..c7c42ab85 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/editors/sublime.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(project) { + +}; diff --git a/lib/commands/new/buildsystems/webpack/editors/vscode.js b/lib/commands/new/buildsystems/webpack/editors/vscode.js new file mode 100644 index 000000000..68a431d25 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/editors/vscode.js @@ -0,0 +1,12 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.addToContent( + ProjectItem.directory('.vscode').add( + ProjectItem.resource('settings.json', 'content/settings.json'), + ProjectItem.resource('extensions.json', 'content/extensions.json'), + ProjectItem.resource('launch.json', 'content/launch.json') + ) + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/editors/webstorm.js b/lib/commands/new/buildsystems/webpack/editors/webstorm.js new file mode 100644 index 000000000..c7c42ab85 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/editors/webstorm.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(project) { + +}; diff --git a/lib/commands/new/buildsystems/webpack/index.js b/lib/commands/new/buildsystems/webpack/index.js new file mode 100644 index 000000000..b96332e6b --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/index.js @@ -0,0 +1,97 @@ +'use strict'; + +const ProjectItem = require('../../../../project-item').ProjectItem; + +module.exports = function(project, options) { + let model = project.model; + + const configurePlatform = require('./platforms'); + configurePlatform(project); + + let configureTranspiler = require(`./transpilers/${model.transpiler.id}`); + configureTranspiler(project); + + let unitTestRunner = project.model.unitTestRunner; + if (unitTestRunner) { + for (let i = 0; i < unitTestRunner.length; i++) { + let configureUnitTestRunner = require(`./unit-test-runners/${unitTestRunner[i].id}`); + configureUnitTestRunner(project); + } + } + + if (model.integrationTestRunner) { + let configureIntegrationTestRunner = require(`./integration-test-runner/${model.integrationTestRunner.id}`); + configureIntegrationTestRunner(project); + } + + let configureCSSProcessor = require(`./css-processors/${model.cssProcessor.id}`); + configureCSSProcessor(project); + + let configureEditor = require(`./editors/${model.editor.id}`); + configureEditor(project); + + project.addToSource( + ProjectItem.resource('main.ext', 'src/main-webpack.ext', model.transpiler) + ).addToTasks( + ProjectItem.resource('build.ext', 'tasks/build-webpack.ext', project.model.transpiler), + ProjectItem.resource('build.json', 'tasks/build.json'), + ProjectItem.resource('run.ext', 'tasks/run-webpack.ext', project.model.transpiler), + ProjectItem.resource('run.json', 'tasks/run-webpack.json') + ).addToContent( + ProjectItem.resource('index.ejs', 'content/index-webpack.ejs'), + ProjectItem.resource('package-scripts.js', 'content/package-scripts.template.js') + .asTemplate(normalizeForPreprocess(model)), + ProjectItem.resource('static/favicon.ico', 'content/favicon.ico'), + ProjectItem.resource('webpack.config.js', 'content/webpack.config.template.js') + .asTemplate(model) + ).addToDevDependencies( + 'html-webpack-plugin', + 'copy-webpack-plugin', + 'extract-text-webpack-plugin', + 'aurelia-webpack-plugin', + 'webpack', + 'webpack-dev-server', + 'expose-loader', + 'style-loader', + 'url-loader', + 'css-loader', + 'nps-utils', + 'istanbul-instrumenter-loader', + 'opn' + ).addToDependencies( + 'aurelia-polyfills' + ); + + project.package.engines = { + 'node': '>= 6.0.0' + }; + + project.package.scripts = { + start: 'nps', + test: 'nps test' + }; + + project.package.main = 'dist/app.bundle.js'; +}; + +function normalizeForPreprocess(model) { + function arrToObj(arr) { + if (!arr) { + return {}; + } + if (!(arr instanceof Array)) { + throw new Error(`Expected ${arr} to be an array`); + } + + let obj = {}; + arr.map(x => obj[x.id] = x); + return obj; + } + + let result = { + model: model, + testRunners: arrToObj(model.unitTestRunner) + }; + + return result; +} diff --git a/lib/commands/new/buildsystems/webpack/integration-test-runner/none.js b/lib/commands/new/buildsystems/webpack/integration-test-runner/none.js new file mode 100644 index 000000000..24a3553b7 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/integration-test-runner/none.js @@ -0,0 +1,4 @@ +'use strict'; + +module.exports = function(project) { +}; diff --git a/lib/commands/new/buildsystems/webpack/integration-test-runner/protractor.js b/lib/commands/new/buildsystems/webpack/integration-test-runner/protractor.js new file mode 100644 index 000000000..5c93eac62 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/integration-test-runner/protractor.js @@ -0,0 +1,28 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + let transpilerId = project.model.transpiler.id; + let testContentRoot = `test/webpack/${transpilerId}`; + + if (project.model.transpiler.id === 'babel') { + project.addToDevDependencies( + 'ts-node' + ); + } + + project.addToDevDependencies( + 'aurelia-protractor-plugin', + 'protractor', + 'wait-on' + ).addToContent( + project.tests.add( + project.e2eTests.add( + ProjectItem.resource('demo.e2e.ext', `${testContentRoot}/e2e/demo.e2e.ext`, project.model.transpiler), + ProjectItem.resource('skeleton.po.ext', `${testContentRoot}/e2e/skeleton.po.ext`, project.model.transpiler), + ProjectItem.resource('welcome.po.ext', `${testContentRoot}/e2e/welcome.po.ext`, project.model.transpiler) + ), + ProjectItem.resource('protractor.conf.js', `${testContentRoot}/protractor.conf.js`) + ) + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/platforms/aspnetcore.js b/lib/commands/new/buildsystems/webpack/platforms/aspnetcore.js new file mode 100644 index 000000000..637f1cd2e --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/platforms/aspnetcore.js @@ -0,0 +1,18 @@ +'use strict'; + +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.configureVisualStudioStructure() + .configureDist(ProjectItem.directory('dist')) + .configureJavascriptServices() + .configureDefaultSetup() + .addToDevDependencies( + 'aspnet-webpack', + 'webpack-hot-middleware' + ) + .addPostInstallProcess({ + command: 'dotnet', + args: ['restore'] + }); +}; diff --git a/lib/commands/new/buildsystems/webpack/platforms/index.js b/lib/commands/new/buildsystems/webpack/platforms/index.js new file mode 100644 index 000000000..5329edf28 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/platforms/index.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(project) { + let model = project.model; + + Object.assign(model.platform, { + port: 8080, + hmr: true, + open: false + }); + + const configurePlatform = require(`./${model.platform.id}`); + configurePlatform(project); +}; diff --git a/lib/commands/new/platforms/web.js b/lib/commands/new/buildsystems/webpack/platforms/web.js similarity index 50% rename from lib/commands/new/platforms/web.js rename to lib/commands/new/buildsystems/webpack/platforms/web.js index ae1597897..3563c4d0a 100644 --- a/lib/commands/new/platforms/web.js +++ b/lib/commands/new/buildsystems/webpack/platforms/web.js @@ -1,6 +1,9 @@ 'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + module.exports = function(project) { project.configureDefaultStructure(); + project.configureDist(ProjectItem.directory('dist')); project.configureDefaultSetup(); }; diff --git a/lib/commands/new/buildsystems/webpack/transpilers/babel.js b/lib/commands/new/buildsystems/webpack/transpilers/babel.js new file mode 100644 index 000000000..d77abe82e --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/transpilers/babel.js @@ -0,0 +1,33 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.model.transpiler.options = { + 'plugins': [ + 'transform-es2015-modules-amd' + ] + }; + + project.addToContent( + ProjectItem.resource('.eslintrc.json', 'content/eslintrc.json'), + ProjectItem.resource('.babelrc.js', 'content/babelrc.webpack.js'), + ProjectItem.resource('.babelrc', 'content/babelrc.webpack'), + ProjectItem.resource('jsconfig.json', 'content/jsconfig-webpack.json') + ) + .addToDevDependencies( + 'babel-eslint@7.2.3', + 'eslint@3.19.0', + 'babel-loader', + 'babel-core', + 'babel-plugin-syntax-flow', + 'babel-plugin-transform-class-properties', + 'babel-plugin-transform-decorators-legacy', + 'babel-plugin-transform-flow-strip-types', + 'babel-polyfill', + 'babel-preset-env', + 'babel-preset-es2015', + 'babel-preset-stage-1', + 'babel-register', + 'babel-plugin-istanbul' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/transpilers/typescript.js b/lib/commands/new/buildsystems/webpack/transpilers/typescript.js new file mode 100644 index 000000000..7eb16ae91 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/transpilers/typescript.js @@ -0,0 +1,20 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.addToContent( + ProjectItem.resource('tsconfig.json', 'content/tsconfig.json'), + ProjectItem.directory('custom_typings') + .add( + ProjectItem.resource('fetch.d.ts', 'content/custom_typings_webpack/fetch.d.ts'), + ProjectItem.resource('system.d.ts', 'content/custom_typings_webpack/system.d.ts') + ) + ).addToDevDependencies( + 'awesome-typescript-loader', + 'ts-node', + '@types/node', + '@types/lodash', + '@types/webpack', + 'typescript' + ); +}; diff --git a/lib/commands/new/buildsystems/webpack/unit-test-runners/jasmine.js b/lib/commands/new/buildsystems/webpack/unit-test-runners/jasmine.js new file mode 100644 index 000000000..e6426a1d3 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/unit-test-runners/jasmine.js @@ -0,0 +1,30 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + project.model.testFramework = { + 'id': 'jasmine', + 'displayName': 'Jasmine' + }; + + let transpilerId = project.model.transpiler.id; + let testContentRoot = `test/webpack/${transpilerId}`; + + project.addToContent( + project.tests.add( + project.unitTests.add( + ProjectItem.resource('app.spec.ext', `${testContentRoot}/unit/app.spec.ext`, project.model.transpiler) + ) + ) + ); + + if (project.model.transpiler.id === 'babel') { + project.addToContent( + project.tests.add( + project.unitTests.add( + ProjectItem.resource('.eslintrc', `${testContentRoot}/unit/.eslintrc`) + ) + ) + ); + } +}; diff --git a/lib/commands/new/buildsystems/webpack/unit-test-runners/jest.js b/lib/commands/new/buildsystems/webpack/unit-test-runners/jest.js new file mode 100644 index 000000000..bf4b6984e --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/unit-test-runners/jest.js @@ -0,0 +1,110 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + let configureJasmine = require('./jasmine'); + configureJasmine(project); + + let transpilerId = project.model.transpiler.id; + let testContentRoot = `test/webpack/${transpilerId}`; + + project.addToTasks( + ProjectItem.resource('jest.ext', 'tasks/jest.ext', project.model.transpiler), + ProjectItem.resource('jest.json', 'tasks/jest.json') + ).addToContent( + project.tests.add( + ProjectItem.resource('jest-pretest.ext', `${testContentRoot}/jest-pretest.ext`, project.model.transpiler) + ) + ).addToDevDependencies( + 'jest', + 'jest-cli', + 'gulp-util', + 'aurelia-loader-nodejs', + 'aurelia-pal-nodejs' + ); + + if (project.model.transpiler.id === 'babel') { + project.addToDevDependencies( + 'babel-jest' + ); + + project.package.jest = { + modulePaths: [ + '/src', + '/node_modules' + ], + moduleFileExtensions: [ + 'js', + 'json' + ], + transform: { + '^.+\\.jsx?$': 'babel-jest' + }, + testRegex: '\\.spec\\.(ts|js)x?$', + setupFiles: [ + '/test/jest-pretest.js' + ], + testEnvironment: 'node', + moduleNameMapper: { + 'aurelia-(.*)': '/node_modules/$1' + }, + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.{js,ts}', + '!**/*.spec.{js,ts}', + '!**/node_modules/**', + '!**/test/**' + ], + coverageDirectory: '/test/coverage-jest', + coverageReporters: [ + 'json', + 'lcov', + 'text', + 'html' + ] + }; + } else if (project.model.transpiler.id === 'typescript') { + project.addToDevDependencies( + 'ts-jest', + '@types/jest' + ); + + project.package.jest = { + modulePaths: [ + '/src', + '/node_modules' + ], + moduleFileExtensions: [ + 'js', + 'json', + 'ts' + ], + transform: { + '^.+\\.(ts|tsx)$': '/node_modules/ts-jest/preprocessor.js' + }, + testRegex: '\\.spec\\.(ts|js)x?$', + setupFiles: [ + '/test/jest-pretest.ts' + ], + testEnvironment: 'node', + moduleNameMapper: { + 'aurelia-(.*)': '/node_modules/$1' + }, + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.{js,ts}', + '!**/*.spec.{js,ts}', + '!**/node_modules/**', + '!**/test/**' + ], + coverageDirectory: '/test/coverage-jest', + coverageReporters: [ + 'json', + 'lcov', + 'text', + 'html' + ], + mapCoverage: true + }; + } +}; diff --git a/lib/commands/new/buildsystems/webpack/unit-test-runners/karma.js b/lib/commands/new/buildsystems/webpack/unit-test-runners/karma.js new file mode 100644 index 000000000..3a90361b3 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/unit-test-runners/karma.js @@ -0,0 +1,41 @@ +'use strict'; +const ProjectItem = require('../../../../../project-item').ProjectItem; + +module.exports = function(project) { + let configureJasmine = require('./jasmine'); + configureJasmine(project); + + let transpilerId = project.model.transpiler.id; + let testContentRoot = `test/webpack/${transpilerId}`; + + project.addToTasks( + ProjectItem.resource('karma.ext', 'tasks/karma.ext', project.model.transpiler), + ProjectItem.resource('karma.json', 'tasks/karma.json') + ).addToContent( + project.tests.add( + ProjectItem.resource('karma.conf.js', `${testContentRoot}/karma.conf.js`), + ProjectItem.resource('karma-bundle.js', `${testContentRoot}/karma-bundle.js`) + ) + ).addToDevDependencies( + 'jasmine-core', + 'karma', + 'karma-chrome-launcher', + 'karma-coverage', + 'karma-jasmine', + 'karma-mocha-reporter', + 'karma-webpack', + 'karma-coverage-istanbul-reporter', + 'jest-jasmine2' + ); + + if (project.model.transpiler.id === 'babel') { + project.addToDevDependencies('karma-babel-preprocessor'); + } else { + project.addToDevDependencies('karma-typescript-preprocessor'); + + // prevent duplicate typescript definitions + if (!project.model.unitTestRunner.find(x => x.id === 'jest')) { + project.addToDevDependencies('@types/jasmine'); + } + } +}; diff --git a/lib/commands/new/buildsystems/webpack/unit-test-runners/none.js b/lib/commands/new/buildsystems/webpack/unit-test-runners/none.js new file mode 100644 index 000000000..24a3553b7 --- /dev/null +++ b/lib/commands/new/buildsystems/webpack/unit-test-runners/none.js @@ -0,0 +1,4 @@ +'use strict'; + +module.exports = function(project) { +}; diff --git a/lib/commands/new/loaders/require.js b/lib/commands/new/loaders/require.js deleted file mode 100644 index 6f4b50e51..000000000 --- a/lib/commands/new/loaders/require.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; - -module.exports = function(project) { - project.loader = project.model.loader.id; - delete project.model.loader; // remove loader from model as it is actually a property of model.build - - project.loaderTextPlugin = 'text'; - project.loaderScript = 'node_modules/requirejs/require.js'; - project.addToContent( - ProjectItem.resource('index.html', 'content/require.index.html') - ).addToClientDependencies( - 'requirejs', - 'text' - ); -}; diff --git a/lib/commands/new/loaders/system.js b/lib/commands/new/loaders/system.js deleted file mode 100644 index ea495842b..000000000 --- a/lib/commands/new/loaders/system.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -const ProjectItem = require('../../../project-item').ProjectItem; - -module.exports = function(project) { - project.loader = project.model.loader.id; - delete project.model.loader; // remove loader from model as it is actually a property of model.build - - project.loaderTextPlugin = { - 'name': 'text', - 'path': '../node_modules/systemjs-plugin-text', - 'main': 'text' - }; - project.loaderScript = 'node_modules/systemjs/dist/system.js'; - project.addToContent( - ProjectItem.resource('index.html', 'content/system.index.html') - ).addToClientDependencies( - 'systemjs', - 'systemjs-plugin-text' - ); -}; diff --git a/lib/commands/new/new-application.json b/lib/commands/new/new-application.json index f71daeee5..10878f97a 100644 --- a/lib/commands/new/new-application.json +++ b/lib/commands/new/new-application.json @@ -2,15 +2,11 @@ "name": "New Aurelia Application", "activities": [ { - "id": 1, + "id": 100, "type": "state-assign", - "nextActivity": 2, + "nextActivity": 200, "state": { "type": "project:application", - "loader": { - "id": "require", - "displayName": "RequireJS" - }, "transpiler": { "id": "babel", "displayName": "Babel", @@ -37,93 +33,198 @@ "editor": { "id": "vscode", "displayName": "Visual Studio Code" - }, + }, "platform": { "id": "web", "displayName": "Web" + }, + "loader": { + "id": "require", + "displayName": "RequireJS" + }, + "bundler": { + "id": "cli", + "displayName": "Aurelia-CLI" } } }, { - "id": 2, "type": "input-text", - "nextActivity": 3, + "id": 200, + "nextActivity": 300, "question": "Please enter a name for your new project below.", "stateProperty": "name", "defaultValue": "aurelia-app" }, { - "id": 3, - "type": "input-select", - "nextActivity": 4, - "question": "Which module loader would you like to use?", - "stateProperty": "loader", - "options": [ - { - "displayName": "RequireJS", - "description": "A file and module loader for JavaScript.", - "value": { - "id": "require", - "displayName": "RequireJS" - } - }, - { - "displayName": "SystemJS", - "description": "Dynamic ES module loader", - "value": { - "id": "system", - "displayName": "SystemJS" - } - } - ] - }, - { - "id": 4, "type": "input-select", - "nextActivity": 5, + "id": 300, + "nextActivity": 400, "question": "Would you like to use the default setup or customize your choices?", "stateProperty": "defaultOrCustom", "options": [ { "displayName": "Default ESNext", - "description": "A basic web-oriented setup with Babel for modern JavaScript development.", + "description": "A basic web-oriented setup with Babel and RequireJS for modern JavaScript development.", "value": "default-esnext" }, { "displayName": "Default TypeScript", - "description": "A basic web-oriented setup with TypeScript for modern JavaScript development.", + "description": "A basic web-oriented setup with TypeScript and RequireJS for modern JavaScript development.", "value": "default-typescript" }, { "displayName": "Custom", - "description": "Select transpilers, CSS pre-processors and more.", + "description": "Select loaders (requirejs/systemjs), bundlers (cli/webpack), transpilers, CSS pre-processors and more.", "value": "custom" } ] }, { - "id": 5, "type": "branch-switch", + "id": 400, "stateProperty": "defaultOrCustom", "branches": [ { "case": "default-esnext", - "nextActivity": 15 + "nextActivity": 1000 }, { "case": "default-typescript", - "nextActivity": 13 + "nextActivity": 500 }, { "case": "custom", - "nextActivity": 6 + "nextActivity": 600 + } + ] + }, + { + "type": "state-assign", + "id": 500, + "nextActivity": 1000, + "state": { + "transpiler": { + "id": "typescript", + "displayName": "TypeScript", + "fileExtension": ".ts" + } + } + }, + { + "id": 600, + "type": "input-select", + "nextActivity": 610, + "question": "Which module loader / bundler would you like to use?", + "stateProperty": "loaderBundler", + "options": [ + { + "displayName": "RequireJS", + "description": "A file and module loader for JavaScript.", + "value": "requirejs" + }, + { + "displayName": "SystemJS", + "description": "Dynamic ES module loader", + "value": "systemjs" + }, + { + "displayName": "Webpack", + "description": "A powerful bundler", + "value": "webpack" + } + ] + }, + { + "id": 610, + "type": "branch-switch", + "stateProperty": "loaderBundler", + "branches": [ + { + "case": "requirejs", + "nextActivity": 611 + }, + { + "case": "systemjs", + "nextActivity": 612 + }, + { + "case": "webpack", + "nextActivity": 613 } ] }, { - "id": 6, + "id": 611, + "type": "state-assign", + "nextActivity": 620, + "state": { + "loader": { + "id": "require", + "displayName": "RequireJS" + }, + "bundler": { + "id": "cli", + "displayName": "Aurelia-CLI" + } + } + }, + { + "id": 612, + "type": "state-assign", + "nextActivity": 620, + "state": { + "loader": { + "id": "system", + "displayName": "SystemJS" + }, + "bundler": { + "id": "cli", + "displayName": "Aurelia-CLI" + } + } + }, + { + "id": 613, + "type": "state-assign", + "nextActivity": 621, + "state": { + "loader": { + "id": "none", + "displayName": "None" + }, + "bundler": { + "id": "webpack", + "displayName": "Webpack" + }, + "build": { + "options": { + "server": "dev", + "extractCss": "prod", + "coverage": "dev" + } + }, + "unitTestRunner": [{ + "id": "jest", + "displayName": "Jest" + }, { + "id": "karma", + "displayName": "Karma" + }], + "testFramework": { + "id": "jasmine", + "displayName": "Jasmine" + }, + "integrationTestRunner": { + "id": "protractor", + "displayName": "Protractor" + } + } + }, + { + "id": 620, "type": "input-select", - "nextActivity": 7, + "nextActivity": 630, "question": "What platform are you targeting?", "stateProperty": "platform", "options": [ @@ -147,9 +248,34 @@ ] }, { - "id": 7, + "id": 621, "type": "input-select", - "nextActivity": 8, + "nextActivity": 630, + "question": "What platform are you targeting?", + "stateProperty": "platform", + "options": [ + { + "displayName": "Web", + "description": "The default web platform setup.", + "value": { + "id": "web", + "displayName": "Web" + } + }, + { + "displayName": "ASP.NET Core", + "description": "A powerful, patterns-based way to build dynamic websites with .NET.", + "value": { + "id": "aspnetcore", + "displayName": "ASP.NET Core" + } + } + ] + }, + { + "id": 630, + "type": "input-select", + "nextActivity": 640, "question": "What transpiler would you like to use?", "stateProperty": "transpiler", "options": [ @@ -174,9 +300,9 @@ ] }, { - "id": 8, + "id": 640, "type": "input-select", - "nextActivity": 9, + "nextActivity": 650, "question": "How would you like to setup your template?", "stateProperty": "markupProcessor", "options": [ @@ -210,9 +336,9 @@ ] }, { - "id": 9, + "id": 650, "type": "input-select", - "nextActivity": 10, + "nextActivity": 660, "question": "What css processor would you like to use?", "stateProperty": "cssProcessor", "options": [ @@ -264,9 +390,24 @@ ] }, { - "id": 10, + "type": "branch-switch", + "id": 660, + "stateProperty": "bundler", + "branches": [ + { + "case": "cli", + "nextActivity": 670 + }, + { + "case": "webpack", + "nextActivity": 680 + } + ] + }, + { + "id": 670, "type": "input-select", - "nextActivity": 11, + "nextActivity": 700, "question": "Would you like to configure unit testing?", "stateProperty": "unitTestRunner", "options": [ @@ -289,24 +430,93 @@ ] }, { - "id": 11, + "id": 680, + "type": "input-multiselect", + "nextActivity": 681, + "question": "Which unit test runners would you like to use?", + "stateProperty": "unitTestRunner", + "options": [ + { + "displayName": "None", + "description": "Skip testing. My code is always perfect anyway.", + "value": { + "id": "none", + "displayName": "None" + } + }, + { + "displayName": "Karma", + "description": "Configure your app with Karma and Jasmine", + "value": { + "id": "karma", + "displayName": "Karma" + } + }, + { + "displayName": "Jest", + "description": "Configure your app with Jest and Jasmine", + "value": { + "id": "jest", + "displayName": "Jest" + } + } + ] + }, + { + "id": 681, + "type": "input-select", + "nextActivity": 700, + "question": "Would you like to configure integration testing?", + "stateProperty": "integrationTestRunner", + "options": [ + { + "displayName": "Protractor", + "description": "Configure your app with Protractor", + "value": { + "id": "protractor", + "displayName": "Protractor" + } + }, + { + "displayName": "No", + "description": "Skip testing. My code is always perfect anyway.", + "value": { + "id": "none", + "displayName": "None" + } + } + ] + }, + { + "id": 700, "type": "branch-switch", "stateProperty": "platform", "branches": [ { "case": "aspnetcore", - "nextActivity": 14 + "nextActivity": 800 }, { "case": "default", - "nextActivity": 12 + "nextActivity": 900 } ] }, { - "id": 12, + "id": 800, + "type": "state-assign", + "nextActivity": 900, + "state": { + "editor": { + "id": "visualstudio", + "displayName": "Visual Studio" + } + } + }, + { + "id": 900, "type": "input-select", - "nextActivity": 15, + "nextActivity": 1000, "question": "What is your default code editor?", "stateProperty": "editor", "options": [ @@ -353,45 +563,22 @@ ] }, { - "id": 13, - "type": "state-assign", - "nextActivity": 15, - "state": { - "transpiler": { - "id": "typescript", - "displayName": "TypeScript", - "fileExtension": ".ts" - } - } - }, - { - "id": 14, - "type": "state-assign", - "nextActivity": 15, - "state": { - "editor": { - "id": "visualstudio", - "displayName": "Visual Studio" - } - } - }, - { - "id": 15, "type": "project-create", - "nextActivity": 16, - "restartActivity": 17 + "id": 1000, + "nextActivity": 1100, + "restartActivity": 1200 }, { - "id": 16, + "id": 1100, "type": "project-install" }, { - "id": 17, + "id": 1200, "type": "state-assign", - "nextActivity": 1, + "nextActivity": 100, "state": { "name": null } } ] -} +} \ No newline at end of file diff --git a/lib/commands/new/platforms/aspnetcore.js b/lib/commands/new/platforms/aspnetcore.js deleted file mode 100644 index 3b55ac3f5..000000000 --- a/lib/commands/new/platforms/aspnetcore.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = function(project) { - project.configureVisualStudioStructure(project.model.loader.id); - project.configureDefaultSetup(); -}; diff --git a/lib/commands/new/project-template.js b/lib/commands/new/project-template.js index 601b6692f..9f6d593b1 100644 --- a/lib/commands/new/project-template.js +++ b/lib/commands/new/project-template.js @@ -2,10 +2,12 @@ const ProjectItem = require('../../project-item').ProjectItem; const NPM = require('../../package-managers/npm').NPM; const path = require('path'); +const fs = require('../../file-system'); const string = require('../../string'); const getSupportedVersion = require('../../dependencies').getSupportedVersion; const spawn = require('child_process').spawn; const add = ProjectItem.prototype.add; +const CLIOptions = require('../../cli-options').CLIOptions; exports.ProjectTemplate = class { constructor(model, options) { @@ -56,6 +58,8 @@ exports.ProjectTemplate = class { this.generators = ProjectItem.directory('generators'); this.environments = ProjectItem.directory('environments'); + this.manualInstructions = ProjectItem.text('instructions.txt'); + this.projectFolder = ProjectItem.directory('aurelia_project').add( this.tasks, this.generators, @@ -68,7 +72,7 @@ exports.ProjectTemplate = class { return this.package.name; } - configureVisualStudioStructure(loader) { + configureVisualStudioStructure() { this.content = this.root; this.projectOutput = ProjectItem.directory('wwwroot'); @@ -77,13 +81,63 @@ exports.ProjectTemplate = class { this.projectFolder, this.src, this.projectOutput.add( - ProjectItem.resource('index.html', `content/${loader}.index.html`).askUserIfExists(), ProjectItem.resource('favicon.ico', 'img/favicon.ico').skipIfExists() ), ProjectItem.jsonObject('package.json', this.package).mergeIfExists(), ProjectItem.resource('.editorconfig', 'content/editorconfig').skipIfExists(), ProjectItem.resource('.gitignore', 'content/gitignore').skipIfExists() ); + + return this; + } + + configureJavascriptServices() { + let projFile = 'Project.csproj'; + + if (CLIOptions.hasFlag('here')) { + let csProjs = fs.readdirSync(this.root.name).filter(p => path.extname(p) === '.csproj'); + if (csProjs.length > 0) { + projFile = csProjs[0]; + } + } + + this.addToContent( + ProjectItem.directory('Controllers') + .add( + ProjectItem.resource('HomeController.cs', 'content/javascriptservices/Controllers/HomeController.cs').skipIfExists() + ), + ProjectItem.directory('Views') + .add( + ProjectItem.resource('_ViewImports.cshtml', 'content/javascriptservices/Views/_ViewImports.cshtml').skipIfExists(), + ProjectItem.resource('_ViewStart.cshtml', 'content/javascriptservices/Views/_ViewStart.cshtml').skipIfExists(), + ProjectItem.directory('Home').add( + ProjectItem.resource('Index.cshtml', 'content/javascriptservices/Views/Home/Index.cshtml').skipIfExists() + ), + ProjectItem.directory('Shared').add( + ProjectItem.resource('_Layout.cshtml', 'content/javascriptservices/Views/Shared/_Layout.cshtml').skipIfExists(), + ProjectItem.resource('Error.cshtml', 'content/javascriptservices/Views/Shared/Error.cshtml').skipIfExists() + ) + ), + ProjectItem.resource(projFile, 'content/javascriptservices/Project.csproj').skipIfExists(), + ProjectItem.resource('Program.cs', 'content/javascriptservices/Program.cs').skipIfExists(), + ProjectItem.resource('Startup.cs', 'content/javascriptservices/Startup.cs') + .buildReadmeIfExists( + ProjectItem.resource('Startup.cs.readme.txt', 'content/javascriptservices/Startup.cs.readme.txt'), + this.manualInstructions, + this.content + ), + ProjectItem.resource('appsettings.json', 'content/javascriptservices/appsettings.json').skipIfExists(), + ProjectItem.resource('global.json', 'content/javascriptservices/global.json').skipIfExists() + ); + + return this; + } + + configureDist(directory) { + this.dist = directory; + this.projectOutput.add(this.dist); + + return this; } // If content is always empty (web.js platform invokes this method without parameters) we can remove it. Otherwise shouldn't we add it in the configureVisualStudioStructure too? @@ -99,26 +153,17 @@ exports.ProjectTemplate = class { ProjectItem.resource('.gitignore', 'content/gitignore'), ProjectItem.resource('favicon.ico', 'img/favicon.ico') ); + + return this; } configureDefaultSetup() { - this.scripts = ProjectItem.directory('scripts'); - this.projectOutput.add(this.scripts); - this.addToSource( - ProjectItem.resource('main.ext', 'src/main.ext', this.model.transpiler), ProjectItem.resource('app.ext', 'src/app.ext', this.model.transpiler), ProjectItem.resource('app.ext', 'src/app.ext', this.model.markupProcessor), ProjectItem.resource('environment.ext', 'environments/dev.js', this.model.transpiler) ).addToResources( ProjectItem.resource('index.ext', 'src/resources/index.ext', this.model.transpiler) - ).addToTasks( - ProjectItem.resource('build.ext', 'tasks/build.ext', this.model.transpiler), - ProjectItem.resource('build.json', 'tasks/build.json'), - ProjectItem.resource('copy-files.ext', 'tasks/copy-files.ext', this.model.transpiler), - ProjectItem.resource('run.ext', 'tasks/run.ext', this.model.transpiler), - ProjectItem.resource('run.json', 'tasks/run.json'), - ProjectItem.resource('watch.ext', 'tasks/watch.ext', this.model.transpiler) ).addToGenerators( ProjectItem.resource('attribute.ext', 'generators/attribute.ext', this.model.transpiler), ProjectItem.resource('attribute.json', 'generators/attribute.json'), @@ -146,21 +191,14 @@ exports.ProjectTemplate = class { 'aurelia-cli', 'aurelia-testing', 'aurelia-tools', - 'browser-sync', - 'connect-history-api-fallback', - 'debounce', 'gulp', - 'gulp-changed-in-place', - 'gulp-plumber', - 'gulp-rename', - 'gulp-sourcemaps', - 'gulp-notify', - 'gulp-watch', 'minimatch', 'through2', 'uglify-js', 'vinyl-fs' ); + + return this; } addPostInstallProcess(config) { @@ -203,8 +241,8 @@ exports.ProjectTemplate = class { return this; } - addToScripts() { - add.apply(this.scripts, arguments); + addToDist() { + add.apply(this.dist, arguments); return this; } @@ -255,99 +293,7 @@ exports.ProjectTemplate = class { bindingBehaviors: this.bindingBehaviors.calculateRelativePath(this.src) }); - this.model.transpiler.source = path.posix.join(appRoot, '**/*' + this.model.transpiler.fileExtension); - this.model.markupProcessor.source = path.posix.join(appRoot, '**/*' + this.model.markupProcessor.fileExtension); - this.model.cssProcessor.source = path.posix.join(appRoot, '**/*' + this.model.cssProcessor.fileExtension); - this.model.platform.output = this.scripts.calculateRelativePath(this.root); - this.model.platform.index = path.posix.join(this.projectOutput.calculateRelativePath(this.root), 'index.html'); - this.model.platform.baseDir = '.'; - - if (this.model.platform.id === 'aspnetcore') { - this.model.platform.baseUrl = this.scripts.calculateRelativePath(this.projectOutput); - this.model.platform.baseDir = './wwwroot'; - } - - if (this.unitTests.parent) { - this.model.unitTestRunner.source = path.posix.join( - this.unitTests.calculateRelativePath(this.root), - '**/*' + this.model.transpiler.fileExtension - ); - } - - this.model.build = { - 'targets': [ - this.model.platform - ], - 'loader': { - 'type': this.loader, - 'configTarget': 'vendor-bundle.js', - 'includeBundleMetadataInConfig': 'auto', - 'plugins': [ - { 'name': 'text', 'extensions': ['.html', '.css'], 'stub': true } - ] - }, - 'options': { - 'minify': 'stage & prod', - 'sourcemaps': 'dev & stage' - }, - 'bundles': [ - { - 'name': 'app-bundle.js', - 'source': [ - '[**/*.js]', - '**/*.{css,html}' - ] - }, - { - 'name': 'vendor-bundle.js', - 'prepend': [ - 'node_modules/bluebird/js/browser/bluebird.core.js', - 'node_modules/aurelia-cli/lib/resources/scripts/configure-bluebird.js', - this.loaderScript - ], - 'dependencies': [ - 'aurelia-binding', - 'aurelia-bootstrapper', - 'aurelia-dependency-injection', - 'aurelia-event-aggregator', - 'aurelia-framework', - 'aurelia-history', - 'aurelia-history-browser', - 'aurelia-loader', - 'aurelia-loader-default', - 'aurelia-logging', - 'aurelia-logging-console', - 'aurelia-metadata', - 'aurelia-pal', - 'aurelia-pal-browser', - 'aurelia-path', - 'aurelia-polyfills', - 'aurelia-route-recognizer', - 'aurelia-router', - 'aurelia-task-queue', - 'aurelia-templating', - 'aurelia-templating-binding', - this.loaderTextPlugin, - { - 'name': 'aurelia-templating-resources', - 'path': '../node_modules/aurelia-templating-resources/dist/amd', - 'main': 'aurelia-templating-resources' - }, - { - 'name': 'aurelia-templating-router', - 'path': '../node_modules/aurelia-templating-router/dist/amd', - 'main': 'aurelia-templating-router' - }, - { - 'name': 'aurelia-testing', - 'path': '../node_modules/aurelia-testing/dist/amd', - 'main': 'aurelia-testing', - 'env': 'dev' - } - ] - } - ] - }; + this.model.platform.output = this.dist.calculateRelativePath(this.root); return this.root.create(ui, location); } @@ -383,7 +329,12 @@ function addDependencies(current, toAdd) { return; } - current[name] = getSupportedVersion(name).replace('\"', '').replace('\"', ''); + if (name.indexOf('@') <= 0) { + current[name] = getSupportedVersion(name).replace('\"', '').replace('\"', ''); + } else { + let split = name.split('@'); + current[split[0]] = split[1]; + } } } diff --git a/lib/dependencies.json b/lib/dependencies.json index ffc9ac375..cce5274ea 100644 --- a/lib/dependencies.json +++ b/lib/dependencies.json @@ -1,59 +1,99 @@ { + "@types/jasmine": "^2.2.0", + "@types/jest": "19.2.4", + "@types/lodash": "^4.14.69", + "@types/node": "^6.0.45", + "@types/webpack": "^3.0.4", + "aspnet-webpack": "^2.0.1", "aurelia-animator-css": "^1.0.2", "aurelia-bootstrapper": "^2.1.1", "aurelia-cli": "^0.30.1", + "aurelia-loader-nodejs": "^1.0.1", + "aurelia-pal-nodejs": "^1.0.0-beta.1.0.0", + "aurelia-protractor-plugin": "^1.0.3", "aurelia-testing": "^1.0.0-beta.3.0.1", "aurelia-tools": "^1.0.0", + "aurelia-webpack-plugin": "^2.0.0-rc.2", "autoprefixer": "^6.3.6", + "awesome-typescript-loader": "^3.2.1", + "babel-core": "^6.25.0", "babel-eslint": "^6.0.4", + "babel-jest": "^20.0.3", + "babel-loader": "^7.0.0", + "babel-plugin-istanbul": "4.1.4", "babel-plugin-syntax-flow": "^6.8.0", + "babel-plugin-transform-class-properties": "6.24.1", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-es2015-modules-amd": "^6.8.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-polyfill": "^6.9.1", + "babel-preset-env": "1.5.2", "babel-preset-es2015": "^6.13.2", "babel-preset-stage-1": "^6.5.0", - "babel-polyfill": "^6.9.1", - "babel-register": "^6.9.0", + "babel-register": "^6.24.0", "bluebird": "^3.4.1", "browser-sync": "^2.13.0", "connect-history-api-fallback": "^1.2.0", + "copy-webpack-plugin": "4.0.1", + "cross-env": "5.0.1", + "css-loader": "0.28.4", "debounce": "^1.0.2", "event-stream": "^3.3.3", + "expose-loader": "0.7.3", + "extract-text-webpack-plugin": "2.1.2", "gulp": "github:gulpjs/gulp#4.0", "gulp-babel": "^6.1.2", "gulp-changed-in-place": "^2.0.3", "gulp-eslint": "^2.0.0", "gulp-htmlmin": "^3.0.0", + "gulp-less": "^3.1.0", "gulp-notify": "^2.2.0", "gulp-plumber": "^1.1.0", - "gulp-rename": "^1.2.2", - "gulp-sourcemaps": "^2.0.0-alpha", - "gulp-less": "^3.1.0", "gulp-postcss": "6.1.1", + "gulp-rename": "^1.2.2", "gulp-sass": "^3.1.0", + "gulp-sourcemaps": "^2.0.0-alpha", "gulp-stylus": "^2.5.0", - "gulp-typescript": "^3.1.4", "gulp-tslint": "^5.0.0", + "gulp-typescript": "^3.1.4", + "gulp-util": "^3.0.8", "gulp-watch": "^4.3.11", "html-minifier": "^3.2.3", + "html-webpack-plugin": "2.28.0", + "istanbul-instrumenter-loader": "^2.0.0", "jasmine-core": "^2.4.1", + "jest": "20.0.4", + "jest-cli": "20.0.4", "karma": "^0.13.22", + "karma-babel-preprocessor": "^6.0.1", "karma-chrome-launcher": "^2.2.0", + "karma-coverage-istanbul-reporter": "^1.3.0", "karma-jasmine": "^1.0.2", - "karma-babel-preprocessor": "^6.0.1", - "karma-typescript-preprocessor": "^0.2.1", "karma-sourcemap-loader": "^0.3.7", + "karma-typescript-preprocessor": "^0.2.1", "minimatch": "^3.0.2", + "node-sass": "4.5.3", + "nps-utils": "1.2.0", + "opn": "^5.1.0", + "protractor": "^5.1.2", "requirejs": "^2.3.2", + "sass-loader": "^6.0.6", + "style-loader": "0.18.2", + "stylus": "0.54.5", "systemjs": "0.20.13", "systemjs-plugin-text": "0.0.9", "text": "github:requirejs/text#latest", "through2": "^2.0.1", + "ts-jest": "20.0.6", + "ts-node": "^3.2.0", "tslint": "^3.11.0", - "@types/jasmine": "^2.2.0", - "@types/node": "^6.0.45", "typescript": ">=1.9.0-dev || ^2.0.0", "uglify-js": "^3.0.19", - "vinyl-fs": "^2.4.3" -} + "url-loader": "0.5.8", + "vinyl-fs": "^2.4.3", + "wait-on": "2.0.2", + "webpack": "2.6.1", + "webpack-dev-server": "2.4.5", + "webpack-hot-middleware": "^2.18.2" +} \ No newline at end of file diff --git a/lib/file-system.js b/lib/file-system.js index f5eab5ce2..0ba05ac58 100644 --- a/lib/file-system.js +++ b/lib/file-system.js @@ -55,6 +55,10 @@ exports.readdir = function(path) { }); }; +exports.appendFile = function(path, text, cb) { + fs.appendFile(path, text, cb); +}; + exports.readdirSync = function(path) { return fs.readdirSync(path); }; diff --git a/lib/index.js b/lib/index.js index 7cd3baf92..07d6d0e49 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,3 +7,4 @@ exports.Project = require('./project').Project; exports.ProjectItem = require('./project-item').ProjectItem; exports.build = require('./build'); exports.Configuration = require('./configuration').Configuration; +exports.reportWebpackReadiness = require('./build/webpack-reporter'); diff --git a/lib/project-item.js b/lib/project-item.js index a33ef3cf3..8e30b1895 100644 --- a/lib/project-item.js +++ b/lib/project-item.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); const fs = require('./file-system'); +const Utils = require('./build/utils'); const locateResource = require('./resources').locateResource; exports.ProjectItem = class { @@ -41,6 +42,16 @@ exports.ProjectItem = class { return this; } + buildReadmeIfExists(source, target, targetDir) { + this._fileExistsStrategy = 'readme'; + this._readme = { + source: source, + target: target, + directory: targetDir + }; + return this; + } + add() { if (!this.isDirectory) { throw new Error('You cannot add items to a non-directory.'); @@ -82,11 +93,28 @@ exports.ProjectItem = class { return this; } + appendText(text) { + if (this.text) { + this.text += '\r\n\r\n' + text; + } else { + this.text = text; + } + return this; + } + setSourcePath(p) { this.sourcePath = p; return this; } + getSourceContent() { + return fs.readFile(this.sourcePath); + } + + getText() { + return this.text; + } + transformWith(callback) { this.transformers.push(callback); return this; @@ -112,9 +140,7 @@ exports.ProjectItem = class { return fs.stat(fullPath) .then(result => result) .catch(() => fs.mkdir(fullPath)) - .then(() => Promise.all(this.children.map(child => - child.create(ui, fullPath) - ))); + .then(() => Utils.runSequentially(this.children, child => child.create(ui, fullPath))); } else if (this.sourcePath) { return fs.readFile(this.sourcePath).then(data => { return this._write(fullPath, data, ui); @@ -132,11 +158,15 @@ exports.ProjectItem = class { for (let i = 0, ii = this.transformers.length; i < ii; ++i) { content = this.transformers[i](content); } - return fs.stat(fullPath).then(() => { switch (this._fileExistsStrategy) { case 'skip': return Promise.resolve(); + case 'readme': + this._readme.directory.add(this._readme.target); + return this._readme.source.getSourceContent().then(text => { + this._readme.target.appendText(text); + }); case 'merge': if (this.name === 'package.json') { return fs.readFile(fullPath).then(data => { diff --git a/lib/resources/content/javascriptservices/Controllers/HomeController.cs b/lib/resources/content/javascriptservices/Controllers/HomeController.cs new file mode 100644 index 000000000..9d75da882 --- /dev/null +++ b/lib/resources/content/javascriptservices/Controllers/HomeController.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace WebApplicationBasic.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + + public IActionResult Error() + { + return View(); + } + } +} diff --git a/lib/resources/content/javascriptservices/Controllers/SampleDataController.cs b/lib/resources/content/javascriptservices/Controllers/SampleDataController.cs new file mode 100644 index 000000000..1f46d127a --- /dev/null +++ b/lib/resources/content/javascriptservices/Controllers/SampleDataController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace WebApplicationBasic.Controllers +{ + [Route("api/[controller]")] + public class SampleDataController : Controller + { + private static string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + [HttpGet("[action]")] + public IEnumerable WeatherForecasts() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + DateFormatted = DateTime.Now.AddDays(index).ToString("d"), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }); + } + + public class WeatherForecast + { + public string DateFormatted { get; set; } + public int TemperatureC { get; set; } + public string Summary { get; set; } + + public int TemperatureF + { + get + { + return 32 + (int)(this.TemperatureC / 0.5556); + } + } + } + } +} diff --git a/lib/resources/content/javascriptservices/Program.cs b/lib/resources/content/javascriptservices/Program.cs new file mode 100644 index 000000000..193f1e2af --- /dev/null +++ b/lib/resources/content/javascriptservices/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; + +namespace WebApplicationBasic +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/lib/resources/content/javascriptservices/Startup.cs b/lib/resources/content/javascriptservices/Startup.cs new file mode 100644 index 000000000..4e8014548 --- /dev/null +++ b/lib/resources/content/javascriptservices/Startup.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.SpaServices.Webpack; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace WebApplicationBasic +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + + app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { + HotModuleReplacement = true + }); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseStaticFiles(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + routes.MapSpaFallbackRoute( + name: "spa-fallback", + defaults: new { controller = "Home", action = "Index" }); + }); + } + } +} diff --git a/lib/resources/content/javascriptservices/Startup.cs.readme.txt b/lib/resources/content/javascriptservices/Startup.cs.readme.txt new file mode 100644 index 000000000..4ac56a61e --- /dev/null +++ b/lib/resources/content/javascriptservices/Startup.cs.readme.txt @@ -0,0 +1,24 @@ +In order to install JavascriptServices, add the Microsoft.AspNetCore.SpaServices package to your project. + +In the Configure() method of the Startup.cs file, add the following code: + +app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { + HotModuleReplacement = true +}); + +The using statement that's required for this is: "using Microsoft.AspNetCore.SpaServices.Webpack;" + +Also make sure that you have the following in the Configure() method: + +app.UseStaticFiles(); + +app.UseMvc(routes => +{ + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + routes.MapSpaFallbackRoute( + name: "spa-fallback", + defaults: new { controller = "Home", action = "Index" }); +}); \ No newline at end of file diff --git a/lib/resources/content/javascriptservices/Views/Home/Index.cshtml b/lib/resources/content/javascriptservices/Views/Home/Index.cshtml new file mode 100644 index 000000000..f6b179145 --- /dev/null +++ b/lib/resources/content/javascriptservices/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@{ + ViewData["Title"] = "Home Page"; +} + +
Loading...
+ +@section scripts { + + +} diff --git a/lib/resources/content/javascriptservices/Views/Shared/Error.cshtml b/lib/resources/content/javascriptservices/Views/Shared/Error.cshtml new file mode 100644 index 000000000..473b35d6c --- /dev/null +++ b/lib/resources/content/javascriptservices/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

diff --git a/lib/resources/content/javascriptservices/Views/Shared/_Layout.cshtml b/lib/resources/content/javascriptservices/Views/Shared/_Layout.cshtml new file mode 100644 index 000000000..9daed4736 --- /dev/null +++ b/lib/resources/content/javascriptservices/Views/Shared/_Layout.cshtml @@ -0,0 +1,15 @@ + + + + + + @ViewData["Title"] - Aurelia + + + + + @RenderBody() + + @RenderSection("scripts", required: false) + + diff --git a/lib/resources/content/javascriptservices/Views/_ViewImports.cshtml b/lib/resources/content/javascriptservices/Views/_ViewImports.cshtml new file mode 100644 index 000000000..e7b4f83fd --- /dev/null +++ b/lib/resources/content/javascriptservices/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using WebApplicationBasic +@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" +@addTagHelper "*, Microsoft.AspNetCore.SpaServices" diff --git a/lib/resources/content/javascriptservices/Views/_ViewStart.cshtml b/lib/resources/content/javascriptservices/Views/_ViewStart.cshtml new file mode 100644 index 000000000..820a2f6e0 --- /dev/null +++ b/lib/resources/content/javascriptservices/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/lib/resources/content/javascriptservices/appsettings.json b/lib/resources/content/javascriptservices/appsettings.json new file mode 100644 index 000000000..723c096a8 --- /dev/null +++ b/lib/resources/content/javascriptservices/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/lib/resources/content/javascriptservices/global.json b/lib/resources/content/javascriptservices/global.json new file mode 100644 index 000000000..4e82dd4f7 --- /dev/null +++ b/lib/resources/content/javascriptservices/global.json @@ -0,0 +1,3 @@ +{ + "sdk": { "version": "1.0.1" } +} diff --git a/lib/resources/content/javascriptservices/project.csproj b/lib/resources/content/javascriptservices/project.csproj new file mode 100644 index 000000000..287fc6354 --- /dev/null +++ b/lib/resources/content/javascriptservices/project.csproj @@ -0,0 +1,33 @@ + + + netcoreapp1.1 + true + false + + + + + + + + + + + + + + + + + + + + + + + %(DistFiles.Identity) + PreserveNewest + + + + \ No newline at end of file diff --git a/lib/resources/content/javascriptservices/wwwroot/favicon.ico b/lib/resources/content/javascriptservices/wwwroot/favicon.ico new file mode 100644 index 000000000..6884543f9 Binary files /dev/null and b/lib/resources/content/javascriptservices/wwwroot/favicon.ico differ diff --git a/lib/resources/content/package-scripts.template.js b/lib/resources/content/package-scripts.template.js index a9f13ac22..d5b6e9c3f 100644 --- a/lib/resources/content/package-scripts.template.js +++ b/lib/resources/content/package-scripts.template.js @@ -11,6 +11,7 @@ module.exports = { default: 'nps test.jest', // @endif // @if testRunners.jest + // @if model.transpiler.id='babel' jest: { default: series( rimraf('test/coverage-jest'), @@ -20,6 +21,17 @@ module.exports = { watch: crossEnv('BABEL_TARGET=node jest --watch'), }, // @endif + // @if model.transpiler.id='typescript' + jest: { + default: series( + rimraf('test/coverage-jest'), + 'jest' + ), + accept: 'jest -u', + watch: 'jest --watch', + }, + // @endif + // @endif // @if testRunners.karma // @if !testRunners.jest @@ -59,7 +71,7 @@ module.exports = { lint: 'nps test.lint' }) }, - // @if testRunners.protractor + // @if model.integrationTestRunner.id='protractor' e2e: { default: concurrent({ webpack: `webpack-dev-server --inline --port=${E2E_PORT}`, diff --git a/lib/resources/content/tsconfig.json b/lib/resources/content/tsconfig.json index 82ad48db1..b2002d589 100644 --- a/lib/resources/content/tsconfig.json +++ b/lib/resources/content/tsconfig.json @@ -9,6 +9,7 @@ "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "allowJs": true, "moduleResolution": "node", "lib": ["es2017", "dom"] }, diff --git a/lib/resources/content/webpack.config.template.js b/lib/resources/content/webpack.config.template.js index aef595452..3c249431b 100644 --- a/lib/resources/content/webpack.config.template.js +++ b/lib/resources/content/webpack.config.template.js @@ -1,21 +1,22 @@ -const path = require('path') +const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const project = require('./aurelia_project/aurelia.json'); const { AureliaPlugin, ModuleDependenciesPlugin } = require('aurelia-webpack-plugin'); -const { optimize: { CommonsChunkPlugin }, ProvidePlugin } = require('webpack') +const { optimize: { CommonsChunkPlugin }, ProvidePlugin } = require('webpack'); // @if transpiler.id='typescript' const { TsConfigPathsPlugin, CheckerPlugin } = require('awesome-typescript-loader'); // @endif // config helpers: -const ensureArray = (config) => config && (Array.isArray(config) ? config : [config]) || [] +const ensureArray = (config) => config && (Array.isArray(config) ? config : [config]) || []; const when = (condition, config, negativeConfig) => - condition ? ensureArray(config) : ensureArray(negativeConfig) + condition ? ensureArray(config) : ensureArray(negativeConfig); // primary config: const title = 'Aurelia Navigation Skeleton'; -const outDir = path.resolve(__dirname, 'dist'); +const outDir = path.resolve(__dirname, project.platform.output); const srcDir = path.resolve(__dirname, 'src'); const nodeModulesDir = path.resolve(__dirname, 'node_modules'); const baseUrl = '/'; @@ -28,9 +29,9 @@ const cssRules = [ options: { plugins: () => [require('autoprefixer')({ browsers: ['last 2 versions'] })]} } // @endif -] +]; -module.exports = ({production, server, extractCss, coverage} = {}) => ({ +module.exports = ({production, server, extractCss, coverage} = {}) => ({ resolve: { // @if transpiler.id='typescript' extensions: ['.ts', '.js'], @@ -49,12 +50,12 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ publicPath: baseUrl, filename: production ? '[name].[chunkhash].bundle.js' : '[name].[hash].bundle.js', sourceMapFilename: production ? '[name].[chunkhash].bundle.map' : '[name].[hash].bundle.map', - chunkFilename: production ? '[name].[chunkhash].chunk.js' : '[name].[hash].chunk.js', + chunkFilename: production ? '[name].[chunkhash].chunk.js' : '[name].[hash].chunk.js' }, devServer: { contentBase: outDir, // serve index.html for all 404 (required for push-state) - historyApiFallback: true, + historyApiFallback: true }, module: { rules: [ @@ -65,7 +66,7 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ issuer: [{ not: [{ test: /\.html$/i }] }], use: extractCss ? ExtractTextPlugin.extract({ fallback: 'style-loader', - use: cssRules, + use: cssRules }) : ['style-loader', ...cssRules], }, { @@ -73,9 +74,8 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ issuer: [{ test: /\.html$/i }], // CSS required in templates cannot be extracted safely // because Aurelia would try to require it again in runtime - use: cssRules, + use: cssRules }, - // @if cssProcessor.id='less' { test: /\.less$/i, @@ -88,7 +88,6 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ issuer: /\.html?$/i }, // @endif - // @if cssProcessor.id='stylus' { test: /\.styl$/i, @@ -101,22 +100,19 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ issuer: /\.html?$/i }, // @endif - // @if cssProcessor.id='sass' { test: /\.scss$/, - use: ["style-loader", "css-loader", "sass-loader"], + use: ['style-loader', 'css-loader', 'sass-loader'], issuer: /\.[tj]s$/i }, { test: /\.scss$/, - use: ["css-loader", "sass-loader"], + use: ['css-loader', 'sass-loader'], issuer: /\.html?$/i }, // @endif - { test: /\.html$/i, loader: 'html-loader' }, - // @if transpiler.id='babel' { test: /\.js$/i, loader: 'babel-loader', exclude: nodeModulesDir, options: coverage ? { sourceMap: 'inline', plugins: [ 'istanbul' ] } : {}, @@ -125,7 +121,6 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ // @if transpiler.id='typescript' { test: /\.ts$/i, loader: 'awesome-typescript-loader', exclude: nodeModulesDir }, // @endif - { test: /\.json$/i, loader: 'json-loader' }, // use Bluebird as the global Promise implementation: { test: /[\/\\]node_modules[\/\\]bluebird[\/\\].+\.js$/, loader: 'expose-loader?Promise' }, @@ -135,6 +130,13 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ { test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff' } }, // load these fonts normally, as files: { test: /\.(ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'file-loader' }, + // @if transpiler.id='typescript' + ...when(coverage, { + test: /\.[jt]s$/i, loader: 'istanbul-instrumenter-loader', + include: srcDir, exclude: [/\.{spec,test}\.[jt]s$/i], + enforce: 'post', options: { esModules: true }, + }) + // @endif ] }, plugins: [ @@ -143,7 +145,7 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ 'Promise': 'bluebird' }), new ModuleDependenciesPlugin({ - "aurelia-testing": [ "./compile-spy", "./view-spy" ] + 'aurelia-testing': [ './compile-spy', './view-spy' ] }), // @if transpiler.id='typescript' new TsConfigPathsPlugin(), @@ -174,11 +176,11 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ metadata: { // available in index.ejs // title, server, baseUrl - }, + } }), ...when(extractCss, new ExtractTextPlugin({ filename: production ? '[contenthash].css' : '[id].css', - allChunks: true, + allChunks: true })), ...when(production, new CommonsChunkPlugin({ name: ['common'] @@ -186,5 +188,5 @@ module.exports = ({production, server, extractCss, coverage} = {}) => ({ ...when(production, new CopyWebpackPlugin([ { from: 'static/favicon.ico', to: 'favicon.ico' } ])) - ], -}) \ No newline at end of file + ] +}); diff --git a/lib/resources/tasks/build-webpack.js b/lib/resources/tasks/build-webpack.js index 034a9fc2b..88702e66b 100644 --- a/lib/resources/tasks/build-webpack.js +++ b/lib/resources/tasks/build-webpack.js @@ -25,7 +25,7 @@ function onBuild(err, stats) { if (err.details) console.error(err.details); process.exit(1); } else { - process.stdout.write(stats.toString() + '\n'); + process.stdout.write(stats.toString({ colors: require('supports-color') }) + '\n'); } } diff --git a/lib/resources/tasks/build-webpack.ts b/lib/resources/tasks/build-webpack.ts index 968a55b4f..d4cc76edb 100644 --- a/lib/resources/tasks/build-webpack.ts +++ b/lib/resources/tasks/build-webpack.ts @@ -25,7 +25,7 @@ function onBuild(err, stats) { if (err.details) console.error(err.details); process.exit(1); } else { - process.stdout.write(stats.toString() + '\n'); + process.stdout.write(stats.toString({ colors: require('supports-color') }) + '\n'); } } diff --git a/lib/resources/tasks/jest.js b/lib/resources/tasks/jest.js new file mode 100644 index 000000000..76b4c6bfd --- /dev/null +++ b/lib/resources/tasks/jest.js @@ -0,0 +1,22 @@ +import jest from 'jest-cli'; +import gutil from 'gulp-util'; +import through2 from 'through2'; +import path from 'path'; +import packageJson from '../../package.json'; +import {CLIOptions} from 'aurelia-cli'; + +export default (cb) => { + let options = packageJson.jest; + + if (CLIOptions.hasFlag('watch')) { + Object.assign(options, { watch: true}); + } + + jest.runCLI(options, [path.resolve(__dirname, '../../')], (result) => { + if(result.numFailedTests || result.numFailedTestSuites) { + cb(new gutil.PluginError('gulp-jest', { message: 'Tests Failed' })); + } else { + cb(); + } + }); +}; \ No newline at end of file diff --git a/lib/resources/tasks/jest.json b/lib/resources/tasks/jest.json new file mode 100644 index 000000000..c11176c9b --- /dev/null +++ b/lib/resources/tasks/jest.json @@ -0,0 +1,11 @@ +{ + "name": "jest", + "description": "Runs Jest and reports the results.", + "parameters": [ + { + "name": "watch", + "description": "Watches test files for changes and re-runs the tests automatically.", + "type": "boolean" + } + ] +} diff --git a/lib/resources/tasks/jest.ts b/lib/resources/tasks/jest.ts new file mode 100644 index 000000000..ac207f1ba --- /dev/null +++ b/lib/resources/tasks/jest.ts @@ -0,0 +1,22 @@ +import * as jest from 'jest-cli'; +import gutil from 'gulp-util'; +import through2 from 'through2'; +import * as path from 'path'; +import * as packageJson from '../../package.json'; +import {CLIOptions} from 'aurelia-cli'; + +export default (cb) => { + let options = packageJson.jest; + + if (CLIOptions.hasFlag('watch')) { + Object.assign(options, { watch: true}); + } + + jest.runCLI(options, [path.resolve(__dirname, '../../')], (result) => { + if(result.numFailedTests || result.numFailedTestSuites) { + cb(new gutil.PluginError('gulp-jest', { message: 'Tests Failed' })); + } else { + cb(); + } + }); +}; \ No newline at end of file diff --git a/lib/resources/tasks/karma.js b/lib/resources/tasks/karma.js new file mode 100644 index 000000000..f5a63bb3f --- /dev/null +++ b/lib/resources/tasks/karma.js @@ -0,0 +1,16 @@ +import {Server as Karma} from 'karma'; +import {CLIOptions} from 'aurelia-cli'; +import path from 'path'; + +let karma = done => { + new Karma({ + configFile: path.join(__dirname, '/../../test/karma.conf.js'), + singleRun: !CLIOptions.hasFlag('watch'), + autoWatch: CLIOptions.hasFlag('watch') + }, function(exitCode) { + console.log('Karma has exited with ' + exitCode) + process.exit(exitCode) + }).start(); +}; + +export { karma as default }; diff --git a/lib/resources/tasks/karma.json b/lib/resources/tasks/karma.json new file mode 100644 index 000000000..cd9931bf6 --- /dev/null +++ b/lib/resources/tasks/karma.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "description": "Runs Karma and reports the results.", + "flags": [ + { + "name": "watch", + "description": "Watches test files for changes and re-runs the tests automatically.", + "type": "boolean" + } + ] +} diff --git a/lib/resources/tasks/karma.ts b/lib/resources/tasks/karma.ts new file mode 100644 index 000000000..1e98d86dd --- /dev/null +++ b/lib/resources/tasks/karma.ts @@ -0,0 +1,16 @@ +import {Server as Karma} from 'karma'; +import {CLIOptions} from 'aurelia-cli'; +import * as path from 'path'; + +let karma = done => { + new Karma({ + configFile: path.join(__dirname, '/../../test/karma.conf.js'), + singleRun: !CLIOptions.hasFlag('watch'), + autoWatch: CLIOptions.hasFlag('watch') + }, function(exitCode) { + console.log('Karma has exited with ' + exitCode) + process.exit(exitCode) + }).start(); +}; + +export { karma as default }; diff --git a/lib/resources/tasks/run-webpack.js b/lib/resources/tasks/run-webpack.js index 234c3d109..244aa2923 100644 --- a/lib/resources/tasks/run-webpack.js +++ b/lib/resources/tasks/run-webpack.js @@ -1,34 +1,22 @@ import {config} from './build'; import webpack from 'webpack'; import Server from 'webpack-dev-server'; -import open from 'opn'; -import yargs from 'yargs'; -import url from 'url'; import project from '../aurelia.json'; -import {CLIOptions} from 'aurelia-cli'; - -const argv = yargs.argv; -argv.color = require('supports-color'); +import {CLIOptions, reportWebpackReadiness} from 'aurelia-cli'; function run(done) { + // https://webpack.github.io/docs/webpack-dev-server.html let opts = { host: 'localhost', publicPath: config.output.publicPath, filename: config.output.filename, - watchOptions: undefined, - hot: project.platform.hmr, - clientLogLevel: 'info', + hot: project.platform.hmr || CLIOptions.hasFlag('hmr'), port: project.platform.port, - contentBase: config.output.path, - // serve index.html for all 404 (required for push-state) historyApiFallback: true, - open: project.platform.open, stats: { - cached: false, - cachedAssets: false, - colors: argv.color + colors: require('supports-color') } }; @@ -36,7 +24,7 @@ function run(done) { opts.watch = false; } - if (project.platform.hmr) { + if (project.platform.hmr || CLIOptions.hasFlag('hmr')) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.entry.app.unshift(`webpack-dev-server/client?http://${opts.host}:${opts.port}/`, 'webpack/hot/dev-server'); } @@ -46,56 +34,9 @@ function run(done) { server.listen(opts.port, opts.host, function(err) { if (err) throw err; - reportReadiness(createDomain(opts), opts); + reportWebpackReadiness(opts); done(); }); } -function reportReadiness(uri, options) { - const useColor = argv.color; - - let startSentence = `Project is running at ${colorInfo(useColor, uri)}`; - - if (options.socket) { - startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`; - } - console.log((argv.progress ? '\n' : '') + startSentence); - - console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`); - const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') : options.contentBase; - - if (contentBase) { - console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`); - } - - if (options.historyApiFallback) { - console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || '/index.html')}`); - } - - if (options.open) { - open(uri).catch(function() { - console.log('Unable to open browser. If you are running in a headless environment, please do not use the open flag.'); - }); - } -} - -function createDomain(opts) { - const protocol = opts.https ? 'https' : 'http'; - - // the formatted domain (url without path) of the webpack server - return opts.public ? `${protocol}://${opts.public}` : url.format({ - protocol: protocol, - hostname: opts.host, - port: opts.socket ? 0 : opts.port.toString() - }); -} - -function colorInfo(useColor, msg) { - if (useColor) { - // Make text blue and bold, so it *pops* - return `\u001b[1m\u001b[33m${msg}\u001b[39m\u001b[22m`; - } - return msg; -} - export { run as default }; diff --git a/lib/resources/tasks/run-webpack.json b/lib/resources/tasks/run-webpack.json new file mode 100644 index 000000000..8f2394dac --- /dev/null +++ b/lib/resources/tasks/run-webpack.json @@ -0,0 +1,21 @@ +{ + "name": "run", + "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.", + "flags": [ + { + "name": "env", + "description": "Sets the build environment.", + "type": "string" + }, + { + "name": "watch", + "description": "Watches source files for changes and refreshes the app automatically.", + "type": "boolean" + }, + { + "name": "hmr", + "description": "Enable Hot Module Reload", + "type": "boolean" + } + ] +} diff --git a/lib/resources/tasks/run-webpack.ts b/lib/resources/tasks/run-webpack.ts index 39fac495e..be6180d06 100644 --- a/lib/resources/tasks/run-webpack.ts +++ b/lib/resources/tasks/run-webpack.ts @@ -1,34 +1,22 @@ import {config} from './build'; import * as webpack from 'webpack'; import * as Server from 'webpack-dev-server'; -import * as open from 'opn'; -import * as yargs from 'yargs'; -import * as url from 'url'; import * as project from '../aurelia.json'; -import {CLIOptions} from 'aurelia-cli'; - -const argv = yargs.argv; -argv.color = require('supports-color'); +import {CLIOptions, reportWebpackReadiness} from 'aurelia-cli'; function run(done) { + // https://webpack.github.io/docs/webpack-dev-server.html let opts = { host: 'localhost', publicPath: config.output.publicPath, filename: config.output.filename, - watchOptions: undefined, - hot: project.platform.hmr, - clientLogLevel: 'info', + hot: project.platform.hmr || CLIOptions.hasFlag('hmr'), port: project.platform.port, - contentBase: config.output.path, - // serve index.html for all 404 (required for push-state) historyApiFallback: true, - open: project.platform.open, stats: { - cached: false, - cachedAssets: false, - colors: argv.color + colors: require('supports-color') } }; @@ -36,7 +24,7 @@ function run(done) { opts.watch = false; } - if (project.platform.hmr) { + if (project.platform.hmr || CLIOptions.hasFlag('hmr')) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.entry.app.unshift(`webpack-dev-server/client?http://${opts.host}:${opts.port}/`, 'webpack/hot/dev-server'); } @@ -46,56 +34,9 @@ function run(done) { server.listen(opts.port, opts.host, function(err) { if (err) throw err; - reportReadiness(createDomain(opts), opts); + reportWebpackReadiness(opts); done(); }); } -function reportReadiness(uri, options) { - const useColor = argv.color; - - let startSentence = `Project is running at ${colorInfo(useColor, uri)}`; - - if (options.socket) { - startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`; - } - console.log((argv.progress ? '\n' : '') + startSentence); - - console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`); - const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') : options.contentBase; - - if (contentBase) { - console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`); - } - - if (options.historyApiFallback) { - console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || '/index.html')}`); - } - - if (options.open) { - open(uri).catch(function() { - console.log('Unable to open browser. If you are running in a headless environment, please do not use the open flag.'); - }); - } -} - -function createDomain(opts) { - const protocol = opts.https ? 'https' : 'http'; - - // the formatted domain (url without path) of the webpack server - return opts.public ? `${protocol}://${opts.public}` : url.format({ - protocol: protocol, - hostname: opts.host, - port: opts.socket ? 0 : opts.port.toString() - }); -} - -function colorInfo(useColor, msg) { - if (useColor) { - // Make text blue and bold, so it *pops* - return `\u001b[1m\u001b[33m${msg}\u001b[39m\u001b[22m`; - } - return msg; -} - export { run as default }; diff --git a/lib/workflow/activities/project-create.js b/lib/workflow/activities/project-create.js index bfb2a4300..b94fd23e7 100644 --- a/lib/workflow/activities/project-create.js +++ b/lib/workflow/activities/project-create.js @@ -5,6 +5,7 @@ const UI = require('../../ui').UI; const transform = require('../../colors/transform'); const CLIOptions = require('../../cli-options').CLIOptions; const fs = require('../../file-system'); +const logger = require('aurelia-logging').getLogger('ProjectCreation'); module.exports = class { static inject() { return [UI, CLIOptions]; } @@ -18,6 +19,8 @@ module.exports = class { let model = { name: context.state.name, type: context.state.type, + bundler: context.state.bundler, + build: context.state.build, platform: context.state.platform, loader: context.state.loader, transpiler: context.state.transpiler, @@ -30,6 +33,10 @@ module.exports = class { model.unitTestRunner = context.state.unitTestRunner; } + if (context.state.integrationTestRunner) { + model.integrationTestRunner = context.state.integrationTestRunner; + } + let project = context.state.project = new ProjectTemplate(model, this.options); return this.ui.clearScreen() @@ -37,30 +44,13 @@ module.exports = class { .then(() => this.projectConfirmation(project)) .then(answer => { if (answer.value === 'yes') { - let configurePlatform = require(`../../commands/new/platforms/${model.platform.id}`); - configurePlatform(project, this.options); - - let configureLoader = require(`../../commands/new/loaders/${model.loader.id}`); - configureLoader(project, this.options); - - let configureTranspiler = require(`../../commands/new/transpilers/${model.transpiler.id}`); - configureTranspiler(project, this.options); - - let configureMarkupProcessor = require(`../../commands/new/markup-processors/${model.markupProcessor.id}`); - configureMarkupProcessor(project, this.options); - - let configureCSSProcessor = require(`../../commands/new/css-processors/${model.cssProcessor.id}`); - configureCSSProcessor(project, this.options); - - let configureUnitTestRunner = require(`../../commands/new/unit-test-runners/${model.unitTestRunner.id}`); - configureUnitTestRunner(project, this.options); - - let configureEditor = require(`../../commands/new/editors/${model.editor.id}`); - configureEditor(project, this.options); + let configurator = require(`../../commands/new/buildsystems/${model.bundler.id}`); + configurator(project, this.options); return project.create(this.ui, this.options.hasFlag('here') ? undefined : process.cwd()) .then(() => this.ui.clearScreen()) - .then(() => this.ui.log('Project structure created and configured.')) + .then(() => this.ui.log('Project structure created and configured.' + os.EOL)) + .then(() => this.renderManualInstructions(project)) .then(() => context.next(this.nextActivity)); } else if (answer.value === 'restart') { return context.next(this.restartActivity); @@ -68,6 +58,10 @@ module.exports = class { return this.ui.log(os.EOL + 'Project creation aborted.') .then(() => context.next()); + }) + .catch(e => { + logger.error(`Failed to create the project due to an error: ${e.message}`); + logger.info(e.stack); }); } @@ -76,13 +70,23 @@ module.exports = class { text += ` Name: ${model.name}` + os.EOL; text += ` Platform: ${model.platform.displayName}` + os.EOL; - text += ` Loader: ${model.loader.displayName}` + os.EOL; + text += ` Bundler: ${model.bundler.displayName}` + os.EOL; + if (model.loader) { + text += ` Loader: ${model.loader.displayName}` + os.EOL; + } text += ` Transpiler: ${model.transpiler.displayName}` + os.EOL; text += ` Markup Processor: ${model.markupProcessor.displayName}` + os.EOL; text += ` CSS Processor: ${model.cssProcessor.displayName}` + os.EOL; if (model.unitTestRunner) { - text += ` Unit Test Runner: ${model.unitTestRunner.displayName}` + os.EOL; + let testRunners = model.unitTestRunner instanceof Array ? model.unitTestRunner : [model.unitTestRunner]; + for (let i = 0; i < testRunners.length; i++) { + text += ` Unit Test Runner: ${testRunners[i].displayName}` + os.EOL; + } + } + + if (model.integrationTestRunner) { + text += ` Integration Test Runner: ${model.integrationTestRunner.displayName}` + os.EOL; } text += ` Editor: ${model.editor.displayName}` + os.EOL; @@ -120,4 +124,13 @@ module.exports = class { } ]); } + + renderManualInstructions(project) { + let instructions = project.manualInstructions.getText(); + if (instructions) { + this.ui.log(transform('Manual changes are necessary:' + os.EOL)); + this.ui.log(instructions + os.EOL); + this.ui.log(transform(`These instructions can also be found in a file called ${project.manualInstructions.name} in the project directory`)); + } + } }; diff --git a/lib/workflow/activities/project-install.js b/lib/workflow/activities/project-install.js index 4b7a7d554..ef13624a8 100644 --- a/lib/workflow/activities/project-install.js +++ b/lib/workflow/activities/project-install.js @@ -41,16 +41,29 @@ module.exports = class { } displayCompletionMessage(project) { - let notHereMessage = ` First, change directory into your new project's folder. You can use cd ${project.model.name} to get there. Once in your project folder, simply run your new app with au run.`; - let hereMessage = ' Simply run your new app with au run.'; + let message = 'Now it\'s time for you to get started. It\'s easy.'; - let message = transform('Now it\'s time for you to get started. It\'s easy.' + - (this.options.hasFlag('here') ? hereMessage : notHereMessage) + - ' Your app will run fully bundled. If you would like to have it auto-refresh whenever you make changes to your HTML, JavaScript or CSS, simply use the --watch flag. If you want to build your app for production, run au build --env prod. That\'s just about all there is to it. If you need help, simply run au help.'); + let runCommand = 'au run'; + + if (project.model.bundler.id === 'webpack' && project.model.platform.id === 'aspnetcore') { + runCommand = 'dotnet run'; + } + + if (this.options.hasFlag('here')) { + message += ` Simply run your new app with ${runCommand}.`; + } else { + message += ` First, change directory into your new project's folder. You can use cd ${project.model.name} to get there. Once in your project folder, simply run your new app with ${runCommand}.`; + } + + if (project.model.bundler.id === 'cli' || project.model.platform.id === 'web') { + message += ' Your app will run fully bundled. If you would like to have it auto-refresh whenever you make changes to your HTML, JavaScript or CSS, simply use the --watch flag'; + } + + message += ' If you want to build your app for production, run au build --env prod. That\'s just about all there is to it. If you need help, simply run au help.'; return this.ui.clearScreen() .then(() => this.ui.log(`Congratulations! Your Project "${project.model.name}" Has Been Created!` + os.EOL + os.EOL)) - .then(() => this.ui.log(createLines(message, '', this.ui.getWidth()))) + .then(() => this.ui.log(createLines(transform(message), '', this.ui.getWidth()))) .then(() => this.ui.log(os.EOL + os.EOL + 'Happy Coding!')); } }; diff --git a/spec/lib/project-item.spec.js b/spec/lib/project-item.spec.js index e913972cb..aab242329 100644 --- a/spec/lib/project-item.spec.js +++ b/spec/lib/project-item.spec.js @@ -1,9 +1,12 @@ 'use strict'; +const path = require('path'); + describe('The project-item module', () => { let mockfs; let fs; + let ui; let ProjectItem; let projectItem; @@ -12,6 +15,7 @@ describe('The project-item module', () => { mockfs = require('mock-fs'); fs = require('../../lib/file-system'); + ui = new (require('../mocks/ui')); ProjectItem = require('../../lib/project-item').ProjectItem; @@ -60,19 +64,42 @@ describe('The project-item module', () => { .catch(e => done.fail(e)); }); - it('creates the childs', done => { - const ui = {}; - const child = { create: () => { } }; - projectItem.children.push(child); - spyOn(child, 'create'); + it('creates the children in sequence', done => { + let calls = []; + + let create1 = jasmine.createSpy('create1').and.callFake(() => new Promise(r => { + setTimeout(() => { + calls.push('create1'); + r(); + }, 200); + })); + let create2 = jasmine.createSpy('create2').and.callFake(() => new Promise(r => { + setTimeout(() => { + calls.push('create2'); + r(); + }, 400); + })); + let create3 = jasmine.createSpy('create3').and.callFake(() => new Promise(r => { + setTimeout(() => { + calls.push('create3'); + r(); + }, 0); + })); + + projectItem.children.push({ create: create1 }); + projectItem.children.push({ create: create2 }); + projectItem.children.push({ create: create3 }); projectItem.name = 'cli-app'; projectItem.create(ui) - .then(() => { - expect(child.create).toHaveBeenCalledWith(ui, projectItem.name); - }) - .then(done) - .catch(e => done.fail(e)); + .then(() => { + expect(calls[0]).toBe('create1'); + expect(calls[1]).toBe('create2'); + expect(calls[2]).toBe('create3'); + + done(); + }) + .catch(e => done.fail(e)); }); }); }); @@ -117,5 +144,53 @@ describe('The project-item module', () => { .catch(e => done.fail(e)); }); }); + + describe('in `ask` strategy', () => { + beforeEach(() => { + projectItem._fileExistsStrategy = 'ask'; + }); + + it('asks the user whether to replace the file or not if already present', done => { + let fsConfig = {}; + fsConfig[path.resolve('./index.html')] = ''; + mockfs(fsConfig); + + const file = { + path: path.resolve('./index.html'), + content: '' + }; + + projectItem._write(file.path, '', ui) + .then(() => { + expect(ui.question).toHaveBeenCalled(); + }) + .then(done) + .catch(e => done.fail(e)); + }); + + it('does not write file if user chooses not to replace the file', done => { + let fsConfig = {}; + fsConfig[path.resolve('./index.html')] = ''; + mockfs(fsConfig); + + spyOn(fs, 'writeFile'); + // choose the first option (Keep it) + ui.question.and.callFake((question, answers) => { + return Promise.resolve(answers[0]); + }); + + const file = { + path: path.resolve('./index.html'), + content: '' + }; + + projectItem._write(file.path, '', ui) + .then(() => { + expect(fs.writeFile).not.toHaveBeenCalled(); + }) + .then(done) + .catch(e => done.fail(e)); + }); + }); }); });