├── spec
├── helpers
│ ├── polyfills.js
│ └── reporter.js
├── mocks
│ ├── package-analyzer.js
│ ├── project-mock.js
│ ├── ui.js
│ ├── cli-options.js
│ ├── bundler.js
│ └── mock-fs.js
├── support
│ └── jasmine.json
└── lib
│ ├── build
│ ├── stub-module.spec.js
│ ├── dependency-description.spec.js
│ ├── package-installer.spec.js
│ ├── module-id-processor.spec.js
│ ├── source-inclusion.spec.js
│ ├── inject-css.spec.js
│ └── utils.spec.js
│ ├── commands
│ └── config
│ │ ├── util.spec.js
│ │ └── configuration.spec.js
│ ├── project-item.spec.js
│ ├── project.spec.js
│ ├── cli-options.spec.ts
│ ├── file-system.spec.js
│ ├── cli.spec.js
│ └── configuration.spec.js
├── lib
├── commands
│ ├── alias.json
│ ├── help
│ │ ├── command.json
│ │ └── command.js
│ ├── generate
│ │ ├── command.json
│ │ └── command.js
│ ├── new
│ │ ├── command.js
│ │ └── command.json
│ ├── config
│ │ ├── util.js
│ │ ├── command.js
│ │ ├── command.json
│ │ └── configuration.js
│ └── gulp.js
├── resources
│ ├── scripts
│ │ ├── keep-them.md
│ │ ├── configure-bluebird.js
│ │ └── configure-bluebird-no-long-stacktraces.js
│ └── logo.txt
├── package-managers
│ ├── npm.js
│ ├── yarn.js
│ └── base-package-manager.js
├── index.js
├── build
│ ├── amodro-trace
│ │ ├── read
│ │ │ ├── es.js
│ │ │ └── cjs.js
│ │ ├── write
│ │ │ ├── all.js
│ │ │ ├── stubs.js
│ │ │ ├── replace.js
│ │ │ └── defines.js
│ │ └── lib
│ │ │ └── lang.js
│ ├── loader-plugin.js
│ ├── module-id-processor.js
│ ├── webpack-reporter.js
│ ├── package-installer.js
│ ├── source-inclusion.js
│ ├── inject-css.js
│ ├── dependency-description.js
│ ├── stub-module.js
│ ├── index.js
│ ├── loader.js
│ ├── dependency-inclusion.js
│ ├── utils.js
│ ├── package-analyzer.js
│ └── ast-matcher.js
├── get-tty-size.js
├── logger.js
├── pretty-choices.js
├── string.js
├── configuration.js
├── project-item.js
├── cli-options.js
├── file-system.js
├── project.js
├── cli.js
└── ui.js
├── docs
└── MAINTAINERS.md
├── .nycrc
├── .gitignore
├── .editorconfig
├── .gitattributes
├── wallaby.conf.js
├── CONTRIBUTING.md
├── .github
└── workflows
│ └── ci.yml
├── eslint.config.mjs
├── LICENSE
├── bin
└── aurelia-cli.js
├── ACKNOWLEDGEMENTS.md
├── ISSUE_TEMPLATE.md
├── README.md
└── package.json
/spec/helpers/polyfills.js:
--------------------------------------------------------------------------------
1 | require('aurelia-polyfills');
2 |
--------------------------------------------------------------------------------
/lib/commands/alias.json:
--------------------------------------------------------------------------------
1 | {
2 | "g":"generate",
3 | "h":"help",
4 | "n":"new"
5 | }
6 |
--------------------------------------------------------------------------------
/spec/mocks/package-analyzer.js:
--------------------------------------------------------------------------------
1 | module.exports = class PackageAnalyzer {
2 | constructor() {
3 | }
4 | };
5 |
--------------------------------------------------------------------------------
/lib/commands/help/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "help",
3 | "description": "Displays the Aurelia CLI help."
4 | }
5 |
--------------------------------------------------------------------------------
/docs/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | Release process:
2 |
3 | - get latest
4 | - update version field in package.json + commit + tag
5 | - run `npm run version`
--------------------------------------------------------------------------------
/spec/mocks/project-mock.js:
--------------------------------------------------------------------------------
1 | module.exports = class ProjectMock {
2 | constructor() {
3 | this.paths = {
4 | root: ''
5 | };
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/lib/resources/scripts/keep-them.md:
--------------------------------------------------------------------------------
1 | Although new app doesn't use bluebird anymore, need to keep the two bluebird config files for their usage in existing apps.
2 |
--------------------------------------------------------------------------------
/lib/resources/scripts/configure-bluebird.js:
--------------------------------------------------------------------------------
1 | //Configure Bluebird Promises.
2 | Promise.config({
3 | warnings: {
4 | wForgottenReturn: false
5 | }
6 | });
7 |
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "lib/**/*.js"
4 | ],
5 | "exclude": [
6 | "lib/build/amodro-trace/lib/**/*.js"
7 | ],
8 | "reporter": [
9 | "lcov",
10 | "text"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/lib/resources/scripts/configure-bluebird-no-long-stacktraces.js:
--------------------------------------------------------------------------------
1 | //Configure Bluebird Promises.
2 | Promise.config({
3 | longStackTraces: false,
4 | warnings: {
5 | wForgottenReturn: false
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "spec",
3 | "spec_files": [
4 | "**/*[sS]pec.js"
5 | ],
6 | "helpers": [
7 | "helpers/**/*.js"
8 | ],
9 | "stopSpecOnExpectationFailure": false,
10 | "random": false
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | jspm_packages
3 | bower_components
4 | .idea
5 | .DS_STORE
6 | *.swp
7 | .blog
8 | dist
9 | /release-checks-results
10 | /.nyc_output
11 | /coverage
12 | /package-lock.json
13 | /yarn.lock
14 | /tmpdir*
15 | .npmrc
16 |
--------------------------------------------------------------------------------
/lib/package-managers/npm.js:
--------------------------------------------------------------------------------
1 | const BasePackageManager = require('./base-package-manager').default;
2 |
3 | exports.NPM = class extends BasePackageManager {
4 | constructor() {
5 | super('npm');
6 | }
7 | };
8 |
9 | exports.default = exports.NPM;
10 |
--------------------------------------------------------------------------------
/lib/resources/logo.txt:
--------------------------------------------------------------------------------
1 | _ _ ____ _ ___
2 | __ _ _ _ _ __ ___| (_) __ _ / ___| | |_ _|
3 | / _` | | | | '__/ _ \ | |/ _` | | | | | | |
4 | | (_| | |_| | | | __/ | | (_| | | |___| |___ | |
5 | \__,_|\__,_|_| \___|_|_|\__,_| \____|_____|___|
6 |
--------------------------------------------------------------------------------
/lib/commands/generate/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generate",
3 | "description": "Generates code for common use cases in Aurelia applications.",
4 | "parameters": [
5 | {
6 | "name": "generator-name",
7 | "description": "The name of the generator you want to run."
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/spec/helpers/reporter.js:
--------------------------------------------------------------------------------
1 | const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
2 |
3 | jasmine.getEnv().clearReporters(); // remove default reporter logs
4 | jasmine.getEnv().addReporter(new SpecReporter({ // add jasmine-spec-reporter
5 | spec: {
6 | displayPending: true
7 | }
8 | }));
9 |
--------------------------------------------------------------------------------
/lib/commands/new/command.js:
--------------------------------------------------------------------------------
1 | const {spawn} = require('child_process');
2 |
3 | module.exports = class {
4 | async execute(args) {
5 | // Calls "npx makes aurelia/v1"
6 | // https://github.com/aurelia/v1
7 | spawn('npx', ['makes', 'aurelia/v1', ...args], {stdio: 'inherit', shell: true});
8 | }
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # 2 space indentation
12 | [**.*]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text eol=lf
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.js text
7 | *.md text
8 | *.json text
9 |
10 | # Denote all files that are truly binary and should not be modified.
11 | *.png binary
12 | *.jpg binary
13 |
--------------------------------------------------------------------------------
/spec/mocks/ui.js:
--------------------------------------------------------------------------------
1 | module.exports = class UI {
2 | constructor() {
3 | this.multiselect = jasmine.createSpy('multiselect').and.returnValue(Promise.resolve());
4 | this.question = jasmine.createSpy('question').and.returnValue(Promise.resolve());
5 | this.ensureAnswer = jasmine.createSpy('ensureAnswer').and.returnValue(Promise.resolve());
6 | this.log = jasmine.createSpy('log');
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/lib/package-managers/yarn.js:
--------------------------------------------------------------------------------
1 | const BasePackageManager = require('./base-package-manager').default;
2 |
3 | exports.Yarn = class extends BasePackageManager {
4 | constructor() {
5 | super('yarn');
6 | }
7 |
8 | install(packages = [], workingDirectory = process.cwd()) {
9 | return super.install(packages, workingDirectory, !packages.length ? 'install' : 'add');
10 | }
11 | };
12 |
13 | exports.default = exports.Yarn;
14 |
--------------------------------------------------------------------------------
/wallaby.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | return {
3 | files: [
4 | 'lib/**/*.js',
5 | 'package.json',
6 | {pattern: 'spec/mocks/**/*', load: false},
7 | {pattern: 'spec/helpers/polyfills.js', load: false}
8 | ],
9 |
10 | tests: [
11 | 'spec/**/*[Ss]pec.js'
12 | ],
13 |
14 | env: {
15 | type: 'node'
16 | },
17 |
18 | bootstrap: function(wallaby) {
19 | require('aurelia-polyfills');
20 | },
21 |
22 | testFramework: 'jasmine'
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | require('aurelia-polyfills');
2 |
3 | exports.CLI = require('./cli').CLI;
4 | exports.CLIOptions = require('./cli-options').CLIOptions;
5 | exports.UI = require('./ui').UI;
6 | exports.Project = require('./project').Project;
7 | exports.ProjectItem = require('./project-item').ProjectItem;
8 | exports.build = require('./build');
9 | exports.Configuration = require('./configuration').Configuration;
10 | exports.reportWebpackReadiness = require('./build/webpack-reporter');
11 | exports.NPM = require('./package-managers/npm').NPM;
12 | exports.Yarn = require('./package-managers/yarn').Yarn;
13 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/read/es.js:
--------------------------------------------------------------------------------
1 | const transformSync = require('@babel/core').transformSync;
2 |
3 | /**
4 | * Use babel to translate native es module into AMD module
5 | * @param {string} fileName
6 | * @param {string} fileContents
7 | * @param {{} | boolean} inputSourceMap
8 | * @returns {babel.BabelFileResult}
9 | */
10 | module.exports = function es(fileName, fileContents, inputSourceMap) {
11 | return transformSync(fileContents, {
12 | babelrc: false,
13 | plugins: [['@babel/plugin-transform-modules-amd', {loose: true}]],
14 | inputSourceMap: inputSourceMap
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We'd love for you to contribute and to make this project even better than it is today! If this interests you, please begin by reading [our contributing guidelines](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md). The contributing document will provide you with all the information you need to get started. Once you have read that, you will need to also [sign our CLA](http://goo.gl/forms/dI8QDDSyKR) before we can accept a Pull Request from you. More information on the process is included in the [contributor's guide](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md).
4 |
--------------------------------------------------------------------------------
/spec/mocks/cli-options.js:
--------------------------------------------------------------------------------
1 | let OriginalCLIOptions = require('../../lib/cli-options').CLIOptions;
2 |
3 | module.exports = class CLIOptionsMock {
4 | constructor() {
5 | this.originalFns = {};
6 | if (!OriginalCLIOptions.instance) {
7 | // eslint-disable-next-line no-unused-vars
8 | let instance = new OriginalCLIOptions();
9 | }
10 | }
11 |
12 | attach() {
13 | this.originalFns.getEnvironment = OriginalCLIOptions.prototype.getEnvironment;
14 | OriginalCLIOptions.getEnvironment = jasmine.createSpy('getEnvironment');
15 | }
16 |
17 | detach() {
18 | OriginalCLIOptions.getEnvironment = this.originalFns.getEnvironment;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | test:
13 | name: Nodejs ${{ matrix.node_version }} on ${{ matrix.os }}
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | matrix:
17 | node_version: ['18', '20']
18 | os: [ubuntu-latest, windows-latest, macOS-latest]
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - name: Use Node.js ${{ matrix.node_version }}
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: ${{ matrix.node_version }}
26 | - run: npm install
27 | - run: npm test
28 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import eslint from "@eslint/js";
3 |
4 | export default [
5 | {
6 | ignores: ["lib/build/amodro-trace", "**/dist"],
7 | },
8 | eslint.configs.recommended,
9 | {
10 | languageOptions: {
11 | globals: {
12 | ...globals.node,
13 | ...globals.jasmine,
14 | },
15 |
16 | ecmaVersion: 2019,
17 | sourceType: "commonjs",
18 | },
19 |
20 | rules: {
21 | "no-prototype-builtins": 0,
22 | "no-console": 0,
23 | "getter-return": 0,
24 | "no-inner-declarations": 0,
25 |
26 | "comma-dangle": ["error", {
27 | arrays: "never",
28 | objects: "never",
29 | imports: "never",
30 | exports: "never",
31 | functions: "never",
32 | }],
33 | },
34 | }
35 | ];
36 |
--------------------------------------------------------------------------------
/lib/get-tty-size.js:
--------------------------------------------------------------------------------
1 | const tty = require('tty');
2 |
3 | let size;
4 |
5 | module.exports = function() {
6 | // Only run it once.
7 | if (size) return size;
8 |
9 | let width;
10 | let height;
11 |
12 | if (tty.isatty(1) && tty.isatty(2)) {
13 | if (process.stdout.getWindowSize) {
14 | width = process.stdout.getWindowSize(1)[0];
15 | height = process.stdout.getWindowSize(1)[1];
16 | } else if (tty.getWindowSize) {
17 | width = tty.getWindowSize()[1];
18 | height = tty.getWindowSize()[0];
19 | } else if (process.stdout.columns && process.stdout.rows) {
20 | height = process.stdout.rows;
21 | width = process.stdout.columns;
22 | }
23 | } else {
24 | width = 80;
25 | height = 100;
26 | }
27 |
28 | size = { height: height, width: width };
29 | return size;
30 | };
31 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | const UI = require('./ui').UI;
2 | const c = require('ansi-colors');
3 |
4 | exports.Logger = class {
5 | static inject() { return [UI]; }
6 |
7 | constructor(ui) {
8 | this.ui = ui;
9 | }
10 |
11 | debug(logger, message) {
12 | this.log(logger, c.bold('DEBUG'), message, arguments);
13 | }
14 |
15 | info(logger, message) {
16 | this.log(logger, c.bold('INFO'), message, arguments);
17 | }
18 |
19 | warn(logger, message) {
20 | this.log(logger, c.bgYellow('WARN'), message, arguments);
21 | }
22 |
23 | error(logger, message) {
24 | this.log(logger, c.bgRed('ERROR'), message, arguments);
25 | }
26 |
27 | log(logger, level, message, rest) {
28 | let msg = `${level} [${logger.id}] ${message}`;
29 | let args = Array.prototype.slice.call(rest, 2);
30 |
31 | if (args.length > 0) {
32 | msg += ` ${args.map(x => JSON.stringify(x)).join(' ')}`;
33 | }
34 |
35 | this.ui.log(msg);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/lib/commands/new/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "new",
3 | "description": "Creates a new Aurelia application project. Wraps \"npx makes aurelia/v1\". https://github.com/aurelia/v1",
4 | "parameters": [
5 | {
6 | "name": "project-name",
7 | "optional": true,
8 | "description": "The name to create the project with. If one isn't provided, the wizard will ask for one."
9 | }
10 | ],
11 | "flags": [
12 | {
13 | "name": "here",
14 | "description": "Creates a new project with the current working directory as the root of the project instead of creating a new project folder.",
15 | "type": "boolean"
16 | },
17 | {
18 | "name": "plugin",
19 | "description": "Creates a new Aurelia plugin project.",
20 | "type": "boolean"
21 | },
22 | {
23 | "name": "select",
24 | "description": "Preselect features (e.g. --select cli-bundler,alameda,karma). This option also turns on unattended mode.",
25 | "type": "string"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/lib/build/loader-plugin.js:
--------------------------------------------------------------------------------
1 | const {moduleIdWithPlugin} = require('./utils');
2 |
3 | exports.LoaderPlugin = class {
4 | constructor(type, config) {
5 | this.type = type;
6 | this.config = config;
7 | this.name = config.name;
8 | this.stub = config.stub;
9 | this.test = config.test ? new RegExp(config.test) : regExpFromExtensions(config.extensions);
10 | }
11 |
12 | matches(filePath) {
13 | return this.test.test(filePath);
14 | }
15 |
16 | transform(moduleId, filePath, contents) {
17 | contents = `define('${this.createModuleId(moduleId)}',[],function(){return ${JSON.stringify(contents)};});`;
18 | return contents;
19 | }
20 |
21 | createModuleId(moduleId) {
22 | // for backward compatibility, use 'text' as plugin name,
23 | // to not break existing app with additional json plugin in aurelia.json
24 | return moduleIdWithPlugin(moduleId, 'text', this.type);
25 | }
26 | };
27 |
28 | function regExpFromExtensions(extensions) {
29 | return new RegExp('^.*(' + extensions.map(x => '\\' + x).join('|') + ')$');
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/lib/build/module-id-processor.js:
--------------------------------------------------------------------------------
1 | // if moduleId is above surface (default src/), the '../../' confuses hell out of
2 | // requirejs as it tried to understand it as a relative module id.
3 | // replace '..' with '__dot_dot__' to enforce absolute module id.
4 | const toDotDot = (moduleId) => moduleId.split('/').map(p => p === '..' ? '__dot_dot__' : p).join('/');
5 | const fromDotDot = (moduleId) => moduleId.split('/').map(p => p === '__dot_dot__' ? '..' : p).join('/');
6 |
7 | const getAliases = (moduleId, paths) => {
8 | const aliases = [];
9 | const _moduleId = fromDotDot(moduleId);
10 | for (let i = 0, keys = Object.keys(paths); i < keys.length; i++) {
11 | let key = keys[i];
12 | let target = paths[key];
13 | if (key === 'root') continue;
14 | if (key === target) continue;
15 |
16 | if (_moduleId.startsWith(target + '/')) {
17 | aliases.push({
18 | fromId: toDotDot(key + _moduleId.slice(target.length)),
19 | toId: toDotDot(moduleId)
20 | });
21 | }
22 | }
23 |
24 | return aliases;
25 | };
26 |
27 | module.exports = { toDotDot, fromDotDot, getAliases };
28 |
--------------------------------------------------------------------------------
/spec/mocks/bundler.js:
--------------------------------------------------------------------------------
1 | const Configuration = require('../../lib/configuration').Configuration;
2 | const CLIOptions = require('../../lib/cli-options').CLIOptions;
3 | const ProjectMock = require('./project-mock');
4 | const LoaderPlugin = require('../../lib/build/loader-plugin').LoaderPlugin;
5 |
6 | module.exports = class Bundler {
7 | constructor() {
8 | this.itemIncludedInBuild = jasmine.createSpy('itemIncludedInBuild');
9 | this.interpretBuildOptions = jasmine.createSpy('interpretBuildOptions');
10 | this.configureDependency = jasmine.createSpy('configureDependency');
11 | this.addFile = jasmine.createSpy('addFile');
12 | this.configTargetBundle = {
13 | addAlias: jasmine.createSpy('addAlias')
14 | };
15 |
16 | CLIOptions.instance = new CLIOptions();
17 | this.buildOptions = new Configuration({}, {});
18 | this.project = new ProjectMock();
19 | this.loaderOptions = {
20 | type: 'require',
21 | plugins: [new LoaderPlugin('require', {
22 | name: 'text',
23 | extensions: ['.html', '.css']
24 | })]
25 | };
26 | this.environment = 'dev';
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2010 - 2016 Blue Spire Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/write/all.js:
--------------------------------------------------------------------------------
1 | // The order of these transforms is informed by how they were done in the
2 | // requirejs optimizer.
3 | var transforms = [
4 | require('./stubs'),
5 | require('./defines'),
6 | require('./replace')
7 | ];
8 |
9 | /**
10 | * Chains all the default set of transforms to return one function to be used
11 | * for transform operations on traced module content.
12 | * @param {Object} options object for holding options. The same options object
13 | * is used and passed to all transforms. See individual transforms for their
14 | * options.
15 | * @return {Function} A function that can be used for multiple content transform
16 | * calls.
17 | */
18 | module.exports = function all(options) {
19 | options = options || {};
20 |
21 | var transformFns = transforms.map(function(transform) {
22 | return transform(options);
23 | });
24 |
25 | return function(context, moduleName, filePath, contents) {
26 | contents = transformFns.reduce(function(contents, transformFn) {
27 | return transformFn(context, moduleName, filePath, contents);
28 | }, contents);
29 |
30 | return contents;
31 | };
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/write/stubs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Replaces module content for a given set of module IDs with stub define calls.
3 | * @param {Object} options object for holding options. Supported options:
4 | * - stubModules: Array of module IDs to place in stubs.
5 | * @return {Function} A function that can be used for multiple content transform
6 | * calls.
7 | */
8 | module.exports = function stubs(options) {
9 | options = options || {};
10 |
11 | return function(context, moduleName, filePath, contents) {
12 | if (options.stubModules
13 | && (options.stubModules.indexOf(moduleName) !== -1
14 | || options.stubModules.indexOf(context.pkgsMainMap[moduleName]) !== -1)) {
15 | //Just want to insert a simple module definition instead
16 | //of the source module. Useful for plugins that inline
17 | //all their resources.
18 | //Slightly different content for plugins, to indicate
19 | //that dynamic loading will not work.
20 | return 'define({load: function(id){' +
21 | 'throw new Error("Dynamic load not allowed: " + id);}});';
22 | } else {
23 | return contents;
24 | }
25 | };
26 |
27 | };
28 |
--------------------------------------------------------------------------------
/bin/aurelia-cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const resolve = require('resolve');
4 |
5 | const semver = require('semver');
6 | const nodeVersion = process.versions.node;
7 | if (semver.lt(nodeVersion, '10.12.0')) {
8 | console.error(`You are running Node.js v${nodeVersion}.
9 | aurelia-cli requires Node.js v10.12.0 or above.
10 | Please upgrade to latest Node.js https://nodejs.org`);
11 | process.exit(1);
12 | }
13 |
14 | process.title = 'aurelia';
15 |
16 | const userArgs = process.argv.slice(2);
17 | const commandName = userArgs[0];
18 | const commandArgs = userArgs.slice(1);
19 |
20 | let originalBaseDir = process.cwd();
21 |
22 | resolve('aurelia-cli', {
23 | basedir: originalBaseDir
24 | }, function(error, projectLocalCli) {
25 | let cli;
26 |
27 | if (commandName === 'new' || error) {
28 | cli = new (require('../lib/index').CLI);
29 | cli.options.runningGlobally = true;
30 | } else {
31 | cli = new (require(projectLocalCli).CLI);
32 | cli.options.runningLocally = true;
33 | }
34 |
35 | cli.options.originalBaseDir = originalBaseDir;
36 |
37 | cli.run(commandName, commandArgs).catch(err => {
38 | console.log(err);
39 | process.exit(1);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/lib/commands/config/util.js:
--------------------------------------------------------------------------------
1 | class ConfigurationUtilities {
2 | constructor(options, args) {
3 | this.options = options;
4 | this.args = args;
5 | }
6 |
7 | getArg(arg) {
8 | let args = this.args;
9 | if (args) {
10 | for (let i = 0; i < args.length; i++) {
11 | if (args[i].startsWith('--')) {
12 | arg++;
13 | }
14 | if (i === arg) {
15 | return args[i];
16 | }
17 | }
18 | }
19 | }
20 |
21 | getValue(value) {
22 | if (value) {
23 | if (!value.startsWith('"') &&
24 | !value.startsWith('[') &&
25 | !value.startsWith('{')) {
26 | value = `"${value}"`;
27 | }
28 | value = JSON.parse(value);
29 | }
30 | return value;
31 | }
32 |
33 | getAction(value) {
34 | let actions = ['add', 'remove', 'set', 'clear', 'get'];
35 | for (let action of actions) {
36 | if (this.options.hasFlag(action)) {
37 | return action;
38 | }
39 | }
40 | if (!value) {
41 | return 'get';
42 | }
43 | if (Array.isArray(value) || typeof value === 'object') {
44 | return 'add';
45 | }
46 | return 'set';
47 | }
48 | }
49 |
50 | module.exports = ConfigurationUtilities;
51 |
--------------------------------------------------------------------------------
/ACKNOWLEDGEMENTS.md:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 |
3 | ## amodro-trace (embedded)
4 |
5 | MIT License
6 | -----------
7 |
8 | Copyright (c) 2010-2015, The Dojo Foundation
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in
18 | all copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | THE SOFTWARE.
27 |
--------------------------------------------------------------------------------
/spec/mocks/mock-fs.js:
--------------------------------------------------------------------------------
1 | const { mkdtempSync, rmSync, mkdirSync, writeFileSync } = require('fs');
2 | const { join, dirname } = require('path');
3 | const tmpdir = mkdtempSync(join(__dirname, '..', '..', 'tmpdir-'));
4 | // By default work in a child folder. Some tests run against parent folder
5 | const defaultdir = join(tmpdir, 'a');
6 |
7 | function fillFiles(fileTree, baseDir = defaultdir) {
8 | mkdirSync(baseDir, { recursive: true });
9 | for (const key in fileTree) {
10 | const val = fileTree[key];
11 | const p = join(baseDir, key);
12 | if (typeof val === 'string') {
13 | mkdirSync(dirname(p), { recursive: true });
14 | writeFileSync(p, val);
15 | } else if (typeof val === 'object') {
16 | fillFiles(val, p);
17 | }
18 | }
19 | }
20 |
21 | let oldCwd;
22 |
23 | // Simple implementation of mockfs in local tmp dir.
24 | function mockfs(fileTree) {
25 | fillFiles(fileTree);
26 | if (!oldCwd) {
27 | oldCwd = process.cwd();
28 | process.chdir(defaultdir);
29 | }
30 | }
31 |
32 | mockfs.restore = function() {
33 | if (oldCwd) {
34 | process.chdir(oldCwd);
35 | oldCwd = undefined;
36 | }
37 | rmSync(tmpdir, { force: true, recursive: true });
38 | }
39 |
40 | process.on('exit', mockfs.restore);
41 |
42 | module.exports = mockfs;
43 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/read/cjs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
3 | * Available via the MIT or new BSD license.
4 | */
5 | var parse = require('../lib/parse');
6 |
7 | // modified to add forceWrap for dealing with
8 | // nodjs dist/commonjs/*js or dist/cjs/*.js
9 | // bypass r.js parse bug in usesCommonJs when checking
10 | // node_modules/@aspnet/signalr/dist/cjs/IHubProtocol.js
11 | // node_modules/@aspnet/signalr/dist/cjs/ILogger.js
12 | // https://github.com/requirejs/r.js/issues/980
13 | module.exports = function cjs(fileName, fileContents, forceWrap) {
14 | // Strip out comments.
15 | var preamble = '',
16 | commonJsProps = parse.usesCommonJs(fileName, fileContents);
17 |
18 | // First see if the module is not already RequireJS-formatted.
19 | if (!forceWrap && (parse.usesAmdOrRequireJs(fileName, fileContents) || !commonJsProps)) {
20 | return fileContents;
21 | }
22 |
23 | if (commonJsProps && (commonJsProps.dirname || commonJsProps.filename)) {
24 | preamble = 'var __filename = module.uri || \'\', ' +
25 | '__dirname = ' +
26 | '__filename.slice(0, __filename.lastIndexOf(\'/\') + 1); ';
27 | }
28 |
29 | // Construct the wrapper boilerplate.
30 | return 'define(function (require, exports, module) {' +
31 | preamble +
32 | fileContents +
33 | '\n});\n';
34 | };
35 |
--------------------------------------------------------------------------------
/lib/package-managers/base-package-manager.js:
--------------------------------------------------------------------------------
1 | const {spawn} = require('child_process');
2 | const npmWhich = require('npm-which');
3 | const isWindows = process.platform === "win32";
4 |
5 | exports.BasePackageManager = class {
6 | constructor(executableName) {
7 | this.executableName = executableName;
8 | }
9 |
10 | install(packages = [], workingDirectory = process.cwd(), command = 'install') {
11 | return this.run(command, packages, workingDirectory);
12 | }
13 |
14 | run(command, args = [], workingDirectory = process.cwd()) {
15 | let executable = this.getExecutablePath(workingDirectory);
16 | if (isWindows) {
17 | executable = JSON.stringify(executable); // Add quotes around path
18 | }
19 |
20 | return new Promise((resolve, reject) => {
21 | this.proc = spawn(
22 | executable,
23 | [command, ...args],
24 | { stdio: "inherit", cwd: workingDirectory, shell: isWindows }
25 | )
26 | .on('close', resolve)
27 | .on('error', reject);
28 | });
29 | }
30 |
31 | getExecutablePath(directory) {
32 | try {
33 | return npmWhich(directory).sync(this.executableName);
34 | } catch {
35 | return null;
36 | }
37 | }
38 |
39 | isAvailable(directory) {
40 | return !!this.getExecutablePath(directory);
41 | }
42 | };
43 |
44 | exports.default = exports.BasePackageManager;
45 |
--------------------------------------------------------------------------------
/lib/pretty-choices.js:
--------------------------------------------------------------------------------
1 | const {wordWrap} = require('enquirer/lib/utils');
2 | const getTtySize = require('./get-tty-size');
3 |
4 | // Check all values, indent hint line.
5 | module.exports = function(...choices) {
6 | if (choices.length && Array.isArray(choices[0])) {
7 | choices = choices[0];
8 | }
9 | return choices.map(c => {
10 | // for {role: 'separator'}
11 | if (c.role) return c;
12 |
13 | // displayName and description are for compatibility in lib/ui.js
14 | const value = c.value || c.displayName;
15 | const message = c.title || c.message || c.displayName;
16 | const hint = c.hint || c.description;
17 |
18 | if (typeof value !== 'string') {
19 | throw new Error(`Value type ${typeof value} is not supported. Only support string value.`);
20 | }
21 |
22 | const choice = {
23 | value: value,
24 | // TODO after https://github.com/enquirer/enquirer/issues/115
25 | // add ${idx + 1}. in front of message
26 | message: message,
27 | // https://github.com/enquirer/enquirer/issues/121
28 | name: c.message,
29 | if: c.if // used by lib/workflow/run-questionnaire
30 | };
31 |
32 | if (hint) {
33 | // indent hint, need to adjust indent after ${idx + 1}. was turned on
34 | choice.hint = '\n' + wordWrap(hint, {indent: ' ', width: getTtySize().width});
35 | }
36 |
37 | return choice;
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/lib/commands/help/command.js:
--------------------------------------------------------------------------------
1 | const UI = require('../../ui').UI;
2 | const CLIOptions = require('../../cli-options').CLIOptions;
3 | const Optional = require('aurelia-dependency-injection').Optional;
4 | const Project = require('../../project').Project;
5 | const string = require('../../string');
6 |
7 | module.exports = class {
8 | static inject() { return [CLIOptions, UI, Optional.of(Project)]; }
9 |
10 | constructor(options, ui, project) {
11 | this.options = options;
12 | this.ui = ui;
13 | this.project = project;
14 | }
15 |
16 | execute() {
17 | return this.ui.displayLogo()
18 | .then(() => {
19 | if (this.options.runningGlobally) {
20 | return this.getGlobalCommandText();
21 | }
22 |
23 | return this.getLocalCommandText();
24 | }).then(text => this.ui.log(text));
25 | }
26 |
27 | getGlobalCommandText() {
28 | return string.buildFromMetadata([
29 | require('../new/command.json'),
30 | require('./command.json')
31 | ], this.ui.getWidth());
32 | }
33 |
34 | getLocalCommandText() {
35 | const commands = [
36 | require('../generate/command.json'),
37 | require('../config/command.json'),
38 | require('./command.json')
39 | ];
40 |
41 | return this.project.getTaskMetadata().then(metadata => {
42 | return string.buildFromMetadata(metadata.concat(commands), this.ui.getWidth());
43 | });
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/lib/commands/config/command.js:
--------------------------------------------------------------------------------
1 | const UI = require('../../ui').UI;
2 | const CLIOptions = require('../../cli-options').CLIOptions;
3 | const Container = require('aurelia-dependency-injection').Container;
4 | const os = require('os');
5 |
6 | const Configuration = require('./configuration');
7 | const ConfigurationUtilities = require('./util');
8 |
9 | module.exports = class {
10 | static inject() { return [Container, UI, CLIOptions]; }
11 |
12 | constructor(container, ui, options) {
13 | this.container = container;
14 | this.ui = ui;
15 | this.options = options;
16 | }
17 |
18 | execute(args) {
19 | this.config = new Configuration(this.options);
20 | this.util = new ConfigurationUtilities(this.options, args);
21 | let key = this.util.getArg(0) || '';
22 | let value = this.util.getValue(this.util.getArg(1));
23 | let save = !CLIOptions.hasFlag('no-save');
24 | let backup = !CLIOptions.hasFlag('no-backup');
25 | let action = this.util.getAction(value);
26 |
27 | this.displayInfo(`Performing configuration action '${action}' on '${key}'`, (value ? `with '${value}'` : ''));
28 | this.displayInfo(this.config.execute(action, key, value));
29 |
30 | if (action !== 'get') {
31 | if (save) {
32 | this.config.save(backup).then((name) => {
33 | this.displayInfo('Configuration saved. ' + (backup ? `Backup file '${name}' created.` : 'No backup file was created.'));
34 | });
35 | } else {
36 | this.displayInfo(`Action was '${action}', but no save was performed!`);
37 | }
38 | }
39 | }
40 |
41 | displayInfo(message) {
42 | return this.ui.log(message + os.EOL);
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/lib/build/webpack-reporter.js:
--------------------------------------------------------------------------------
1 | module.exports = function reportReadiness(options) {
2 | const uri = createDomain(options);
3 | const yargs = require('yargs');
4 | const argv = yargs.argv;
5 | argv.color = require('supports-color');
6 | const useColor = argv.color;
7 |
8 | let startSentence = `Project is running at ${colorInfo(useColor, uri)}`;
9 |
10 | if (options.socket) {
11 | startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`;
12 | }
13 | console.log((argv.progress ? '\n' : '') + startSentence);
14 |
15 | console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`);
16 | const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') : options.contentBase;
17 |
18 | if (contentBase) {
19 | console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`);
20 | }
21 |
22 | if (options.historyApiFallback) {
23 | console.log(`404s will fallback to ${colorInfo(useColor, options.historyApiFallback.index || '/index.html')}`);
24 | }
25 | };
26 |
27 | function createDomain(opts) {
28 | const protocol = opts.https ? 'https' : 'http';
29 | const url = require('url');
30 |
31 | // the formatted domain (url without path) of the webpack server
32 | return opts.public ? `${protocol}://${opts.public}` : url.format({
33 | protocol: protocol,
34 | hostname: opts.host,
35 | port: opts.socket ? 0 : opts.port.toString()
36 | });
37 | }
38 |
39 | function colorInfo(useColor, msg) {
40 | if (useColor) {
41 | // Make text blue and bold, so it *pops*
42 | return `\u001b[1m\u001b[33m${msg}\u001b[39m\u001b[22m`;
43 | }
44 | return msg;
45 | }
46 |
--------------------------------------------------------------------------------
/spec/lib/build/stub-module.spec.js:
--------------------------------------------------------------------------------
1 | const stubModule = require('../../../lib/build/stub-module');
2 |
3 | describe('StubCoreNodejsModule', () => {
4 | it('stubs some core module with subfix -browserify', () => {
5 | expect(stubModule('os', 'src')).toEqual({
6 | name: 'os',
7 | path: '../node_modules/os-browserify'
8 | });
9 | });
10 |
11 | it('stubs domain', () => {
12 | expect(stubModule('domain', 'src')).toEqual({
13 | name: 'domain',
14 | path: '../node_modules/domain-browser'
15 | });
16 | });
17 |
18 | it('stubs http', () => {
19 | expect(stubModule('http', 'src')).toEqual({
20 | name: 'http',
21 | path: '../node_modules/stream-http'
22 | });
23 | });
24 |
25 | it('stubs querystring', () => {
26 | expect(stubModule('querystring', 'src')).toEqual({
27 | name: 'querystring',
28 | path: '../node_modules/querystring-browser-stub'
29 | });
30 | });
31 |
32 | it('stubs fs', () => {
33 | expect(stubModule('fs', 'src')).toEqual({
34 | name: 'fs',
35 | path: '../node_modules/fs-browser-stub'
36 | });
37 | });
38 |
39 | it('ignores sys', () => {
40 | expect(stubModule('sys', 'src')).toBeUndefined();
41 | });
42 |
43 | it('stubModule stubs zlib', () => {
44 | expect(stubModule('zlib', 'src')).toEqual({
45 | name: 'zlib',
46 | path: '../node_modules/browserify-zlib'
47 | });
48 | });
49 |
50 | it('stubs empty module for some core module', () => {
51 | expect(stubModule('dns', 'src')).toBe('define(function(){return {};});');
52 | });
53 |
54 | it('stubs empty module for __ignore__', () => {
55 | expect(stubModule('__ignore__', 'src')).toBe('define(function(){return {};});');
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/lib/build/package-installer.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('../file-system');
3 | const logger = require('aurelia-logging').getLogger('Package-installer');
4 |
5 | exports.PackageInstaller = class {
6 | constructor(project) {
7 | this.project = project;
8 | }
9 |
10 | determinePackageManager() {
11 | if (this._packageManager) return this._packageManager;
12 |
13 | let packageManager = this.project.packageManager;
14 |
15 | if (!packageManager && fs.existsSync(path.resolve(process.cwd(), './yarn.lock'))) {
16 | // Have to make best guess on yarn.
17 | // If user is using yarn, then we use npm to install package,
18 | // it will highly likely end in error.
19 | packageManager = 'yarn';
20 | }
21 |
22 | if (!packageManager) {
23 | packageManager = 'npm';
24 | }
25 |
26 | this._packageManager = packageManager;
27 | return packageManager;
28 | }
29 |
30 | install(packages) {
31 | let packageManager = this.determinePackageManager();
32 | let Ctor;
33 |
34 | logger.info(`Using '${packageManager}' to install the package(s). You can change this by setting the 'packageManager' property in the aurelia.json file to 'npm' or 'yarn'.`);
35 |
36 | try {
37 | Ctor = require(`../package-managers/${packageManager}`).default;
38 | } catch (e) {
39 | logger.error(`Could not load the ${packageManager} package installer. Falling back to NPM`, e);
40 |
41 | packageManager = 'npm';
42 | Ctor = require(`../package-managers/${packageManager}`).default;
43 | }
44 |
45 | let installer = new Ctor();
46 |
47 | logger.info(`[${packageManager}] installing ${packages}. It would take a while.`);
48 |
49 | return installer.install(packages);
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
19 | **I'm submitting a bug report**
20 | **I'm submitting a feature request**
21 |
22 | * **Library Version:**
23 | major.minor.patch-pre
24 |
25 |
26 | **Please tell us about your environment:**
27 | * **Operating System:**
28 | OSX 10.x|Linux (distro)|Windows [7|8|8.1|10]
29 |
30 | * **Node Version:**
31 | 6.2.0
32 |
36 |
37 | * **NPM Version:**
38 | 3.8.9
39 |
43 |
44 | * **Browser:**
45 | all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView
46 |
47 | * **Language:**
48 | all | TypeScript X.X | ESNext
49 |
50 | * **Loader/bundler:**
51 | all | Webpack | SystemJS | RequireJS
52 |
53 | **Current behavior:**
54 |
55 | * **What is the expected behavior?**
56 |
59 |
60 | * **What is the motivation / use case for changing the behavior?**
61 |
--------------------------------------------------------------------------------
/lib/commands/generate/command.js:
--------------------------------------------------------------------------------
1 | const UI = require('../../ui').UI;
2 | const CLIOptions = require('../../cli-options').CLIOptions;
3 | const Container = require('aurelia-dependency-injection').Container;
4 | const Project = require('../../project').Project;
5 | const string = require('../../string');
6 | const os = require('os');
7 |
8 | module.exports = class {
9 | static inject() { return [Container, UI, CLIOptions, Project]; }
10 |
11 | constructor(container, ui, options, project) {
12 | this.container = container;
13 | this.ui = ui;
14 | this.options = options;
15 | this.project = project;
16 | }
17 |
18 | execute(args) {
19 | if (args.length < 1) {
20 | return this.displayGeneratorInfo('No Generator Specified. Available Generators:');
21 | }
22 |
23 | this.project.installTranspiler();
24 |
25 | return this.project.resolveGenerator(args[0]).then(generatorPath => {
26 | Object.assign(this.options, {
27 | generatorPath: generatorPath,
28 | args: args.slice(1)
29 | });
30 |
31 | if (generatorPath) {
32 | let generator = this.project.getExport(require(generatorPath));
33 |
34 | if (generator.inject) {
35 | generator = this.container.get(generator);
36 | generator = generator.execute.bind(generator);
37 | }
38 |
39 | return generator();
40 | }
41 |
42 | return this.displayGeneratorInfo(`Invalid Generator: ${args[0]}. Available Generators:`);
43 | });
44 | }
45 |
46 | displayGeneratorInfo(message) {
47 | return this.ui.displayLogo()
48 | .then(() => this.ui.log(message + os.EOL))
49 | .then(() => this.project.getGeneratorMetadata())
50 | .then(metadata => string.buildFromMetadata(metadata, this.ui.getWidth()))
51 | .then(str => this.ui.log(str));
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/spec/lib/build/dependency-description.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const DependencyDescription = require('../../../lib/build/dependency-description').DependencyDescription;
3 |
4 | describe('The DependencyDescription', () => {
5 | let sut;
6 |
7 | beforeEach(() => {
8 | sut = new DependencyDescription('foo', 'npm');
9 | });
10 |
11 | it('gets mainId, calculates main path', () => {
12 | sut.loaderConfig = {
13 | name: 'foo',
14 | path: '../node_modules/foo',
15 | main: 'dist/bar'};
16 | expect(sut.mainId).toBe('foo/dist/bar');
17 | expect(sut.calculateMainPath('src')).toBe(path.join(process.cwd(), 'node_modules/foo/dist/bar.js'));
18 | });
19 |
20 | it('gets empty browser replacement if no browser replacement defined', () => {
21 | sut.metadata = {};
22 | expect(sut.browserReplacement()).toBeUndefined();
23 | sut.metadata = {browser: 'dist/browser.js'};
24 | expect(sut.browserReplacement()).toBeUndefined();
25 | });
26 |
27 | it('gets browser replacement', () => {
28 | sut.metadata = {
29 | browser: {
30 | 'module-a': false,
31 | 'module/b': 'shims/module/b.js',
32 | './server/only.js': './shims/server-only.js'
33 | }
34 | };
35 | expect(sut.browserReplacement()).toEqual({
36 | 'module-a': false,
37 | 'module/b': './shims/module/b',
38 | './server/only': './shims/server-only'
39 | });
40 | });
41 |
42 | it('gets browser replacement but leave . for main replacement', () => {
43 | sut.metadata = {
44 | browser: {
45 | "readable-stream": "./lib/readable-stream-browser.js",
46 | ".": "dist/jszip.min.js"
47 | }
48 | };
49 | expect(sut.browserReplacement()).toEqual({
50 | "readable-stream": "./lib/readable-stream-browser"
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/lib/string.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const c = require('ansi-colors');
3 | const {wordWrap} = require('enquirer/lib/utils');
4 |
5 | exports.buildFromMetadata = function(metadata, width) {
6 | let text = '';
7 | metadata.forEach(json => text += transformCommandToStyledText(json, width));
8 | return text;
9 | };
10 |
11 | function transformCommandToStyledText(json, width) {
12 | const indent = ' '.repeat(4);
13 |
14 | let text = c.magenta.bold(json.name);
15 | width = width || 1000;
16 |
17 | if (json.parameters) {
18 | json.parameters.forEach(parameter => {
19 | if (parameter.optional) {
20 | text += ' ' + c.dim(parameter.name);
21 | } else {
22 | text += ' ' + parameter.name;
23 | }
24 | });
25 | }
26 |
27 | if (json.flags) {
28 | json.flags.forEach(flag => {
29 | text += ' ' + c.yellow('--' + flag.name);
30 |
31 | if (flag.type !== 'boolean') {
32 | text += ' value';
33 | }
34 | });
35 | }
36 |
37 | text += os.EOL + os.EOL;
38 | text += wordWrap(json.description, {indent, width});
39 |
40 | if (json.parameters) {
41 | json.parameters.forEach(parameter => {
42 | text += os.EOL + os.EOL;
43 | let parameterInfo = parameter.name;
44 |
45 | if (parameter.optional) {
46 | parameterInfo += ' (optional)';
47 | }
48 |
49 | parameterInfo = c.blue(parameterInfo) + ' - ' + parameter.description;
50 | text += wordWrap(parameterInfo, {indent, width});
51 | });
52 | }
53 |
54 | if (json.flags) {
55 | json.flags.forEach(flag => {
56 | text += os.EOL + os.EOL;
57 | let flagInfo = c.yellow('--' + flag.name);
58 | flagInfo += ' - ' + flag.description;
59 | text += wordWrap(flagInfo, {indent, width});
60 | });
61 | }
62 |
63 | text += os.EOL + os.EOL;
64 |
65 | return text;
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/spec/lib/build/package-installer.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../../mocks/mock-fs');
2 | const PackageInstaller = require('../../../lib/build/package-installer').PackageInstaller;
3 |
4 | describe('The PackageInstaller', () => {
5 | let project;
6 | let sut;
7 |
8 | describe('when there is no yarn.lock file', () => {
9 | beforeEach(() => {
10 | project = {};
11 | sut = new PackageInstaller(project);
12 | const fsConfig = {};
13 | mockfs(fsConfig);
14 | });
15 |
16 | afterEach(() => {
17 | mockfs.restore();
18 | });
19 |
20 | it('uses npm by default', () => {
21 | expect(sut.determinePackageManager()).toBe('npm');
22 | });
23 |
24 | it('uses npm if specified in project', () => {
25 | project.packageManager = 'npm';
26 | expect(sut.determinePackageManager()).toBe('npm');
27 | });
28 |
29 | it('uses yarn if specified in project', () => {
30 | project.packageManager = 'yarn';
31 | expect(sut.determinePackageManager()).toBe('yarn');
32 | });
33 | });
34 |
35 | describe('when there is yarn.lock file', () => {
36 | beforeEach(() => {
37 | project = {};
38 | sut = new PackageInstaller(project);
39 | const fsConfig = { 'yarn.lock': 'some-content'};
40 | mockfs(fsConfig);
41 | });
42 |
43 | afterEach(() => {
44 | mockfs.restore();
45 | });
46 |
47 | it('uses yarn if project did not specify, and there is yarn.lock file', () => {
48 | expect(sut.determinePackageManager()).toBe('yarn');
49 | });
50 |
51 | it('uses npm if specified in project, despite yarn.lock file', () => {
52 | project.packageManager = 'npm';
53 | expect(sut.determinePackageManager()).toBe('npm');
54 | });
55 |
56 | it('uses yarn if specified in project, and there is yarn.lock file', () => {
57 | project.packageManager = 'yarn';
58 | expect(sut.determinePackageManager()).toBe('yarn');
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/lib/configuration.js:
--------------------------------------------------------------------------------
1 | const CLIOptions = require('./cli-options').CLIOptions;
2 |
3 | exports.Configuration = class {
4 | constructor(options, defaultOptions, environment) {
5 | this.options = Object.assign({}, defaultOptions, options);
6 | this.environment = environment || CLIOptions.getEnvironment();
7 | }
8 |
9 | getAllOptions() {
10 | return this.options;
11 | }
12 |
13 | getValue(propPath) {
14 | let split = propPath.split('.');
15 | let cur = this.options;
16 |
17 | for (let i = 0; i < split.length; i++) {
18 | if (!cur) {
19 | return undefined;
20 | }
21 |
22 | cur = cur[split[i]];
23 | }
24 |
25 | if (typeof cur === 'object') {
26 | let keys = Object.keys(cur);
27 | let result = undefined;
28 |
29 | if (keys.indexOf('default') > -1 && typeof(cur.default) === 'object') {
30 | result = cur.default;
31 | }
32 |
33 | for (let i = 0; i < keys.length; i++) {
34 | if (this.matchesEnvironment(this.environment, keys[i])) {
35 | if (typeof(cur[keys[i]]) === 'object') {
36 | result = Object.assign(result || {}, cur[keys[i]]);
37 | } else {
38 | return cur[keys[i]];
39 | }
40 | }
41 | }
42 |
43 | return result;
44 | }
45 |
46 | return cur;
47 | }
48 |
49 | isApplicable(propPath) {
50 | let value = this.getValue(propPath);
51 |
52 | if (!value) {
53 | return false;
54 | }
55 |
56 | if (typeof value === 'boolean') {
57 | return value;
58 | }
59 |
60 | if (typeof value === 'string') {
61 | return this.matchesEnvironment(this.environment, value);
62 | }
63 |
64 | if (typeof value === 'object') {
65 | return true;
66 | }
67 |
68 | return false;
69 | }
70 |
71 | matchesEnvironment(environment, value) {
72 | let parts = value.split('&').map(x => x.trim().toLowerCase());
73 | return parts.indexOf(environment) !== -1;
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/lib/commands/config/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "config",
3 | "description": "Gets or sets configuration for the Aurelia application.",
4 | "parameters": [
5 | {
6 | "name": "key",
7 | "optional": true,
8 | "description": "The key you want to get or set. Supports hierarchies and array indexes, for example build.targets[0] and arrayWithArray[2].[1]"
9 | },
10 | {
11 | "name": "value",
12 | "optional": true,
13 | "description": "The value you want to set the key to. Supports json, for example \"{ \\\"myKey\\\": \\\"myValue\\\" }\""
14 | }
15 | ],
16 | "flags": [
17 | {
18 | "name": "get",
19 | "description": "Gets the content of key, ignoring value parameter (the same as not specifying a value).",
20 | "type": "boolean"
21 | },
22 | {
23 | "name": "set",
24 | "description": "Sets the content of key to value, replacing any existing content.",
25 | "type": "boolean"
26 | },
27 | {
28 | "name": "clear",
29 | "description": "Deletes the key and all its content from the configuration.",
30 | "type": "boolean"
31 | },
32 | {
33 | "name": "add",
34 | "description": "If value or existing content of the key is an array, adds value(s) to existing content. If value is an object, merges it into existing content of key.",
35 | "type": "boolean"
36 | },
37 | {
38 | "name": "remove",
39 | "description": "If value or existing content of the key is an array, removes value(s) from existing content. If value or existing content of the key is an object, removes key(s) from existing content of key.",
40 | "type": "boolean"
41 | },
42 | {
43 | "name": "no-save",
44 | "description": "Don't save the changes in the configuration file.",
45 | "type": "boolean"
46 | },
47 | {
48 | "name": "no-backup",
49 | "description": "Don't create a backup configuration file before saving changes.",
50 | "type": "boolean"
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/spec/lib/commands/config/util.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../../../mocks/mock-fs');
2 |
3 | describe('The config command - util', () => {
4 | const CLIOptions = require('../../../../lib/cli-options').CLIOptions;
5 | const ConfigurationUtilities = require('../../../../lib/commands/config/util');
6 |
7 | beforeEach(() => {
8 | mockfs({
9 | 'aurelia_project/aurelia.json': '{ "build": {} }'
10 | });
11 | CLIOptions.instance = new CLIOptions();
12 | Object.assign(CLIOptions.instance, { originalBaseDir: '.' });
13 | });
14 | afterEach(() => {
15 | mockfs.restore();
16 | });
17 |
18 | it('gets the right arg withouth --flag', () => {
19 | const args = ['zero', 'one', 'two'];
20 | const configurationUtilities = new ConfigurationUtilities(CLIOptions, args);
21 | expect(configurationUtilities.getArg(1)).toBe('one');
22 | });
23 |
24 | it('gets the right arg with --flag', () => {
25 | const args = ['--zero', 'one', 'two'];
26 | const configurationUtilities = new ConfigurationUtilities(CLIOptions, args);
27 | expect(configurationUtilities.getArg(1)).toBe('two');
28 | });
29 |
30 | it('gets the right action', () => {
31 | const args = ['--zero', '--remove', '--two', 'three'];
32 | Object.assign(CLIOptions.instance, { args: args });
33 |
34 | const configurationUtilities = new ConfigurationUtilities(CLIOptions, args);
35 | expect(configurationUtilities.getAction()).toBe('remove');
36 | });
37 |
38 | it('gets the right JSON values', () => {
39 | const args = {};
40 | const values = {
41 | '123': '123',
42 | 'one two three': 'one two three',
43 | '"456"': '456',
44 | '{ "myKey": "myValue" }': { myKey: 'myValue' },
45 | '[ "one", 2, "thre ee" ]': [ 'one', 2, 'thre ee' ]
46 | };
47 | Object.assign(CLIOptions.instance, { args: args });
48 | const configurationUtilities = new ConfigurationUtilities(CLIOptions, args);
49 |
50 | for (let key of Object.keys(values)) {
51 | expect(configurationUtilities.getValue(key)).toEqual(values[key]);
52 | }
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/lib/commands/gulp.js:
--------------------------------------------------------------------------------
1 | const UI = require('../ui').UI;
2 | const CLIOptions = require('../cli-options').CLIOptions;
3 | const Container = require('aurelia-dependency-injection').Container;
4 | const Project = require('../project').Project;
5 |
6 | module.exports = class {
7 | static inject() { return [Container, UI, CLIOptions, Project]; }
8 |
9 | constructor(container, ui, options, project) {
10 | this.container = container;
11 | this.ui = ui;
12 | this.options = options;
13 | this.project = project;
14 | }
15 |
16 | execute() {
17 | return new Promise((resolve, reject) => {
18 | const gulp = require('gulp');
19 | this.connectLogging(gulp);
20 |
21 | this.project.installTranspiler();
22 |
23 | makeInjectable(gulp, 'series', this.container);
24 | makeInjectable(gulp, 'parallel', this.container);
25 |
26 | process.nextTick(() => {
27 | let task = this.project.getExport(require(this.options.taskPath), this.options.commandName);
28 |
29 | gulp.series(task)(error => {
30 | if (error) reject(error);
31 | else resolve();
32 | });
33 | });
34 | });
35 | }
36 |
37 | connectLogging(gulp) {
38 | gulp.on('start', e => {
39 | if (e.name[0] === '<') return;
40 | this.ui.log(`Starting '${e.name}'...`);
41 | });
42 |
43 | gulp.on('stop', e => {
44 | if (e.name[0] === '<') return;
45 | this.ui.log(`Finished '${e.name}'`);
46 | });
47 |
48 | gulp.on('error', e => this.ui.log(e));
49 | }
50 | };
51 |
52 | function makeInjectable(gulp, name, container) {
53 | let original = gulp[name];
54 |
55 | gulp[name] = function() {
56 | let args = new Array(arguments.length);
57 |
58 | for (let i = 0, ii = arguments.length; i < ii; ++i) {
59 | let task = arguments[i];
60 |
61 | if (task.inject) {
62 | let taskName = task.name;
63 | task = container.get(task);
64 | task = task.execute.bind(task);
65 | task.displayName = taskName;
66 | }
67 |
68 | args[i] = task;
69 | }
70 |
71 | return original.apply(gulp, args);
72 | };
73 | }
74 |
--------------------------------------------------------------------------------
/lib/project-item.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('./file-system');
3 | const Utils = require('./build/utils');
4 |
5 | // Legacy code, kept only for supporting `au generate`
6 | exports.ProjectItem = class {
7 | constructor(name, isDirectory) {
8 | this.name = name;
9 | this.isDirectory = !!isDirectory;
10 | }
11 |
12 | get children() {
13 | if (!this._children) {
14 | this._children = [];
15 | }
16 |
17 | return this._children;
18 | }
19 |
20 | add() {
21 | if (!this.isDirectory) {
22 | throw new Error('You cannot add items to a non-directory.');
23 | }
24 |
25 | for (let i = 0; i < arguments.length; ++i) {
26 | let child = arguments[i];
27 |
28 | if (this.children.indexOf(child) !== -1) {
29 | continue;
30 | }
31 |
32 | child.parent = this;
33 | this.children.push(child);
34 | }
35 |
36 | return this;
37 | }
38 |
39 | calculateRelativePath(fromLocation) {
40 | if (this === fromLocation) {
41 | return '';
42 | }
43 |
44 | let parentRelativePath = (this.parent && this.parent !== fromLocation)
45 | ? this.parent.calculateRelativePath(fromLocation)
46 | : '';
47 |
48 | return path.posix.join(parentRelativePath, this.name);
49 | }
50 |
51 | create(relativeTo) {
52 | let fullPath = relativeTo ? this.calculateRelativePath(relativeTo) : this.name;
53 |
54 | // Skip empty folder
55 | if (this.isDirectory && this.children.length) {
56 | return fs.stat(fullPath)
57 | .then(result => result)
58 | .catch(() => fs.mkdir(fullPath))
59 | .then(() => Utils.runSequentially(this.children, child => child.create(fullPath)));
60 | }
61 |
62 | if (this.text) {
63 | return fs.writeFile(fullPath, this.text);
64 | }
65 |
66 | return Promise.resolve();
67 | }
68 |
69 |
70 | setText(text) {
71 | this.text = text;
72 | return this;
73 | }
74 |
75 | getText() {
76 | return this.text;
77 | }
78 |
79 | static text(name, text) {
80 | return new exports.ProjectItem(name, false).setText(text);
81 | }
82 |
83 | static directory(p) {
84 | return new exports.ProjectItem(p, true);
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/lib/build/source-inclusion.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const mapStream = require('map-stream');
3 |
4 | exports.SourceInclusion = class {
5 | constructor(bundle, pattern, includedBy) {
6 | this.bundle = bundle;
7 | this.orignalPattern = pattern;
8 | // source-inclusion could be included by a dependency-inclusion
9 | this.includedBy = includedBy;
10 |
11 | if (pattern[0] === '[' && pattern[pattern.length - 1] === ']') {
12 | // strip "[**/*.js]" into "**/*.js"
13 | // this format is obsolete, but kept for backwards compatibility
14 | pattern = pattern.slice(1, -1);
15 | }
16 |
17 | this.pattern = pattern;
18 | this.matcher = this.bundle.createMatcher(pattern);
19 | this.excludes = this.bundle.excludes;
20 | this.items = [];
21 |
22 | this.vfs = require('vinyl-fs');
23 | }
24 |
25 | addItem(item) {
26 | item.includedBy = this;
27 | item.includedIn = this.bundle;
28 | this.items.push(item);
29 | }
30 |
31 | _isExcluded(item) {
32 | let found = this.excludes.findIndex(exclusion => {
33 | return exclusion.match(item.path);
34 | });
35 | return found > -1;
36 | }
37 |
38 | trySubsume(item) {
39 | if (this.matcher.match(item.path) && !this._isExcluded(item)) {
40 | this.addItem(item);
41 | return true;
42 | }
43 |
44 | return false;
45 | }
46 |
47 | addAllMatchingResources() {
48 | return new Promise((resolve, reject) => {
49 | let bundler = this.bundle.bundler;
50 | let pattern = path.resolve(this._getProjectRoot(), this.pattern);
51 |
52 | let subsume = (file, cb) => {
53 | bundler.addFile(file, this);
54 | cb(null, file);
55 | };
56 |
57 | this.vfs.src(pattern).pipe(mapStream(subsume))
58 | .on('error', e => {
59 | console.log(`Error while adding all matching resources of pattern "${this.pattern}": ${e.message}`);
60 | reject(e);
61 | })
62 | .on('end', resolve);
63 | });
64 | }
65 |
66 | _getProjectRoot() {
67 | return this.bundle.bundler.project.paths.root;
68 | }
69 |
70 | getAllModuleIds() {
71 | return this.items.map(x => x.moduleId);
72 | }
73 |
74 | getAllFiles() {
75 | return this.items;
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/lib/build/inject-css.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 | let cssUrlMatcher = /url\s*\(\s*(?!['"]data)([^) ]+)\s*\)/gi;
3 |
4 | // copied from aurelia-templating-resources css-resource
5 | // This behaves differently from webpack's style-loader.
6 | // Here we change './hello.png' to 'foo/hello.png' if base address is 'foo/bar'.
7 | // Note 'foo/hello.png' is technically a relative path in css,
8 | // this is designed to work with aurelia-router setup.
9 | // We inject css into a style tag on html head, it means the 'foo/hello.png'
10 | // is related to current url (not css url on link tag), or tag in html
11 | // head (which is recommended setup of router if not using hash).
12 | function fixupCSSUrls(address, css) {
13 | if (typeof css !== 'string') {
14 | throw new Error(`Failed loading required CSS file: ${address}`);
15 | }
16 | return css.replace(cssUrlMatcher, (match, p1) => {
17 | let quote = p1.charAt(0);
18 | if (quote === '\'' || quote === '"') {
19 | p1 = p1.substr(1, p1.length - 2);
20 | }
21 | const absolutePath = absoluteModuleId(address, p1);
22 | if (absolutePath === p1) {
23 | return match;
24 | }
25 | return 'url(\'' + absolutePath + '\')';
26 | });
27 | }
28 |
29 | function absoluteModuleId(baseId, moduleId) {
30 | if (moduleId[0] !== '.') return moduleId;
31 |
32 | let parts = baseId.split('/');
33 | parts.pop();
34 |
35 | moduleId.split('/').forEach(p => {
36 | if (p === '.') return;
37 | if (p === '..') {
38 | parts.pop();
39 | return;
40 | }
41 | parts.push(p);
42 | });
43 |
44 | return parts.join('/');
45 | }
46 |
47 | // copied from aurelia-pal-browser DOM.injectStyles
48 | function injectCSS(css, id) {
49 | if (typeof document === 'undefined' || !css) return;
50 | css = fixupCSSUrls(id, css);
51 |
52 | if (id) {
53 | let oldStyle = document.getElementById(id);
54 | if (oldStyle) {
55 | let isStyleTag = oldStyle.tagName.toLowerCase() === 'style';
56 |
57 | if (isStyleTag) {
58 | oldStyle.innerHTML = css;
59 | return;
60 | }
61 |
62 | throw new Error('The provided id does not indicate a style tag.');
63 | }
64 | }
65 |
66 | let node = document.createElement('style');
67 | node.innerHTML = css;
68 | node.type = 'text/css';
69 |
70 | if (id) {
71 | node.id = id;
72 | }
73 |
74 | document.head.appendChild(node);
75 | }
76 |
77 | injectCSS.fixupCSSUrls = fixupCSSUrls;
78 |
79 | module.exports = injectCSS;
80 |
--------------------------------------------------------------------------------
/spec/lib/build/module-id-processor.spec.js:
--------------------------------------------------------------------------------
1 | const { toDotDot, fromDotDot, getAliases } = require('../../../lib/build/module-id-processor');
2 |
3 | describe('module-id-processor', () => {
4 | const moduleId = '../src/elements/hello-world.ts';
5 | const escapedModuleId = '__dot_dot__/src/elements/hello-world.ts';
6 | const paths = {
7 | 'resources': '../src',
8 | 'elements': '../src/elements'
9 | };
10 |
11 | describe('toDotDot', () => {
12 | it('should replace ../ in module id', () => {
13 | expect(toDotDot(moduleId)).toEqual(escapedModuleId);
14 | });
15 |
16 | it('should replace multiple ../ in module id', () => {
17 | expect(toDotDot('../' + moduleId)).toEqual('__dot_dot__/' + escapedModuleId);
18 | });
19 | });
20 |
21 | describe('fromDotDot', () => {
22 | it('should convert moduleId to original path', () => {
23 | expect(fromDotDot(escapedModuleId)).toEqual(moduleId);
24 | });
25 |
26 | it('should replace multiple ../ in moduleId', () => {
27 | expect(fromDotDot('__dot_dot__/' + escapedModuleId)).toEqual('../' + moduleId);
28 | });
29 | });
30 |
31 | describe('getAliases', () => {
32 | it('should return a single match', () => {
33 | expect(getAliases('../src/hello-world.ts', paths)).toEqual([
34 | { fromId: 'resources/hello-world.ts', toId: '__dot_dot__/src/hello-world.ts' }
35 | ]);
36 | });
37 |
38 | it('should return an empty array when no match is found', () => {
39 | expect(getAliases('no/match/hello-world.ts', paths)).toEqual([]);
40 | });
41 |
42 | it('should return multiple matches', () => {
43 | expect(getAliases(moduleId, paths)).toEqual([
44 | { fromId: 'resources/elements/hello-world.ts', toId: '__dot_dot__/src/elements/hello-world.ts' },
45 | { fromId: 'elements/hello-world.ts', toId: '__dot_dot__/src/elements/hello-world.ts' }
46 | ]);
47 | });
48 |
49 | it('should support different aliases with same paths', () => {
50 | const duplicatePaths = {
51 | ...paths,
52 | '@resources': '../src'
53 | };
54 |
55 | expect(getAliases(moduleId, duplicatePaths)).toEqual([
56 | { fromId: 'resources/elements/hello-world.ts', toId: '__dot_dot__/src/elements/hello-world.ts' },
57 | { fromId: 'elements/hello-world.ts', toId: '__dot_dot__/src/elements/hello-world.ts' },
58 | { fromId: '@resources/elements/hello-world.ts', toId: '__dot_dot__/src/elements/hello-world.ts' }
59 | ]);
60 | });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/lib/build/dependency-description.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('../file-system');
3 | const Utils = require('./utils');
4 |
5 | exports.DependencyDescription = class {
6 | constructor(name, source) {
7 | this.name = name;
8 | this.source = source;
9 | }
10 |
11 | get mainId() {
12 | return this.name + '/' + this.loaderConfig.main;
13 | }
14 |
15 | get banner() {
16 | const {metadata, name} = this;
17 | const version = (metadata && metadata.version) || '';
18 | return `package: ${version}${' '.repeat(version.length < 10 ? (10 - version.length) : 0)} ${name}`;
19 | }
20 |
21 | calculateMainPath(root) {
22 | let config = this.loaderConfig;
23 | let part = path.join(config.path, config.main);
24 |
25 | let ext = path.extname(part).toLowerCase();
26 | if (!ext || Utils.knownExtensions.indexOf(ext) === -1) {
27 | part = part + '.js';
28 | }
29 |
30 | return path.join(process.cwd(), root, part);
31 | }
32 |
33 | readMainFileSync(root) {
34 | let p = this.calculateMainPath(root);
35 |
36 | try {
37 | return fs.readFileSync(p).toString();
38 | } catch {
39 | console.log('error', p);
40 | return '';
41 | }
42 | }
43 |
44 | // https://github.com/defunctzombie/package-browser-field-spec
45 | browserReplacement() {
46 | const browser = this.metadata && this.metadata.browser;
47 | // string browser field is handled in package-analyzer
48 | if (!browser || typeof browser === 'string') return;
49 |
50 | let replacement = {};
51 |
52 | for (let i = 0, keys = Object.keys(browser); i < keys.length; i++) {
53 | let key = keys[i];
54 | // leave {".": "dist/index.js"} for main replacement
55 | if (key === '.') continue;
56 | let target = browser[key];
57 |
58 | let sourceModule = filePathToModuleId(key);
59 |
60 | if (key.startsWith('.')) {
61 | sourceModule = './' + sourceModule;
62 | }
63 |
64 | if (typeof target === 'string') {
65 | let targetModule = filePathToModuleId(target);
66 | if (!targetModule.startsWith('.')) {
67 | targetModule = './' + targetModule;
68 | }
69 | replacement[sourceModule] = targetModule;
70 | } else {
71 | replacement[sourceModule] = false;
72 | }
73 | }
74 |
75 | return replacement;
76 | }
77 | };
78 |
79 | function filePathToModuleId(filePath) {
80 | let moduleId = path.normalize(filePath).replace(/\\/g, '/');
81 |
82 | if (moduleId.toLowerCase().endsWith('.js')) {
83 | moduleId = moduleId.slice(0, -3);
84 | }
85 |
86 | return moduleId;
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aurelia CLI
2 |
3 | 
4 | [](https://www.npmjs.com/package/aurelia-cli)
5 | [](https://gitter.im/aurelia/discuss?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6 | [](https://twitter.com/intent/follow?screen_name=aureliaeffect)
7 |
8 | This library is part of the [Aurelia](http://www.aurelia.io/) platform and contains its CLI implementation.
9 | To keep up to date on [Aurelia](http://www.aurelia.io/), please visit and subscribe to [the official blog](http://blog.aurelia.io/) and [our email list](http://eepurl.com/ces50j). We also invite you to [follow us on twitter](https://twitter.com/aureliaeffect). If you have questions look around our [Discourse forums](https://discourse.aurelia.io/), chat in our [community on Gitter](https://gitter.im/aurelia/discuss) or use [stack overflow](http://stackoverflow.com/search?q=aurelia). Documentation can be found [in our developer hub](http://aurelia.io/docs).
10 |
11 | ## Documentation
12 |
13 | You can read documentation on the cli [here](https://aurelia.io/docs/cli). If you would like to help improve this documentation, visit [aurelia/documentation](https://github.com/aurelia/documentation/tree/master/current/en-us/11.%20cli).
14 |
15 | ## Contributing
16 |
17 | Please see the [contributing guidelines](./CONTRIBUTING.md).
18 |
19 | ## Providing new feature to app skeleton
20 |
21 | App skeleton is no longer in this repo, it has been moved to a dedicated repo [aurelia/v1](https://github.com/aurelia/v1). Any contribution to app skeleton should go into [aurelia/v1](https://github.com/aurelia/v1).
22 |
23 | The `au new` command now simplify wraps `npx makes aurelia/v1`. Users can directly use that makes command to create new project.
24 |
25 | ## Building
26 |
27 | 1. Clone the aurelia-cli: `git clone https://github.com/aurelia/cli.git`
28 | 2. Go into the cli directory: `cd cli`
29 | 3. Run `npm install`
30 | 4. Link the cli with: `npm link`
31 | 5. Create a new project with `au new` or use an existing project. The linked CLI will be used to create the project.
32 | 6. In the project directory, run `npm link aurelia-cli`. The linked CLI will then be used for `au` commands such as `au run`
33 |
34 | ## Running the Tests
35 |
36 | Run `npm test` to run the unit tests.
37 |
38 | ## Release new aurelia-cli version
39 |
40 | Just run `npm version patch` (or minor or major)
41 |
42 | ## License
43 |
44 | MIT.
45 |
--------------------------------------------------------------------------------
/lib/build/stub-module.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const Utils = require('./utils');
3 | const logger = require('aurelia-logging').getLogger('StubNodejs');
4 |
5 | // stub core Node.js modules based on https://github.com/webpack/node-libs-browser/blob/master/index.js
6 | // no need stub for following modules, they got same name on npm package
7 | //
8 | // assert
9 | // buffer
10 | // events
11 | // punycode
12 | // process
13 | // string_decoder
14 | // url
15 | // util
16 |
17 | // fail on following core modules has no stub
18 | const UNAVAIABLE_CORE_MODULES = [
19 | 'child_process',
20 | 'cluster',
21 | 'dgram',
22 | 'dns',
23 | // 'fs',
24 | 'net',
25 | 'readline',
26 | 'repl',
27 | 'tls'
28 | ];
29 |
30 | const EMPTY_MODULE = 'define(function(){return {};});';
31 |
32 | function resolvePath(packageName, root) {
33 | return path.relative(root, Utils.resolvePackagePath(packageName)).replace(/\\/g, '/');
34 | }
35 |
36 | // note all paths here assumes local node_modules folder
37 | module.exports = function(moduleId, root) {
38 | // with subfix -browserify
39 | if (['crypto', 'https', 'os', 'path', 'stream', 'timers', 'tty', 'vm'].indexOf(moduleId) !== -1) {
40 | return {name: moduleId, path: resolvePath(`${moduleId}-browserify`, root)};
41 | }
42 |
43 | if (moduleId === 'domain') {
44 | logger.warn('core Node.js module "domain" is deprecated');
45 | return {name: 'domain', path: resolvePath('domain-browser', root)};
46 | }
47 |
48 | if (moduleId === 'http') {
49 | return {name: 'http', path: resolvePath('stream-http', root)};
50 | }
51 |
52 | if (moduleId === 'querystring') {
53 | return {name: 'querystring', path: resolvePath('querystring-browser-stub', root)};
54 | }
55 |
56 | if (moduleId === 'fs') {
57 | return {name: 'fs', path: resolvePath('fs-browser-stub', root)};
58 | }
59 |
60 | if (moduleId === 'sys') {
61 | logger.warn('core Node.js module "sys" is deprecated, the stub is disabled in CLI bundler due to conflicts with "util"');
62 | }
63 |
64 | if (moduleId === 'zlib') {
65 | return {name: 'zlib', path: resolvePath('browserify-zlib', root)};
66 | }
67 |
68 | if (UNAVAIABLE_CORE_MODULES.indexOf(moduleId) !== -1) {
69 | logger.warn(`No avaiable stub for core Node.js module "${moduleId}", stubbed with empty module`);
70 | return EMPTY_MODULE;
71 | }
72 |
73 | // https://github.com/defunctzombie/package-browser-field-spec
74 | // {"module-a": false}
75 | // replace with special placeholder __ignore__
76 | if (moduleId === '__ignore__') {
77 | return EMPTY_MODULE;
78 | }
79 |
80 | if (moduleId === '__inject_css__') {
81 | return {
82 | name: '__inject_css__',
83 | path: resolvePath('aurelia-cli', root),
84 | main: 'lib/build/inject-css'
85 | };
86 | }
87 | };
88 |
89 |
--------------------------------------------------------------------------------
/lib/build/index.js:
--------------------------------------------------------------------------------
1 | const {Transform} = require('stream');
2 | const Bundler = require('./bundler').Bundler;
3 | const PackageAnalyzer = require('./package-analyzer').PackageAnalyzer;
4 | const PackageInstaller = require('./package-installer').PackageInstaller;
5 | const cacheDir = require('./utils').cacheDir;
6 | const fs = require('fs');
7 |
8 | let bundler;
9 | let project;
10 | let isUpdating = false;
11 |
12 | exports.src = function(p) {
13 | if (bundler) {
14 | isUpdating = true;
15 | return Promise.resolve(bundler);
16 | }
17 |
18 | project = p;
19 | return Bundler.create(
20 | project,
21 | new PackageAnalyzer(project),
22 | new PackageInstaller(project)
23 | ).then(b => bundler = b);
24 | };
25 |
26 | exports.createLoaderCode = function(p) {
27 | const createLoaderCode = require('./loader').createLoaderCode;
28 | project = p || project;
29 | return buildLoaderConfig(project)
30 | .then(() => {
31 | let platform = project.build.targets[0];
32 | return createLoaderCode(platform, bundler);
33 | });
34 | };
35 |
36 | exports.createLoaderConfig = function(p) {
37 | const createLoaderConfig = require('./loader').createLoaderConfig;
38 | project = p || project;
39 |
40 | return buildLoaderConfig(project)
41 | .then(() => {
42 | let platform = project.build.targets[0];
43 | return createLoaderConfig(platform, bundler);
44 | });
45 | };
46 |
47 | exports.bundle = function() {
48 | return new Transform({
49 | objectMode: true,
50 | transform: function(file, encoding, callback) {
51 | callback(null, capture(file));
52 | }
53 | });
54 | };
55 |
56 | exports.dest = function(opts) {
57 | return bundler.build(opts)
58 | .then(() => bundler.write());
59 | };
60 |
61 | exports.clearCache = function() {
62 | // delete cache folder outside of cwd
63 | return fs.promises.rm(cacheDir, { recursive: true, force: true });
64 | };
65 |
66 | function buildLoaderConfig(p) {
67 | project = p || project;
68 | let configPromise = Promise.resolve();
69 |
70 | if (!bundler) {
71 | //If a bundler doesn't exist then chances are we have not run through getting all the files, and therefore the "bundles" will not be complete
72 | configPromise = configPromise.then(() => {
73 | return Bundler.create(
74 | project,
75 | new PackageAnalyzer(project),
76 | new PackageInstaller(project)
77 | ).then(b => bundler = b);
78 | });
79 | }
80 |
81 | return configPromise.then(() => {
82 | return bundler.build();
83 | });
84 | }
85 |
86 | function capture(file) {
87 | // ignore type declaration file generated by TypeScript compiler
88 | if (file.path.endsWith('d.ts')) return;
89 |
90 | if (isUpdating) {
91 | bundler.updateFile(file);
92 | } else {
93 | bundler.addFile(file);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/write/replace.js:
--------------------------------------------------------------------------------
1 | // browser replacement
2 | // https://github.com/defunctzombie/package-browser-field-spec
3 | // see bundled-source.js for more details
4 |
5 | // and also dep string cleanup
6 | // remove tailing '/', '.js'
7 | const meriyah = require('meriyah');
8 | const astMatcher = require('../../ast-matcher').astMatcher;
9 | // it is definitely a named AMD module at this stage
10 | var amdDep = astMatcher('define(__str, [__anl_deps], __any)');
11 | var cjsDep = astMatcher('require(__any_dep)');
12 | var isUMD = astMatcher('typeof define === "function" && define.amd');
13 | var isUMD2 = astMatcher('typeof define == "function" && define.amd');
14 |
15 | module.exports = function stubs(options) {
16 | options = options || {};
17 |
18 | return function(context, moduleName, filePath, contents) {
19 | const replacement = options.replacement;
20 | const toReplace = [];
21 |
22 | const _find = node => {
23 | if (node.type !== 'Literal') return;
24 | let dep = node.value;
25 | // remove tailing '/'
26 | if (dep.endsWith('/')) {
27 | dep = dep.slice(0, -1);
28 | }
29 | // remove tailing '.js', but only when dep is not
30 | // referencing a npm package main
31 | if (dep.endsWith('.js') && !isPackageName(dep)) {
32 | dep = dep.slice(0, -3);
33 | }
34 | // browser replacement;
35 | if (replacement && replacement[dep]) {
36 | dep = replacement[dep];
37 | }
38 |
39 | if (node.value !== dep) {
40 | toReplace.push({
41 | start: node.range[0],
42 | end: node.range[1],
43 | text: `'${dep}'`
44 | });
45 | }
46 | };
47 |
48 | // need node location
49 | const parsed = meriyah.parseScript(contents, {ranges: true, next: true, webcompat: true});
50 |
51 | if (isUMD(parsed) || isUMD2(parsed)) {
52 | // Skip lib in umd format, because browersify umd build could
53 | // use require('./file.js') which we should not strip .js
54 | return contents;
55 | }
56 |
57 | const amdMatch = amdDep(parsed);
58 | if (amdMatch) {
59 | amdMatch.forEach(result => {
60 | result.match.deps.forEach(_find);
61 | });
62 | }
63 |
64 | const cjsMatch = cjsDep(parsed);
65 | if (cjsMatch) {
66 | cjsMatch.forEach(result => {
67 | _find(result.match.dep);
68 | });
69 | }
70 |
71 | // reverse sort by "start"
72 | toReplace.sort((a, b) => b.start - a.start);
73 |
74 | toReplace.forEach(r => {
75 | contents = modify(contents, r);
76 | });
77 |
78 | return contents;
79 | };
80 | };
81 |
82 | function modify(contents, replacement) {
83 | return contents.slice(0, replacement.start) +
84 | replacement.text +
85 | contents.slice(replacement.end);
86 | }
87 |
88 | function isPackageName(path) {
89 | if (path.startsWith('.')) return false;
90 | const parts = path.split('/');
91 | // package name, or scope package name
92 | return parts.length === 1 || (parts.length === 2 && parts[0].startsWith('@'));
93 | }
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aurelia-cli",
3 | "version": "3.0.7",
4 | "description": "The command line tooling for Aurelia.",
5 | "keywords": [
6 | "aurelia",
7 | "cli",
8 | "bundle",
9 | "scaffold"
10 | ],
11 | "homepage": "http://aurelia.io",
12 | "bugs": {
13 | "url": "https://github.com/aurelia/cli/issues"
14 | },
15 | "bin": {
16 | "aurelia": "bin/aurelia-cli.js",
17 | "au": "bin/aurelia-cli.js"
18 | },
19 | "scripts": {
20 | "lint": "eslint lib spec",
21 | "pretest": "npm run lint",
22 | "test": "jasmine",
23 | "coverage": "c8 jasmine",
24 | "test:watch": "nodemon -x 'npm test'",
25 | "preversion": "npm test",
26 | "version": "standard-changelog && git add CHANGELOG.md",
27 | "postversion": "git push && git push --tags && npm publish"
28 | },
29 | "license": "MIT",
30 | "author": "Rob Eisenberg (http://robeisenberg.com/)",
31 | "main": "lib/index.js",
32 | "files": [
33 | "bin",
34 | "lib"
35 | ],
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/aurelia/cli"
39 | },
40 | "dependencies": {
41 | "@babel/core": "^7.26.0",
42 | "@babel/plugin-proposal-decorators": "^7.25.9",
43 | "@babel/plugin-transform-class-properties": "^7.25.9",
44 | "@babel/plugin-transform-modules-amd": "^7.25.9",
45 | "@babel/plugin-transform-modules-commonjs": "^7.25.9",
46 | "@babel/register": "^7.25.9",
47 | "ansi-colors": "^4.1.3",
48 | "assert": "^2.1.0",
49 | "aurelia-dependency-injection": "^1.6.1",
50 | "aurelia-logging": "^1.5.2",
51 | "aurelia-polyfills": "^1.3.4",
52 | "browserify-zlib": "^0.2.0",
53 | "buffer": "^6.0.3",
54 | "concat-with-sourcemaps": "^1.1.0",
55 | "console-browserify": "^1.2.0",
56 | "constants-browserify": "^1.0.0",
57 | "convert-source-map": "^2.0.0",
58 | "crypto-browserify": "^3.12.1",
59 | "domain-browser": "^5.7.0",
60 | "enquirer": "^2.4.1",
61 | "events": "^3.3.0",
62 | "fs-browser-stub": "^1.0.1",
63 | "gulp": "^4.0.2",
64 | "htmlparser2": "^9.1.0",
65 | "https-browserify": "^1.0.0",
66 | "lodash": "^4.17.21",
67 | "map-stream": "0.0.7",
68 | "meriyah": "^6.0.2",
69 | "minimatch": "^10.0.1",
70 | "npm-which": "^3.0.1",
71 | "os-browserify": "^0.3.0",
72 | "path-browserify": "1.0.1",
73 | "process": "^0.11.10",
74 | "punycode": "^2.3.1",
75 | "querystring-browser-stub": "^1.0.0",
76 | "readable-stream": "^4.5.2",
77 | "resolve": "^1.22.8",
78 | "semver": "^7.6.3",
79 | "stream-browserify": "^3.0.0",
80 | "stream-http": "^3.2.0",
81 | "string_decoder": "^1.3.0",
82 | "terser": "^5.36.0",
83 | "timers-browserify": "^2.0.12",
84 | "tty-browserify": "0.0.1",
85 | "typescript": "^5.6.3",
86 | "url": "^0.11.4",
87 | "util": "^0.12.5",
88 | "vm-browserify": "^1.1.2"
89 | },
90 | "devDependencies": {
91 | "@types/node": "^22.8.1",
92 | "c8": "^10.1.2",
93 | "eslint": "^9.13.0",
94 | "globals": "^15.11.0",
95 | "jasmine": "^5.4.0",
96 | "jasmine-spec-reporter": "^7.0.0",
97 | "nodemon": "^3.1.7",
98 | "standard-changelog": "^6.0.0",
99 | "yargs": "^17.7.2"
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/cli-options.js:
--------------------------------------------------------------------------------
1 | const fs = require('./file-system');
2 |
3 | function definedEnvironments() {
4 | let envs = [];
5 | // read user defined environment files
6 | let files;
7 | try {
8 | files = fs.readdirSync('aurelia_project/environments');
9 | } catch {
10 | // ignore
11 | }
12 | files && files.forEach(file => {
13 | const m = file.match(/^(.+)\.(t|j)s$/);
14 | if (m) envs.push(m[1]);
15 | });
16 | return envs;
17 | }
18 |
19 | exports.CLIOptions = class {
20 | constructor() {
21 | exports.CLIOptions.instance = this;
22 | }
23 |
24 | taskName() {
25 | let name = this.taskPath.split(/[/\\]/).pop();
26 | let parts = name.split('.');
27 | parts.pop();
28 | return parts.join('.');
29 | }
30 |
31 | getEnvironment() {
32 | if (this._env) return this._env;
33 |
34 | let env = this.getFlagValue('env') || process.env.NODE_ENV || 'dev';
35 | const envs = definedEnvironments();
36 |
37 | if (!envs.includes(env)) {
38 | // normalize NODE_ENV production/development (Node.js convention) to prod/dev
39 | // only if user didn't define production.js or development.js
40 | if (env === 'production' && envs.includes('prod')) {
41 | env = 'prod';
42 | } else if (env === 'development' && envs.includes('dev')) {
43 | env = 'dev';
44 | } else if (env !== 'dev') {
45 | // forgive missing aurelia_project/environments/dev.js as dev is our default env
46 | console.error(`The selected environment "${env}" is not defined in your aurelia_project/environments folder.`);
47 | process.exit(1);
48 | return;
49 | }
50 | }
51 |
52 | this._env = env;
53 | return env;
54 | }
55 |
56 | hasFlag(name, shortcut) {
57 | if (this.args) {
58 | let lookup = '--' + name;
59 | let found = this.args.indexOf(lookup) !== -1;
60 |
61 | if (found) {
62 | return true;
63 | }
64 |
65 | lookup = shortcut || ('-' + name[0]);
66 | found = this.args.indexOf(lookup) !== -1;
67 |
68 | if (found) {
69 | return true;
70 | }
71 |
72 | lookup = '-' + name;
73 | return this.args.indexOf(lookup) !== -1;
74 | }
75 |
76 | return false;
77 | }
78 |
79 | getFlagValue(name, shortcut) {
80 | if (this.args) {
81 | let lookup = '--' + name;
82 | let index = this.args.indexOf(lookup);
83 |
84 | if (index !== -1) {
85 | return this.args[index + 1] || null;
86 | }
87 |
88 | lookup = shortcut || ('-' + name[0]);
89 | index = this.args.indexOf(lookup);
90 |
91 | if (index !== -1) {
92 | return this.args[index + 1] || null;
93 | }
94 |
95 | lookup = '-' + name;
96 | index = this.args.indexOf(lookup);
97 |
98 | if (index !== -1) {
99 | return this.args[index + 1] || null;
100 | }
101 |
102 | return null;
103 | }
104 |
105 | return null;
106 | }
107 |
108 | static taskName() {
109 | return exports.CLIOptions.instance.taskName();
110 | }
111 |
112 | static hasFlag(name, shortcut) {
113 | return exports.CLIOptions.instance.hasFlag(name, shortcut);
114 | }
115 |
116 | static getFlagValue(name, shortcut) {
117 | return exports.CLIOptions.instance.getFlagValue(name, shortcut);
118 | }
119 |
120 | static getEnvironment() {
121 | return exports.CLIOptions.instance.getEnvironment();
122 | }
123 | };
124 |
--------------------------------------------------------------------------------
/lib/file-system.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const nodePath = require('path');
3 |
4 | exports.fs = fs;
5 |
6 | /**
7 | * @deprecated
8 | * fs.exists() is deprecated.
9 | * See https://nodejs.org/api/fs.html#fs_fs_exists_path_callback.
10 | * Functions using it can also not be properly tested.
11 | */
12 | exports.exists = function(path) {
13 | return new Promise(resolve => fs.exists(path, resolve));
14 | };
15 |
16 | exports.stat = function(path) {
17 | return new Promise((resolve, reject) => {
18 | fs.stat(path, (error, stats) => {
19 | if (error) reject(error);
20 | else resolve(stats);
21 | });
22 | });
23 | };
24 |
25 | exports.existsSync = function(path) {
26 | return fs.existsSync(path);
27 | };
28 |
29 | exports.mkdir = function(path) {
30 | return new Promise((resolve, reject) => {
31 | fs.mkdir(path, error => {
32 | if (error) reject(error);
33 | else resolve();
34 | });
35 | });
36 | };
37 |
38 | exports.mkdirp = function(path) {
39 | return new Promise((resolve, reject) => {
40 | fs.mkdir(path, {recursive: true}, error => {
41 | if (error) reject(error);
42 | else resolve();
43 | });
44 | });
45 | };
46 |
47 | exports.readdir = function(path) {
48 | return new Promise((resolve, reject) => {
49 | fs.readdir(path, (error, files) => {
50 | if (error) reject(error);
51 | else resolve(files);
52 | });
53 | });
54 | };
55 |
56 | exports.appendFile = function(path, text, cb) {
57 | fs.appendFile(path, text, cb);
58 | };
59 |
60 | exports.readdirSync = function(path) {
61 | return fs.readdirSync(path);
62 | };
63 |
64 | exports.readFile = function(path, encoding) {
65 | if (encoding !== null) {
66 | encoding = encoding || 'utf8';
67 | }
68 |
69 | return new Promise((resolve, reject) => {
70 | fs.readFile(path, encoding, (error, data) => {
71 | if (error) reject(error);
72 | else resolve(data);
73 | });
74 | });
75 | };
76 |
77 | exports.readFileSync = fs.readFileSync;
78 |
79 | exports.readFileSync = function(path, encoding) {
80 | if (encoding !== null) {
81 | encoding = encoding || 'utf8';
82 | }
83 |
84 | return fs.readFileSync(path, encoding);
85 | };
86 |
87 | exports.copySync = function(sourceFile, targetFile) {
88 | fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));
89 | };
90 |
91 | exports.resolve = function(path) {
92 | return nodePath.resolve(path);
93 | };
94 |
95 | exports.join = function() {
96 | return nodePath.join.apply(this, Array.prototype.slice.call(arguments));
97 | };
98 |
99 | exports.statSync = function(path) {
100 | return fs.statSync(path);
101 | };
102 |
103 | exports.isFile = function(path) {
104 | try {
105 | return fs.statSync(path).isFile();
106 | } catch {
107 | // ignore
108 | return false;
109 | }
110 | };
111 |
112 | exports.isDirectory = function(path) {
113 | try {
114 | return fs.statSync(path).isDirectory();
115 | } catch {
116 | // ignore
117 | return false;
118 | }
119 | };
120 |
121 | exports.writeFile = function(path, content, encoding) {
122 | return new Promise((resolve, reject) => {
123 | fs.mkdir(nodePath.dirname(path), {recursive: true}, err => {
124 | if (err) reject(err);
125 | else {
126 | fs.writeFile(path, content, encoding || 'utf8', error => {
127 | if (error) reject(error);
128 | else resolve();
129 | });
130 | }
131 | });
132 | });
133 | };
134 |
--------------------------------------------------------------------------------
/spec/lib/project-item.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('../../lib/file-system');
3 | const mockfs = require('../mocks/mock-fs');
4 | const {ProjectItem} = require('../../lib/project-item');
5 |
6 | describe('The ProjectItem module', () => {
7 | it('ProjectItem.text() captures text', () => {
8 | const t = ProjectItem.text('file.js', 'lorem');
9 | expect(t.name).toBe('file.js');
10 | expect(t.text).toBe('lorem');
11 | expect(t.isDirectory).toBe(false);
12 | expect(() => t.add(ProjectItem.text('file2.js', 'lorem'))).toThrow();
13 | });
14 |
15 | it('ProjectItem.directory() captures dir', () => {
16 | const t = ProjectItem.directory('dir');
17 | expect(t.name).toBe('dir');
18 | expect(t.text).toBeUndefined();
19 | expect(t.isDirectory).toBe(true);
20 | const file = ProjectItem.text('file.js', 'lorem');
21 | const folder = ProjectItem.directory('folder');
22 | t.add(file).add(folder);
23 | expect(t.children.length).toBe(2);
24 | expect(t.children).toEqual([file, folder]);
25 | });
26 |
27 | describe('Creates files', () => {
28 | let folder;
29 | beforeEach(() => {
30 | mockfs();
31 | folder = ProjectItem.directory('folder');
32 | folder.add(ProjectItem.text('file1.js', 'file1'));
33 | folder.add(ProjectItem.text('file2.js', 'file2'));
34 | folder.add(ProjectItem.directory('deepFolder').add(ProjectItem.text('file4.js', 'file4')));
35 | });
36 |
37 | afterEach(() => {
38 | mockfs.restore();
39 | });
40 |
41 | it('creates deep folders and files', async() => {
42 | await folder.create('root');
43 | expect(fs.readdirSync('.')).toEqual(['folder']);
44 | expect(fs.readdirSync('folder').sort()).toEqual(['deepFolder', 'file1.js', 'file2.js']);
45 | expect(fs.readFileSync(path.join('folder', 'file1.js'))).toBe('file1');
46 | expect(fs.readFileSync(path.join('folder', 'file2.js'))).toBe('file2');
47 | expect(fs.readdirSync(path.join('folder', 'deepFolder')).sort()).toEqual(['file4.js']);
48 | expect(fs.readFileSync(path.join('folder', 'deepFolder', 'file4.js'))).toBe('file4');
49 | });
50 |
51 | it('overwrites existing file', async() => {
52 | mockfs({
53 | 'folder': {
54 | 'file1.js': 'oldfile1',
55 | 'file3.js': 'oldfile3'
56 | }
57 | });
58 | await folder.create('root');
59 | expect(fs.readdirSync('.')).toEqual(['folder']);
60 | expect(fs.readdirSync('folder').sort()).toEqual(['deepFolder', 'file1.js', 'file2.js', 'file3.js']);
61 | expect(fs.readFileSync(path.join('folder', 'file1.js'))).toBe('file1');
62 | expect(fs.readFileSync(path.join('folder', 'file2.js'))).toBe('file2');
63 | expect(fs.readFileSync(path.join('folder', 'file3.js'))).toBe('oldfile3');
64 | expect(fs.readdirSync(path.join('folder', 'deepFolder')).sort()).toEqual(['file4.js']);
65 | expect(fs.readFileSync(path.join('folder', 'deepFolder', 'file4.js'))).toBe('file4');
66 | });
67 |
68 | it('skips empty folder', async() => {
69 | folder.add(ProjectItem.directory('empty-folder'));
70 | await folder.create('root');
71 | expect(fs.readdirSync('.')).toEqual(['folder']);
72 | expect(fs.readdirSync('folder').sort()).toEqual(['deepFolder', 'file1.js', 'file2.js']);
73 | expect(fs.readFileSync(path.join('folder', 'file1.js'))).toBe('file1');
74 | expect(fs.readFileSync(path.join('folder', 'file2.js'))).toBe('file2');
75 | expect(fs.readdirSync(path.join('folder', 'deepFolder')).sort()).toEqual(['file4.js']);
76 | expect(fs.readFileSync(path.join('folder', 'deepFolder', 'file4.js'))).toBe('file4');
77 | });
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/spec/lib/project.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../mocks/mock-fs');
2 |
3 | describe('The project module', () => {
4 | let path;
5 |
6 | let fs;
7 |
8 | let Project;
9 | let project;
10 |
11 | beforeEach(() => {
12 | path = require('path');
13 |
14 | fs = require('../../lib/file-system');
15 |
16 | Project = require('../../lib/project').Project;
17 |
18 | mockfs();
19 |
20 | project = new Project('', {
21 | paths: { },
22 | transpiler: {
23 | fileExtension: '.js'
24 | }
25 | });
26 | });
27 |
28 | afterEach(() => {
29 | mockfs.restore();
30 | });
31 |
32 | it('creates project items for all paths in aurelia.json, using the root path as the parent directory', () => {
33 | let model = {
34 | paths: {
35 | 'root': 'src',
36 | 'resources': 'resources',
37 | 'elements': 'resources/elements'
38 | }
39 | };
40 |
41 | project = new Project('', model);
42 |
43 | expect(hasProjectItem(project.locations, 'src', null)).toBe(true);
44 | expect(hasProjectItem(project.locations, 'resources', 'src')).toBe(true);
45 | expect(hasProjectItem(project.locations, 'resources/elements', 'src')).toBe(true);
46 | });
47 |
48 | describe('The resolveGenerator() function', () => {
49 | it('resolves to teh generators location', done => {
50 | fs.writeFile('aurelia_project/generators/test.js', '')
51 | .then(() => project.resolveGenerator('test'))
52 | .then(location => {
53 | expect(location).toBe(path.join('aurelia_project', 'generators', 'test.js'));
54 | }).catch(fail).then(done);
55 | });
56 |
57 | it('resolves to null', done => {
58 | project.resolveGenerator('test')
59 | .then(location => {
60 | expect(location).toBe(null);
61 | }).catch(fail).then(done);
62 | });
63 | });
64 |
65 | describe('The resolveTask() function', () => {
66 | it('resolves to the tasks location', done => {
67 | fs.writeFile('aurelia_project/tasks/test.js', '')
68 | .then(() => project.resolveTask('test'))
69 | .then(location => {
70 | expect(location).toBe(path.join('aurelia_project', 'tasks', 'test.js'));
71 | }).catch(fail).then(done);
72 | });
73 |
74 | it('resolves to null', done => {
75 | project.resolveTask('test')
76 | .then(location => {
77 | expect(location).toBe(null);
78 | }).catch(fail).then(done);
79 | });
80 | });
81 |
82 | it('The makeFileName() function', () => {
83 | expect(project.makeFileName('Foo'), 'foo');
84 | expect(project.makeFileName('foo'), 'foo');
85 | expect(project.makeFileName('fooBar'), 'foo-bar');
86 | expect(project.makeFileName('foo-bar'), 'foo-bar');
87 | expect(project.makeFileName('FOO Bar'), 'foo-bar');
88 | expect(project.makeFileName('_foo_bar_'), 'foo-bar');
89 | });
90 |
91 | it('The makeClassName() function', () => {
92 | expect(project.makeClassName('Foo'), 'Foo');
93 | expect(project.makeClassName('foo'), 'foo');
94 | expect(project.makeClassName('fooBar'), 'FooBar');
95 | expect(project.makeClassName('foo-bar'), 'FooBar');
96 | expect(project.makeClassName('FOO Bar'), 'FooBar');
97 | expect(project.makeClassName('_foo_bar_'), 'FooBar');
98 | });
99 |
100 | it('The makeFunctionName() function', () => {
101 | expect(project.makeFunctionName('Foo'), 'foo');
102 | expect(project.makeFunctionName('foo'), 'foo');
103 | expect(project.makeFunctionName('fooBar'), 'fooBar');
104 | expect(project.makeFunctionName('foo-bar'), 'fooBar');
105 | expect(project.makeFunctionName('FOO Bar'), 'fooBar');
106 | expect(project.makeFunctionName('_foo_bar_'), 'fooBar');
107 | });
108 | });
109 |
110 | function hasProjectItem(locations, name, parent) {
111 | for (let i = 0; i < locations.length; i++) {
112 | if (locations[i].name === name) {
113 | if (!parent && !locations[i].parent) {
114 | return true;
115 | }
116 |
117 | if (locations[i].parent && locations[i].parent.name === parent) {
118 | return true;
119 | }
120 | }
121 | }
122 |
123 | return false;
124 | }
125 |
--------------------------------------------------------------------------------
/spec/lib/build/source-inclusion.spec.js:
--------------------------------------------------------------------------------
1 | const BundlerMock = require('../../mocks/bundler');
2 | const SourceInclusion = require('../../../lib/build/source-inclusion').SourceInclusion;
3 | const mockfs = require('../../mocks/mock-fs');
4 | const Minimatch = require('minimatch').Minimatch;
5 | const path = require('path');
6 |
7 | describe('the SourceInclusion module', () => {
8 | let bundler;
9 |
10 | beforeEach(() => {
11 | bundler = new BundlerMock();
12 | });
13 |
14 | afterEach(() => {
15 | mockfs.restore();
16 | });
17 |
18 | it('captures pattern and excludes', () => {
19 | let bundle = {
20 | bundler: bundler,
21 | addAlias: jasmine.createSpy('addAlias'),
22 | includes: [],
23 | excludes: ['**/*.css'],
24 | createMatcher: function(pattern) {
25 | return new Minimatch(pattern, {
26 | dot: true
27 | });
28 | }
29 | };
30 |
31 | let sut = new SourceInclusion(bundle, 'foo*.js');
32 | sut.trySubsume({path: 'foo-bar.js'});
33 | expect(sut.items.length).toBe(1);
34 | expect(sut.items[0].path).toBe('foo-bar.js');
35 | expect(sut.items[0].includedBy).toBe(sut);
36 | expect(sut.items[0].includedIn).toBe(bundle);
37 |
38 | sut.trySubsume({path: 'fo-bar.js'});
39 | expect(sut.items.length).toBe(1);
40 |
41 | sut.trySubsume({path: 'foo-bar.css'});
42 | expect(sut.items.length).toBe(1);
43 | });
44 |
45 | it('captures [pattern] and excludes', () => {
46 | let bundle = {
47 | bundler: bundler,
48 | addAlias: jasmine.createSpy('addAlias'),
49 | includes: [],
50 | excludes: ['**/*.css'],
51 | createMatcher: function(pattern) {
52 | return new Minimatch(pattern, {
53 | dot: true
54 | });
55 | }
56 | };
57 |
58 | let sut = new SourceInclusion(bundle, '[foo*.js]');
59 | sut.trySubsume({path: 'foo-bar.js'});
60 | expect(sut.items.length).toBe(1);
61 | expect(sut.items[0].path).toBe('foo-bar.js');
62 | expect(sut.items[0].includedBy).toBe(sut);
63 | expect(sut.items[0].includedIn).toBe(bundle);
64 |
65 | sut.trySubsume({path: 'fo-bar.js'});
66 | expect(sut.items.length).toBe(1);
67 |
68 | sut.trySubsume({path: 'foo-bar.css'});
69 | expect(sut.items.length).toBe(1);
70 | });
71 |
72 | it('getAllModuleIds gets all module ids, getAllFiles gets all items', () => {
73 | let bundle = {
74 | bundler: bundler,
75 | addAlias: jasmine.createSpy('addAlias'),
76 | includes: [],
77 | excludes: ['**/*.css'],
78 | createMatcher: function(pattern) {
79 | return new Minimatch(pattern, {
80 | dot: true
81 | });
82 | }
83 | };
84 |
85 | let sut = new SourceInclusion(bundle, '**/*.js');
86 | sut.trySubsume({path: 'foo-bar.js', moduleId: 'foo-bar.js'});
87 | sut.trySubsume({path: 'fop/bar.js', moduleId: 'fop/bar.js'});
88 |
89 | expect(sut.getAllModuleIds().sort()).toEqual(['foo-bar.js', 'fop/bar.js']);
90 | expect(sut.getAllFiles()).toEqual([
91 | {path: 'foo-bar.js', moduleId: 'foo-bar.js', includedBy: sut, includedIn: bundle},
92 | {path: 'fop/bar.js', moduleId: 'fop/bar.js', includedBy: sut, includedIn: bundle}
93 | ]);
94 | });
95 |
96 | it('addAllMatchingResources adds all matching files', done => {
97 | let bundle = {
98 | bundler: bundler,
99 | addAlias: jasmine.createSpy('addAlias'),
100 | includes: [],
101 | excludes: ['**/*.css'],
102 | createMatcher: function(pattern) {
103 | return new Minimatch(pattern, {
104 | dot: true
105 | });
106 | }
107 | };
108 |
109 | let sut = new SourceInclusion(bundle, '../node_modules/foo/**/*.js');
110 | sut._getProjectRoot = () => 'src';
111 | mockfs({
112 | 'node_modules/foo/foo-bar.js': 'some-content',
113 | 'node_modules/foo/fop/bar.js': 'some-content'
114 | });
115 |
116 | sut.addAllMatchingResources()
117 | .then(() => {
118 | expect(bundler.addFile).toHaveBeenCalledTimes(2);
119 | let arg0 = bundler.addFile.calls.argsFor(0);
120 | let arg1 = bundler.addFile.calls.argsFor(1);
121 |
122 | expect(arg0[1]).toEqual(sut);
123 | expect(arg1[1]).toEqual(sut);
124 |
125 | expect(arg0[0].path).toEqual(path.resolve('node_modules/foo/foo-bar.js'));
126 | expect(arg1[0].path).toEqual(path.resolve('node_modules/foo/fop/bar.js'));
127 | done();
128 | })
129 | .catch(e => done.fail(e));
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/lib/project.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('./file-system');
3 | const _ = require('lodash');
4 | const ProjectItem = require('./project-item').ProjectItem;
5 |
6 | exports.Project = class {
7 | static establish(dir) {
8 | process.chdir(dir);
9 |
10 | return fs.readFile(path.join(dir, 'aurelia_project', 'aurelia.json')).then(model => {
11 | return fs.readFile(path.join(dir, 'package.json')).then(pack => {
12 | return new exports.Project(dir, JSON.parse(model.toString()), JSON.parse(pack.toString()));
13 | });
14 | });
15 | }
16 |
17 | constructor(directory, model, pack) {
18 | this.directory = directory;
19 | this.model = model;
20 | this.package = pack;
21 | this.taskDirectory = path.join(directory, 'aurelia_project/tasks');
22 | this.generatorDirectory = path.join(directory, 'aurelia_project/generators');
23 | this.aureliaJSONPath = path.join(directory, 'aurelia_project', 'aurelia.json');
24 |
25 | this.locations = Object.keys(model.paths).map(key => {
26 | this[key] = ProjectItem.directory(model.paths[key]);
27 |
28 | if (key !== 'root') {
29 | this[key] = ProjectItem.directory(model.paths[key]);
30 | this[key].parent = this.root;
31 | }
32 |
33 | return this[key];
34 | });
35 | this.locations.push(this.generators = ProjectItem.directory('aurelia_project/generators'));
36 | this.locations.push(this.tasks = ProjectItem.directory('aurelia_project/tasks'));
37 | }
38 |
39 | // Legacy code. This code and those ProjectItem.directory above, were kept only
40 | // for supporting `au generate`
41 | commitChanges() {
42 | return Promise.all(this.locations.map(x => x.create(this.directory)));
43 | }
44 |
45 | makeFileName(name) {
46 | return _.kebabCase(name);
47 | }
48 |
49 | makeClassName(name) {
50 | const camel = _.camelCase(name);
51 | return camel.slice(0, 1).toUpperCase() + camel.slice(1);
52 | }
53 |
54 | makeFunctionName(name) {
55 | return _.camelCase(name);
56 | }
57 |
58 | installTranspiler() {
59 | switch (this.model.transpiler.id) {
60 | case 'babel':
61 | installBabel.call(this);
62 | break;
63 | case 'typescript':
64 | installTypeScript();
65 | break;
66 | default:
67 | throw new Error(`${this.model.transpiler.id} is not a supported transpiler.`);
68 | }
69 | }
70 |
71 | getExport(m, name) {
72 | return name ? m[name] : m.default;
73 | }
74 |
75 | getGeneratorMetadata() {
76 | return getMetadata(this.generatorDirectory);
77 | }
78 |
79 | getTaskMetadata() {
80 | return getMetadata(this.taskDirectory);
81 | }
82 |
83 | resolveGenerator(name) {
84 | let potential = path.join(this.generatorDirectory, `${name}${this.model.transpiler.fileExtension}`);
85 | return fs.stat(potential).then(() => potential).catch(() => null);
86 | }
87 |
88 | resolveTask(name) {
89 | let potential = path.join(this.taskDirectory, `${name}${this.model.transpiler.fileExtension}`);
90 | return fs.stat(potential).then(() => potential).catch(() => null);
91 | }
92 | };
93 |
94 | function getMetadata(dir) {
95 | return fs.readdir(dir).then(files => {
96 | return Promise.all(
97 | files
98 | .sort()
99 | .map(file => path.join(dir, file))
100 | .filter(file => path.extname(file) === '.json')
101 | .map(file => fs.readFile(file).then(data => JSON.parse(data.toString())))
102 | );
103 | });
104 | }
105 |
106 | function installBabel() {
107 | require('@babel/register')({
108 | babelrc: false,
109 | configFile: false,
110 | plugins: [
111 | ['@babel/plugin-proposal-decorators', { legacy: true }],
112 | ['@babel/plugin-transform-class-properties', { loose: true }],
113 | ['@babel/plugin-transform-modules-commonjs', {loose: true}]
114 | ],
115 | only: [/aurelia_project/]
116 | });
117 | }
118 |
119 | function installTypeScript() {
120 | let ts = require('typescript');
121 |
122 | let json = require.extensions['.json'];
123 | delete require.extensions['.json'];
124 |
125 | require.extensions['.ts'] = function(module, filename) {
126 | let source = fs.readFileSync(filename);
127 | let result = ts.transpile(source, {
128 | module: ts.ModuleKind.CommonJS,
129 | declaration: false,
130 | noImplicitAny: false,
131 | noResolve: true,
132 | removeComments: true,
133 | noLib: false,
134 | emitDecoratorMetadata: true,
135 | experimentalDecorators: true
136 | });
137 |
138 | return module._compile(result, filename);
139 | };
140 |
141 | require.extensions['.json'] = json;
142 | }
143 |
--------------------------------------------------------------------------------
/spec/lib/commands/config/configuration.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../../../mocks/mock-fs');
2 |
3 | describe('The config command - configuration', () => {
4 | const CLIOptions = require('../../../../lib/cli-options').CLIOptions;
5 | const Configuration = require('../../../../lib/commands/config/configuration');
6 | let configuration;
7 | let project;
8 | let projectControl;
9 | beforeEach(() => {
10 | project = {
11 | 'top1': {
12 | 'middle': {
13 | 'bottom1': {
14 | 'rock': 'bottom'
15 | },
16 | 'bottom2': [
17 | 'also',
18 | 'bottom'
19 | ]
20 | }
21 | },
22 | 'top2': ['one', 2, { 'three': 'four' }],
23 | 'top3': 'string3',
24 | 'top4': 4
25 | };
26 | projectControl = JSON.parse(JSON.stringify(project));
27 |
28 | mockfs({
29 | 'aurelia_project': {
30 | 'aurelia.json': JSON.stringify(project)
31 | }
32 | });
33 | CLIOptions.instance = new CLIOptions();
34 | Object.assign(CLIOptions.instance, { originalBaseDir: '.' });
35 | configuration = new Configuration(CLIOptions.instance);
36 | });
37 |
38 | afterEach(() => {
39 | mockfs.restore();
40 | });
41 |
42 | it('normalizes keys', () => {
43 | const values = {
44 | 'one': 'one',
45 | 'one two three': 'one two three',
46 | 'one.two.three': 'one.two.three',
47 | 'one.two three': 'one.two three',
48 | 'one.[two three].four': 'one.two three.four',
49 | 'one[2].three': 'one.[2].three',
50 | 'one.[2].[3][four][5]': 'one.[2].[3].four.[5]',
51 | 'one.2.3[four][5]': 'one.2.3.four.[5]'
52 | };
53 | for (let key of Object.keys(values)) {
54 | expect(configuration.normalizeKey(key)).toEqual(values[key]);
55 | }
56 | });
57 |
58 | it('parses keys', () => {
59 | const values = {
60 | 'one': { index: false, key: true, value: 'one' },
61 | '[2]': { index: true, key: false, value: 2 }
62 | };
63 | for (let key of Object.keys(values)) {
64 | expect(configuration.parsedKey(key)).toEqual(values[key]);
65 | }
66 | });
67 |
68 | it('gets values', () => {
69 | const values = {
70 | 'top1': project.top1,
71 | 'top1.middle': project.top1.middle,
72 | 'top2.[1]': project.top2[1],
73 | 'top1.middle.bottom2.[1]': project.top1.middle.bottom2[1],
74 | 'top2.[2].three': project.top2[2].three
75 | };
76 | for (let key of Object.keys(values)) {
77 | expect(configuration.configEntry(key)).toEqual(values[key]);
78 | }
79 | });
80 |
81 | it('gets the complete project without arguments', () => {
82 | let result = configuration.execute('get', '');
83 |
84 | project = JSON.parse(result.slice(result.indexOf('\n')));
85 | expect(project).toEqual(projectControl);
86 | });
87 |
88 | it('gets a value', () => {
89 | let result = configuration.execute('get', 'top1');
90 |
91 | project = JSON.parse(result.slice(result.indexOf('\n')));
92 | expect(project).toEqual(projectControl.top1);
93 | });
94 |
95 | it('sets a value', () => {
96 | configuration.execute('set', 'top1.middle2', 'added');
97 | projectControl.top1.middle2 = 'added';
98 |
99 | configuration.execute('set', 'top5.new', 'stuff');
100 | projectControl.top5 = { 'new': 'stuff' };
101 |
102 | configuration.execute('set', 'top2[2].three', 'fifth');
103 | projectControl.top2[2].three = 'fifth';
104 |
105 | expect(configuration.project).toEqual(projectControl);
106 | });
107 |
108 | it('clears a value', () => {
109 | configuration.execute('clear', 'top1.middle');
110 | delete projectControl.top1.middle;
111 |
112 | configuration.execute('clear', 'top2[2].three');
113 | delete projectControl.top2[2].three;
114 |
115 | expect(configuration.project).toEqual(projectControl);
116 | });
117 |
118 | it('adds a value', () => {
119 | configuration.execute('add', 'top1.middle2', 'added');
120 | projectControl.top1.middle2 = 'added';
121 |
122 | configuration.execute('add', 'top2[2].five', 'sixth');
123 | projectControl.top2[2].five = 'sixth';
124 |
125 | configuration.execute('add', 'top2', { seventh: 'eight' });
126 | projectControl.top2.push({ seventh: 'eight' });
127 |
128 | expect(configuration.project).toEqual(projectControl);
129 | });
130 |
131 | it('removes a value', () => {
132 | configuration.execute('remove', 'top1.middle');
133 | delete projectControl.top1.middle;
134 |
135 | configuration.execute('remove', 'top2[2]');
136 | projectControl.top2.splice(2, 1);
137 |
138 | expect(configuration.project).toEqual(projectControl);
139 | });
140 | });
141 |
--------------------------------------------------------------------------------
/lib/cli.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const Container = require('aurelia-dependency-injection').Container;
3 | const fs = require('./file-system');
4 | const ui = require('./ui');
5 | const Project = require('./project').Project;
6 | const CLIOptions = require('./cli-options').CLIOptions;
7 | const LogManager = require('aurelia-logging');
8 | const Logger = require('./logger').Logger;
9 |
10 | exports.CLI = class {
11 | constructor(options) {
12 | this.options = options || new CLIOptions();
13 | this.container = new Container();
14 | this.ui = new ui.ConsoleUI(this.options);
15 | this.configureContainer();
16 | this.logger = LogManager.getLogger('CLI');
17 | }
18 |
19 | // Note: cannot use this.logger.error inside run()
20 | // because logger is not configured yet!
21 | // this.logger.error prints nothing in run(),
22 | // directly use this.ui.log.
23 | run(cmd, args) {
24 | const version = `${this.options.runningGlobally ? 'Global' : 'Local'} aurelia-cli v${require('../package.json').version}`;
25 |
26 | if (cmd === '--version' || cmd === '-v') {
27 | return this.ui.log(version);
28 | }
29 |
30 | return (cmd === 'new' ? Promise.resolve() : this._establishProject())
31 | .then(project => {
32 | this.ui.log(version);
33 |
34 | if (project && this.options.runningLocally) {
35 | this.project = project;
36 | this.container.registerInstance(Project, project);
37 | } else if (project && this.options.runningGlobally) {
38 | this.ui.log('The current directory is likely an Aurelia-CLI project, but no local installation of Aurelia-CLI could be found. ' +
39 | '(Do you need to restore node modules using npm install?)');
40 | return Promise.resolve();
41 | } else if (!project && this.options.runningLocally) {
42 | this.ui.log('It appears that the Aurelia CLI is running locally from ' + __dirname + '. However, no project directory could be found. ' +
43 | 'The Aurelia CLI has to be installed globally (npm install -g aurelia-cli) and locally (npm install aurelia-cli) in an Aurelia CLI project directory');
44 | return Promise.resolve();
45 | }
46 |
47 | return this.createCommand(cmd, args)
48 | .then((command) => command.execute(args));
49 | });
50 | }
51 |
52 | configureLogger() {
53 | LogManager.addAppender(this.container.get(Logger));
54 | let level = CLIOptions.hasFlag('debug') ? LogManager.logLevel.debug : LogManager.logLevel.info;
55 | LogManager.setLevel(level);
56 | }
57 |
58 | configureContainer() {
59 | this.container.registerInstance(CLIOptions, this.options);
60 | this.container.registerInstance(ui.UI, this.ui);
61 | }
62 |
63 | createCommand(commandText, commandArgs) {
64 | return new Promise(resolve => {
65 | if (!commandText) {
66 | resolve(this.createHelpCommand());
67 | return;
68 | }
69 |
70 | let parts = commandText.split(':');
71 | let commandModule = parts[0];
72 | let commandName = parts[1] || 'default';
73 |
74 | try {
75 | let alias = require('./commands/alias.json')[commandModule];
76 | let found = this.container.get(require(`./commands/${alias || commandModule}/command`));
77 | Object.assign(this.options, { args: commandArgs });
78 | // need to configure logger after getting args
79 | this.configureLogger();
80 | resolve(found);
81 | } catch {
82 | if (this.project) {
83 | this.project.resolveTask(commandModule).then(taskPath => {
84 | if (taskPath) {
85 | Object.assign(this.options, {
86 | taskPath: taskPath,
87 | args: commandArgs,
88 | commandName: commandName
89 | });
90 | // need to configure logger after getting args
91 | this.configureLogger();
92 |
93 | resolve(this.container.get(require('./commands/gulp')));
94 | } else {
95 | this.ui.log(`Invalid Command: ${commandText}`);
96 | resolve(this.createHelpCommand());
97 | }
98 | });
99 | } else {
100 | this.ui.log(`Invalid Command: ${commandText}`);
101 | resolve(this.createHelpCommand());
102 | }
103 | }
104 | });
105 | }
106 |
107 | createHelpCommand() {
108 | return this.container.get(require('./commands/help/command'));
109 | }
110 |
111 | _establishProject() {
112 | return determineWorkingDirectory(process.cwd())
113 | .then(dir => dir ? Project.establish(dir) : this.ui.log('No Aurelia project found.'));
114 | }
115 | };
116 |
117 | function determineWorkingDirectory(dir) {
118 | let parent = path.join(dir, '..');
119 |
120 | if (parent === dir) {
121 | return Promise.resolve(); // resolve to nothing
122 | }
123 |
124 | return fs.stat(path.join(dir, 'aurelia_project'))
125 | .then(() => dir)
126 | .catch(() => determineWorkingDirectory(parent));
127 | }
128 |
129 | process.on('unhandledRejection', (reason) => {
130 | console.log('Uncaught promise rejection:');
131 | console.log(reason);
132 | });
133 |
--------------------------------------------------------------------------------
/lib/build/loader.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const Configuration = require('../configuration').Configuration;
4 |
5 | exports.createLoaderCode = function createLoaderCode(platform, bundler) {
6 | let loaderCode;
7 | let loaderOptions = bundler.loaderOptions;
8 |
9 | switch (loaderOptions.type) {
10 | case 'require':
11 | loaderCode = 'requirejs.config(' + JSON.stringify(exports.createRequireJSConfig(platform, bundler), null, 2) + ')';
12 | break;
13 | case 'system':
14 | loaderCode = 'window.define=SystemJS.amdDefine; window.require=window.requirejs=SystemJS.amdRequire; SystemJS.config(' + JSON.stringify(exports.createSystemJSConfig(platform, bundler), null, 2) + ');';
15 | break;
16 | default:
17 | //TODO: Enhancement: Look at a designated folder for any custom configurations
18 | throw new Error(`Loader configuration style ${loaderOptions.type} is not supported.`);
19 | }
20 |
21 | return loaderCode;
22 | };
23 |
24 | exports.createLoaderConfig = function createLoaderConfig(platform, bundler) {
25 | let loaderConfig;
26 | let loaderOptions = bundler.loaderOptions;
27 |
28 | switch (loaderOptions.type) {
29 | case 'require':
30 | loaderConfig = exports.createRequireJSConfig(platform, bundler);
31 | break;
32 | case 'system':
33 | loaderConfig = exports.createSystemJSConfig(platform);
34 | break;
35 | default:
36 | //TODO: Enhancement: Look at a designated folder for any custom configurations
37 | throw new Error(`Loader configuration style ${loaderOptions.type} is not supported.`);
38 | }
39 |
40 | return loaderConfig;
41 | };
42 |
43 | exports.createRequireJSConfig = function createRequireJSConfig(platform, bundler) {
44 | let loaderOptions = bundler.loaderOptions;
45 | let loaderConfig = bundler.loaderConfig;
46 | let bundles = bundler.bundles;
47 | let configName = loaderOptions.configTarget;
48 | let bundleMetadata = {};
49 | let includeBundles = shouldIncludeBundleMetadata(bundles, loaderOptions);
50 | let config = Object.assign({}, loaderConfig);
51 | let location = platform.baseUrl || platform.output;
52 |
53 | if (platform.useAbsolutePath) {
54 | location = platform.baseUrl ? location : '/' + location;
55 | } else {
56 | location = '../' + location;
57 | }
58 |
59 | for (let i = 0; i < bundles.length; ++i) {
60 | let currentBundle = bundles[i];
61 | let currentName = currentBundle.config.name;
62 | let buildOptions = new Configuration(currentBundle.config.options, bundler.buildOptions.getAllOptions());
63 | if (currentName === configName) { //skip over the vendor bundle
64 | continue;
65 | }
66 |
67 | if (includeBundles) {
68 | bundleMetadata[currentBundle.moduleId] = currentBundle.getBundledModuleIds();
69 | }
70 | //If build revisions are enabled, append the revision hash to the appropriate module id
71 | config.paths[currentBundle.moduleId] = location + '/' + currentBundle.moduleId + (buildOptions.isApplicable('rev') && currentBundle.hash ? '-' + currentBundle.hash : '');
72 | }
73 |
74 | if (includeBundles) {
75 | config.bundles = bundleMetadata;
76 | }
77 |
78 | return config;
79 | };
80 |
81 | exports.createSystemJSConfig = function createSystemJSConfig(platform, bundler) {
82 | const loaderOptions = bundler.loaderOptions;
83 | const bundles = bundler.bundles;
84 | const configBundleName = loaderOptions.configTarget;
85 | const includeBundles = shouldIncludeBundleMetadata(bundles, loaderOptions);
86 | const location = platform.baseUrl || platform.output;
87 | const systemConfig = Object.assign({}, loaderOptions.config);
88 |
89 | const bundlesConfig = bundles.map(bundle => systemJSConfigForBundle(bundle, bundler, location, includeBundles))
90 | .filter(bundle => bundle.name !== configBundleName)
91 | .reduce((c, bundle) => bundle.addBundleConfig(c), { map: { 'text': 'text' } });
92 |
93 | return Object.assign(systemConfig, bundlesConfig);
94 | };
95 |
96 | function shouldIncludeBundleMetadata(bundles, loaderOptions) {
97 | let setting = loaderOptions.includeBundleMetadataInConfig;
98 |
99 | if (typeof setting === 'string') {
100 | switch (setting.toLowerCase()) {
101 | case 'auto':
102 | return bundles.length > 1;
103 | case 'true':
104 | return true;
105 | default:
106 | return false;
107 | }
108 | }
109 |
110 | return setting === true;
111 | }
112 |
113 | function systemJSConfigForBundle(bundle, bundler, location, includeBundles) {
114 | const buildOptions = new Configuration(bundle.config.options, bundler.buildOptions.getAllOptions());
115 | const mapTarget = location + '/' + bundle.moduleId + (buildOptions.isApplicable('rev') && bundle.hash ? '-' + bundle.hash : '') + path.extname(bundle.config.name);
116 | const moduleId = bundle.moduleId;
117 | const bundledModuleIds = bundle.getBundledModuleIds();
118 |
119 | return {
120 | name: bundle.config.name,
121 | addBundleConfig: function(config) {
122 | config.map[moduleId] = mapTarget;
123 | if (includeBundles) {
124 | config.bundles = (config.bundles || {});
125 | config.bundles[moduleId] = bundledModuleIds;
126 | }
127 |
128 | return config;
129 | }
130 | };
131 | }
132 |
--------------------------------------------------------------------------------
/spec/lib/cli-options.spec.ts:
--------------------------------------------------------------------------------
1 | const mockfs = require('../mocks/mock-fs');
2 |
3 | describe('The cli-options', () => {
4 | let cliOptions;
5 |
6 | beforeEach(() => {
7 | const fsConfig = {
8 | 'aurelia_project/environments/dev.js': 'content',
9 | 'aurelia_project/environments/stage.js': 'content',
10 | 'aurelia_project/environments/prod.js': 'content'
11 | };
12 | mockfs(fsConfig);
13 |
14 | cliOptions = new(require('../../lib/cli-options').CLIOptions)();
15 | });
16 |
17 | afterEach(() => {
18 | delete process.env.NODE_ENV;
19 | mockfs.restore();
20 | });
21 |
22 | describe('The CLIOptions', () => {
23 | it('gets the right task name', () => {
24 | const paths = [
25 | 'C:\\some\\path to\\project\\aurelia_project\\tasks\\',
26 | '/some/path to/project/aurelia_project/tasks/'
27 | ];
28 | const files = {
29 | 'run.ts': 'run',
30 | 'run.js': 'run'
31 | };
32 | for (let path of paths) {
33 | for (let file of Object.keys(files)) {
34 | cliOptions.taskPath = `${path}${file}`;
35 | expect(cliOptions.taskName()).toBe(files[file]);
36 | }
37 | }
38 | });
39 |
40 | it('gets env from arg --env', () => {
41 | cliOptions.args = ['build', '--env', 'prod'];
42 | expect(cliOptions.getEnvironment()).toBe('prod');
43 | });
44 |
45 | it('gets env from NODE_ENV', () => {
46 | process.env.NODE_ENV = 'dev';
47 | cliOptions.args = ['build'];
48 | expect(cliOptions.getEnvironment()).toBe('dev');
49 | });
50 |
51 | it('normalizes env from production to prod', () => {
52 | cliOptions.args = ['build', '--env', 'production'];
53 | expect(cliOptions.getEnvironment()).toBe('prod');
54 | });
55 |
56 | it('does not normalizes env from production to prod if production.js is defined', () => {
57 | const fsConfig = {
58 | 'aurelia_project/environments/development.js': 'content',
59 | 'aurelia_project/environments/stage.js': 'content',
60 | 'aurelia_project/environments/production.js': 'content'
61 | };
62 | mockfs(fsConfig);
63 | cliOptions.args = ['build', '--env', 'production'];
64 | expect(cliOptions.getEnvironment()).toBe('production');
65 | });
66 |
67 | it('normalizes env from development to dev', () => {
68 | cliOptions.args = ['build', '--env', 'development'];
69 | expect(cliOptions.getEnvironment()).toBe('dev');
70 | });
71 |
72 | it('does not normalizes env from development to dev if development.js is defined', () => {
73 | const fsConfig = {
74 | 'aurelia_project/environments/development.js': 'content',
75 | 'aurelia_project/environments/stage.js': 'content',
76 | 'aurelia_project/environments/production.js': 'content'
77 | };
78 | mockfs(fsConfig);
79 | cliOptions.args = ['build', '--env', 'development'];
80 | expect(cliOptions.getEnvironment()).toBe('development');
81 | });
82 |
83 | it('terminates when env is not defined by an env file', () => {
84 | let spy = jasmine.createSpy('exit', process.exit);
85 |
86 | cliOptions.args = ['build', '--env', 'unknown'];
87 | cliOptions.getEnvironment();
88 | expect(spy).toHaveBeenCalledWith(1);
89 | });
90 |
91 | it('normalizes NODE_ENV from production to prod', () => {
92 | process.env.NODE_ENV = 'production';
93 | cliOptions.args = ['build'];
94 | expect(cliOptions.getEnvironment()).toBe('prod');
95 | });
96 |
97 | it('does not normalizes NODE_ENV from production to prod if production.js is defined', () => {
98 | const fsConfig = {
99 | 'aurelia_project/environments/development.js': 'content',
100 | 'aurelia_project/environments/stage.js': 'content',
101 | 'aurelia_project/environments/production.js': 'content'
102 | };
103 | mockfs(fsConfig);
104 | process.env.NODE_ENV = 'production';
105 | cliOptions.args = ['build'];
106 | expect(cliOptions.getEnvironment()).toBe('production');
107 | });
108 |
109 | it('normalizes NODE_ENV from development to dev', () => {
110 | process.env.NODE_ENV = 'development';
111 | cliOptions.args = ['build'];
112 | expect(cliOptions.getEnvironment()).toBe('dev');
113 | });
114 |
115 | it('does not normalizes env from development to dev if development.js is defined', () => {
116 | const fsConfig = {
117 | 'aurelia_project/environments/development.js': 'content',
118 | 'aurelia_project/environments/stage.js': 'content',
119 | 'aurelia_project/environments/production.js': 'content'
120 | };
121 | mockfs(fsConfig);
122 | process.env.NODE_ENV = 'development';
123 | cliOptions.args = ['build'];
124 | expect(cliOptions.getEnvironment()).toBe('development');
125 | });
126 |
127 | it('terminates when NODE_ENV is not defined by an env file', () => {
128 | let spy = jasmine.createSpy('exit', process.exit);
129 |
130 | process.env.NODE_ENV = 'unknown';
131 | cliOptions.args = ['build'];
132 | cliOptions.getEnvironment();
133 | expect(spy).toHaveBeenCalledWith(1);
134 | });
135 | });
136 | });
137 |
--------------------------------------------------------------------------------
/lib/commands/config/configuration.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const copySync = require('../../file-system').copySync;
3 | const readFileSync = require('../../file-system').readFileSync;
4 | const writeFile = require('../../file-system').writeFile;
5 |
6 | class Configuration {
7 | constructor(options) {
8 | this.options = options;
9 | this.aureliaJsonPath = options.originalBaseDir + '/aurelia_project/aurelia.json';
10 | this.project = JSON.parse(readFileSync(this.aureliaJsonPath));
11 | }
12 |
13 | configEntry(key, createKey) {
14 | let entry = this.project;
15 | let keys = key.split('.');
16 |
17 | if (!keys[0]) {
18 | return entry;
19 | }
20 |
21 | while (entry && keys.length) {
22 | key = this.parsedKey(keys.shift());
23 | if (entry[key.value] === undefined || entry[key.value] === null) {
24 | if (!createKey) {
25 | return entry[key.value];
26 | }
27 | let checkKey = this.parsedKey(keys.length ? keys[0] : createKey);
28 | if (checkKey.index) {
29 | entry[key.value] = [];
30 | } else if (checkKey.key) {
31 | entry[key.value] = {};
32 | }
33 | }
34 | entry = entry[key.value];
35 |
36 | // TODO: Add support for finding objects based on input values?
37 | // TODO: Add support for finding string in array?
38 | }
39 |
40 | return entry;
41 | }
42 |
43 | parsedKey(key) {
44 | if (/\[(\d+)\]/.test(key)) {
45 | return { index: true, key: false, value: +(RegExp.$1) };
46 | }
47 |
48 | return { index: false, key: true, value: key };
49 | }
50 |
51 | normalizeKey(key) {
52 | const re = /([^.])\[/;
53 | while (re.exec(key)) {
54 | key = key.replace(re, RegExp.$1 + '.[');
55 | }
56 |
57 | let keys = key.split('.');
58 | for (let i = 0; i < keys.length; i++) {
59 | if (/\[(\d+)\]/.test(keys[i])) {
60 | // console.log(`keys[${i}] is index: ${keys[i]}`);
61 | } else if (/\[(.+)\]/.test(keys[i])) {
62 | // console.log(`keys[${i}] is indexed name: ${keys[i]}`);
63 | keys[i] = RegExp.$1;
64 | } else {
65 | // console.log(`keys[${i}] is name: ${keys[i]}`);
66 | }
67 | }
68 |
69 | return keys.join('.');
70 | }
71 |
72 | execute(action, key, value) {
73 | let originalKey = key;
74 |
75 | key = this.normalizeKey(key);
76 |
77 | if (action === 'get') {
78 | return `Configuration key '${key}' is:` + os.EOL + JSON.stringify(this.configEntry(key), null, 2);
79 | }
80 |
81 | let keys = key.split('.');
82 | key = this.parsedKey(keys.pop());
83 | let parent = keys.join('.');
84 |
85 | if (action === 'set') {
86 | let entry = this.configEntry(parent, key.value);
87 | if (entry) {
88 | entry[key.value] = value;
89 | } else {
90 | console.log('Failed to set property', this.normalizeKey(originalKey), '!');
91 | }
92 | } else if (action === 'clear') {
93 | let entry = this.configEntry(parent);
94 | if (entry && (key.value in entry)) {
95 | delete entry[key.value];
96 | } else {
97 | console.log('No property', this.normalizeKey(originalKey), 'to clear!');
98 | }
99 | } else if (action === 'add') {
100 | let entry = this.configEntry(parent, key.value);
101 | if (Array.isArray(entry[key.value]) && !Array.isArray(value)) {
102 | value = [value];
103 | } if (Array.isArray(value) && !Array.isArray(entry[key.value])) {
104 | entry[key.value] = (entry ? [entry[key.value]] : []);
105 | } if (Array.isArray(value)) {
106 | entry[key.value].push.apply(entry[key.value], value);
107 | } else if (Object(value) === value) {
108 | if (Object(entry[key.value]) !== entry[key.value]) {
109 | entry[key.value] = {};
110 | }
111 | Object.assign(entry[key.value], value);
112 | } else {
113 | entry[key.value] = value;
114 | }
115 | } else if (action === 'remove') {
116 | let entry = this.configEntry(parent);
117 |
118 | if (Array.isArray(entry) && key.index) {
119 | entry.splice(key.value, 1);
120 | } else if (Object(entry) === entry && key.key) {
121 | delete entry[key.value];
122 | } else if (!entry) {
123 | console.log('No property', this.normalizeKey(originalKey), 'to remove from!');
124 | } else {
125 | console.log("Can't remove value from", entry[key.value], '!');
126 | }
127 | }
128 | key = this.normalizeKey(originalKey);
129 | return `Configuration key '${key}' is now:` + os.EOL + JSON.stringify(this.configEntry(key), null, 2);
130 | }
131 |
132 | save(backup) {
133 | if (backup === undefined) backup = true;
134 |
135 | const unique = new Date().toISOString().replace(/[T\D]/g, '');
136 | let arr = this.aureliaJsonPath.split(/[\\/]/);
137 | const name = arr.pop();
138 | const path = arr.join('/');
139 | const bak = `${name}.${unique}.bak`;
140 |
141 | if (backup) {
142 | copySync(this.aureliaJsonPath, [path, bak].join('/'));
143 | }
144 |
145 | return writeFile(this.aureliaJsonPath, JSON.stringify(this.project, null, 2), 'utf8')
146 | .then(() => { return bak; });
147 | }
148 | }
149 |
150 | module.exports = Configuration;
151 |
--------------------------------------------------------------------------------
/lib/ui.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const fs = require('./file-system');
3 | const {wordWrap} = require('enquirer/lib/utils');
4 | const getTtySize = require('./get-tty-size');
5 | const {Input, Select} = require('enquirer');
6 | const prettyChoices = require('./pretty-choices');
7 | const {Writable} = require('stream');
8 | const _ = require('lodash');
9 |
10 | exports.UI = class { };
11 |
12 | exports.ConsoleUI = class {
13 | constructor(cliOptions) {
14 | this.cliOptions = cliOptions;
15 | }
16 |
17 | log(text, indent) {
18 | if (indent !== undefined) {
19 | text = wordWrap(text, {indent, width: this.getWidth()});
20 | }
21 | return new Promise(resolve => {
22 | console.log(text);
23 | resolve();
24 | });
25 | }
26 |
27 | ensureAnswer(answer, question, suggestion) {
28 | return this._ensureAnswer(answer, question, suggestion);
29 | }
30 |
31 | // _debug is used to pass in answers for prompts.
32 | async _ensureAnswer(answer, question, suggestion, _debug = []) {
33 | if (answer) return answer;
34 | return await this._question(question, suggestion, undefined, _debug);
35 | }
36 |
37 | question(question, options, defaultValue) {
38 | return this._question(question, options, defaultValue);
39 | }
40 |
41 | // _debug is used to pass in answers for prompts.
42 | async _question(question, options, defaultValue, _debug = []) {
43 | let opts;
44 | let PromptType;
45 | if (!options || typeof options === 'string') {
46 | opts = {
47 | message: question,
48 | initial: options || '',
49 | result: r => r.trim(),
50 | validate: r => _.trim(r) ? true : 'Please provide an answer'
51 | };
52 | PromptType = Input;
53 | } else {
54 | options = options.filter(x => includeOption(this.cliOptions, x));
55 |
56 | if (options.length === 1) {
57 | return options[0].value || options[0].displayName;
58 | }
59 |
60 | opts = {
61 | type: 'select',
62 | name: 'answer',
63 | message: question,
64 | initial: defaultValue || options[0].value || options[0].displayName,
65 | choices: prettyChoices(options),
66 | // https://github.com/enquirer/enquirer/issues/121#issuecomment-468413408
67 | result(name) {
68 | return this.map(name)[name];
69 | }
70 | };
71 | PromptType = Select;
72 | }
73 |
74 | if (_debug && _debug.length) {
75 | // Silent output in debug mode
76 | opts.stdout = new Writable({write(c, e, cb) {cb();}});
77 | }
78 |
79 | return await _run(new PromptType(opts), _debug);
80 | }
81 |
82 | multiselect(question, options) {
83 | return this._multiselect(question, options);
84 | }
85 |
86 | // _debug is used to pass in answers for prompts.
87 | async _multiselect(question, options, _debug = []) {
88 | options = options.filter(x => includeOption(this.cliOptions, x));
89 |
90 | const opts = {
91 | multiple: true,
92 | message: question,
93 | choices: prettyChoices(options),
94 | validate: results => results.length === 0 ? 'Need at least one selection' : true,
95 | // https://github.com/enquirer/enquirer/issues/121#issuecomment-468413408
96 | result(names) {
97 | return Object.values(this.map(names));
98 | }
99 | };
100 |
101 | if (_debug && _debug.length) {
102 | // Silent output in debug mode
103 | opts.stdout = new Writable({write(c, e, cb) {cb();}});
104 | }
105 |
106 | return await _run(new Select(opts), _debug);
107 | }
108 |
109 | getWidth() {
110 | return getTtySize().width;
111 | }
112 |
113 | getHeight() {
114 | return getTtySize().height;
115 | }
116 |
117 | displayLogo() {
118 | if (this.getWidth() < 50) {
119 | return this.log('Aurelia CLI' + os.EOL);
120 | }
121 |
122 | let logoLocation = require.resolve('./resources/logo.txt');
123 |
124 | return fs.readFile(logoLocation).then(logo => {
125 | this.log(logo.toString());
126 | });
127 | }
128 | };
129 |
130 | function includeOption(cliOptions, option) {
131 | if (option.disabled) {
132 | return false;
133 | }
134 |
135 | if (option.flag) {
136 | return cliOptions.hasFlag(option.flag);
137 | }
138 |
139 | return true;
140 | }
141 |
142 | async function _run(prompt, _debug = []) {
143 | if (_debug && _debug.length) {
144 | prompt.once('run', async() => {
145 | for (let d = 0, dd = _debug.length; d < dd; d++) {
146 | let debugChoice = _debug[d];
147 |
148 | if (typeof debugChoice === 'number') {
149 | // choice index is 1-based.
150 | while (debugChoice-- > 0) {
151 | if (debugChoice) {
152 | await prompt.keypress(null, {name: 'down'});
153 | } else {
154 | await prompt.submit();
155 | }
156 | }
157 | } else if (typeof debugChoice === 'string') {
158 | for (let i = 0, ii = debugChoice.length; i < ii; i++) {
159 | await prompt.keypress(debugChoice[i]);
160 | }
161 | await prompt.submit();
162 | } else if (typeof debugChoice === 'function') {
163 | await debugChoice(prompt);
164 | }
165 | }
166 | });
167 | }
168 |
169 | return await prompt.run();
170 | }
171 |
--------------------------------------------------------------------------------
/spec/lib/build/inject-css.spec.js:
--------------------------------------------------------------------------------
1 | const fixupCSSUrls = require('../../../lib/build/inject-css').fixupCSSUrls;
2 |
3 | // tests partly copied from
4 | // https://github.com/webpack-contrib/style-loader/blob/master/test/fixUrls.test.js
5 | describe('fixupCSSUrls', () => {
6 | it('throws on null/undefined', () => {
7 | expect(() => fixupCSSUrls('foo/bar', null)).toThrow();
8 | expect(() => fixupCSSUrls('foo/bar', undefined)).toThrow();
9 | });
10 |
11 | it('Blank css is not modified', () => {
12 | const css = '';
13 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
14 | });
15 |
16 | it('No url is not modified', () => {
17 | const css = 'body { }';
18 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
19 | });
20 |
21 | it("Full url isn't changed (no quotes)", () => {
22 | const css = 'body { background-image:url ( http://example.com/bg.jpg ); }';
23 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
24 | });
25 |
26 | it("Full url isn't changed (no quotes, spaces)", () => {
27 | const css = 'body { background-image:url ( http://example.com/bg.jpg ); }';
28 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
29 | });
30 |
31 | it("Full url isn't changed (double quotes)", () => {
32 | const css = 'body { background-image:url("http://example.com/bg.jpg"); }';
33 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
34 | });
35 |
36 | it("Full url isn't changed (double quotes, spaces)", () => {
37 | const css = 'body { background-image:url ( "http://example.com/bg.jpg" ); }';
38 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
39 | });
40 |
41 | it("Full url isn't changed (single quotes)", () => {
42 | const css = 'body { background-image:url(\'http://example.com/bg.jpg\'); }';
43 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
44 | });
45 |
46 | it("Full url isn't changed (single quotes, spaces)", () => {
47 | const css = 'body { background-image:url ( \'http://example.com/bg.jpg\' ); }';
48 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
49 | });
50 |
51 | it('Multiple full urls are not changed', () => {
52 | const css = "body { background-image:url(http://example.com/bg.jpg); }\ndiv.main { background-image:url ( 'https://www.anothersite.com/another.png' ); }";
53 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
54 | });
55 |
56 | it("Http url isn't changed", function() {
57 | const css = 'body { background-image:url(http://example.com/bg.jpg); }';
58 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
59 | });
60 |
61 | it("Https url isn't changed", function() {
62 | const css = 'body { background-image:url(https://example.com/bg.jpg); }';
63 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
64 | });
65 |
66 | it("HTTPS url isn't changed", function() {
67 | const css = 'body { background-image:url(HTTPS://example.com/bg.jpg); }';
68 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
69 | });
70 |
71 | it("File url isn't changed", function() {
72 | const css = 'body { background-image:url(file:///example.com/bg.jpg); }';
73 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
74 | });
75 |
76 | it("Double slash url isn't changed", function() {
77 | const css = 'body { background-image:url(//example.com/bg.jpg); }';
78 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
79 | });
80 |
81 | it("Image data uri url isn't changed", function() {
82 | const css = 'body { background-image:url(data:image/png;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg==); }';
83 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
84 | });
85 |
86 | it("Font data uri url isn't changed", function() {
87 | const css = 'body { background-image:url(data:application/x-font-woff;charset=utf-8;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg); }';
88 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
89 | });
90 |
91 | it('Relative url with dot slash', function() {
92 | const css = 'body { background-image:url(./c/d/bg.jpg); }';
93 | const expected = "body { background-image:url('foo/c/d/bg.jpg'); }";
94 | expect(fixupCSSUrls('foo/bar', css)).toBe(expected);
95 | });
96 |
97 | it('Multiple relative urls', function() {
98 | const css = 'body { background-image:URL ( "./bg.jpg" ); }\ndiv.main { background-image:url(../c/d/bg.jpg); }';
99 | const expected = "body { background-image:url('foo/bg.jpg'); }\ndiv.main { background-image:url('c/d/bg.jpg'); }";
100 | expect(fixupCSSUrls('foo/bar', css)).toBe(expected);
101 | });
102 |
103 | it("url with hash isn't changed", function() {
104 | const css = 'body { background-image:url(#bg.jpg); }';
105 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
106 | });
107 |
108 | it('Empty url should be skipped', function() {
109 | let css = 'body { background-image:url(); }';
110 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
111 | css = 'body { background-image:url( ); }';
112 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
113 | css = 'body { background-image:url(\n); }';
114 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
115 | css = 'body { background-image:url(\'\'); }';
116 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
117 | css = 'body { background-image:url(\' \'); }';
118 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
119 | css = 'body { background-image:url(""); }';
120 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
121 | css = 'body { background-image:url(" "); }';
122 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
123 | });
124 |
125 | it("Rooted url isn't changed", function() {
126 | let css = 'body { background-image:url(/bg.jpg); }';
127 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
128 | css = 'body { background-image:url(/a/b/bg.jpg); }';
129 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
130 | });
131 |
132 | it("Doesn't break inline SVG", function() {
133 | const css = "body { background-image:url('data:image/svg+xml;charset=utf-8,'); }";
134 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
135 | });
136 | it("Doesn't break inline SVG with HTML comment", function() {
137 | const css = "body { background-image:url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3C!--%20Comment%20--%3E%0A%3Csvg%3E%3C%2Fsvg%3E%0A'); }";
138 | expect(fixupCSSUrls('foo/bar', css)).toBe(css);
139 | });
140 | });
141 |
--------------------------------------------------------------------------------
/lib/build/dependency-inclusion.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const SourceInclusion = require('./source-inclusion').SourceInclusion;
3 | const { minimatch } = require('minimatch');
4 | const Utils = require('./utils');
5 | const logger = require('aurelia-logging').getLogger('DependencyInclusion');
6 |
7 | const knownNonJsExtensions = ['.json', '.css', '.svg', '.html'];
8 |
9 | exports.DependencyInclusion = class {
10 | constructor(bundle, description) {
11 | this.bundle = bundle;
12 | this.description = description;
13 | this.mainTraced = false;
14 | }
15 |
16 | traceMain() {
17 | if (this.mainTraced) return Promise.resolve();
18 |
19 | this.mainTraced = true;
20 | let mainId = this.description.mainId;
21 | let ext = path.extname(mainId).toLowerCase();
22 | let mainIsJs = !ext || knownNonJsExtensions.indexOf(ext) === -1;
23 |
24 | if (mainIsJs || ext === path.extname(this.description.name).toLowerCase()) {
25 | // only create alias when main is js file
26 | // or package name shares same extension (like normalize.css with main file normalize.css)
27 | this.bundle.addAlias(this.description.name, mainId);
28 | }
29 |
30 | let main = this.description.loaderConfig.main;
31 | if (mainIsJs && Utils.knownExtensions.indexOf(ext) === -1) {
32 | main += '.js';
33 | }
34 |
35 | return this._tracePattern(main);
36 | }
37 |
38 | traceResources() {
39 | let work = Promise.resolve();
40 | // when user import from 'lodash/map',
41 | // only bundle node_modules/lodash/map.js,
42 | // without bundle node_modules/lodash/lodash.js,
43 | // which in addition trace and bundle everything.
44 | if (!this.description.loaderConfig.lazyMain) {
45 | work = work.then(() => this.traceMain());
46 | }
47 |
48 | let loaderConfig = this.description.loaderConfig;
49 | let resources = loaderConfig.resources;
50 |
51 | if (resources) {
52 | resources.forEach(x => {
53 | work = work.then(() => this._tracePattern(x));
54 | });
55 | }
56 |
57 | return work;
58 | }
59 |
60 | traceResource(resource) {
61 | let resolved = resolvedResource(resource, this.description, this._getProjectRoot());
62 |
63 | if (!resolved) {
64 | logger.error(`Error: could not find "${resource}" in package ${this.description.name}`);
65 | return Promise.resolve();
66 | }
67 |
68 | if (Utils.removeJsExtension(resolved) !== Utils.removeJsExtension(resource)) {
69 | // alias bootstrap/css/bootstrap.css to bootstrap/lib/css/bootstrap.css
70 | this.bundle.addAlias(
71 | this.description.name + '/' + Utils.removeJsExtension(resource),
72 | this.description.name + '/' + Utils.removeJsExtension(resolved)
73 | );
74 | }
75 |
76 | let covered = this.bundle.includes.find(inclusion =>
77 | inclusion.includedBy === this &&
78 | minimatch(resolved, inclusion.pattern)
79 | );
80 |
81 | if (covered) {
82 | return Promise.resolve();
83 | }
84 |
85 | return this._tracePattern(resolved);
86 | }
87 |
88 | _tracePattern(resource) {
89 | let loaderConfig = this.description.loaderConfig;
90 | let bundle = this.bundle;
91 | let pattern = path.join(loaderConfig.path, resource);
92 | let inclusion = new SourceInclusion(bundle, pattern, this);
93 | let promise = inclusion.addAllMatchingResources();
94 | bundle.includes.push(inclusion);
95 | bundle.requiresBuild = true;
96 | return promise;
97 | }
98 |
99 | // If all resources has same prefix like dist/type,
100 | // create conventional aliases like:
101 | // define('package/foo/bar', ['package/dist/type/foo/bar'], function(m) {return m;});
102 | conventionalAliases() {
103 | let ids = [];
104 | this.bundle.includes.forEach(inclusion => {
105 | if (inclusion.includedBy === this) {
106 | ids.push.apply(ids, inclusion.getAllModuleIds());
107 | }
108 | });
109 |
110 | if (ids.length < 2) return {};
111 |
112 | let nameLength = this.description.name.length;
113 |
114 | let commonLen = commonLength(ids);
115 | if (!commonLen || commonLen <= nameLength + 1 ) return {};
116 |
117 | let aliases = {};
118 | ids.forEach(id => {
119 | // for aurelia-templating-resources/dist/commonjs/if
120 | // compact name is aurelia-templating-resources/if
121 | let compactResource = id.slice(commonLen);
122 | if (compactResource) {
123 | let compactId = this.description.name + '/' + compactResource;
124 | aliases[compactId] = id;
125 | }
126 | });
127 |
128 | return aliases;
129 | }
130 |
131 | trySubsume() {
132 | return false;
133 | }
134 |
135 | getAllModuleIds() {
136 | // placeholder
137 | // all module ids are provided by source inclusion, including main module
138 | return [];
139 | }
140 |
141 | getAllFiles() {
142 | return []; // return this.items;
143 | }
144 |
145 | _getProjectRoot() {
146 | return this.bundle.bundler.project.paths.root;
147 | }
148 | };
149 |
150 | function resolvedResource(resource, description, projectRoot) {
151 | const base = path.resolve(projectRoot, description.loaderConfig.path);
152 | let mainShift = description.loaderConfig.main.split('/');
153 |
154 | // when mainShift is [dist,commonjs]
155 | // try dist/commonjs/resource first
156 | // then dist/resource
157 | // then resource
158 | let resolved;
159 |
160 | do {
161 | mainShift.pop();
162 | let res;
163 | if (mainShift.length) {
164 | res = mainShift.join('/') + '/' + resource;
165 | } else {
166 | res = resource;
167 | }
168 |
169 | resolved = validResource(res, base);
170 | if (resolved) break;
171 | } while (mainShift.length);
172 |
173 | return resolved;
174 | }
175 |
176 | function validResource(resource, base) {
177 | const resourcePath = path.resolve(base, resource);
178 | const loaded = Utils.nodejsLoad(resourcePath);
179 | if (loaded) return path.relative(base, loaded).replace(/\\/g, '/');
180 | }
181 |
182 | function commonLength(ids) {
183 | let parts = ids[0].split('/');
184 | let rest = ids.slice(1);
185 | parts.pop(); // ignore last part
186 |
187 | let common = '';
188 |
189 | for (let i = 0, len = parts.length; i < len; i++) {
190 | let all = common + parts[i] + '/';
191 | if (rest.every(id => id.startsWith(all))) {
192 | common = all;
193 | } else {
194 | break;
195 | }
196 | }
197 |
198 | return common.length;
199 | }
200 |
--------------------------------------------------------------------------------
/spec/lib/file-system.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../mocks/mock-fs');
2 |
3 | const ERROR_CODES = {
4 | ENOENT: 'ENOENT',
5 | EEXIST: 'EEXIST'
6 | };
7 |
8 | describe('The file-system module', () => {
9 | let path;
10 | let fs;
11 |
12 | let readDir;
13 | let readFile;
14 | let writeDir;
15 | let writeFile;
16 |
17 | beforeEach(() => {
18 | path = require('path');
19 | fs = require('../../lib/file-system');
20 |
21 | readDir = 'read';
22 | readFile = {
23 | name: 'read.js',
24 | content: 'content'
25 | };
26 | readFile.path = path.join(readDir, readFile.name);
27 |
28 | writeDir = 'write';
29 | writeFile = {
30 | name: 'write.js',
31 | content: 'content'
32 | };
33 | writeFile.path = path.join(writeDir, writeFile.name);
34 |
35 | const config = {};
36 | config[readFile.path] = readFile.content;
37 |
38 | mockfs(config);
39 | });
40 |
41 | afterEach(() => {
42 | mockfs.restore();
43 | });
44 |
45 | describe('The isFile function', () => {
46 | it('returns true for file', () => {
47 | expect(fs.isFile(readFile.path)).toBeTruthy();
48 | });
49 |
50 | it('returns false for directory', () => {
51 | expect(fs.isFile(readDir)).toBeFalsy();
52 | });
53 |
54 | it('returns false for non-existing file', () => {
55 | expect(fs.isFile(path.join(readDir, 'non-existing'))).toBeFalsy();
56 | });
57 | });
58 |
59 | describe('The isDirectory function', () => {
60 | it('returns false for file', () => {
61 | expect(fs.isDirectory(readFile.path)).toBeFalsy();
62 | });
63 |
64 | it('returns true for directory', () => {
65 | expect(fs.isDirectory(readDir)).toBeTruthy();
66 | });
67 |
68 | it('returns false for non-existing file', () => {
69 | expect(fs.isDirectory(path.join(readDir, 'non-existing'))).toBeFalsy();
70 | });
71 | });
72 |
73 | describe('The stat() function', () => {
74 | it('reads the stats for a directory', done => {
75 | fs.stat(readDir).then(stats => {
76 | expect(stats).toBeDefined();
77 | }).catch(fail).then(done);
78 | });
79 |
80 | it('reads the stats for a file', done => {
81 | fs.stat(readFile.path).then(stats => {
82 | expect(stats).toBeDefined();
83 | }).catch(fail).then(done);
84 | });
85 |
86 | it('rejects with an ENOENT error on a non-existing directory', done => {
87 | fs.stat(writeDir).then(() => {
88 | fail('expected promise to be rejected');
89 | }).catch(e => {
90 | expect(e.code).toBe(ERROR_CODES.ENOENT);
91 | }).then(done);
92 | });
93 |
94 | it('rejects with an ENOENT error on a non-existing file', done => {
95 | fs.stat(writeFile.path).then(() => {
96 | fail('expected promise to be rejected');
97 | }).catch(e => {
98 | expect(e.code).toBe(ERROR_CODES.ENOENT);
99 | }).then(done);
100 | });
101 | });
102 |
103 | describe('The readdir() function', () => {
104 | it('reads a directory', done => {
105 | fs.readdir(readDir).then(files => {
106 | expect(files).toEqual([readFile.name]);
107 | }).catch(fail).then(done);
108 | });
109 |
110 | it('rejects with ENOENT', done => {
111 | fs.readdir(writeDir).then(() => {
112 | fail('expected promise to be rejected');
113 | }).catch(e => {
114 | expect(e.code).toBe(ERROR_CODES.ENOENT);
115 | }).then(done);
116 | });
117 | });
118 |
119 | describe('The mkdir() function', () => {
120 | it('makes a directory', done => {
121 | fs.mkdir(writeDir)
122 | .catch(fail)
123 | .then(() => fs.readdir(writeDir))
124 | .catch(fail)
125 | .then((files) => {
126 | expect(files.length).toBe(0);
127 | done();
128 | });
129 | });
130 |
131 | it('rejects with EEXIST', done => {
132 | fs.mkdir(readDir)
133 | .then(() => fail('expected promise to be rejected'))
134 | .catch(e => expect(e.code).toBe(ERROR_CODES.EEXIST))
135 | .then(done);
136 | });
137 | });
138 |
139 | describe('The mkdirp() function', () => {
140 | it('makes deep directories', done => {
141 | fs.mkdirp(writeDir + readDir).then(() => {
142 | return fs.readdir(writeDir + readDir);
143 | })
144 | .catch(fail)
145 | .then((files) => {
146 | expect(files.length).toBe(0);
147 | done();
148 | });
149 | });
150 | });
151 |
152 | describe('The readFile() function', () => {
153 | it('returns a promise resolving to the files content', done => {
154 | fs.readFile(readFile.path).then(content => {
155 | expect(content).toBe(readFile.content);
156 | }).catch(fail).then(done);
157 | });
158 |
159 | it('returns a promise resolving to raw buffer of the files content when encoding is null', done => {
160 | fs.readFile(readFile.path, null).then(buf => {
161 | expect(Buffer.isBuffer(buf)).toBe(true);
162 | expect(buf.toString('utf8')).toBe(readFile.content);
163 | }).catch(fail).then(done);
164 | });
165 |
166 | it('rejects with ENOENT error', done => {
167 | fs.readFile(writeFile.path).then(() => {
168 | fail('expected promise to be rejected');
169 | }).catch(e => {
170 | expect(e.code).toBe(ERROR_CODES.ENOENT);
171 | done();
172 | });
173 | });
174 | });
175 |
176 | describe('The readFileSync() function', () => {
177 | it('returns the files content', () => {
178 | expect(fs.readFileSync(readFile.path))
179 | .toBe(readFile.content);
180 | });
181 |
182 | it('returns raw buffer of files content when encoding is null', () => {
183 | let buf = fs.readFileSync(readFile.path, null);
184 | expect(Buffer.isBuffer(buf)).toBe(true);
185 | expect(buf.toString('utf8')).toBe(readFile.content);
186 | });
187 |
188 | it('throws an ENOENT error', () => {
189 | try {
190 | fs.readFileSync(writeFile.path);
191 | fail(`expected fs.readFileSync('${writeFile.path}') to throw`);
192 | } catch (e) {
193 | expect(e.code).toBe(ERROR_CODES.ENOENT);
194 | }
195 | });
196 | });
197 |
198 | describe('The writeFile() function', () => {
199 | it('creates a new file', done => {
200 | fs.writeFile(writeFile.path, writeFile.content).then(() => {
201 | return fs.readFile(writeFile.path);
202 | }).then(content => {
203 | expect(content).toBe(writeFile.content);
204 | done();
205 | });
206 | });
207 | });
208 | });
209 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/write/defines.js:
--------------------------------------------------------------------------------
1 | var lang = require('../lib/lang'),
2 | parse = require('../lib/parse'),
3 | transform = require('../lib/transform'),
4 | falseProp = lang.falseProp,
5 | getOwn = lang.getOwn,
6 | makeJsArrayString = lang.makeJsArrayString;
7 |
8 | // options should include skipModuleInsertion and tracing for transform?
9 |
10 | /**
11 | * For modules that are inside a package config, this transform will write out
12 | * adapter define() entries for the package manin value, so that package config
13 | * is not needed to map 'packageName' to 'packageName/mainModuleId'.
14 | * @param {Object} options object for holding options. Supported options:
15 | * - wrapShim: if shim config is used for the module ID, then wrap it in a
16 | * function closure. This can be hazardous if the scripts assumes acces to
17 | * other variables at the top level. However, wrapping can be useful for shimmed
18 | * IDs that have dependencies, and where those dependencies may not be
19 | * immediately available or inlined with this shimmed script.
20 | * @return {Function} A function that can be used for multiple content transform
21 | * calls.
22 | */
23 | function defines(options) {
24 | options = options || {};
25 |
26 | return function(context, moduleName, filePath, _contents) {
27 | var namedModule,
28 | config = context.config,
29 | packageName = context.pkgsMainMap[moduleName];
30 |
31 | if (packageName === 'moment') {
32 | // Expose moment to global var to improve compatibility with some legacy libs.
33 | // It also load momentjs up immediately.
34 | _contents = _contents.replace(/\bdefine\((\w+)\)/, (match, factoryName) =>
35 | `(function(){var m=${factoryName}();if(typeof moment === 'undefined'){window.moment=m;} define(function(){return m;})})()`
36 | );
37 | }
38 |
39 | function onFound(info) {
40 | if (info.foundId) {
41 | namedModule = info.foundId;
42 | }
43 | }
44 |
45 | let contents = toTransport(context, moduleName,
46 | filePath, _contents, onFound, options);
47 |
48 | //Some files may not have declared a require module, and if so,
49 | //put in a placeholder call so the require does not try to load them
50 | //after the module is processed.
51 | //If we have a name, but no defined module, then add in the placeholder.
52 | if (moduleName) {
53 | var shim = config.shim && (getOwn(config.shim, moduleName) ||
54 | (packageName && getOwn(config.shim, packageName)));
55 |
56 |
57 | let amdProps = parse.usesAmdOrRequireJs(filePath, contents);
58 | // mimic requirejs runtime behaviour,
59 | // if no module defined, add an empty shim
60 | if (!shim && contents === _contents) {
61 | if (!amdProps || !amdProps.define) {
62 | shim = { deps: [] };
63 | }
64 | } else if (shim && amdProps && amdProps.define) {
65 | // ignore shim for AMD/UMD
66 | shim = null;
67 | }
68 |
69 | if (shim) {
70 | if (options.wrapShim) {
71 | contents = '(function(root) {\n' +
72 | 'define("' + moduleName + '", ' +
73 | (shim.deps && shim.deps.length ?
74 | makeJsArrayString(shim.deps) + ', ' : '[], ') +
75 | 'function() {\n' +
76 | ' return (function() {\n' +
77 | contents +
78 | // Start with a \n in case last line is a comment
79 | // in the contents, like a sourceURL comment.
80 | '\n' + exportsFn(shim.exports, true) +
81 | '\n' +
82 | ' }).apply(root, arguments);\n' +
83 | '});\n' +
84 | '}(this));\n';
85 | } else {
86 | contents += '\n' + 'define("' + moduleName + '", ' +
87 | (shim.deps && shim.deps.length ?
88 | makeJsArrayString(shim.deps) + ', ' : '') +
89 | exportsFn(shim.exports) +
90 | ');\n';
91 | }
92 | } else {
93 | // we don't need placeholder in aurelia-cli bundle
94 | // contents += '\n' + 'define("' + moduleName + '", function(){});\n';
95 |
96 | if (packageName && namedModule && namedModule !== packageName) {
97 | // for main module, if named module name doesn't match package name
98 | // make an alias from moduleName (not packageName) to namedModule
99 | contents += '\n;define("' + moduleName + '", ["' + namedModule + '"], function(m){return m;});\n';
100 | }
101 | }
102 | }
103 |
104 | return contents;
105 | };
106 | }
107 |
108 | function exportsFn(_exports, wrapShim) {
109 | if (_exports) {
110 | if (wrapShim) return 'return root.' + _exports + ' = ' + _exports +';';
111 | else return '(function (global) {\n' +
112 | ' return function () {\n' +
113 | ' return global.' + _exports + ';\n' +
114 | ' };\n' +
115 | '}(this))';
116 | } else {
117 | if (wrapShim) return '';
118 | return 'function(){}';
119 | }
120 | }
121 |
122 | /**
123 | * Modifies a define() call to make sure it has a module ID as the first
124 | * argument and to make sure the sugared define(function(require), {}) syntax
125 | * has an array of dependencies extracted and explicitly passed in, so that the
126 | * define() call works in JS environments that do not give the full function
127 | * bodies for Function.protototype.toString calls.
128 | * @param {Object} context loader context
129 | * @param {String} moduleName module ID
130 | * @param {String} filePath file path
131 | * @param {String} contents contents of the file for the given module ID.
132 | * @param {Object} options object for holding options. Supported options:
133 | * - logger: Object of logging functions. Currently only logger.warn is used
134 | * if a module output for an ID cannot be properly normalized for string
135 | * transport.
136 | * @return {String} transformed content. May not be different from
137 | * the input contents string.
138 | */
139 | function toTransport(context, moduleName,
140 | filePath, contents, onFound, options) {
141 | options = options || {};
142 | return transform.toTransport('', moduleName, filePath,
143 | contents, onFound, options);
144 | };
145 |
146 | module.exports = defines;
147 |
--------------------------------------------------------------------------------
/lib/build/utils.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const crypto = require('crypto');
3 | const fs = require('../file-system');
4 | const tmpDir = require('os').tmpdir();
5 |
6 | exports.knownExtensions = ['.js', '.cjs', '.mjs', '.json', '.css', '.svg', '.html'];
7 |
8 | exports.couldMissGulpPreprocess = function(id) {
9 | const ext = path.extname(id).toLowerCase();
10 | return ext && ext !== '.js' && ext !== '.html' && ext !== '.css';
11 | };
12 |
13 | function getPackagePaths() {
14 | // require.resolve(packageName) cannot resolve package has no main.
15 | // for instance: font-awesome v4.7.0
16 | // manually try resolve paths
17 | return [
18 | // normal search from cli
19 | ...require.resolve.paths('not-core/'),
20 | // additional search from app's folder, this is necessary to support
21 | // lerna hoisting where cli is out of app's local node_modules folder.
22 | ...require('resolve/lib/node-modules-paths')(process.cwd(), {})
23 | ];
24 | }
25 |
26 | // resolve npm package path
27 | exports.resolvePackagePath = function(packageName) {
28 | const packagePaths = getPackagePaths();
29 | for (let i = 0, len = packagePaths.length; i < len; i++) {
30 | const dirname = path.join(packagePaths[i], packageName);
31 | if (fs.isDirectory(dirname)) return dirname;
32 | }
33 |
34 | throw new Error(`cannot resolve npm package folder for "${packageName}"`);
35 | };
36 |
37 | exports.moduleIdWithPlugin = function(moduleId, pluginName, type) {
38 | switch (type) {
39 | case 'require':
40 | return pluginName + '!' + moduleId;
41 | case 'system':
42 | return moduleId + '!' + pluginName;
43 | default:
44 | throw new Error(`Loader configuration style ${type} is not supported.`);
45 | }
46 | };
47 |
48 | const CACHE_DIR = path.resolve(tmpDir, 'aurelia-cli-cache');
49 | exports.cacheDir = CACHE_DIR;
50 |
51 | function cachedFilePath(hash) {
52 | const folder = hash.slice(0, 2);
53 | const fileName = hash.slice(2);
54 | return path.resolve(CACHE_DIR, folder, fileName);
55 | }
56 |
57 | exports.getCache = function(hash) {
58 | const filePath = cachedFilePath(hash);
59 | try {
60 | return JSON.parse(fs.readFileSync(filePath));
61 | } catch {
62 | // ignore
63 | }
64 | };
65 |
66 | exports.setCache = function(hash, object) {
67 | const filePath = cachedFilePath(hash);
68 | // async write
69 | fs.writeFile(filePath, JSON.stringify(object));
70 | };
71 |
72 | exports.runSequentially = function(tasks, cb) {
73 | let index = -1;
74 | let result = [];
75 |
76 | function exec() {
77 | index ++;
78 |
79 | if (index < tasks.length) {
80 | return cb(tasks[index], index).then(r => result.push(r)).then(exec);
81 | }
82 |
83 | return Promise.resolve();
84 | }
85 |
86 | return exec().then(() => result);
87 | };
88 |
89 | exports.generateHashedPath = function(pth, hash) {
90 | if (arguments.length !== 2) {
91 | throw new Error('`path` and `hash` required');
92 | }
93 |
94 | return modifyFilename(pth, function(filename, ext) {
95 | return filename + '-' + hash + ext;
96 | });
97 | };
98 |
99 | exports.revertHashedPath = function(pth, hash) {
100 | if (arguments.length !== 2) {
101 | throw new Error('`path` and `hash` required');
102 | }
103 |
104 | return modifyFilename(pth, function(filename, ext) {
105 | return filename.replace(new RegExp('-' + hash + '$'), '') + ext;
106 | });
107 | };
108 |
109 | exports.generateHash = function(bufOrStr) {
110 | return crypto.createHash('md5').update(bufOrStr).digest('hex');
111 | };
112 |
113 | exports.escapeForRegex = function(str) {
114 | let matchers = /[|\\{}()[\]^$+*?.]/g;
115 | return str.replace(matchers, '\\$&');
116 | };
117 |
118 | exports.createBundleFileRegex = function(bundleName) {
119 | return new RegExp(exports.escapeForRegex(bundleName) + '[^"\']*?\\.js', 'g');
120 | };
121 |
122 | function modifyFilename(pth, modifier) {
123 | if (arguments.length !== 2) {
124 | throw new Error('`path` and `modifier` required');
125 | }
126 |
127 | if (Array.isArray(pth)) {
128 | return pth.map(function(el) {
129 | return modifyFilename(el, modifier);
130 | });
131 | }
132 |
133 | let ext = path.extname(pth);
134 | return path.posix.join(path.dirname(pth), modifier(path.basename(pth, ext), ext));
135 | }
136 |
137 | // https://nodejs.org/dist/latest-v10.x/docs/api/modules.html
138 | // after "high-level algorithm in pseudocode of what require.resolve() does"
139 | function nodejsLoadAsFile(resourcePath) {
140 | if (fs.isFile(resourcePath)) {
141 | return resourcePath;
142 | }
143 |
144 | if (fs.isFile(resourcePath + '.js')) {
145 | return resourcePath + '.js';
146 | }
147 |
148 | if (fs.isFile(resourcePath + '.json')) {
149 | return resourcePath + '.json';
150 | }
151 | // skip .node file that nobody uses
152 | }
153 |
154 | function nodejsLoadIndex(resourcePath) {
155 | if (!fs.isDirectory(resourcePath)) return;
156 |
157 | const indexJs = path.join(resourcePath, 'index.js');
158 | if (fs.isFile(indexJs)) {
159 | return indexJs;
160 | }
161 |
162 | const indexJson = path.join(resourcePath, 'index.json');
163 | if (fs.isFile(indexJson)) {
164 | return indexJson;
165 | }
166 | // skip index.node file that nobody uses
167 | }
168 |
169 | function nodejsLoadAsDirectory(resourcePath) {
170 | if (!fs.isDirectory(resourcePath)) return;
171 |
172 | const packageJson = path.join(resourcePath, 'package.json');
173 |
174 | if (fs.isFile(packageJson)) {
175 | let metadata;
176 | try {
177 | metadata = JSON.parse(fs.readFileSync(packageJson));
178 | } catch (err) {
179 | console.error(err);
180 | return;
181 | }
182 | let metaMain;
183 | // try 1.browser > 2.module > 3.main
184 | // the order is to target browser.
185 | // when aurelia-cli introduces multi-targets build,
186 | // it probably should use different order for electron app
187 | // for electron 1.module > 2.browser > 3.main
188 | if (typeof metadata.browser === 'string') {
189 | // use package.json browser field if possible.
190 | metaMain = metadata.browser;
191 | } else if (typeof metadata.browser === 'object' && typeof metadata.browser['.'] === 'string') {
192 | // use package.json browser mapping {".": "dist/index.js"} if possible.
193 | metaMain = metadata.browser['.'];
194 | } else if (typeof metadata.module === 'string' &&
195 | !(metadata.name && metadata.name.startsWith('aurelia-'))) {
196 | // prefer es module format over cjs, just like webpack.
197 | // this improves compatibility with TypeScript.
198 | // ignores aurelia-* core npm packages as their module
199 | // field is pointing to es2015 folder.
200 | metaMain = metadata.module;
201 | } else if (typeof metadata.main === 'string') {
202 | metaMain = metadata.main;
203 | }
204 |
205 | let mainFile = metaMain || 'index';
206 | const mainResourcePath = path.resolve(resourcePath, mainFile);
207 | return nodejsLoadAsFile(mainResourcePath) || nodejsLoadIndex(mainResourcePath);
208 | }
209 |
210 | return nodejsLoadIndex(resourcePath);
211 | }
212 |
213 | exports.nodejsLoad = function(resourcePath) {
214 | return nodejsLoadAsFile(resourcePath) || nodejsLoadAsDirectory(resourcePath);
215 | };
216 |
217 | exports.removeJsExtension = function(filePath) {
218 | if (path.extname(filePath).toLowerCase() === '.js') {
219 | return filePath.slice(0, -3);
220 | }
221 |
222 | return filePath;
223 | };
224 |
225 |
--------------------------------------------------------------------------------
/lib/build/package-analyzer.js:
--------------------------------------------------------------------------------
1 | const fs = require('../file-system');
2 | const path = require('path');
3 | const DependencyDescription = require('./dependency-description').DependencyDescription;
4 | const logger = require('aurelia-logging').getLogger('PackageAnalyzer');
5 | const Utils = require('./utils');
6 |
7 | exports.PackageAnalyzer = class {
8 | constructor(project) {
9 | this.project = project;
10 | }
11 |
12 | analyze(packageName) {
13 | let description = new DependencyDescription(packageName, 'npm');
14 |
15 | return loadPackageMetadata(this.project, description)
16 | .then(() => {
17 | if (!description.metadataLocation) {
18 | throw new Error(`Unable to find package metadata (package.json) of ${description.name}`);
19 | }
20 | })
21 | .then(() => determineLoaderConfig(this.project, description))
22 | .then(() => description);
23 | }
24 |
25 | reverseEngineer(loaderConfig) {
26 | loaderConfig = JSON.parse(JSON.stringify(loaderConfig));
27 | let description = new DependencyDescription(loaderConfig.name);
28 | description.loaderConfig = loaderConfig;
29 |
30 | if (!loaderConfig.packageRoot && (!loaderConfig.path || loaderConfig.path.indexOf('node_modules') !== -1)) {
31 | description.source = 'npm';
32 | } else {
33 | description.source = 'custom';
34 | if (!loaderConfig.packageRoot) {
35 | fillUpPackageRoot(this.project, description);
36 | }
37 | }
38 |
39 | return loadPackageMetadata(this.project, description)
40 | .then(() => {
41 | if (!loaderConfig.path) {
42 | // fillup main and path
43 | determineLoaderConfig(this.project, description);
44 | } else {
45 | if (!loaderConfig.main) {
46 | if (description.source === 'custom' && loaderConfig.path === loaderConfig.packageRoot) {
47 | // fillup main and path
48 | determineLoaderConfig(this.project, description);
49 | } else {
50 | const fullPath = path.resolve(this.project.paths.root, loaderConfig.path);
51 | if (fullPath === description.location) {
52 | // fillup main and path
53 | determineLoaderConfig(this.project, description);
54 | return;
55 | }
56 |
57 | // break single path into main and dir
58 | let pathParts = path.parse(fullPath);
59 |
60 | // when path is node_modules/package/foo/bar
61 | // set path to node_modules/package
62 | // set main to foo/bar
63 | loaderConfig.path = path.relative(this.project.paths.root, description.location).replace(/\\/g, '/');
64 |
65 | if (pathParts.dir.length > description.location.length + 1) {
66 | const main = path.join(pathParts.dir.slice(description.location.length + 1), Utils.removeJsExtension(pathParts.base));
67 | loaderConfig.main = main.replace(/\\/g, '/');
68 | } else if (pathParts.dir.length === description.location.length) {
69 | loaderConfig.main = Utils.removeJsExtension(pathParts.base).replace(/\\/g, '/');
70 | } else {
71 | throw new Error(`Path: "${loaderConfig.path}" is not in: ${description.location}`);
72 | }
73 | }
74 | } else {
75 | loaderConfig.main = Utils.removeJsExtension(loaderConfig.main).replace(/\\/g, '/');
76 | }
77 | }
78 | })
79 | .then(() => description);
80 | }
81 | };
82 |
83 | function fillUpPackageRoot(project, description) {
84 | let _path = description.loaderConfig.path;
85 |
86 | let ext = path.extname(_path).toLowerCase();
87 | if (!ext || Utils.knownExtensions.indexOf(ext) === -1) {
88 | // main file could be non-js file like css/font-awesome.css
89 | _path += '.js';
90 | }
91 |
92 | if (fs.isFile(path.resolve(project.paths.root, _path))) {
93 | description.loaderConfig.packageRoot = path.dirname(description.loaderConfig.path).replace(/\\/g, '/');
94 | }
95 |
96 | if (!description.loaderConfig.packageRoot) {
97 | description.loaderConfig.packageRoot = description.loaderConfig.path;
98 | }
99 | }
100 |
101 | function loadPackageMetadata(project, description) {
102 | return setLocation(project, description)
103 | .then(() => {
104 | if (description.metadataLocation) {
105 | return fs.readFile(description.metadataLocation).then(data => {
106 | description.metadata = JSON.parse(data.toString());
107 | });
108 | }
109 | })
110 | .catch(e => {
111 | logger.error(`Unable to load package metadata (package.json) of ${description.name}:`);
112 | logger.info(e);
113 | });
114 | }
115 |
116 | // loaderConfig.path is simplified when use didn't provide explicit config.
117 | // In auto traced nodejs package, loaderConfig.path always matches description.location.
118 | // We then use auto-generated moduleId aliases in dependency-inclusion to make AMD
119 | // module system happy.
120 | function determineLoaderConfig(project, description) {
121 | let location = path.resolve(description.location);
122 | let mainPath = Utils.nodejsLoad(location);
123 |
124 | if (!description.loaderConfig) {
125 | description.loaderConfig = {name: description.name};
126 | }
127 |
128 | description.loaderConfig.path = path.relative(project.paths.root, description.location).replace(/\\/g, '/');
129 |
130 | if (mainPath) {
131 | description.loaderConfig.main = Utils.removeJsExtension(mainPath.slice(location.length + 1).replace(/\\/g, '/'));
132 | } else {
133 | logger.warn(`The "${description.name}" package has no valid main file, fall back to index.js.`);
134 | description.loaderConfig.main = 'index';
135 | }
136 | }
137 |
138 | function setLocation(project, description) {
139 | switch (description.source) {
140 | case 'npm':
141 | return getPackageFolder(project, description)
142 | .then(packageFolder => {
143 | description.location = packageFolder;
144 |
145 | return tryFindMetadata(project, description);
146 | });
147 | case 'custom':
148 | description.location = path.resolve(project.paths.root, description.loaderConfig.packageRoot);
149 |
150 | return tryFindMetadata(project, description);
151 | default:
152 | return Promise.reject(`The package source "${description.source}" is not supported.`);
153 | }
154 | }
155 |
156 | function tryFindMetadata(project, description) {
157 | return fs.stat(path.join(description.location, 'package.json'))
158 | .then(() => description.metadataLocation = path.join(description.location, 'package.json'))
159 | .catch(() => {});
160 | }
161 |
162 | function getPackageFolder(project, description) {
163 | if (!description.loaderConfig || !description.loaderConfig.path) {
164 | return new Promise(resolve => {
165 | resolve(Utils.resolvePackagePath(description.name));
166 | });
167 | }
168 |
169 | return lookupPackageFolderRelativeStrategy(project.paths.root, description.loaderConfig.path);
170 | }
171 |
172 | // Looks for the node_modules folder from the root path of aurelia
173 | // with the defined loaderConfig.
174 | function lookupPackageFolderRelativeStrategy(root, relativePath) {
175 | let pathParts = relativePath.replace(/\\/g, '/').split('/');
176 | let packageFolder = '';
177 | let stopOnNext = false;
178 |
179 | for (let i = 0; i < pathParts.length; ++i) {
180 | let part = pathParts[i];
181 |
182 | packageFolder = path.join(packageFolder, part);
183 |
184 | if (stopOnNext && !part.startsWith('@')) {
185 | break;
186 | } else if (part === 'node_modules') {
187 | stopOnNext = true;
188 | }
189 | }
190 |
191 | return Promise.resolve(path.resolve(root, packageFolder));
192 | }
193 |
--------------------------------------------------------------------------------
/spec/lib/cli.spec.js:
--------------------------------------------------------------------------------
1 | const mockfs = require('../mocks/mock-fs');
2 |
3 | describe('The cli', () => {
4 | let fs;
5 | let path;
6 | let cli;
7 | let Project;
8 | let project;
9 |
10 | let dir;
11 | let aureliaProject;
12 |
13 | beforeEach(() => {
14 | fs = require('../../lib/file-system');
15 | path = require('path');
16 | cli = new (require('../../lib/cli').CLI)();
17 | Project = require('../../lib/project').Project;
18 | project = {};
19 |
20 | dir = 'workspaces';
21 | aureliaProject = 'aurelia_project';
22 | const fsConfig = {};
23 | fsConfig[dir] = {};
24 | fsConfig['package.json'] = '{"version": "1.0.0"}';
25 | mockfs(fsConfig);
26 | });
27 |
28 | afterEach(() => {
29 | mockfs.restore();
30 | });
31 |
32 | describe('The _establishProject() function', () => {
33 | let establish;
34 |
35 | beforeEach(() => {
36 | establish = spyOn(Project, 'establish').and.returnValue(project);
37 | });
38 |
39 | it('resolves to nothing', done => {
40 | cli._establishProject({})
41 | .then(proj => {
42 | expect(proj).not.toBeDefined();
43 | })
44 | .catch(fail).then(done);
45 | });
46 |
47 | it('calls and resolves to Project.establish()', done => {
48 | fs.mkdirp(path.join(process.cwd(), aureliaProject))
49 | .then(() => cli._establishProject({
50 | runningLocally: true
51 | }))
52 | .then(proj => {
53 | expect(Project.establish)
54 | .toHaveBeenCalledWith(path.join(process.cwd()));
55 | expect(proj).toBe(proj);
56 | })
57 | .catch(fail).then(done);
58 | });
59 |
60 | it('does not catch Project.establish()', done => {
61 | establish.and.callFake(() => new Promise((resolve, reject) => reject()));
62 |
63 | fs.mkdirp(path.join(process.cwd(), aureliaProject))
64 | .then(() => cli._establishProject({
65 | runningLocally: true
66 | }))
67 | .then(() => {
68 | fail('expected promise to be rejected.');
69 | done();
70 | })
71 | .catch(done);
72 | });
73 |
74 | it('logs \'No Aurelia project found.\'', done => {
75 | spyOn(cli.ui, 'log');
76 | cli._establishProject({
77 | runningLocally: true
78 | }).then(() => {
79 | expect(cli.ui.log).toHaveBeenCalledWith('No Aurelia project found.');
80 | }).catch(fail).then(done);
81 | });
82 | });
83 |
84 | describe('The createHelpCommand() function', () => {
85 | it('gets the help command', () => {
86 | mockfs({
87 | 'lib/commands/help/command.js': 'module.exports = {}',
88 | 'lib/string.js': 'module.exports = {}'
89 | });
90 |
91 | spyOn(cli.container, 'get');
92 |
93 | cli.createHelpCommand();
94 | expect(cli.container.get)
95 | .toHaveBeenCalledWith(require('../../lib/commands/help/command'));
96 | });
97 | });
98 |
99 | describe('The configureContainer() function', () => {
100 | it('registers the instances', () => {
101 | const registerInstanceSpy = spyOn(cli.container, 'registerInstance');
102 |
103 | cli.configureContainer();
104 |
105 | expect(registerInstanceSpy.calls.count()).toBe(2);
106 | });
107 | });
108 |
109 | describe('The run() function', () => {
110 | function getVersionSpec(command) {
111 | return () => {
112 | beforeEach(() => {
113 | spyOn(cli.ui, 'log')
114 | .and.callFake(() => new Promise(resolve => resolve()));
115 | });
116 |
117 | // Without real mockfs, it doesn't require the mocked package.json.
118 | xit('logs the cli version', () => {
119 | console.log('cwd', process.cwd());
120 | cli.run(command);
121 | expect(cli.ui.log).toHaveBeenCalledWith('Local aurelia-cli v1.0.0');
122 | });
123 |
124 | it('returns an empty promise', done => {
125 | cli.run(command).then(resolved => {
126 | expect(resolved).not.toBeDefined();
127 | }).catch(fail).then(done);
128 | });
129 | };
130 | }
131 |
132 | describe('The --version arg', getVersionSpec('--version'));
133 |
134 | describe('The -v arg', getVersionSpec('-v'));
135 |
136 | it('uses the _establishProject() function', done => {
137 | // const project = {};
138 | spyOn(cli, '_establishProject').and.returnValue(new Promise(resolve => {
139 | resolve(project);
140 | }));
141 | spyOn(cli.container, 'registerInstance');
142 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve({ execute: () => {} }));
143 |
144 | cli.run()
145 | .then(() => {
146 | expect(cli._establishProject).toHaveBeenCalled();
147 | }).catch(fail).then(done);
148 | });
149 |
150 | it('registers the project instance', done => {
151 | cli.options.runningLocally = true;
152 |
153 | spyOn(cli, '_establishProject').and.returnValue(new Promise(resolve => {
154 | resolve(project);
155 | }));
156 |
157 | spyOn(cli.container, 'registerInstance');
158 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve({ execute: () => {} }));
159 |
160 | cli.run().then(() => {
161 | expect(cli.container.registerInstance)
162 | .toHaveBeenCalledWith(Project, project);
163 | }).catch(fail).then(done);
164 | });
165 |
166 | it('creates the command', done => {
167 | const command = 'run';
168 | const args = {};
169 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve({ execute: () => {} }));
170 |
171 | cli.run(command, args).then(() => {
172 | expect(cli.createCommand).toHaveBeenCalledWith(command, args);
173 | }).catch(fail).then(done);
174 | });
175 |
176 | it('executes the command', done => {
177 | const command = {
178 | execute: jasmine.createSpy('execute').and.returnValue(Promise.resolve({}))
179 | };
180 | const args = {};
181 | spyOn(cli, '_establishProject').and.returnValue(Promise.resolve(project));
182 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve(command));
183 |
184 | cli.run('run', args).then(() => {
185 | expect(command.execute).toHaveBeenCalledWith(args);
186 | }).catch(fail).then(done);
187 | });
188 |
189 | it('fails gracefully when Aurelia-CLI is ran from a root folder (non-project directory)', done => {
190 | cli.options.runningLocally = true;
191 | const command = {
192 | execute: jasmine.createSpy('execute').and.returnValue(Promise.resolve({}))
193 | };
194 | const args = {};
195 | spyOn(cli, '_establishProject').and.returnValue(Promise.resolve(null)); // no project could be found
196 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve(command));
197 | const errorSpy = spyOn(cli.ui, 'log');
198 |
199 | cli.run('', args).then(() => {
200 | expect(command.execute).not.toHaveBeenCalledWith(args);
201 | expect(errorSpy).toHaveBeenCalledTimes(2);
202 | expect(errorSpy.calls.first().args[0]).toContain('Local aurelia-cli');
203 | expect(errorSpy.calls.argsFor(1)[0]).toContain('It appears that the Aurelia CLI is running locally');
204 | }).catch(fail).then(done);
205 | });
206 | });
207 |
208 | describe('The config command', () => {
209 | it('creates the command', done => {
210 | const command = 'config';
211 | const args = {};
212 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve({ execute: () => {} }));
213 |
214 | cli.run(command, args).then(() => {
215 | expect(cli.createCommand).toHaveBeenCalledWith(command, args);
216 | }).catch(fail).then(done);
217 | });
218 |
219 | it('executes the command', done => {
220 | const command = {
221 | execute: jasmine.createSpy('execute').and.returnValue(Promise.resolve({}))
222 | };
223 | const args = {};
224 | spyOn(cli, '_establishProject').and.returnValue(new Promise(resolve =>
225 | resolve(project)
226 | ));
227 | spyOn(cli, 'createCommand').and.returnValue(Promise.resolve(command));
228 |
229 | cli.run('config', args).then(() => {
230 | expect(command.execute).toHaveBeenCalledWith(args);
231 | }).catch(fail).then(done);
232 | });
233 | });
234 | });
235 |
--------------------------------------------------------------------------------
/lib/build/amodro-trace/lib/lang.js:
--------------------------------------------------------------------------------
1 | var define = function(fn) { module.exports = fn(); };
2 |
3 | /**
4 | * @license Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
5 | * Available via the MIT or new BSD license.
6 | * see: http://github.com/jrburke/requirejs for details
7 | */
8 |
9 | /*jslint plusplus: true */
10 | /*global define, java */
11 |
12 | define(function () {
13 | 'use strict';
14 |
15 | var lang, isJavaObj,
16 | hasOwn = Object.prototype.hasOwnProperty;
17 |
18 | function hasProp(obj, prop) {
19 | return hasOwn.call(obj, prop);
20 | }
21 |
22 | isJavaObj = function () {
23 | return false;
24 | };
25 |
26 | //Rhino, but not Nashorn (detected by importPackage not existing)
27 | //Can have some strange foreign objects.
28 | if (typeof java !== 'undefined' && java.lang && java.lang.Object && typeof importPackage !== 'undefined') {
29 | isJavaObj = function (obj) {
30 | return obj instanceof java.lang.Object;
31 | };
32 | }
33 |
34 | lang = {
35 | // makeJsArrayString added after porting to this project
36 | //Converts an JS array of strings to a string representation.
37 | //Not using JSON.stringify() for Rhino's sake.
38 | makeJsArrayString: function (ary) {
39 | return '["' + ary.map(function (item) {
40 | //Escape any double quotes, backslashes
41 | return lang.jsEscape(item);
42 | }).join('","') + '"]';
43 | },
44 |
45 | backSlashRegExp: /\\/g,
46 | ostring: Object.prototype.toString,
47 |
48 | isArray: Array.isArray || function (it) {
49 | return lang.ostring.call(it) === "[object Array]";
50 | },
51 |
52 | isFunction: function(it) {
53 | return lang.ostring.call(it) === "[object Function]";
54 | },
55 |
56 | isRegExp: function(it) {
57 | return it && it instanceof RegExp;
58 | },
59 |
60 | hasProp: hasProp,
61 |
62 | //returns true if the object does not have an own property prop,
63 | //or if it does, it is a falsy value.
64 | falseProp: function (obj, prop) {
65 | return !hasProp(obj, prop) || !obj[prop];
66 | },
67 |
68 | //gets own property value for given prop on object
69 | getOwn: function (obj, prop) {
70 | return hasProp(obj, prop) && obj[prop];
71 | },
72 |
73 | _mixin: function(dest, source, override){
74 | var name;
75 | for (name in source) {
76 | if(source.hasOwnProperty(name) &&
77 | (override || !dest.hasOwnProperty(name))) {
78 | dest[name] = source[name];
79 | }
80 | }
81 |
82 | return dest; // Object
83 | },
84 |
85 | /**
86 | * mixin({}, obj1, obj2) is allowed. If the last argument is a boolean,
87 | * then the source objects properties are force copied over to dest.
88 | */
89 | mixin: function(dest){
90 | var parameters = Array.prototype.slice.call(arguments),
91 | override, i, l;
92 |
93 | if (!dest) { dest = {}; }
94 |
95 | if (parameters.length > 2 && typeof arguments[parameters.length-1] === 'boolean') {
96 | override = parameters.pop();
97 | }
98 |
99 | for (i = 1, l = parameters.length; i < l; i++) {
100 | lang._mixin(dest, parameters[i], override);
101 | }
102 | return dest; // Object
103 | },
104 |
105 | /**
106 | * Does a deep mix of source into dest, where source values override
107 | * dest values if a winner is needed.
108 | * @param {Object} dest destination object that receives the mixed
109 | * values.
110 | * @param {Object} source source object contributing properties to mix
111 | * in.
112 | * @return {[Object]} returns dest object with the modification.
113 | */
114 | deepMix: function(dest, source) {
115 | lang.eachProp(source, function (value, prop) {
116 | if (typeof value === 'object' && value &&
117 | !lang.isArray(value) && !lang.isFunction(value) &&
118 | !(value instanceof RegExp)) {
119 |
120 | if (!dest[prop]) {
121 | dest[prop] = {};
122 | }
123 | lang.deepMix(dest[prop], value);
124 | } else {
125 | dest[prop] = value;
126 | }
127 | });
128 | return dest;
129 | },
130 |
131 | /**
132 | * Does a type of deep copy. Do not give it anything fancy, best
133 | * for basic object copies of objects that also work well as
134 | * JSON-serialized things, or has properties pointing to functions.
135 | * For non-array/object values, just returns the same object.
136 | * @param {Object} obj copy properties from this object
137 | * @param {Object} [result] optional result object to use
138 | * @return {Object}
139 | */
140 | deeplikeCopy: function (obj) {
141 | var type, result;
142 |
143 | if (lang.isArray(obj)) {
144 | result = [];
145 | obj.forEach(function(value) {
146 | result.push(lang.deeplikeCopy(value));
147 | });
148 | return result;
149 | }
150 |
151 | type = typeof obj;
152 | if (obj === null || obj === undefined || type === 'boolean' ||
153 | type === 'string' || type === 'number' || lang.isFunction(obj) ||
154 | lang.isRegExp(obj)|| isJavaObj(obj)) {
155 | return obj;
156 | }
157 |
158 | //Anything else is an object, hopefully.
159 | result = {};
160 | lang.eachProp(obj, function(value, key) {
161 | result[key] = lang.deeplikeCopy(value);
162 | });
163 | return result;
164 | },
165 |
166 | delegate: (function () {
167 | // boodman/crockford delegation w/ cornford optimization
168 | function TMP() {}
169 | return function (obj, props) {
170 | TMP.prototype = obj;
171 | var tmp = new TMP();
172 | TMP.prototype = null;
173 | if (props) {
174 | lang.mixin(tmp, props);
175 | }
176 | return tmp; // Object
177 | };
178 | }()),
179 |
180 | /**
181 | * Helper function for iterating over an array. If the func returns
182 | * a true value, it will break out of the loop.
183 | */
184 | each: function each(ary, func) {
185 | if (ary) {
186 | var i;
187 | for (i = 0; i < ary.length; i += 1) {
188 | if (func(ary[i], i, ary)) {
189 | break;
190 | }
191 | }
192 | }
193 | },
194 |
195 | /**
196 | * Cycles over properties in an object and calls a function for each
197 | * property value. If the function returns a truthy value, then the
198 | * iteration is stopped.
199 | */
200 | eachProp: function eachProp(obj, func) {
201 | var prop;
202 | for (prop in obj) {
203 | if (hasProp(obj, prop)) {
204 | if (func(obj[prop], prop)) {
205 | break;
206 | }
207 | }
208 | }
209 | },
210 |
211 | //Similar to Function.prototype.bind, but the "this" object is specified
212 | //first, since it is easier to read/figure out what "this" will be.
213 | bind: function bind(obj, fn) {
214 | return function () {
215 | return fn.apply(obj, arguments);
216 | };
217 | },
218 |
219 | //Escapes a content string to be be a string that has characters escaped
220 | //for inclusion as part of a JS string.
221 | jsEscape: function (content) {
222 | return content.replace(/(["'\\])/g, '\\$1')
223 | .replace(/[\f]/g, "\\f")
224 | .replace(/[\b]/g, "\\b")
225 | .replace(/[\n]/g, "\\n")
226 | .replace(/[\t]/g, "\\t")
227 | .replace(/[\r]/g, "\\r");
228 | }
229 | };
230 | return lang;
231 | });
232 |
--------------------------------------------------------------------------------
/spec/lib/configuration.spec.js:
--------------------------------------------------------------------------------
1 | const Configuration = require('../../lib/configuration').Configuration;
2 | const CLIOptionsMock = require('../mocks/cli-options');
3 |
4 | describe('the Configuration module', () => {
5 | let cliOptionsMock;
6 |
7 | beforeEach(() => {
8 | cliOptionsMock = new CLIOptionsMock();
9 | cliOptionsMock.attach();
10 | });
11 |
12 | afterEach(() => {
13 | cliOptionsMock.detach();
14 | });
15 |
16 | it('overrides default options with customized options', () => {
17 | let config = new Configuration({ fromString: true }, { fromString: false });
18 | expect(config.getAllOptions().fromString).toBe(true);
19 | });
20 |
21 | describe('the getAllOptions() function', () => {
22 | it('returns the entire options object', () => {
23 | let options = {
24 | rev: 'dev & prod',
25 | minify: true,
26 | inject: { dev: { value: true } }
27 | };
28 | let config = new Configuration({}, options);
29 | expect(config.getAllOptions()).toEqual(options);
30 | });
31 | });
32 |
33 | describe('the getValue() function', () => {
34 | it('supports multi level options', () => {
35 | let options = new Configuration({}, {
36 | foo: {
37 | bar: {
38 | dev: {
39 | value: 'someValue'
40 | },
41 | staging: {
42 | value: 'someOtherValue'
43 | }
44 | }
45 | }
46 | }, 'dev');
47 | expect(options.getValue('foo.bar').value).toBe('someValue');
48 |
49 | options = new Configuration({}, {
50 | foo: {
51 | bar: {
52 | 'dev & prod': {
53 | value: 'someValue'
54 | }
55 | }
56 | }
57 | }, 'dev');
58 | expect(options.getValue('foo.bar').value).toBe('someValue');
59 |
60 | options = new Configuration({}, {
61 | foo: {
62 | bar: {
63 | dev: {
64 | value: {
65 | options: true
66 | }
67 | }
68 | }
69 | }
70 | }, 'dev');
71 | expect(options.getValue('foo.bar').value.options).toBe(true);
72 |
73 | options = new Configuration({}, {
74 | foo: {
75 | bar: 'abcd'
76 | }
77 | }, 'dev');
78 | expect(options.getValue('foo.bar')).toBe('abcd');
79 | });
80 |
81 | it('supports one level options', () => {
82 | let options = new Configuration({}, { foo: 'someValue' }, 'dev');
83 | expect(options.getValue('foo')).toBe('someValue');
84 | });
85 |
86 | it('returns undefined when property is not defined', () => {
87 | let options = new Configuration({}, { foo: 'someValue' }, 'dev');
88 | expect(options.getValue('foobarbaz')).toBe(undefined);
89 |
90 | options = new Configuration({}, { foo: 'someValue' }, 'dev');
91 | expect(options.getValue('foo.bar.baz')).toBe(undefined);
92 |
93 | options = new Configuration({}, { }, 'dev');
94 | expect(options.getValue('foo.bar.baz')).toBe(undefined);
95 | });
96 |
97 | it('applies default config, then environment config', () => {
98 | let options = new Configuration({}, {
99 | foo: {
100 | bar: {
101 | default: {
102 | cutoff: 15,
103 | maxLength: 5000
104 | },
105 | dev: {
106 | maxLength: 3000
107 | }
108 | }
109 | }
110 | }, 'dev');
111 |
112 | expect(options.getValue('foo.bar')).toEqual({
113 | cutoff: 15,
114 | maxLength: 3000
115 | });
116 |
117 | options = new Configuration({}, {
118 | foo: {
119 | bar: {
120 | dev: {
121 | maxLength: 3000
122 | }
123 | }
124 | }
125 | }, 'dev');
126 |
127 | expect(options.getValue('foo.bar')).toEqual({
128 | maxLength: 3000
129 | });
130 |
131 | options = new Configuration({}, {
132 | foo: {
133 | bar: {
134 | dev: {
135 | maxLength: 3000
136 | },
137 | 'dev & staging': {
138 | cutoff: 15
139 | }
140 | }
141 | }
142 | }, 'dev');
143 |
144 | expect(options.getValue('foo.bar')).toEqual({
145 | maxLength: 3000,
146 | cutoff: 15
147 | });
148 | });
149 | });
150 |
151 | describe('isApplicable', () => {
152 | it('supports multi level options', () => {
153 | let options = new Configuration({}, {
154 | foo: {
155 | bar: {
156 | baz: 'dev & prod'
157 | }
158 | }
159 | }, 'dev');
160 | expect(options.isApplicable('foo.bar.baz')).toBe(true);
161 |
162 | options = new Configuration({}, {
163 | foo: {
164 | bar: true
165 | }
166 | }, 'staging');
167 | expect(options.isApplicable('foo.bar')).toBe(true);
168 |
169 | options = new Configuration({}, {
170 | foo: {
171 | bar: {
172 | dev: false,
173 | staging: true
174 | }
175 | }
176 | }, 'staging');
177 | expect(options.isApplicable('foo.bar')).toBe(true);
178 |
179 | options = new Configuration({}, {
180 | foo: {
181 | bar: {
182 | dev: false,
183 | 'staging & prod': true
184 | }
185 | }
186 | }, 'staging');
187 | expect(options.isApplicable('foo.bar')).toBe(true);
188 |
189 | options = new Configuration({}, {
190 | foo: {
191 | bar: false
192 | }
193 | }, 'staging');
194 | expect(options.isApplicable('foo.bar')).toBe(false);
195 | });
196 |
197 | it('returns false if env not found', () => {
198 | let options = new Configuration({}, {
199 | foo: {
200 | bar: {
201 | staging: {
202 | somevalue: 123
203 | }
204 | }
205 | }
206 | }, 'dev');
207 | expect(options.isApplicable('foo.bar')).toBe(false);
208 | });
209 |
210 | it('supports first level options', () => {
211 | let options = new Configuration({}, {
212 | foo: 'dev & prod'
213 | }, 'dev');
214 | expect(options.isApplicable('foo')).toBe(true);
215 |
216 | options = new Configuration({}, {
217 | foo: 'dev & prod'
218 | }, 'staging');
219 | expect(options.isApplicable('foo')).toBe(false);
220 | });
221 |
222 | it('interprets strings', () => {
223 | let options = new Configuration({}, { foo: 'dev & prod' }, 'dev');
224 | expect(options.isApplicable('foo')).toBe(true);
225 |
226 | options = new Configuration({}, {
227 | foo: {
228 | bar: {
229 | baz: 'dev & prod'
230 | }
231 | }
232 | }, 'dev');
233 | expect(options.isApplicable('foo.bar.baz')).toBe(true);
234 |
235 | options = new Configuration({}, { foo: 'dev & prod' }, 'staging');
236 | expect(options.isApplicable('foo')).toBe(false);
237 |
238 | options = new Configuration({}, {
239 | foo: {
240 | bar: {
241 | baz: 'dev & prod'
242 | }
243 | }
244 | }, 'staging');
245 | expect(options.isApplicable('foo.bar.baz')).toBe(false);
246 |
247 | options = new Configuration({}, {
248 | foo: {
249 | bar: {
250 | 'dev & prod': {
251 | value: true
252 | }
253 | }
254 | }
255 | }, 'dev');
256 | expect(options.isApplicable('foo.bar')).toBe(true);
257 |
258 | options = new Configuration({}, {
259 | foo: {
260 | bar: {
261 | 'dev & prod': true
262 | }
263 | }
264 | }, 'staging');
265 | expect(options.isApplicable('foo.bar')).toBe(false);
266 |
267 | options = new Configuration({}, {
268 | foo: {
269 | bar: {
270 | 'dev & prod': true
271 | }
272 | }
273 | }, 'dev');
274 | expect(options.isApplicable('foo.bar')).toBe(true);
275 | });
276 |
277 | it('supports booleans', () => {
278 | let options = new Configuration({}, { foo: true }, 'dev');
279 | expect(options.isApplicable('foo')).toBe(true);
280 |
281 | options = new Configuration({}, { foo: false }, 'dev');
282 | expect(options.isApplicable('foo')).toBe(false);
283 | });
284 |
285 | it('supports environments inside an object', () => {
286 | let options = new Configuration({}, { foo: { dev: true, staging: false } }, 'dev');
287 | expect(options.isApplicable('foo')).toBe(true);
288 |
289 | options = new Configuration({}, { foo: { dev: false, staging: true } }, 'dev');
290 | expect(options.isApplicable('foo')).toBe(false);
291 | });
292 | });
293 | });
294 |
--------------------------------------------------------------------------------
/spec/lib/build/utils.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const mockfs = require('../../mocks/mock-fs');
3 | const Utils = require('../../../lib/build/utils');
4 |
5 | describe('the Utils.runSequentially function', () => {
6 | it('calls the callback function for all items', (d) => {
7 | let items = [{ name: 'first' }, { name: 'second' }];
8 | let cb = jasmine.createSpy('cb').and.returnValue(Promise.resolve());
9 | Utils.runSequentially(items, cb).then(() => {
10 | expect(cb.calls.count()).toBe(2);
11 | expect(cb.calls.argsFor(0)[0].name).toBe('first');
12 | expect(cb.calls.argsFor(1)[0].name).toBe('second');
13 | d();
14 | });
15 | });
16 |
17 | it('runs in sequence', (d) => {
18 | let items = [{ name: 'first' }, { name: 'second' }, { name: 'third' }];
19 | let cb = jasmine.createSpy('cb').and.callFake((item) => {
20 | return new Promise(resolve => {
21 | if (item.name === 'first' || item.name === 'second') {
22 | setTimeout(() => resolve(), 200);
23 | } else {
24 | resolve();
25 | }
26 | });
27 | });
28 | Utils.runSequentially(items, cb).then(() => {
29 | expect(cb.calls.argsFor(0)[0].name).toBe('first');
30 | expect(cb.calls.argsFor(1)[0].name).toBe('second');
31 | expect(cb.calls.argsFor(2)[0].name).toBe('third');
32 | d();
33 | });
34 | });
35 |
36 | it('handles empty items array', (done) => {
37 | let items = [];
38 | Utils.runSequentially(items, () => {})
39 | .catch(e => {
40 | done.fail(e, '', 'expected no error');
41 | throw e;
42 | })
43 | .then(() => {
44 | done();
45 | });
46 | });
47 | });
48 |
49 | describe('the Utils.createBundleFileRegex function', () => {
50 | it('matches script tag with double quotes', () => {
51 | expect(''.match(Utils.createBundleFileRegex('vendor-bundle'))).not.toBeFalsy();
52 |
53 | expect(''.replace(Utils.createBundleFileRegex('vendor-bundle'), 'vendor-bundle-123.js')).toBe('');
54 |
55 | // dev to prod
56 | expect(''.replace(Utils.createBundleFileRegex('app-bundle'), 'app-bundle-123.js').replace(Utils.createBundleFileRegex('vendor-bundle'), 'vendor-bundle-abc.js')).toBe('');
57 |
58 | // prod to dev
59 | expect(''.replace(Utils.createBundleFileRegex('app-bundle'), 'app-bundle.js').replace(Utils.createBundleFileRegex('vendor-bundle'), 'vendor-bundle.js')).toBe('');
60 | });
61 | it('matches script tag with single quotes', () => {
62 | expect(''.match(Utils.createBundleFileRegex('vendor-bundle'))).not.toBeFalsy();
63 | });
64 | it('matches script tag without quotes', () => {
65 | expect(''.match(Utils.createBundleFileRegex('vendor-bundle'))).not.toBeFalsy();
66 | });
67 | it('does not match other bundles', () => {
68 | expect(''.match(Utils.createBundleFileRegex('vendor-bundle'))).toBeFalsy();
69 | });
70 | });
71 |
72 | describe('the Utils.moduleIdWithPlugin function', () => {
73 | it('generates requirejs style module id', () => {
74 | expect(Utils.moduleIdWithPlugin('foo/bar', 'plugin', 'require')).toBe('plugin!foo/bar');
75 | });
76 |
77 | it('generates systemjs style module id', () => {
78 | expect(Utils.moduleIdWithPlugin('foo/bar', 'plugin', 'system')).toBe('foo/bar!plugin');
79 | });
80 |
81 | it('complains unknown type', () => {
82 | expect(() => Utils.moduleIdWithPlugin('foo/bar', 'plugin', 'unknown')).toThrow();
83 | });
84 | });
85 |
86 | describe('the Utils.couldMissGulpPreprocess function', () => {
87 | it('returns false for js/html/css files', () => {
88 | expect(Utils.couldMissGulpPreprocess('foo/bar')).toBeFalsy();
89 | expect(Utils.couldMissGulpPreprocess('foo/bar.js')).toBeFalsy();
90 | expect(Utils.couldMissGulpPreprocess('foo/bar.html')).toBeFalsy();
91 | expect(Utils.couldMissGulpPreprocess('bar.css')).toBeFalsy();
92 | });
93 |
94 | it('returns true for unknown file extension', () => {
95 | expect(Utils.couldMissGulpPreprocess('foo/bar.json')).toBeTruthy();
96 | expect(Utils.couldMissGulpPreprocess('foo/bar.yaml')).toBeTruthy();
97 | });
98 | });
99 |
100 | describe('the Utils.nodejsLoad function', () => {
101 | beforeEach(() => {
102 | const fsConfig = {};
103 | mockfs(fsConfig);
104 | });
105 |
106 | afterEach(() => {
107 | mockfs.restore();
108 | });
109 |
110 | it('load file first', () => {
111 | const fsConfig = {};
112 | fsConfig[path.join('foo', 'bar')] = 'bar';
113 | fsConfig[path.join('foo', 'bar.js')] = 'js';
114 | fsConfig[path.join('foo', 'bar.json')] = 'json';
115 | mockfs(fsConfig);
116 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar'));
117 | });
118 |
119 | it('load .js file first', () => {
120 | const fsConfig = {};
121 | fsConfig[path.join('foo', 'bar.js')] = 'js';
122 | fsConfig[path.join('foo', 'bar.json')] = 'json';
123 | mockfs(fsConfig);
124 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar.js'));
125 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar.js'))).toBe(path.resolve('foo', 'bar.js'));
126 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar.json'))).toBe(path.resolve('foo', 'bar.json'));
127 | });
128 |
129 | it('load .json file', () => {
130 | const fsConfig = {};
131 | fsConfig[path.join('foo', 'bar.json')] = 'json';
132 | mockfs(fsConfig);
133 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar.json'));
134 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar.json'))).toBe(path.resolve('foo', 'bar.json'));
135 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar.js'))).toBeUndefined();
136 | });
137 |
138 | it('load directory', () => {
139 | const fsConfig = {};
140 | fsConfig[path.join('foo', 'bar', 'index.js')] = 'bar/index';
141 | fsConfig[path.join('foo', 'bar', 'index.json')] = 'bar/index.json';
142 | mockfs(fsConfig);
143 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'index.js'));
144 | });
145 |
146 | it('load directory .json', () => {
147 | const fsConfig = {};
148 | fsConfig[path.join('foo', 'bar', 'index.json')] = 'bar/index.json';
149 | mockfs(fsConfig);
150 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'index.json'));
151 | });
152 |
153 | it('load directory with package.json', () => {
154 | const fsConfig = {};
155 | fsConfig[path.join('foo', 'bar', 'package.json')] = '{"main": "lo.js"}';
156 | fsConfig[path.join('foo', 'bar', 'lo.js')] = 'bar/lo.js';
157 | mockfs(fsConfig);
158 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'lo.js'));
159 | });
160 |
161 | it('load directory with package.json browser field', () => {
162 | const fsConfig = {};
163 | fsConfig[path.join('foo', 'bar', 'package.json')] = '{"main": "lo2.js", "browser": "lo.js"}';
164 | fsConfig[path.join('foo', 'bar', 'lo.js')] = 'bar/lo.js';
165 | mockfs(fsConfig);
166 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'lo.js'));
167 | });
168 |
169 | it('load directory with package.json browser "." mapping', () => {
170 | const fsConfig = {};
171 | fsConfig[path.join('foo', 'bar', 'package.json')] = '{"main": "lo2.js", "browser": {".": "lo.js"}}';
172 | fsConfig[path.join('foo', 'bar', 'lo.js')] = 'bar/lo.js';
173 | mockfs(fsConfig);
174 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'lo.js'));
175 | });
176 |
177 | it('load directory with package.json browser field', () => {
178 | const fsConfig = {};
179 | fsConfig[path.join('foo', 'bar', 'package.json')] = '{"main": "lo2.js", "module": "lo.js"}';
180 | fsConfig[path.join('foo', 'bar', 'lo.js')] = 'bar/lo.js';
181 | mockfs(fsConfig);
182 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'lo.js'));
183 | });
184 |
185 | it('load directory with package.json, case2', () => {
186 | const fsConfig = {};
187 | fsConfig[path.join('foo', 'bar', 'package.json')] = '{"main": "lo.js"}';
188 | fsConfig[path.join('foo', 'bar', 'lo.js', 'index.js')] = 'bar/lo.js/index.js';
189 | mockfs(fsConfig);
190 | expect(Utils.nodejsLoad(path.resolve('foo', 'bar'))).toBe(path.resolve('foo', 'bar', 'lo.js', 'index.js'));
191 | });
192 | });
193 |
194 | describe('the Utils.removeJsExtension function', () => {
195 | it('keep other extension', () => {
196 | expect(Utils.removeJsExtension('a.html')).toBe('a.html');
197 | expect(Utils.removeJsExtension('c/d.css')).toBe('c/d.css');
198 | expect(Utils.removeJsExtension('c/d.min')).toBe('c/d.min');
199 | });
200 | it('strips .js extension', () => {
201 | expect(Utils.removeJsExtension('a.js')).toBe('a');
202 | expect(Utils.removeJsExtension('c/d.js')).toBe('c/d');
203 | expect(Utils.removeJsExtension('c/d.min.js')).toBe('c/d.min');
204 | });
205 | });
206 |
--------------------------------------------------------------------------------
/lib/build/ast-matcher.js:
--------------------------------------------------------------------------------
1 | const meriyah = require('meriyah');
2 |
3 | const STOP = false;
4 | const SKIP_BRANCH = 1;
5 |
6 | // ignore position info, and raw
7 | const IGNORED_KEYS = ['start', 'end', 'loc', 'location', 'locations', 'line', 'column', 'range', 'ranges', 'raw'];
8 |
9 | // From an meriyah example for traversing its ast.
10 | // modified to support branch skip.
11 | function traverse(object, visitor) {
12 | let child;
13 | if (!object) return;
14 |
15 | let r = visitor.call(null, object);
16 | if (r === STOP) return STOP; // stop whole traverse immediately
17 | if (r === SKIP_BRANCH) return; // skip going into AST branch
18 |
19 | for (let i = 0, keys = Object.keys(object); i < keys.length; i++) {
20 | let key = keys[i];
21 | if (IGNORED_KEYS.indexOf(key) !== -1) continue;
22 |
23 | child = object[key];
24 | if (typeof child === 'object' && child !== null) {
25 | if (traverse(child, visitor) === STOP) {
26 | return STOP;
27 | }
28 | }
29 | }
30 | }
31 |
32 | const ANY = 1;
33 | const ANL = 2;
34 | const STR = 3;
35 | const ARR = 4;
36 |
37 | function matchTerm(pattern) {
38 | let possible;
39 | if (pattern.type === 'Identifier') {
40 | possible = pattern.name.toString();
41 | } else if (pattern.type === 'ExpressionStatement' &&
42 | pattern.expression.type === 'Identifier') {
43 | possible = pattern.expression.name.toString();
44 | }
45 |
46 | if (!possible || !possible.startsWith('__')) return;
47 |
48 | let type;
49 | if (possible === '__any' || possible.startsWith('__any_')) {
50 | type = ANY;
51 | } else if (possible === '__anl' || possible.startsWith('__anl_')) {
52 | type = ANL;
53 | } else if (possible === '__str' || possible.startsWith('__str_')) {
54 | type = STR;
55 | } else if (possible === '__arr' || possible.startsWith('__arr_')) {
56 | type = ARR;
57 | }
58 |
59 | if (type) return {type: type, name: possible.slice(6)};
60 | }
61 |
62 | /**
63 | * Extract info from a partial meriyah syntax tree, see astMatcher for pattern format
64 | * @param pattern The pattern used on matching
65 | * @param part The target partial syntax tree
66 | * @return Returns named matches, or false.
67 | */
68 | exports.extract = function(pattern, part) {
69 | if (!pattern) throw new Error('missing pattern');
70 | // no match
71 | if (!part) return STOP;
72 |
73 | let term = matchTerm(pattern);
74 | if (term) {
75 | // if single __any
76 | if (term.type === ANY) {
77 | if (term.name) {
78 | // if __any_foo
79 | // get result {foo: astNode}
80 | let r = {};
81 | r[term.name] = part;
82 | return r;
83 | }
84 | // always match
85 | return {};
86 |
87 | // if single __str_foo
88 | } else if (term.type === STR) {
89 | if (part.type === 'Literal') {
90 | if (term.name) {
91 | // get result {foo: value}
92 | let r = {};
93 | r[term.name] = part.value;
94 | return r;
95 | }
96 | // always match
97 | return {};
98 | }
99 | // no match
100 | return STOP;
101 | }
102 | }
103 |
104 |
105 | if (Array.isArray(pattern)) {
106 | // no match
107 | if (!Array.isArray(part)) return STOP;
108 |
109 | if (pattern.length === 1) {
110 | let arrTerm = matchTerm(pattern[0]);
111 | if (arrTerm) {
112 | // if single __arr_foo
113 | if (arrTerm.type === ARR) {
114 | // find all or partial Literals in an array
115 | let arr = part.filter(it => it.type === 'Literal').map(it => it.value);
116 | if (arr.length) {
117 | if (arrTerm.name) {
118 | // get result {foo: array}
119 | let r = {};
120 | r[arrTerm.name] = arr;
121 | return r;
122 | }
123 | // always match
124 | return {};
125 | }
126 | // no match
127 | return STOP;
128 | } else if (arrTerm.type === ANL) {
129 | if (arrTerm.name) {
130 | // get result {foo: nodes array}
131 | let r = {};
132 | r[arrTerm.name] = part;
133 | return r;
134 | }
135 |
136 | // always match
137 | return {};
138 | }
139 | }
140 | }
141 |
142 | if (pattern.length !== part.length) {
143 | // no match
144 | return STOP;
145 | }
146 | }
147 |
148 | let allResult = {};
149 |
150 | for (let i = 0, keys = Object.keys(pattern); i < keys.length; i++) {
151 | let key = keys[i];
152 | if (IGNORED_KEYS.indexOf(key) !== -1) continue;
153 |
154 | let nextPattern = pattern[key];
155 | let nextPart = part[key];
156 |
157 | if (!nextPattern || typeof nextPattern !== 'object') {
158 | // primitive value. string or null
159 | if (nextPattern === nextPart) continue;
160 |
161 | // no match
162 | return STOP;
163 | }
164 |
165 | const result = exports.extract(nextPattern, nextPart);
166 | // no match
167 | if (result === STOP) return STOP;
168 | if (result) Object.assign(allResult, result);
169 | }
170 |
171 | return allResult;
172 | };
173 |
174 | /**
175 | * Compile a pattern into meriyah syntax tree
176 | * @param pattern The pattern used on matching, can be a string or meriyah node
177 | * @return Returns an meriyah node to be used as pattern in extract(pattern, part)
178 | */
179 | exports.compilePattern = function(pattern) {
180 | // pass meriyah syntax tree obj
181 | if (pattern && pattern.type) return pattern;
182 |
183 | if (typeof pattern !== 'string') {
184 | throw new Error('input pattern is neither a string nor an meriyah node.');
185 | }
186 |
187 | let exp = meriyah.parseScript(pattern, {next: true, webcompat: true});
188 |
189 | if (exp.type !== 'Program' || !exp.body) {
190 | throw new Error(`Not a valid expression: "${pattern}".`);
191 | }
192 |
193 | if (exp.body.length === 0) {
194 | throw new Error(`There is no statement in pattern "${pattern}".`);
195 | }
196 |
197 | if (exp.body.length > 1) {
198 | throw new Error(`Multiple statements is not supported "${pattern}".`);
199 | }
200 |
201 | exp = exp.body[0];
202 | // get the real expression underneath
203 | if (exp.type === 'ExpressionStatement') exp = exp.expression;
204 | return exp;
205 | };
206 |
207 | function ensureParsed(codeOrNode) {
208 | // bypass parsed node
209 | if (codeOrNode && codeOrNode.type) return codeOrNode;
210 | return meriyah.parseScript(codeOrNode, {next: true, webcompat: true});
211 | }
212 |
213 | /**
214 | * Pattern matching using AST on JavaScript source code
215 | * @param pattern The pattern to be matched
216 | * @return Returns a function that takes source code string (or meriyah syntax tree) as input, produces matched result or undefined.
217 | *
218 | * __any matches any single node, but no extract
219 | * __anl matches array of nodes, but no extract
220 | * __str matches string literal, but no extract
221 | * __arr matches array of partial string literals, but no extract
222 | * __any_aName matches single node, return {aName: node}
223 | * __anl_aName matches array of nodes, return {aName: array_of_nodes}
224 | * __str_aName matches string literal, return {aName: value}
225 | * __arr_aName matches array, extract string literals, return {aName: [values]}
226 | *
227 | * note: __arr_aName can match partial array
228 | * [foo, "foo", lorem, "bar", lorem] => ["foo", "bar"]
229 | *
230 | * note: __anl, and __arr_*
231 | * use method(__anl) or method(__arr_a) to match method(a, "b");
232 | * use method([__anl]) or method([__arr_a]) to match method([a, "b"]);
233 | *
234 | * Usage:
235 | * let m = astMatcher('__any.method(__str_foo, [__arr_opts])');
236 | * m('au.method("a", ["b", "c"]); jq.method("d", ["e"])');
237 | *
238 | * => [
239 | * {match: {foo: "a", opts: ["b", "c"]}, node: }
240 | * {match: {foo: "d", opts: ["e"]}, node: }
241 | * ]
242 | */
243 | exports.astMatcher = function(pattern) {
244 | let pat = exports.compilePattern(pattern);
245 |
246 | return function(jsStr) {
247 | let node = ensureParsed(jsStr);
248 | let matches = [];
249 |
250 | traverse(node, n => {
251 | let m = exports.extract(pat, n);
252 | if (m) {
253 | matches.push({
254 | match: m,
255 | node: n // this is the full matching node
256 | });
257 | // found a match, don't go deeper on this tree branch
258 | // return SKIP_BRANCH;
259 | // don't skip branch in order to catch both .m1() ad .m2()
260 | // astMater('__any.__any_m()')('a.m1().m2()')
261 | }
262 | });
263 |
264 | return matches.length ? matches : undefined;
265 | };
266 | };
267 |
268 | /**
269 | * Dependency search for dummies, this is a high level api to simplify the usage of astMatcher
270 | * @param arguments Multiple patterns to match, instead of using __str_/__arr_, use __dep and __deps to match string or partial string array.
271 | * @return Returns a function that takes source code string (or meriyah syntax tree) as input, produces an array of string matched, or empty array.
272 | *
273 | * Usage:
274 | * let f = jsDepFinder('a(__dep)', '__any.globalResources([__deps])');
275 | * f('a("a"); a("b"); config.globalResources(["./c", "./d"])');
276 | *
277 | * => ['a', 'b', './c', './d']
278 | */
279 | exports.jsDepFinder = function() {
280 | if (arguments.length === 0) {
281 | throw new Error('No patterns provided.');
282 | }
283 |
284 | let seed = 0;
285 |
286 | let patterns = Array.prototype.map.call(arguments, p =>
287 | // replace __dep and __deps into
288 | // __str_1, __str_2, __arr_3
289 | // wantArr is the result of (s?)
290 | exports.compilePattern(p.replace(/__dep(s?)/g, (m, wantArr) =>
291 | (wantArr ? '__arr_' : '__str_') + (++seed)
292 | ))
293 | );
294 |
295 | let len = patterns.length;
296 |
297 | return function(jsStr) {
298 | let node = ensureParsed(jsStr);
299 |
300 | let deps = [];
301 |
302 | // directly use extract() instead of astMatcher()
303 | // for efficiency
304 | traverse(node, n => {
305 | for (let i = 0; i < len; i += 1) {
306 | let result = exports.extract(patterns[i], n);
307 | if (result) {
308 | // result is like {"1": "dep1", "2": ["dep2", "dep3"]}
309 | // we only want values
310 | Object.keys(result).forEach(k => {
311 | let d = result[k];
312 | if (typeof d === 'string') deps.push(d);
313 | else deps.push.apply(deps, d);
314 | });
315 |
316 | // found a match, don't try other pattern
317 | break;
318 | }
319 | }
320 | });
321 |
322 | return deps;
323 | };
324 | };
325 |
--------------------------------------------------------------------------------