├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── bin └── ember-module-migrator ├── lib ├── engines │ ├── classic │ │ ├── classic-file-info.js │ │ ├── component-file-info.js │ │ ├── component-template-file-info.js │ │ ├── config-file-info.js │ │ ├── index.js │ │ ├── main-file-info.js │ │ ├── misc-file-info.js │ │ ├── mixin-file-info.js │ │ ├── template-file-info.js │ │ ├── test-file-info.js │ │ └── view-file-info.js │ └── index.js ├── index.js ├── models │ ├── file-info-collection.js │ ├── file-info.js │ └── logger.js ├── transforms │ ├── import-declarations.js │ └── rename-helper-import.js └── utils │ ├── calculate-collection-info.js │ ├── default-type-for-collection.js │ ├── is-type-in-single-type-collection.js │ ├── path.js │ └── pods-support.js ├── package.json ├── test ├── .eslintrc.js ├── enable-power-assert.js ├── engines │ ├── classic-test.js │ └── classic │ │ └── template-file-info-test.js ├── eslint-test.js ├── fixtures │ ├── classic-absolute-imports │ │ ├── input.js │ │ └── output.js │ ├── classic-acceptance │ │ ├── input.js │ │ └── output.js │ ├── classic-named-exports │ │ ├── input.js │ │ └── output.js │ ├── classic-nested-component-invocation │ │ ├── input.js │ │ └── output.js │ ├── classic-private-components │ │ ├── input.js │ │ └── output.js │ ├── classic-relative-imports │ │ ├── input.js │ │ └── output.js │ ├── classic-template-only-component │ │ ├── input.js │ │ └── output.js │ ├── classic-view-support │ │ ├── input.js │ │ └── output.js │ ├── config │ │ ├── input.js │ │ └── output.js │ ├── directory-cleanup │ │ ├── input.js │ │ └── output.js │ ├── pods-acceptance │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── pods-custom-name-acceptance │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── pods-private-components │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── pods-relative-imports │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── pods-with-undefined-namespace-acceptance │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── pods-without-namespace-acceptance │ │ ├── config.js │ │ ├── input.js │ │ └── output.js │ ├── qunit-module-test-helpers │ │ ├── input.js │ │ └── output.js │ └── test-helpers │ │ ├── input.js │ │ └── output.js ├── logger-test.js ├── mocha.opts └── models │ └── file-info-test.js ├── testem.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "indent": [ 9 | 2, 10 | 2 11 | ], 12 | "linebreak-style": [ 13 | 2, 14 | "unix" 15 | ], 16 | "quotes": [ 17 | 2, 18 | "single" 19 | ], 20 | "semi": [ 21 | 2, 22 | "always" 23 | ] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /tmp 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | - "10" 6 | - "stable" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-module-migrator 2 | 3 | Migrates ember-cli projects to the newly proposed module format. 4 | 5 | This migrator does not leave an application in a bootable state. See "Usage" 6 | for instructions on how to manually update a migrated app to a bootable 7 | setup. 8 | 9 | See [emberjs/rfcs#143](https://github.com/emberjs/rfcs/pull/143) for details 10 | on the design of this app layout. 11 | 12 | ### Examples 13 | 14 | These are examples of the migrator output on various apps: 15 | 16 | * [Ghost admin client](https://github.com/rwjblue/--ghost-modules-sample/tree/grouped-collections/src) 17 | * [Travis client](https://github.com/rwjblue/--travis-modules-sample/tree/modules/src) 18 | * [Migration of `ember new my-app`](https://github.com/rwjblue/--new-app-blueprint/tree/modules/src) 19 | 20 | ### Usage 21 | 22 | To run the migrator on your app: 23 | 24 | ```sh 25 | npm install -g ember-module-migrator jscodeshift 26 | cd your/project/path 27 | ember-module-migrator 28 | ``` 29 | 30 | After running the migrator itself, you will need to update several files 31 | to boot an application in order to boot it. 32 | 33 | ### Booting a migrated app 34 | 35 | The best path forward is to run the 36 | [ember-octane-blueprint](https://github.com/ember-cli/ember-octane-blueprint) 37 | on the converted app: 38 | 39 | ```sh 40 | # This command will run the blueprint, but it requires Ember-CLI 2.14. 41 | # 42 | # You may want to manually update the following packages before running the 43 | # `ember init` command: 44 | # 45 | # npm i ember-resolver@^4.3.0 ember-cli@github:ember-cli/ember-cli --save-dev 46 | # npm uninstall ember-source --save 47 | # bower install --save components/ember#canary 48 | # 49 | # If you are already running 2.14, you can jump right to the command: 50 | ember init -b ember-octane-app-blueprint 51 | ``` 52 | Additionally any component names not in a template are not recognized by the 53 | migrator. For example if you have a computed property that returns the 54 | string `"widget/some-thing"` using that string with the `{{component` helper 55 | will now cause an error. You must convert these component named to not have `/` 56 | characters in their strings. 57 | 58 | ### Running module unification with fallback to classic app layout 59 | 60 | If an application cannot be converted all at once, or if your application is 61 | dependent upon addons that use `app/` to add files, you may want to use 62 | a setup that falls back to `app/` after checking `src` and honors the `app/` 63 | directory for some files like initializers. 64 | 65 | To do this use a fallback resolver in `src/resolver.js`: 66 | 67 | ```js 68 | import Resolver from 'ember-resolver/resolvers/fallback'; 69 | import buildResolverConfig from 'ember-resolver/ember-config'; 70 | import config from '../config/environment'; 71 | 72 | let moduleConfig = buildResolverConfig(config.modulePrefix); 73 | /* 74 | * If your application has custom types and collections, modify moduleConfig here 75 | * to add support for them. 76 | */ 77 | 78 | export default Resolver.extend({ 79 | config: moduleConfig 80 | }); 81 | ``` 82 | 83 | In `src/main.js` be sure to load initializers in the `app/` directory 84 | (possibly added by an addon) via: 85 | 86 | ```js 87 | /* 88 | * This line should be added by the blueprint 89 | */ 90 | loadInitializers(App, config.modulePrefix+'/src/init'); 91 | 92 | /* 93 | * This line should be added to support `app/` directories 94 | */ 95 | loadInitializers(App, config.modulePrefix); 96 | ``` 97 | 98 | ### Running Tests 99 | 100 | * `npm run test` 101 | 102 | To debug tests: 103 | 104 | * All tests: `mocha --debug-brk --inspect test/**/*-test.js` 105 | * A single test: `mocha --debug-brk --inspect test/**/*-test.js --grep test-helpers` 106 | 107 | ### Important Notes 108 | 109 | Known caveats: 110 | 111 | * Migrates only "classic" structured ember-cli apps at this point. We are 112 | actively working on pods support. 113 | -------------------------------------------------------------------------------- /bin/ember-module-migrator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // Local Variables: 5 | // mode: js2 6 | // End: 7 | 8 | // Provide a title to the process in `ps` 9 | process.title = 'ember-module-migrator'; 10 | 11 | var fs = require('fs'); 12 | 13 | var Migrator = require('../lib'); 14 | 15 | var nopt = require("nopt"); 16 | var options = { 17 | 'project-root': [String], 18 | 'project-name': [String], 19 | 'engine': [String, 'classic'] 20 | }; 21 | var parsed = nopt(options); 22 | 23 | var projectRoot = parsed['project-root'] || process.cwd(); 24 | var projectName = parsed['project-name']; 25 | 26 | var environment = require(projectRoot + '/config/environment.js'); 27 | 28 | var modulePrefix = environment.modulePrefix; 29 | var podModulePrefix = (environment.podModulePrefix || '').replace(modulePrefix + '\/', ''); 30 | 31 | if (!projectName) { 32 | // determine name from package.json in projectRoot 33 | try { 34 | projectName = JSON.parse(fs.readFileSync(projectRoot + '/package.json', 'utf8')).name; 35 | } catch (e) {} 36 | } 37 | 38 | var migrator = new Migrator({ 39 | projectRoot: projectRoot, 40 | projectName: projectName, 41 | engine: parsed['engine'], 42 | podModulePrefix: podModulePrefix || undefined, 43 | verbose: true 44 | }); 45 | 46 | migrator.processFiles() 47 | .then(function() { 48 | /* eslint no-console: 0 */ 49 | 50 | console.log('Finished successfully!'); 51 | }) 52 | .catch(function(error) { 53 | console.error(error.stack); 54 | }); 55 | -------------------------------------------------------------------------------- /lib/engines/classic/classic-file-info.js: -------------------------------------------------------------------------------- 1 | var FileInfo = require('../../models/file-info'); 2 | 3 | var ClassicFileInfo = FileInfo.extend({ 4 | fileInfoType: 'ClassicFileInfo' 5 | }); 6 | 7 | module.exports = ClassicFileInfo; 8 | -------------------------------------------------------------------------------- /lib/engines/classic/component-file-info.js: -------------------------------------------------------------------------------- 1 | var ClassicFileInfo = require('./classic-file-info'); 2 | 3 | var ComponentFileInfo = ClassicFileInfo.extend({ 4 | fileInfoType: 'ComponentFileInfo', 5 | 6 | shouldUseDotFormNaming: function() { 7 | // components should always use /. 8 | return false; 9 | } 10 | }); 11 | 12 | module.exports = ComponentFileInfo; 13 | -------------------------------------------------------------------------------- /lib/engines/classic/component-template-file-info.js: -------------------------------------------------------------------------------- 1 | var TemplateFileInfo = require('./template-file-info'); 2 | 3 | var ComponentTemplateFileInfo = TemplateFileInfo.extend({ 4 | type: 'ComponentTemplateFileInfo', 5 | 6 | init: function(options) { 7 | this._super(options); 8 | 9 | this.collection = 'components'; 10 | this.collectionGroup = 'ui'; 11 | }, 12 | populateName: function() { 13 | this._super.populateName.apply(this, arguments); 14 | 15 | var namespace = this.namespace.replace(/^components\/?/, ''); 16 | 17 | this.namespace = namespace; 18 | } 19 | }); 20 | 21 | module.exports = ComponentTemplateFileInfo; 22 | -------------------------------------------------------------------------------- /lib/engines/classic/config-file-info.js: -------------------------------------------------------------------------------- 1 | var ClassicFileInfo = require('./classic-file-info'); 2 | 3 | var ConfigFileInfo = ClassicFileInfo.extend({ 4 | fileInfoType: 'ConfigFileInfo', 5 | 6 | 7 | init: function(options) { 8 | // sourceRoot needs to be set before the super call so that non js files stay in place 9 | options.sourceRoot = '.'; 10 | this._super(options); 11 | 12 | options.type = 'config'; 13 | options.base = '.'; 14 | }, 15 | 16 | populateCollection: function() { 17 | this.collection = '..'; 18 | this.collectionGroup = ''; 19 | } 20 | }); 21 | 22 | module.exports = ConfigFileInfo; -------------------------------------------------------------------------------- /lib/engines/classic/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var inflection = require('inflection'); 3 | var ClassicFileInfo = require('./classic-file-info'); 4 | var TestFileInfo = require('./test-file-info'); 5 | var ComponentFileInfo = require('./component-file-info'); 6 | var ViewFileInfo = require('./view-file-info'); 7 | var TemplateFileInfo = require('./template-file-info'); 8 | var ComponentTemplateFileInfo = require('./component-template-file-info'); 9 | var MainFileInfo = require('./main-file-info'); 10 | var MixinFileInfo = require('./mixin-file-info'); 11 | var ConfigFileInfo = require('./config-file-info'); 12 | var MiscFileInfo = require('./misc-file-info'); 13 | var PodsSupport = require('../../utils/pods-support'); 14 | var typeForPodFile = PodsSupport.typeForPodFile; 15 | var hasPodNamespace = PodsSupport.hasPodNamespace; 16 | 17 | module.exports = { 18 | buildFor: function(sourceRelativePath, options) { 19 | var ext = path.extname(sourceRelativePath); 20 | var pathParts = sourceRelativePath.split('/'); 21 | var filename = pathParts.slice(-1)[0]; 22 | 23 | if (filename === '.gitkeep') { 24 | // ignore .gitkeep files 25 | return null; 26 | } 27 | 28 | options.sourceRelativePath = sourceRelativePath; 29 | var sourceRoot = options.sourceRoot = pathParts[0]; 30 | var topLevelDirectory; 31 | 32 | if (filename[0] === '.' && ext === '.js') { 33 | // keep js dotfiles in their original location 34 | return new MiscFileInfo(options); 35 | } 36 | 37 | if (sourceRoot === 'app') { 38 | options.base = 'src'; 39 | 40 | if (pathParts.length === 2) { 41 | // handle files in the source root 42 | return new MainFileInfo(options); 43 | } 44 | 45 | topLevelDirectory = pathParts[1]; 46 | 47 | var typeBasedOnFolder = inflection.singularize(topLevelDirectory); 48 | options.type = typeForPodFile(sourceRelativePath) || typeBasedOnFolder; 49 | 50 | 51 | if (ext === '.hbs') { 52 | options.type = 'template'; 53 | 54 | if (/^app\/(.+\/)?(templates\/)?components/.test(sourceRelativePath)) { 55 | return new ComponentTemplateFileInfo(options); 56 | } else { 57 | return new TemplateFileInfo(options); 58 | } 59 | } 60 | 61 | switch (topLevelDirectory) { 62 | case 'mixins': 63 | return new MixinFileInfo(options); 64 | case 'components': 65 | return new ComponentFileInfo(options); 66 | case 'views': 67 | return new ViewFileInfo(options); 68 | default: 69 | return new ClassicFileInfo(options); 70 | } 71 | } else if (sourceRoot === 'tests') { 72 | var testType = pathParts[1]; 73 | 74 | var testSubjectType; 75 | 76 | if (testType === 'unit' || testType === 'integration') { 77 | 78 | options.base = 'src'; 79 | options.testType = testType; 80 | 81 | var podType = typeForPodFile(options.sourceRelativePath); 82 | var arePodsNamespaced = hasPodNamespace(options.podModulePrefix); 83 | 84 | if (podType) { 85 | var fileName = pathParts[pathParts.length - 1]; 86 | testSubjectType = fileName.replace(new RegExp('-test.js$'), ''); 87 | options.testSubjectType = testSubjectType; 88 | options.type = testSubjectType + '-' + testType + '-test'; 89 | 90 | if (arePodsNamespaced) { 91 | topLevelDirectory = pathParts[3]; 92 | 93 | 94 | return new TestFileInfo(options); 95 | } 96 | 97 | topLevelDirectory = pathParts[3]; 98 | 99 | return new TestFileInfo(options); 100 | } 101 | 102 | 103 | 104 | topLevelDirectory = pathParts[2]; 105 | testSubjectType = inflection.singularize(topLevelDirectory); 106 | options.testSubjectType = testSubjectType; 107 | 108 | options.type = testSubjectType + '-' + testType + '-test'; 109 | 110 | return new TestFileInfo(options); 111 | } else if (testType !== 'acceptance' && pathParts[2]) { 112 | options.type = testType; 113 | options.base = 'tests'; 114 | return new ClassicFileInfo(options); 115 | } else { 116 | return new MiscFileInfo(options); 117 | } 118 | } else if (sourceRoot === 'config') { 119 | return new ConfigFileInfo(options); 120 | } 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /lib/engines/classic/main-file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ClassicFileInfo = require('./classic-file-info'); 3 | 4 | var ROOT_FILES = ['main', 'router', 'routes']; 5 | 6 | var MainFileInfo = ClassicFileInfo.extend({ 7 | fileInfoType: 'MainFileInfo', 8 | 9 | init: function(options) { 10 | options.type = 'main'; 11 | 12 | if (options.sourceRelativePath === 'app/app.js') { 13 | options.name = 'main'; 14 | } 15 | this._super(options); 16 | 17 | this.collectionGroup = ''; 18 | this.collection = ''; 19 | 20 | if (ROOT_FILES.indexOf(this.name) > -1) { 21 | // do nothing (leave them in `src/` root) 22 | } else if (this.name === 'index' && this.ext === '.html') { 23 | this.collectionGroup = 'ui'; 24 | } 25 | }, 26 | 27 | populateName: function() { 28 | this.namespace = ''; 29 | 30 | if (!this.name) { 31 | this.name = path.basename(this.sourceRelativePath, this.ext); 32 | } 33 | }, 34 | 35 | // do not rewrite exports for main files 36 | updateDefaultExportToNamed: function() { } 37 | }); 38 | 39 | Object.defineProperty(MainFileInfo.prototype, 'destRelativePath', { 40 | get: function() { 41 | return path.join( 42 | 'src/', 43 | this.collectionGroup, 44 | this.collection, 45 | this.name + this.ext 46 | ); 47 | } 48 | }); 49 | 50 | module.exports = MainFileInfo; 51 | -------------------------------------------------------------------------------- /lib/engines/classic/misc-file-info.js: -------------------------------------------------------------------------------- 1 | var ClassicFileInfo = require('./classic-file-info'); 2 | 3 | var MiscFileInfo = ClassicFileInfo.extend({ 4 | fileInfoType: 'MiscFileInfo', 5 | 6 | 7 | init: function(options) { 8 | // sourceRoot needs to be set before the super call so that non js files stay in place 9 | options.sourceRoot = '.'; 10 | this._super(options); 11 | 12 | options.type = 'misc'; 13 | }, 14 | 15 | populateCollection: function() { 16 | this.collection = '..'; 17 | this.collectionGroup = ''; 18 | } 19 | }); 20 | 21 | module.exports = MiscFileInfo; -------------------------------------------------------------------------------- /lib/engines/classic/mixin-file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ClassicFileInfo = require('./classic-file-info'); 3 | 4 | var MixinFileInfo = ClassicFileInfo.extend({ 5 | fileInfoType: 'MixinFileInfo', 6 | 7 | init: function(options) { 8 | options.type = 'util'; 9 | 10 | this._super(options); 11 | this.namespace = path.join('mixins', this.namespace); 12 | this._fileNameType = 'mixin'; 13 | this.populate(); 14 | } 15 | }); 16 | 17 | module.exports = MixinFileInfo; 18 | -------------------------------------------------------------------------------- /lib/engines/classic/template-file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fse = require('fs-extra'); 3 | var compile = require('htmlbars').compile; 4 | var ClassicFileInfo = require('./classic-file-info'); 5 | 6 | function buildDetectorPlugin(fileInfo) { 7 | function Detector(options) { 8 | this.options = options; 9 | this.syntax = null; // set by HTMLBars 10 | } 11 | 12 | Detector.prototype.transform = function(ast) { 13 | this.syntax.traverse(ast, { 14 | MustacheStatement: processNode, 15 | BlockStatement: processNode, 16 | SubExpression: processNode 17 | }); 18 | 19 | return ast; 20 | }; 21 | 22 | function processNode(node) { 23 | var renderable = node.path.original; 24 | logRenderable(renderable, node); 25 | processNodeForStaticComponentHelper(node); 26 | processNodeForViewHelper(node); 27 | } 28 | 29 | function processNodeForStaticComponentHelper(node) { 30 | if (node.path.original !== 'component') { 31 | return; 32 | } 33 | 34 | var componentName = node.params[0]; 35 | if (componentName.type === 'StringLiteral') { 36 | logRenderable(componentName.value, node); 37 | } 38 | } 39 | 40 | function processNodeForViewHelper(node) { 41 | if (node.path.original !== 'view') { 42 | return; 43 | } 44 | 45 | var viewName = node.params[0]; 46 | if (viewName.type === 'StringLiteral') { 47 | fileInfo.registerViewInvocation(viewName.value); 48 | } 49 | } 50 | 51 | function logRenderable(renderableName) { 52 | if (fileInfo.renderablesInvoked.indexOf(renderableName) === -1) { 53 | fileInfo.renderablesInvoked.push(renderableName); 54 | } 55 | } 56 | 57 | return Detector; 58 | } 59 | 60 | var TemplateFileInfo = ClassicFileInfo.extend({ 61 | type: 'TemplateFileInfo', 62 | 63 | populate: function() { 64 | this._super.populate.apply(this, arguments); 65 | 66 | this.detectRenderableInvocations(); 67 | }, 68 | 69 | detectRenderableInvocations: function() { 70 | this.renderablesInvoked = []; 71 | 72 | try { 73 | compile(this._fileContents, { 74 | plugins: { 75 | ast: [buildDetectorPlugin(this)] 76 | } 77 | }); 78 | } catch (e) { 79 | // do nothing 80 | } 81 | 82 | for (var i = 0; i < this.renderablesInvoked.length; i++) { 83 | var renderable = this.renderablesInvoked[i]; 84 | 85 | this.registerRenderableUsage(renderable); 86 | } 87 | }, 88 | 89 | registerViewInvocation: function(viewName) { 90 | this._fileInfoCollection.registerViewInvocation(viewName); 91 | }, 92 | 93 | registerRenderableUsage: function(renderable) { 94 | this._fileInfoCollection.registerRenderableInvocation({ 95 | sourceRelativePath: this.sourceRelativePath, 96 | renderable: renderable, 97 | fileInfo: this 98 | }); 99 | }, 100 | 101 | updateRenderableName: function(source, dest) { 102 | if (!this._fileContents) { return; } 103 | 104 | var newContents = this._fileContents 105 | .replace(new RegExp('{{' + source,'g'), '{{' + dest) 106 | .replace(new RegExp('{{#' + source,'g'), '{{#' + dest) 107 | .replace(new RegExp('{{/' + source,'g'), '{{/' + dest); 108 | 109 | 110 | var fullPath = path.join(this.projectRoot, this.sourceRelativePath); 111 | 112 | fse.writeFileSync(fullPath, newContents, { encoding: 'utf-8' }); 113 | this._fileContents = fse.readFileSync(fullPath, { encoding: 'utf8' }); 114 | } 115 | }); 116 | 117 | module.exports = TemplateFileInfo; 118 | -------------------------------------------------------------------------------- /lib/engines/classic/test-file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ClassicFileInfo = require('./classic-file-info'); 3 | var calculateCollectionInfo = require('../../utils/calculate-collection-info'); 4 | var inflection = require('inflection'); 5 | var PodsSupport = require('../../utils/pods-support'); 6 | var typeForPodFile = PodsSupport.typeForPodFile; 7 | var hasPodNamespace = PodsSupport.hasPodNamespace; 8 | 9 | var TestFileInfo = ClassicFileInfo.extend({ 10 | populate: function() { 11 | this.testType = this.options.testType; 12 | this.testSubjectType = this.options.testSubjectType; 13 | 14 | 15 | this.populateExt(); 16 | this.populateName(); 17 | this.populateCollection(); 18 | 19 | if (this.testSubjectType === 'mixin') { 20 | this.testType = 'utils'; 21 | this.namespace = path.join(this.namespace, 'mixins'); 22 | } 23 | 24 | this.populateBucket(); 25 | this.populateFileContents(); 26 | 27 | this._fileInfoCollection.add(this); 28 | }, 29 | 30 | populateName: function() { 31 | var pathParts = this.sourceRelativePath.split('/'); 32 | var testTypeFolder = pathParts[1]; 33 | var typeFolder = pathParts[2]; 34 | var typeOfTest = ''; 35 | 36 | var podType = typeForPodFile(this.sourceRelativePath); 37 | if (podType) { 38 | var fileName = pathParts[pathParts.length - 1]; 39 | typeOfTest = fileName.replace(new RegExp('-test.js$'), ''); 40 | typeFolder = inflection.pluralize(typeOfTest); 41 | } 42 | 43 | var strippedRelativePath; 44 | 45 | if (podType) { 46 | if (!hasPodNamespace(this.podModulePrefix)) { 47 | // pods without namespace 48 | strippedRelativePath = this.sourceRelativePath 49 | .replace(new RegExp('^' + this.sourceRoot + '/' + testTypeFolder + '/(' + typeFolder + '/)?'), '') // remove leading type dir 50 | .replace(new RegExp('-test.js$'), '') // remove extension 51 | .replace(new RegExp('/' + typeOfTest + '$'), ''); // remove type name if pods 52 | } else { 53 | // pods with namespace 54 | var pathRootRegex = new RegExp(this.podModulePrefix + '\/'); 55 | strippedRelativePath = this.sourceRelativePath 56 | .replace(pathRootRegex, '') // don't care if the top directory is pods 57 | .replace(new RegExp('^' + this.sourceRoot + '/' + testTypeFolder + '/(' + typeFolder + '/)?'), '') // remove leading type dir 58 | .replace(new RegExp('-test.js$'), '') // remove extension 59 | .replace(new RegExp('/' + typeOfTest + '$'), ''); // remove type name if pods 60 | } 61 | } else { 62 | // classic 63 | strippedRelativePath = this.sourceRelativePath 64 | .replace(pathRootRegex, '') // don't care if the top directory is pods 65 | .replace(new RegExp('^' + this.sourceRoot + '/' + testTypeFolder + '/(' + typeFolder + '/)?'), '') // remove leading type dir 66 | .replace(new RegExp('-test.js$'), ''); // remove extension 67 | } 68 | 69 | var parts = strippedRelativePath.split('/'); 70 | 71 | this.name = parts.pop(); 72 | this.namespace = parts.join('/'); 73 | }, 74 | 75 | populateCollection: function() { 76 | var values = calculateCollectionInfo(this.testSubjectType); 77 | 78 | this.collection = values.collection; 79 | this.collectionGroup = values.collectionGroup; 80 | } 81 | }); 82 | 83 | Object.defineProperty(TestFileInfo.prototype, 'destRelativePath', { 84 | get: function() { 85 | if (this.collection === 'components') { 86 | var renderableName = path.join(this.namespace, this.name); 87 | var privateRenderableInvoker = this._fileInfoCollection.detectPrivateRenderableInvoker(renderableName); 88 | 89 | if (privateRenderableInvoker && this._privateRenderableInvoker) { 90 | var invokerLocation = path.dirname(this._privateRenderableInvoker.destRelativePath); 91 | var invokerInComponentsCollection = privateRenderableInvoker.collection === 'components'; 92 | var invokerInPrivateCollection = invokerLocation.indexOf('-components') > -1; 93 | var privateCollection = invokerInPrivateCollection || invokerInComponentsCollection? '' : '-components'; 94 | 95 | return path.join( 96 | invokerLocation, 97 | privateCollection, 98 | this.namespace, 99 | this.name, 100 | this.type + this.ext 101 | ); 102 | } 103 | } 104 | 105 | return path.join( 106 | 'src/', 107 | this.collectionGroup, 108 | this.collection, 109 | this.namespace, 110 | this.name, 111 | this.type + this.ext 112 | ); 113 | } 114 | }); 115 | 116 | module.exports = TestFileInfo; 117 | -------------------------------------------------------------------------------- /lib/engines/classic/view-file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ClassicFileInfo = require('./classic-file-info'); 3 | 4 | var ViewFileInfo = ClassicFileInfo.extend({ 5 | fileInfoType: 'ViewFileInfo', 6 | 7 | repopulate: function() { 8 | var viewName = path.join(this.namespace, this.name); 9 | 10 | if (this._fileInfoCollection.viewInvokedInTemplate(viewName)) { 11 | this.collectionGroup = 'ui'; 12 | this.collection = 'views'; 13 | } 14 | } 15 | }); 16 | 17 | module.exports = ViewFileInfo; 18 | -------------------------------------------------------------------------------- /lib/engines/index.js: -------------------------------------------------------------------------------- 1 | var ClassicEngine = require('./classic'); 2 | 3 | module.exports = { 4 | 'classic': ClassicEngine 5 | }; 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var RSVP = require('rsvp'); 2 | var walkSync = require('walk-sync'); 3 | var fse = require('fs-extra'); 4 | var move = RSVP.denodeify(fse.move); 5 | var CoreObject = require('core-object'); 6 | var engines = require('./engines'); 7 | var FileInfoCollection = require('./models/file-info-collection'); 8 | var Logger = require('./models/logger'); 9 | 10 | var Engine = CoreObject.extend({ 11 | init: function() { 12 | this._super.apply(this, arguments); 13 | 14 | var engineType = typeof this.engine; 15 | 16 | if (engineType === 'string') { 17 | this.engine = engines[this.engine]; 18 | } else if (engineType === 'function') { 19 | // let the user provide their own engine.buildFor 20 | } else { 21 | this.engine = engines.classic; 22 | } 23 | 24 | if (!this.engine) { 25 | throw new Error('Can not find engine to use for migration!'); 26 | } 27 | 28 | this._promise = null; 29 | this._fileInfoCollection = new FileInfoCollection(); 30 | this._logger = new Logger({ 31 | projectRoot: this.projectRoot 32 | }); 33 | this._fileInfos = []; 34 | }, 35 | 36 | _queueMoveFile: function(source, dest) { 37 | if (source === dest) { 38 | return; 39 | } 40 | 41 | var logger = this._logger; 42 | 43 | this._promise = this._promise 44 | .then(function() { 45 | return move(source, dest); 46 | }) 47 | .then(function() { 48 | logger.movedFile(source, dest); 49 | }); 50 | }, 51 | 52 | _safeWalkSync: function(dir) { 53 | var projectDir = this.projectRoot + '/' + dir; 54 | return fse.existsSync(projectDir) ? walkSync(projectDir) : []; 55 | }, 56 | 57 | _filesInDir: function(dir) { 58 | return this._safeWalkSync(dir) 59 | // add the dir back as a prefix 60 | .map( relativePath => dir + '/' + relativePath ) 61 | // remove entries for directories 62 | .filter( relativePath => relativePath.slice(-1) !== '/'); 63 | }, 64 | 65 | _queueRemoveEmptyDirs: function(dir) { 66 | this._promise = this._promise 67 | .then(() => { 68 | var contents = this._safeWalkSync(dir); 69 | var files = contents 70 | .filter(function(entry) { 71 | return entry.slice(-1) !== '/' && entry.slice(-8) !== '.gitkeep'; 72 | }); 73 | var directories = contents 74 | .filter(function(entry) { 75 | return entry.slice(-1) === '/'; 76 | }); 77 | 78 | // there are no files left 79 | if (files.length === 0) { 80 | fse.removeSync(this.projectRoot + '/' + dir); 81 | } 82 | 83 | // there are files, so we need to only delete empty dirs 84 | for (var i = 0; i < directories.length; i++){ 85 | var directory = directories[i]; 86 | var hasFiles = files 87 | .some(function(file) { 88 | return file.indexOf(directory) === 0; 89 | }); 90 | 91 | if (!hasFiles) { 92 | fse.removeSync(this.projectRoot + '/' + dir + '/' + directory); 93 | } 94 | } 95 | }); 96 | }, 97 | 98 | processFiles: function() { 99 | this._promise = RSVP.resolve(); 100 | var inputFiles = [].concat( 101 | this._filesInDir('app'), 102 | this._filesInDir('tests'), 103 | this._filesInDir('config') 104 | ); 105 | 106 | var verbose = this.verbose; 107 | var logger = this._logger; 108 | var projectRoot = this.projectRoot; 109 | var i; 110 | 111 | // build all the fileInfo objects 112 | for (i = 0; i < inputFiles.length; i++) { 113 | var relativePath = inputFiles[i]; 114 | 115 | this.fileInfoFor(relativePath); 116 | } 117 | 118 | this.finalizeFileDiscovery(); 119 | 120 | // move them all. this is split so that 121 | // they can all be populated which helps 122 | // determine the destRelativePath (which is 123 | // a getter) 124 | this._fileInfos.forEach(function(fileInfo) { 125 | var source = projectRoot + '/' + fileInfo.sourceRelativePath; 126 | var dest = projectRoot + '/' + fileInfo.destRelativePath; 127 | 128 | this._queueMoveFile(source, dest); 129 | }, this); 130 | 131 | this._queueRemoveEmptyDirs('app'); 132 | this._queueRemoveEmptyDirs('tests'); 133 | 134 | return this._promise 135 | .then(function() { 136 | /*eslint no-console: 0 */ 137 | if (verbose) { 138 | console.log(logger.flush()); 139 | } 140 | }); 141 | }, 142 | 143 | fileInfoFor: function(path) { 144 | var fileInfo = this.engine.buildFor(path, { 145 | projectRoot: this.projectRoot, 146 | projectName: this.projectName, 147 | podModulePrefix: this.podModulePrefix || '', 148 | _fileInfoCollection: this._fileInfoCollection 149 | }); 150 | 151 | // not all files return a FileInfo 152 | if (fileInfo) { 153 | this._fileInfos.push(fileInfo); 154 | } 155 | 156 | return fileInfo; 157 | }, 158 | 159 | finalizeFileDiscovery: function() { 160 | // invoke the `repopulate` hook on each file 161 | // allowing it to tweak/modify path related 162 | // properties after all files have been processed 163 | this._fileInfos.forEach(function(fileInfo) { 164 | fileInfo.repopulate(); 165 | }); 166 | } 167 | }); 168 | 169 | module.exports = Engine; 170 | -------------------------------------------------------------------------------- /lib/models/file-info-collection.js: -------------------------------------------------------------------------------- 1 | var CoreObject = require('core-object'); 2 | 3 | var FileInfoCollection = CoreObject.extend({ 4 | init: function() { 5 | this._super.init && this._super.init.apply(this, arguments); 6 | 7 | this._fileInfos = []; 8 | this._bucketCounts = {}; 9 | this._renderableInvocations = {}; 10 | this._viewInvocations = {}; 11 | }, 12 | 13 | add: function(fileInfo) { 14 | this._fileInfos.push(fileInfo); 15 | 16 | var bucket = fileInfo.bucket; 17 | if (!this._bucketCounts[bucket]) { 18 | this._bucketCounts[bucket] = 0; 19 | } 20 | 21 | this._bucketCounts[bucket]++; 22 | }, 23 | 24 | registerViewInvocation: function(viewName) { 25 | this._viewInvocations[viewName] = true; 26 | }, 27 | 28 | registerRenderableInvocation: function(options) { 29 | var invocationDetails = this._renderableInvocations[options.renderable]; 30 | if (!invocationDetails) { 31 | this._renderableInvocations[options.renderable] = invocationDetails = { 32 | sourceRelativePaths: [], 33 | fileInfos: [] 34 | }; 35 | } 36 | 37 | invocationDetails.sourceRelativePaths.push(options.sourceRelativePath); 38 | invocationDetails.fileInfos.push(options.fileInfo); 39 | }, 40 | 41 | filesInBucket: function(bucket) { 42 | return this._bucketCounts[bucket]; 43 | }, 44 | 45 | detectPrivateRenderableInvoker: function(renderableName) { 46 | var invocationDetails = this._renderableInvocations[renderableName]; 47 | 48 | if (invocationDetails && invocationDetails.fileInfos.length === 1) { 49 | return invocationDetails.fileInfos[0]; 50 | } 51 | 52 | return null; 53 | }, 54 | 55 | viewInvokedInTemplate: function(viewName) { 56 | return this._viewInvocations[viewName]; 57 | } 58 | }); 59 | 60 | module.exports = FileInfoCollection; 61 | -------------------------------------------------------------------------------- /lib/models/file-info.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var CoreObject = require('core-object'); 3 | var fse = require('fs-extra'); 4 | var existsSync = require('exists-sync'); 5 | var jscodeshift = require('jscodeshift'); 6 | var isTypeInSingleTypeCollection = require('../utils/is-type-in-single-type-collection'); 7 | var defaultTypeForCollection = require('../utils/default-type-for-collection'); 8 | var calculateCollectionInfo = require('../utils/calculate-collection-info'); 9 | var importDeclarationsTransform = require('../transforms/import-declarations'); 10 | var renameImportHelperTransform = require('../transforms/rename-helper-import'); 11 | var inflection = require('inflection'); 12 | var PodsSupport = require('../utils/pods-support'); 13 | var typeForPodFile = PodsSupport.typeForPodFile; 14 | var hasPodNamespace = PodsSupport.hasPodNamespace; 15 | 16 | var FileInfo = CoreObject.extend({ 17 | type: 'FileInfo', 18 | 19 | init: function(_options) { 20 | this._super.init && this._super.init.apply(this, arguments); 21 | 22 | var options = _options || {}; 23 | this.options = options; 24 | this.projectRoot = options.projectRoot; 25 | this.podModulePrefix = options.podModulePrefix; 26 | 27 | this.sourceRelativePath = options.sourceRelativePath; 28 | this.type = options.type; 29 | 30 | this.ext = options.ext; 31 | this.base = options.base || 'src'; 32 | this.name = options.name; 33 | this._bucket = options.bucket; 34 | this.namespace = options.namespace; 35 | this.sourceRoot = options.sourceRoot; 36 | this.collection = options.collection; 37 | this.collectionGroup = options.collectionGroup; 38 | this.destRelativePath = options.destRelativePath; 39 | 40 | this._fileInfoCollection = options._fileInfoCollection; 41 | 42 | this.populate(); 43 | }, 44 | 45 | populate: function() { 46 | this.populateExt(); 47 | this.populateName(); 48 | this.populateCollection(); 49 | this.populateBucket(); 50 | this.populateFileContents(); 51 | 52 | this._fileInfoCollection.add(this); 53 | }, 54 | 55 | populateBucket: function() { 56 | this.bucket = path.join( 57 | this.base, 58 | this.collectionGroup, 59 | this.collection, 60 | this.namespace, 61 | this.name 62 | ); 63 | }, 64 | 65 | populateExt: function() { 66 | if (this.ext) { return this.ext; } 67 | 68 | return this.ext = path.extname(this.sourceRelativePath); 69 | }, 70 | 71 | populateName: function() { 72 | if (this.name) { 73 | return; 74 | } 75 | 76 | var pathParts = this.sourceRelativePath.split('/'); 77 | var typeFolder = pathParts[1]; 78 | 79 | var podType = typeForPodFile(this.sourceRelativePath); 80 | var arePodsNamespaced = hasPodNamespace(this.podModulePrefix); 81 | 82 | var strippedRelativePath; 83 | var pathRootRegex; 84 | var fileName; 85 | var type; 86 | 87 | if (typeFolder === this.podModulePrefix) { 88 | if (pathParts[2] === 'components') { 89 | typeFolder = 'components'; 90 | } else { 91 | fileName = pathParts[pathParts.length - 1]; 92 | type = fileName.split('.')[0]; 93 | typeFolder = inflection.pluralize(type); 94 | } 95 | } 96 | 97 | strippedRelativePath = this.sourceRelativePath; 98 | 99 | // default/classic/namespaced-pods 100 | if (this.podModulePrefix) { 101 | pathRootRegex = new RegExp('(app\/)?' + this.podModulePrefix + '\/(components\/)?'); 102 | strippedRelativePath = strippedRelativePath.replace(pathRootRegex, ''); // don't care if path begins with pods; 103 | } 104 | 105 | strippedRelativePath = strippedRelativePath 106 | .replace(new RegExp('^' + this.sourceRoot + '/' + typeFolder + '/'), '') // remove leading type dir 107 | .replace(new RegExp(this.ext + '$'), '') // remove extension 108 | .replace(new RegExp('/' + this.type + '$'), ''); // remove trailing type 109 | 110 | if (!arePodsNamespaced) { 111 | if (podType) { 112 | fileName = pathParts[pathParts.length - 1]; 113 | type = fileName.split('.')[0]; 114 | typeFolder = inflection.pluralize(type); 115 | 116 | pathRootRegex = new RegExp('(app\/)?'); 117 | var podTypeRegex = new RegExp('(components\/)?'); 118 | 119 | strippedRelativePath = this.sourceRelativePath 120 | .replace(pathRootRegex, '') 121 | .replace(podTypeRegex, '') 122 | .replace(new RegExp(typeFolder + '/'), '') // remove leading type dir 123 | .replace(new RegExp(this.ext + '$'), '') // remove extension 124 | .replace(new RegExp('/' + this.type + '$'), ''); // remove trailing type 125 | } 126 | } 127 | 128 | var parts = strippedRelativePath.split('/'); 129 | this.name = parts.pop(); 130 | this.namespace = parts.join('/'); 131 | }, 132 | 133 | populateCollection: function(_type) { 134 | var type = _type || this.type; 135 | var values = calculateCollectionInfo(type); 136 | 137 | this.collection = values.collection; 138 | this.collectionGroup = values.collectionGroup; 139 | }, 140 | 141 | populateFileContents: function() { 142 | var fullPath = path.join(this.projectRoot, this.sourceRelativePath); 143 | if (!existsSync(fullPath)) { 144 | return; 145 | } 146 | 147 | this._fileContents = fse.readFileSync(fullPath, { encoding: 'utf8' }); 148 | }, 149 | 150 | updateImports: function() { 151 | if (this.ext !== '.js' || !this._fileContents) { return; } // only process JavaScript files 152 | 153 | var appName = this.options.projectName; 154 | 155 | try { 156 | var newContents = importDeclarationsTransform( 157 | { source: this._fileContents, 158 | fileInfo: this, 159 | appName: appName, 160 | fileInfos: this._fileInfoCollection._fileInfos 161 | }, 162 | { jscodeshift }); 163 | 164 | // Fixes "import { helper } from '@ember/component/helper'" 165 | // to "import { helper as buildHelper } from '@ember/component/helper'" 166 | newContents = renameImportHelperTransform( 167 | { 168 | source: newContents 169 | }, 170 | { jscodeshift }); 171 | 172 | var fullPath = path.join(this.projectRoot, this.sourceRelativePath); 173 | 174 | fse.writeFileSync(fullPath, newContents, { encoding: 'utf-8' }); 175 | this._fileContents = newContents; 176 | } catch(e) { 177 | // eslint-disable-next-line no-console 178 | console.log('error parsing file `' + this.sourceRelativePath + '` failed to apply codeshift. Possible invalid JS file. Returning original file unchanged. error: ' + e.message); 179 | } 180 | }, 181 | 182 | repopulate: function() { 183 | this.updateImports(); 184 | 185 | var inComponentsCollection = this.collection === 'components'; 186 | var renderableName = path.join(this.namespace, this.name); 187 | 188 | if (inComponentsCollection) { 189 | var privateRenderableInvoker = this._fileInfoCollection.detectPrivateRenderableInvoker(renderableName); 190 | 191 | this._privateRenderableInvoker = privateRenderableInvoker; 192 | 193 | if (privateRenderableInvoker) { 194 | var invokerPath = path.join(privateRenderableInvoker.namespace, privateRenderableInvoker.name); 195 | if (invokerPath === this.namespace) { 196 | var source = path.join(this.namespace, this.name); 197 | var dest = this.name; 198 | 199 | privateRenderableInvoker.updateRenderableName(source, dest); 200 | this.namespace = ''; 201 | } 202 | } 203 | } 204 | 205 | var isDefaultTypeForCollection = defaultTypeForCollection(this.collection) === this.type; 206 | var shouldUseDotFormNaming = this.shouldUseDotFormNaming(); 207 | 208 | if (!isDefaultTypeForCollection && shouldUseDotFormNaming) { 209 | this.updateDefaultExportToNamed(); 210 | } 211 | }, 212 | 213 | updateDefaultExportToNamed: function() { 214 | if (this.ext !== '.js') { return; } 215 | if (!this._fileContents) { return; } 216 | 217 | var newContents = this._fileContents 218 | .replace('export default ', 'export const ' + this.type + ' = '); 219 | 220 | 221 | var fullPath = path.join(this.projectRoot, this.sourceRelativePath); 222 | 223 | fse.writeFileSync(fullPath, newContents, { encoding: 'utf-8' }); 224 | this._fileContents = newContents; 225 | }, 226 | 227 | shouldUseDotFormNaming: function() { 228 | var filesInBucket = this._fileInfoCollection.filesInBucket(this.bucket); 229 | 230 | // when more than one file is in the same bucket 231 | // we can never use . 232 | if (filesInBucket > 1) { 233 | return false; 234 | } 235 | 236 | // if we are in a collection that only contains one type 237 | // then use . when only one file is present 238 | if (isTypeInSingleTypeCollection(this.type)) { 239 | return true; 240 | } 241 | 242 | if (this.type === 'util') { 243 | return true; 244 | } 245 | 246 | var isDefaultTypeForCollection = defaultTypeForCollection(this.collection) === this.type; 247 | 248 | // use . in the root of a collection if it is the default type 249 | return isDefaultTypeForCollection; 250 | } 251 | }); 252 | 253 | Object.defineProperty(FileInfo.prototype, 'destRelativePath', { 254 | get: function() { 255 | var baseRelativePath = path.join( 256 | this.base + '/', 257 | this.collectionGroup, 258 | this.collection 259 | ); 260 | 261 | if (this._privateRenderableInvoker) { 262 | var invokerLocation = path.dirname(this._privateRenderableInvoker.destRelativePath); 263 | var invokerInComponentsCollection = this._privateRenderableInvoker.collection === 'components'; 264 | var invokerInPrivateCollection = invokerLocation.indexOf('-components') > -1; 265 | var privateCollection = invokerInPrivateCollection || invokerInComponentsCollection? '' : '-components'; 266 | 267 | baseRelativePath = path.join( 268 | invokerLocation, 269 | privateCollection 270 | ); 271 | } 272 | var destRelativePath; 273 | 274 | if (this.shouldUseDotFormNaming() && this.type !== 'component') { 275 | destRelativePath = path.join( 276 | baseRelativePath, 277 | this.namespace, 278 | this.name + this.ext 279 | ); 280 | } else { 281 | destRelativePath = path.join( 282 | baseRelativePath, 283 | this.namespace, 284 | this.name, 285 | (this._fileNameType || this.type) + this.ext 286 | ); 287 | } 288 | 289 | return destRelativePath; 290 | } 291 | }); 292 | 293 | module.exports = FileInfo; 294 | -------------------------------------------------------------------------------- /lib/models/logger.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var chalk = require('chalk'); 3 | var CoreObject = require('core-object'); 4 | var Table = require('cli-table2'); 5 | 6 | var Logger = CoreObject.extend({ 7 | init: function() { 8 | this._super.init && this._super.init.apply(this, arguments); 9 | 10 | this._movedFiles = {}; 11 | this._renamedRenderables = []; 12 | }, 13 | 14 | movedFile: function(_source, _destination) { 15 | var source = path.relative(this.projectRoot, _source); 16 | var destination = path.relative(this.projectRoot, _destination); 17 | 18 | this._movedFiles[source] = destination; 19 | }, 20 | 21 | flush: function() { 22 | // guard against environments such as travis that report zero columns 23 | var columns = process.stdout.columns || 80; 24 | var maxWidth = columns - 4; 25 | var halfWidth = Math.floor(maxWidth / 2); 26 | var table = new Table({ 27 | head: ['Source', 'Destination'], 28 | colWidths: [halfWidth, halfWidth] 29 | }); 30 | 31 | 32 | for (var source in this._movedFiles) { 33 | var destination = this._movedFiles[source]; 34 | 35 | table.push([chalk.cyan(source), chalk.yellow(destination)]); 36 | } 37 | 38 | return table.toString(); 39 | } 40 | }); 41 | 42 | module.exports = Logger; 43 | -------------------------------------------------------------------------------- /lib/transforms/import-declarations.js: -------------------------------------------------------------------------------- 1 | var path_utils = require('../utils/path'); 2 | 3 | function transformer(file, api) { 4 | var j = api.jscodeshift; 5 | 6 | var root = j(file.source); 7 | 8 | root.find(j.ImportDeclaration) 9 | .find(j.Literal) 10 | .forEach(function(path) { 11 | var importPath = path.value.value + '.js'; 12 | var appName = file.appName; 13 | 14 | if (!appName) { 15 | // skip import transforms if appName is not set 16 | return; 17 | } 18 | 19 | // determine the actual path that importPath points to 20 | // TODO: extract these resolutions into classic-file-info and test-file-info 21 | var relative = path_utils.isRelative(importPath); 22 | if (relative) { 23 | var sourceRelativePath = file.fileInfo.sourceRelativePath; 24 | 25 | if (sourceRelativePath.startsWith('tests/')) { 26 | importPath = path_utils.makeAbsolute('app/' + sourceRelativePath, importPath, appName); 27 | 28 | // see if we stayed within tests 29 | var inTestsPrefix = appName + '/app/tests/'; 30 | if (importPath.startsWith(inTestsPrefix)) { 31 | importPath = appName + '/tests/' + importPath.substring(inTestsPrefix.length); 32 | } 33 | } else { 34 | importPath = path_utils.makeAbsolute(sourceRelativePath, importPath, appName); 35 | } 36 | } else { 37 | // check if import path starts with appName and add /app 38 | if (importPath.startsWith(appName + '/') && !importPath.startsWith(appName + '/app/')) { 39 | importPath = appName + '/app' + importPath.substring(appName.length); 40 | } 41 | } 42 | 43 | var targetFileInfo = file.fileInfos.find(function(f) { 44 | return (appName + '/' + f.sourceRelativePath) === importPath; 45 | }); 46 | 47 | if (!targetFileInfo) { 48 | // TODO error message 49 | return; 50 | } 51 | 52 | var newImportPath = appName + '/' + targetFileInfo.destRelativePath; 53 | if (relative) { 54 | newImportPath = path_utils.makeRelative(file.fileInfo.destRelativePath, targetFileInfo.destRelativePath); 55 | } 56 | // remove extension 57 | newImportPath = newImportPath.slice(0, -targetFileInfo.ext.length); 58 | j(path).replaceWith(j.literal(newImportPath)); 59 | }); 60 | 61 | return root.toSource(); 62 | } 63 | 64 | module.exports = transformer; 65 | -------------------------------------------------------------------------------- /lib/transforms/rename-helper-import.js: -------------------------------------------------------------------------------- 1 | function transformer(file, api) { 2 | var j = api.jscodeshift; 3 | 4 | var foundHelper = false; 5 | 6 | var root = j(file.source); 7 | 8 | // Replaces: 9 | // import { helper } from '@ember/component/helper'; 10 | // With: 11 | // import { helper as buildHelper } from '@ember/component/helper'; 12 | root.find(j.ImportDeclaration, { 13 | source: { value: '@ember/component/helper' }, 14 | }).filter(path => { 15 | return path.value.specifiers[0].local.name === 'helper'; 16 | }) 17 | .forEach(function(path) { 18 | foundHelper = true; 19 | var specifier = j.importSpecifier( 20 | j.identifier('helper'), 21 | j.identifier('buildHelper') 22 | ); 23 | 24 | path.value.specifiers[0] = specifier; 25 | }); 26 | 27 | // Replace helper(...) with buildHelper(...) 28 | if (foundHelper) { 29 | root.find(j.CallExpression, { 30 | callee: { name: 'helper' } 31 | }).forEach(path => { 32 | path.value.callee.name = 'buildHelper'; 33 | }); 34 | } 35 | 36 | return root.toSource(); 37 | } 38 | 39 | module.exports = transformer; 40 | -------------------------------------------------------------------------------- /lib/utils/calculate-collection-info.js: -------------------------------------------------------------------------------- 1 | var inflection = require('inflection'); 2 | 3 | var PLURAL_OVERRIDES = { 4 | 'mirage': 'mirage', 5 | 'html': 'html' 6 | }; 7 | 8 | module.exports = function(type) { 9 | var pluralizedType = inflection.pluralize(type, PLURAL_OVERRIDES[type]); 10 | var collection, collectionGroup; 11 | 12 | switch (type) { 13 | case 'service': 14 | collection = 'services'; 15 | collectionGroup = ''; 16 | break; 17 | 18 | case 'util': 19 | case 'mixin': 20 | collection = 'utils'; 21 | collectionGroup = ''; 22 | break; 23 | 24 | case 'authenticator': 25 | case 'authorizer': 26 | case 'session-store': 27 | collection = pluralizedType; 28 | collectionGroup = 'simple-auth'; 29 | break; 30 | 31 | case 'adapter': 32 | case 'serializer': 33 | case 'model': 34 | collection = 'models'; 35 | collectionGroup = 'data'; 36 | break; 37 | 38 | case 'transform': 39 | collection = 'transforms'; 40 | collectionGroup = 'data'; 41 | break; 42 | 43 | case 'view': 44 | case 'route': 45 | case 'controller': 46 | case 'template': 47 | collection = 'routes'; 48 | collectionGroup = 'ui'; 49 | break; 50 | 51 | case 'helper': 52 | case 'component': 53 | collection = 'components'; 54 | collectionGroup = 'ui'; 55 | break; 56 | 57 | case 'style': 58 | collection = 'styles'; 59 | collectionGroup = 'ui'; 60 | break; 61 | 62 | case 'initializer': 63 | case 'instance-initializer': 64 | collection = pluralizedType; 65 | collectionGroup = 'init'; 66 | break; 67 | 68 | default: 69 | collection = pluralizedType; 70 | collectionGroup = ''; 71 | } 72 | 73 | return { 74 | collection: collection, 75 | collectionGroup: collectionGroup 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /lib/utils/default-type-for-collection.js: -------------------------------------------------------------------------------- 1 | var inflection = require('inflection'); 2 | 3 | module.exports = function(collection) { 4 | return inflection.singularize(collection); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/utils/is-type-in-single-type-collection.js: -------------------------------------------------------------------------------- 1 | module.exports = function(type) { 2 | switch (type) { 3 | // data 4 | case 'adapter': 5 | case 'serializer': 6 | case 'model': 7 | return false; 8 | 9 | // routes 10 | case 'route': 11 | case 'controller': 12 | case 'template': 13 | return false; 14 | 15 | // components 16 | case 'component': 17 | return false; 18 | 19 | default: 20 | return true; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /lib/utils/path.js: -------------------------------------------------------------------------------- 1 | var p = require('path'); 2 | 3 | function isRelative(path) { 4 | return path[0] === '.'; 5 | } 6 | 7 | function makeRelative(from, to) { 8 | var path = p.relative(p.dirname(from), to); 9 | if (!isRelative(path)) { 10 | path = './' + path; 11 | } 12 | return path; 13 | } 14 | 15 | function makeAbsolute(base, path, appName) { 16 | var baseDir = p.dirname(`/${appName}/${base}`); 17 | 18 | // check if path goes over the root config folder and adjust base 19 | var configIndex = path.indexOf('/config/'); 20 | if (configIndex >= 0) { 21 | var configPath = p.resolve(baseDir, path.substring(0, configIndex) + '/config').substring(1); 22 | if (configPath == `${appName}/app/config`) { 23 | baseDir += '/..'; 24 | } 25 | } 26 | 27 | return p.resolve(baseDir, path).substring(1); 28 | } 29 | 30 | module.exports = { 31 | isRelative, 32 | makeRelative, 33 | makeAbsolute 34 | }; -------------------------------------------------------------------------------- /lib/utils/pods-support.js: -------------------------------------------------------------------------------- 1 | 2 | const podFileTypes = [ 3 | 'component', 4 | 'template', 5 | 'controller', 6 | 'route', 7 | 'model', 8 | 'adapter', 9 | 'serializer' 10 | ]; 11 | 12 | function typeForPodFile(path) { 13 | const parts = path.split('/'); 14 | const fileName = parts[parts.length - 1]; 15 | 16 | const type = podFileTypes.find(f => fileName.startsWith(f)); 17 | 18 | return type; 19 | } 20 | 21 | function hasPodNamespace(namespace) { 22 | return ( 23 | namespace !== undefined && 24 | namespace !== null && 25 | namespace !== '' 26 | ); 27 | } 28 | 29 | module.exports = { 30 | typeForPodFile, 31 | hasPodNamespace 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-module-migrator", 3 | "version": "0.8.0", 4 | "description": "Migration script for ember-cli apps.", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "ember-module-migrator": "./bin/ember-module-migrator" 8 | }, 9 | "scripts": { 10 | "test": "mocha test/*-test.js test/**/*-test.js" 11 | }, 12 | "author": "Robert Jackson ", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "assert-diff": "^2.0.3", 16 | "common-tags": "^1.7.2", 17 | "espower-loader": "^1.0.0", 18 | "fixturify": "^1.0.1", 19 | "jscodeshift": "^0.3.30", 20 | "mocha": "^6.1.1", 21 | "mocha-eslint": "^3.0.1", 22 | "power-assert": "^1.3.1", 23 | "rimraf": "^2.5.2" 24 | }, 25 | "dependencies": { 26 | "chalk": "^2.0.1", 27 | "cli-table2": "^0.2.0", 28 | "core-object": "^3.1.3", 29 | "exists-sync": "^0.0.4", 30 | "fs-extra": "^7.0.1", 31 | "htmlbars": "^0.14.17", 32 | "inflection": "^1.10.0", 33 | "nopt": "^4.0.1", 34 | "rsvp": "^4.8.4", 35 | "walk-sync": "^1.0.0" 36 | }, 37 | "engines": { 38 | "node": ">= 4" 39 | }, 40 | "directories": { 41 | "test": "test" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/rwjblue/ember-module-migrator.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/rwjblue/ember-module-migrator/issues" 49 | }, 50 | "homepage": "https://github.com/rwjblue/ember-module-migrator#readme" 51 | } 52 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "mocha": true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/enable-power-assert.js: -------------------------------------------------------------------------------- 1 | require('espower-loader')({ 2 | // directory where match starts with 3 | cwd: process.cwd(), 4 | // glob pattern using minimatch module 5 | pattern: 'test/**/*.js' 6 | }); 7 | -------------------------------------------------------------------------------- /test/engines/classic-test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var assert = require('power-assert'); 3 | var assertDiff = require('assert-diff'); 4 | var fixturify = require('fixturify'); 5 | var fse = require('fs-extra'); 6 | var Migrator = require('../../lib'); 7 | 8 | assertDiff.options.strict = true; 9 | 10 | describe('classic engine', function() { 11 | describe('fileInfoFor', function() { 12 | var engine; 13 | 14 | beforeEach(function() { 15 | engine = new Migrator({ 16 | projectRoot: '.', 17 | projectName: 'my-app' 18 | }); 19 | }); 20 | 21 | it('returns an object', function() { 22 | var file = engine.fileInfoFor('app/components/foo-bar.js'); 23 | 24 | assert(file); 25 | }); 26 | 27 | describe('file info properties', function() { 28 | function confirm(path, expected) { 29 | it(path + ' has the expected properties', function() { 30 | var file = engine.fileInfoFor(path); 31 | 32 | var keys = Object.keys(expected); 33 | for (var i = 0; i < keys.length; i++) { 34 | var key = keys[i]; 35 | 36 | assert(file[key] === expected[key]); 37 | } 38 | }); 39 | } 40 | 41 | confirm('app/components/foo-bar.js', {type: 'component', name: 'foo-bar', namespace: '', collection: 'components', collectionGroup: 'ui'}); 42 | confirm('app/helpers/foo.js', {type: 'helper', name: 'foo', namespace: '', collection: 'components', collectionGroup: 'ui'}); 43 | confirm('app/helpers/foo/bar.js', {type: 'helper', name: 'bar', namespace: 'foo', collection: 'components', collectionGroup: 'ui'}); 44 | confirm('app/components/foo-bar/component.js', {type: 'component', name: 'foo-bar', namespace: '', collection: 'components', collectionGroup: 'ui' }); 45 | confirm('app/templates/components/foo-bar.hbs', {type: 'template', name: 'foo-bar', namespace: '', collection: 'components', collectionGroup: 'ui'}); 46 | confirm('app/components/foo-bar/template.hbs', {type: 'template', name: 'foo-bar', namespace: '', collection: 'components', collectionGroup: 'ui'}); 47 | confirm('app/routes/foo-bar.js', {type: 'route', name: 'foo-bar', namespace: '', collection: 'routes', collectionGroup: 'ui'}); 48 | confirm('app/routes/foo-bar/baz/index.js', {type: 'route', name: 'index', namespace: 'foo-bar/baz', collection: 'routes', collectionGroup: 'ui'}); 49 | confirm('app/templates/foo-bar.hbs', {type: 'template', name: 'foo-bar', collection: 'routes', collectionGroup: 'ui'}); 50 | confirm('app/templates/foo-bar/baz/index.hbs', {type: 'template', name: 'index', namespace: 'foo-bar/baz', collection: 'routes', collectionGroup: 'ui'}); 51 | confirm('app/adapters/application.js', {type: 'adapter', name: 'application', namespace: '', collection: 'models', collectionGroup: 'data' }); 52 | confirm('app/app.js', {type: 'main', name: 'main', namespace: '', collection: '', collectionGroup: ''}); 53 | confirm('app/router.js', {type: 'main', name: 'router', namespace: '', collection: '', collectionGroup: ''}); 54 | confirm('app/index.md', { name: 'index', namespace: '', collection: '', collectionGroup: '' }); 55 | confirm('app/index.html', { name: 'index', namespace: '', collection: '', collectionGroup: 'ui' }); 56 | confirm('app/styles/app.css', { type: 'style', name: 'app', namespace: '', collection: 'styles', collectionGroup: 'ui' }); 57 | confirm('app/styles/components/badges.css', { type: 'style', name: 'badges', namespace: 'components', collection: 'styles', collectionGroup: 'ui' }); 58 | confirm('app/mixins/foo/bar.js', { type: 'util', name: 'bar', namespace: 'mixins/foo', collection: 'utils' }); 59 | confirm('app/authorizers/oauth2.js', { type: 'authorizer', name: 'oauth2', namespace: '', collection: 'authorizers', collectionGroup: 'simple-auth'}); 60 | 61 | // tests 62 | confirm('tests/unit/routes/foo-bar-test.js', { type: 'route-unit-test', name: 'foo-bar', collection: 'routes', collectionGroup: 'ui'}); 63 | confirm('tests/unit/mixins/bar-test.js', { type: 'mixin-unit-test', name: 'bar', collection: 'utils', collectionGroup: ''}); 64 | confirm('tests/unit/services/foo-test.js', { type: 'service-unit-test', name: 'foo', collection: 'services', collectionGroup: ''}); 65 | confirm('tests/unit/utils/foo-test.js', { type: 'util-unit-test', name: 'foo', collection: 'utils', collectionGroup: ''}); 66 | confirm('tests/unit/validators/foo-test.js', { type: 'validator-unit-test', name: 'foo', collection: 'validators', collectionGroup: ''}); 67 | }); 68 | 69 | describe('file info destinations', function() { 70 | var mappings = { 71 | 'app/components/foo-bar.js': 'src/ui/components/foo-bar/component.js', 72 | 'app/components/qux-derp/component.js': 'src/ui/components/qux-derp/component.js', 73 | 'app/templates/components/foo-bar.hbs': 'src/ui/components/foo-bar/template.hbs', 74 | 'app/components/qux-derp/template.hbs': 'src/ui/components/qux-derp/template.hbs', 75 | 'app/routes/post/index.js': 'src/ui/routes/post/index.js', 76 | 'app/templates/post/index.hbs': 'src/ui/routes/post/index/template.hbs', 77 | 'app/routes/foo/bar/baz.js': 'src/ui/routes/foo/bar/baz.js', 78 | 'app/templates/foo/bar/baz.hbs': 'src/ui/routes/foo/bar/baz/template.hbs', 79 | 'app/adapters/post.js': 'src/data/models/post/adapter.js', 80 | 'app/serializers/post.js': 'src/data/models/post/serializer.js', 81 | 'app/controllers/foo/bar/baz.js': 'src/ui/routes/foo/bar/baz/controller.js', 82 | 'app/templates/posts/post/index.hbs': 'src/ui/routes/posts/post/index/template.hbs', 83 | 'app/app.js': 'src/main.js', 84 | 'app/routes.js': 'src/routes.js', 85 | 'app/router.js': 'src/router.js', 86 | 'app/README.md': 'src/README.md', 87 | 'app/_config.yml': 'src/_config.yml', 88 | 'app/index.html': 'src/ui/index.html', 89 | 'app/styles/app.css': 'src/ui/styles/app.css', 90 | 'app/styles/components/badges.css': 'src/ui/styles/components/badges.css', 91 | 'app/mirage/config.js': 'src/mirage/config.js', 92 | 'app/mirage/factories/foo.js': 'src/mirage/factories/foo.js', 93 | 'app/mixins/foo/bar.js': 'src/utils/mixins/foo/bar.js', 94 | 'app/initializers/foo.js': 'src/init/initializers/foo.js', 95 | 'app/instance-initializers/bar.js': 'src/init/instance-initializers/bar.js', 96 | 'app/routes/foo.js': 'src/ui/routes/foo.js', 97 | 'app/models/post.js': 'src/data/models/post.js', 98 | 99 | // tests 100 | 'tests/unit/routes/foo-bar-test.js': 'src/ui/routes/foo-bar/route-unit-test.js', 101 | 'tests/unit/.gitkeep': null, 102 | 'tests/unit/mixins/bar-test.js': 'src/utils/mixins/bar/mixin-unit-test.js', 103 | 'tests/unit/services/foo-test.js': 'src/services/foo/service-unit-test.js', 104 | 'tests/unit/utils/some-thing-test.js': 'src/utils/some-thing/util-unit-test.js', 105 | 106 | // simple auth 107 | 'app/authorizers/oauth2.js': 'src/simple-auth/authorizers/oauth2.js' 108 | }; 109 | 110 | function confirm(src, expected) { 111 | it('should map ' + src + ' to ' + expected, function() { 112 | var file = engine.fileInfoFor(src); 113 | 114 | if (expected === null) { 115 | assert(!file); 116 | } else { 117 | assert(file.destRelativePath === expected); 118 | } 119 | }); 120 | } 121 | 122 | for (var src in mappings) { 123 | var expected = mappings[src]; 124 | confirm(src, expected); 125 | } 126 | }); 127 | }); 128 | 129 | describe('acceptance', function() { 130 | var tmpPath = 'tmp/process-files'; 131 | var fixturesPath = path.resolve(__dirname, '../fixtures'); 132 | 133 | beforeEach(function() { 134 | fse.mkdirsSync(tmpPath); 135 | }); 136 | 137 | afterEach(function() { 138 | fse.removeSync(tmpPath); 139 | }); 140 | 141 | var entries = fse.readdirSync(fixturesPath); 142 | 143 | entries.forEach(function(entry) { 144 | it('should migrate ' + entry + ' fixture properly', function() { 145 | var fixturePath = path.join(fixturesPath, entry); 146 | var input = require(fixturePath + '/input'); 147 | var expected = require(fixturePath + '/output'); 148 | var migratorConfig = {}; 149 | try { 150 | migratorConfig = require(fixturePath + '/config'); 151 | } catch (e) { 152 | // fixture uses default config... 153 | } 154 | 155 | fixturify.writeSync(tmpPath, input); 156 | var migratorOptions = Object.assign({}, { 157 | projectRoot: tmpPath, 158 | projectName: 'my-app' 159 | }, migratorConfig); 160 | 161 | var engine = new Migrator(migratorOptions); 162 | 163 | return engine.processFiles() 164 | .then(function() { 165 | var actual = fixturify.readSync(tmpPath); 166 | 167 | assertDiff.deepEqual(actual, expected); 168 | }); 169 | }); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/engines/classic/template-file-info-test.js: -------------------------------------------------------------------------------- 1 | var assertDiff = require('assert-diff'); 2 | var fixturify = require('fixturify'); 3 | var fse = require('fs-extra'); 4 | var Migrator = require('../../../lib'); 5 | 6 | assertDiff.options.strict = true; 7 | 8 | describe('template-file-info', function() { 9 | describe('detectRenderableInvocations', function() { 10 | var tmpPath = 'tmp/detect-templates'; 11 | 12 | beforeEach(function() { 13 | fse.mkdirsSync(tmpPath); 14 | }); 15 | 16 | afterEach(function() { 17 | fse.removeSync(tmpPath); 18 | }); 19 | 20 | function confirmDetectsRenderables(templateSnippet, expectedRenderables) { 21 | it('`' + templateSnippet + '` should include ' + expectedRenderables, function() { 22 | fixturify.writeSync(tmpPath, { 23 | app: { 24 | templates: { 25 | 'post.hbs': templateSnippet 26 | } 27 | } 28 | }); 29 | 30 | var engine = new Migrator({ 31 | projectRoot: tmpPath 32 | }); 33 | 34 | var file = engine.fileInfoFor('app/templates/post.hbs'); 35 | file.detectRenderableInvocations(); 36 | 37 | assertDiff.deepEqual(file.renderablesInvoked, expectedRenderables); 38 | }); 39 | } 40 | 41 | // components 42 | confirmDetectsRenderables('{{#foo-bar}}{{/foo-bar}}', ['foo-bar']); 43 | confirmDetectsRenderables('{{foo-bar derp="blammo"}}', ['foo-bar']); 44 | confirmDetectsRenderables('
{{#foo-bar}}{{huz-zah blah="lolol"}}{{/foo-bar}}
', ['foo-bar', 'huz-zah']); 45 | confirmDetectsRenderables('{{yield (hash foo=(build-thing) bar=(component "bar-baz"))}}', ['yield', 'hash', 'build-thing', 'component', 'bar-baz']); 46 | confirmDetectsRenderables('{{component "foo-bar"}}', ['component', 'foo-bar']); 47 | confirmDetectsRenderables('{{foo/bar/baz/x-blah}}', ['foo/bar/baz/x-blah']); 48 | 49 | // helpers 50 | confirmDetectsRenderables('{{t "some thing"}}', ['t']); 51 | confirmDetectsRenderables('{{t "some thing"}}{{t "whopdee doo"}}', ['t']); 52 | confirmDetectsRenderables('{{derp blah="haha"}}', ['derp']); 53 | confirmDetectsRenderables('
', ['derp']); 54 | 55 | // error tolerance 56 | confirmDetectsRenderables('
', []); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/eslint-test.js: -------------------------------------------------------------------------------- 1 | var lint = require('mocha-eslint'); 2 | 3 | var paths = [ 4 | 'bin', 5 | 'lib', 6 | 'test' 7 | ]; 8 | 9 | var options = {}; 10 | lint(paths, options); 11 | -------------------------------------------------------------------------------- /test/fixtures/classic-absolute-imports/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | models: { 4 | 'application.js': '//app model', 5 | 'post.js': '// post' 6 | }, 7 | adapters: { 8 | 'application.js': '// application adapter', 9 | 'post.js': 'import ApplicationAdapter from "my-app/adapters/application";' 10 | }, 11 | utils: { 12 | 'nested.js': '"nested util"', 13 | 'single.js': '"single util"' 14 | }, 15 | routes: { 16 | 'application.js': 'import NestedUtil from "my-app/utils/nested"; import SingleUtil from "my-app/utils/single";' 17 | } 18 | }, 19 | tests: { 20 | unit: { 21 | utils: { 22 | 'nested-test.js': '"nested util test"' 23 | }, 24 | routes: { 25 | 'application-test.js': 'import ApplicationRoute from "my-app/routes/application";' 26 | } 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/fixtures/classic-absolute-imports/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | data: { 4 | models: { 5 | application: { 6 | 'model.js': '//app model', 7 | 'adapter.js': '// application adapter' 8 | }, 9 | post: { 10 | 'model.js': '// post', 11 | 'adapter.js': 'import ApplicationAdapter from "my-app/src/data/models/application/adapter";' 12 | } 13 | } 14 | }, 15 | ui: { 16 | routes: { 17 | application: { 18 | 'route.js': 'import NestedUtil from "my-app/src/utils/nested/util"; import SingleUtil from "my-app/src/utils/single";', 19 | 'route-unit-test.js': 'import ApplicationRoute from "my-app/src/ui/routes/application/route";' 20 | } 21 | } 22 | }, 23 | utils: { 24 | nested: { 25 | 'util.js': '"nested util"', 26 | 'util-unit-test.js': '"nested util test"' 27 | }, 28 | 'single.js': '"single util"' 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /test/fixtures/classic-acceptance/input.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | 'app': { 5 | 'app.js': '"app.js"', 6 | 'router.js': '"router.js"', 7 | 'index.html': 'index.html contents', 8 | 'components': { 9 | 'foo-bar.js': '"foo-bar component"', 10 | 'baz-derp': { 11 | 'component.js': '"baz-derp component"', 12 | 'template.hbs': 'baz-derp template' 13 | }, 14 | 'post-display': { 15 | 'component.js': '"post-display component"', 16 | 'template.hbs': 'post-display component template\n{{post-footer}}' 17 | }, 18 | 'post-footer': { 19 | 'component.js': '"post-footer component"', 20 | 'template.hbs': 'post-footer component template' 21 | } 22 | }, 23 | 'helpers': { 24 | 'i18n.js': stripIndents` 25 | import { helper } from '@ember/component/helper'; 26 | export default helper(i18n); 27 | `, 28 | 'blerg.js': stripIndents` 29 | import { helper } from '@ember/component/helper'; 30 | export default helper(blerg); 31 | `, 32 | 'main-greeting-text.js': stripIndents` 33 | import { helper } from '@ember/component/helper'; 34 | export default helper(mainGreetingText); 35 | `, 36 | 'show-default-title.js': stripIndents` 37 | import { helper } from '@ember/component/helper'; 38 | export default helper(showDefaultTitle); 39 | `, 40 | }, 41 | 'routes': { 42 | 'index.js': '"index route"', 43 | 'posts': { 44 | 'index.js': '"posts/index route"', 45 | 'post': { 46 | 'index.js': '"posts/post/index route"', 47 | 'edit.js': '"posts/post/edit route"' 48 | }, 49 | 'new.js': '"posts/new route"' 50 | } 51 | }, 52 | 'adapters': { 53 | 'application.js': '"application adapter"', 54 | 'post.js': '"post adapter"', 55 | 'comment.js': '"comment adapter"' 56 | }, 57 | 'serializers': { 58 | 'application.js': '"application serializer"', 59 | 'post.js': '"post serializer"', 60 | 'comment.js': '"comment serializer"' 61 | }, 62 | 'models': { 63 | 'post.js': '"post model"', 64 | 'comment.js': '"comment model"', 65 | 'tag.js': '"tag model"' 66 | }, 67 | 'initializers': { 68 | 'blah.js': '"blah initializer"', 69 | 'derp.js': '"derp initializer"' 70 | }, 71 | 'instance-initializers': { 72 | 'blammo.js': '"blammo instance initializer"' 73 | }, 74 | 'controllers': { 75 | 'index.js': '"index controller"', 76 | 'posts': { 77 | 'index.js': '"posts/index controller"', 78 | 'post': { 79 | 'index.js': '"posts/post/index controller"', 80 | 'edit.js': '"posts/post/edit controller"' 81 | }, 82 | 'new.js': '"posts/new controller"' 83 | } 84 | }, 85 | 'templates': { 86 | 'index.hbs': 'index route template {{main-greeting-text}}', 87 | 'posts': { 88 | 'index.hbs': 'posts/index route template', 89 | 'post': { 90 | 'index.hbs': 'posts/post/index route template\n{{post-display}}', 91 | 'edit.hbs': 'posts/post/edit route template' 92 | }, 93 | 'new.hbs': 'posts/new route template {{show-default-title}}', 94 | 'show.hbs': 'posts/post/show route template' 95 | }, 96 | 'components': { 97 | 'foo-bar.hbs': 'foo-bar component template' 98 | } 99 | }, 100 | 'transforms': { 101 | 'date.js': '"custom date transform"' 102 | }, 103 | 'mixins': { 104 | 'foo.js': '"foo mixin"' 105 | }, 106 | 'services': { 107 | 'ajax.js': '"ajax service"' 108 | }, 109 | 'validators': { 110 | 'blahzorz.js': '"blahzorz validator"' 111 | } 112 | }, 113 | 114 | 'config': { 115 | 'environment.js': '"ENV"', 116 | 'foo': { 117 | 'baz.sh': 'yolo' 118 | } 119 | }, 120 | 121 | 'tests': { 122 | 'acceptance': { 123 | 'post-test.js': '"post acceptance test"' 124 | }, 125 | 'unit': { 126 | 'mixins': { 127 | 'foo-test.js': '"foo mixin unit test"' 128 | }, 129 | 'service': { 130 | 'ajax-test.js': '"ajax service unit test"' 131 | }, 132 | 'routes': { 133 | 'posts': { 134 | 'index-test.js': '"posts/index unit test"' 135 | } 136 | }, 137 | 'validators': { 138 | 'blahzorz-test.js': '"blahzorz validator test"' 139 | } 140 | }, 141 | 'integration': { 142 | 'routes': { 143 | 'posts': { 144 | 'index-test.js': '"posts/index integration test"' 145 | } 146 | }, 147 | 'components': { 148 | 'post-display-test.js': '"post-display component integration test"', 149 | 'post-footer-test.js': '"post-footer integration test"' 150 | }, 151 | 'helpers': { 152 | 'show-default-title-test.js': '"show-default-title helper integration test"' 153 | } 154 | } 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /test/fixtures/classic-acceptance/output.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | 'src': { 5 | 'main.js': '"app.js"', 6 | 'router.js': '"router.js"', 7 | 'init': { 8 | 'initializers': { 9 | 'blah.js': '"blah initializer"', 10 | 'derp.js': '"derp initializer"' 11 | }, 12 | 'instance-initializers': { 13 | 'blammo.js': '"blammo instance initializer"' 14 | } 15 | }, 16 | 'ui': { 17 | 'index.html': 'index.html contents', 18 | 'components': { 19 | 'foo-bar': { 20 | 'component.js': '"foo-bar component"', 21 | 'template.hbs': 'foo-bar component template' 22 | }, 23 | 'baz-derp': { 24 | 'component.js': '"baz-derp component"', 25 | 'template.hbs': 'baz-derp template' 26 | }, 27 | 'i18n.js': stripIndents` 28 | import { helper as buildHelper } from '@ember/component/helper'; 29 | export const helper = buildHelper(i18n); 30 | `, 31 | 'blerg.js': stripIndents` 32 | import { helper as buildHelper } from '@ember/component/helper'; 33 | export const helper = buildHelper(blerg); 34 | `, 35 | }, 36 | 'routes': { 37 | 'index': { 38 | '-components': { 39 | 'main-greeting-text.js': stripIndents` 40 | import { helper as buildHelper } from '@ember/component/helper'; 41 | export const helper = buildHelper(mainGreetingText); 42 | ` 43 | }, 44 | 'controller.js': '"index controller"', 45 | 'route.js': '"index route"', 46 | 'template.hbs': 'index route template {{main-greeting-text}}' 47 | }, 48 | 'posts': { 49 | 'index': { 50 | 'controller.js': '"posts/index controller"', 51 | 'route.js': '"posts/index route"', 52 | 'template.hbs': 'posts/index route template', 53 | 'route-unit-test.js': '"posts/index unit test"', 54 | 'route-integration-test.js': '"posts/index integration test"' 55 | }, 56 | 'show': { 57 | 'template.hbs': 'posts/post/show route template' 58 | }, 59 | 'post': { 60 | 'index': { 61 | '-components': { 62 | 'post-display': { 63 | 'post-footer': { 64 | 'component.js': '"post-footer component"', 65 | 'template.hbs': 'post-footer component template', 66 | 'component-integration-test.js': '"post-footer integration test"' 67 | }, 68 | 'component.js': '"post-display component"', 69 | 'template.hbs': 'post-display component template\n{{post-footer}}', 70 | 'component-integration-test.js': '"post-display component integration test"' 71 | } 72 | }, 73 | 'controller.js': '"posts/post/index controller"', 74 | 'route.js': '"posts/post/index route"', 75 | 'template.hbs': 'posts/post/index route template\n{{post-display}}' 76 | }, 77 | 'edit': { 78 | 'controller.js': '"posts/post/edit controller"', 79 | 'route.js': '"posts/post/edit route"', 80 | 'template.hbs': 'posts/post/edit route template' 81 | } 82 | }, 83 | 'new': { 84 | '-components': { 85 | 'show-default-title': { 86 | 'helper.js': stripIndents` 87 | import { helper as buildHelper } from '@ember/component/helper'; 88 | export default buildHelper(showDefaultTitle); 89 | `, 90 | 'helper-integration-test.js': '"show-default-title helper integration test"' 91 | } 92 | }, 93 | 'controller.js': '"posts/new controller"', 94 | 'route.js': '"posts/new route"', 95 | 'template.hbs': 'posts/new route template {{show-default-title}}' 96 | } 97 | } 98 | } 99 | }, 100 | 'data': { 101 | 'models': { 102 | 'application': { 103 | 'adapter.js': '"application adapter"', 104 | 'serializer.js': '"application serializer"' 105 | }, 106 | 'post': { 107 | 'adapter.js': '"post adapter"', 108 | 'serializer.js': '"post serializer"', 109 | 'model.js': '"post model"' 110 | }, 111 | 'comment': { 112 | 'adapter.js': '"comment adapter"', 113 | 'serializer.js': '"comment serializer"', 114 | 'model.js': '"comment model"' 115 | }, 116 | 'tag.js': '"tag model"' 117 | }, 118 | transforms: { 119 | 'date.js': '"custom date transform"' 120 | } 121 | }, 122 | 'services': { 123 | 'ajax': { 124 | 'service-unit-test.js': '"ajax service unit test"', 125 | 'service.js': '"ajax service"' 126 | } 127 | }, 128 | 'utils': { 129 | 'mixins': { 130 | 'foo': { 131 | 'mixin.js': '"foo mixin"', 132 | 'mixin-unit-test.js': '"foo mixin unit test"' 133 | } 134 | } 135 | }, 136 | 'validators': { 137 | 'blahzorz': { 138 | 'validator.js': '"blahzorz validator"', 139 | 'validator-unit-test.js': '"blahzorz validator test"' 140 | } 141 | } 142 | }, 143 | 'config': { 144 | 'environment.js': '"ENV"', 145 | 'foo': { 146 | 'baz.sh': 'yolo' 147 | } 148 | }, 149 | 'tests': { 150 | 'acceptance': { 151 | 'post-test.js': '"post acceptance test"' 152 | } 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /test/fixtures/classic-named-exports/input.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | app: { 5 | // ensure we do not rewrite main files 6 | 'app.js': 'export default App', 7 | 'router.js': 'export default Router', 8 | helpers: { 9 | 'titleize.js': stripIndents` 10 | import { helper } from '@ember/component/helper'; 11 | export default helper(function() { }); 12 | `, 13 | 'capitalize.js': stripIndents` 14 | import { helper } from '@ember/component/helper'; 15 | export default helper(function() { }); 16 | ` 17 | } 18 | }, 19 | 20 | tests: { 21 | integration: { 22 | helpers: { 23 | 'capitalize-test.js': '"capitalize helper test"' 24 | } 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/fixtures/classic-named-exports/output.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | src: { 5 | 'main.js': 'export default App', 6 | 'router.js': 'export default Router', 7 | ui: { 8 | components: { 9 | 'titleize.js': stripIndents` 10 | import { helper as buildHelper } from '@ember/component/helper'; 11 | export const helper = buildHelper(function() { }); 12 | `, 13 | 'capitalize': { 14 | 'helper.js': stripIndents` 15 | import { helper as buildHelper } from '@ember/component/helper'; 16 | export default buildHelper(function() { }); 17 | `, 18 | 'helper-integration-test.js': '"capitalize helper test"' 19 | } 20 | } 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/classic-nested-component-invocation/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | templates: { 4 | components: { 5 | posts: { 6 | edit: { 7 | 'x-button.hbs': 'x-button template' 8 | } 9 | } 10 | }, 11 | posts: { 12 | 'edit.hbs': '{{posts/edit/x-button}}\n{{#posts/edit/x-button}}\n foo\n{{/posts/edit/x-button}}' 13 | } 14 | } 15 | }, 16 | 17 | tests: { 18 | 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/fixtures/classic-nested-component-invocation/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | ui: { 4 | routes: { 5 | posts: { 6 | edit: { 7 | '-components': { 8 | 'x-button': { 9 | 'template.hbs': 'x-button template' 10 | } 11 | }, 12 | 'template.hbs': '{{x-button}}\n{{#x-button}}\n foo\n{{/x-button}}' 13 | } 14 | } 15 | } 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/classic-private-components/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | templates: { 4 | components: { 5 | 'x-button.hbs': 'x-button template', 6 | 'x-foo.hbs': 'x-foo template: {{x-bar}}', 7 | 'x-bar.hbs': 'x-bar template' 8 | }, 9 | posts: { 10 | 'edit.hbs': '{{x-button}}\n{{#x-button}}\n foo\n{{/x-button}}' 11 | } 12 | } 13 | }, 14 | 15 | tests: { 16 | integration: { 17 | components: { 18 | 'x-bar-test.js': '"x-bar component test"' 19 | } 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/classic-private-components/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | ui: { 4 | components: { 5 | 'x-foo': { 6 | 'template.hbs': 'x-foo template: {{x-bar}}', 7 | 'x-bar': { 8 | 'template.hbs': 'x-bar template', 9 | 'component-integration-test.js': '"x-bar component test"' 10 | } 11 | } 12 | }, 13 | routes: { 14 | posts: { 15 | edit: { 16 | '-components': { 17 | 'x-button': { 18 | 'template.hbs': 'x-button template' 19 | } 20 | }, 21 | 'template.hbs': '{{x-button}}\n{{#x-button}}\n foo\n{{/x-button}}' 22 | } 23 | } 24 | } 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/fixtures/classic-relative-imports/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | models: { 4 | 'application.js': '//app model', 5 | 'post.js': 'import ApplicationModel from "./application";' 6 | }, 7 | adapters: { 8 | 'application.js': '//app serializer', 9 | 'post.js': 'import ApplicationSerializer from "./application";' 10 | }, 11 | utils: { 12 | 'nested.js': '"nested util"', 13 | 'single.js': '"single util"', 14 | 'derived-nested.js': 'import NestedUtil from "./nested";', 15 | 'derived-single.js': 'import SingleUtil from "./single";' 16 | }, 17 | routes: { 18 | 'application.js': 'import NestedUtil from "../utils/nested"; import SingleUtil from "../utils/single";', 19 | 'index.js': 'import ApplicationRoute from "./application";', 20 | post: { 21 | 'index.js': 'import ApplicationRoute from "../application";' 22 | } 23 | } 24 | }, 25 | tests: { 26 | unit: { 27 | utils: { 28 | 'nested-test.js': '"nested util test"' 29 | }, 30 | routes: { 31 | 'application-test.js': 'import ApplicationRoute from "../../../routes/application"; import NestedTest from "../utils/nested-test";' 32 | } 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /test/fixtures/classic-relative-imports/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | data: { 4 | models: { 5 | application: { 6 | 'adapter.js': '//app serializer', 7 | 'model.js': '//app model' 8 | }, 9 | post: { 10 | 'adapter.js': 'import ApplicationSerializer from "../application/adapter";', 11 | 'model.js': 'import ApplicationModel from "../application/model";' 12 | } 13 | } 14 | }, 15 | ui: { 16 | routes: { 17 | application: { 18 | 'route.js': 'import NestedUtil from "../../../utils/nested/util"; import SingleUtil from "../../../utils/single";', 19 | 'route-unit-test.js': 'import ApplicationRoute from "./route"; import NestedTest from "../../../utils/nested/util-unit-test";' 20 | }, 21 | 'index.js': 'import ApplicationRoute from "./application/route";', 22 | post: { 23 | 'index.js': 'import ApplicationRoute from "../application/route";' 24 | } 25 | } 26 | }, 27 | utils: { 28 | 'derived-nested.js': 'import NestedUtil from "./nested/util";', 29 | 'derived-single.js': 'import SingleUtil from "./single";', 30 | nested: { 31 | 'util.js': '"nested util"', 32 | 'util-unit-test.js': '"nested util test"' 33 | }, 34 | 'single.js': '"single util"' 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /test/fixtures/classic-template-only-component/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | templates: { 4 | components: { 5 | 'x-button.hbs': 'x-button template', 6 | } 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/classic-template-only-component/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | ui: { 4 | components: { 5 | 'x-button': { 6 | 'template.hbs': 'x-button template' 7 | } 8 | } 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/classic-view-support/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'app': { 3 | 'views': { 4 | 'foo.js': '"foo view no template invocation"', 5 | 'bar.js': '"bar view with template invocation"' 6 | }, 7 | 'templates': { 8 | 'index.hbs': '{{view "bar"}}', 9 | 'foo.hbs': 'foo template' 10 | } 11 | }, 12 | 'tests': {} 13 | }; 14 | -------------------------------------------------------------------------------- /test/fixtures/classic-view-support/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'src': { 3 | 'ui': { 4 | 'views': { 5 | 'bar.js': '"bar view with template invocation"' 6 | }, 7 | 'routes': { 8 | 'index': { 9 | 'template.hbs': '{{view "bar"}}' 10 | }, 11 | 'foo': { 12 | 'view.js': '"foo view no template invocation"', 13 | 'template.hbs': 'foo template' 14 | } 15 | } 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/config/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | 'app.js': 'import config from "./config/environment";', 4 | utils: { 5 | 'first.js': 'import ConfigUtils from "./config/third";', 6 | 'second.js': 'import config from "../config/environment";', 7 | config: { 8 | 'third.js': 'import config from "../../config/environment";', 9 | } 10 | } 11 | }, 12 | config: { 13 | 'environment.js': '"ENV"', 14 | foo: { 15 | 'baz.sh': 'yolo' 16 | } 17 | }, 18 | tests: { 19 | 'helpers': { 20 | 'resolver.js': 'import config from "../../config/environment";', 21 | 'start-app.js': 'import config from "my-app/config/environment";' 22 | }, 23 | unit: { 24 | utils: { 25 | 'first-test.js': 'import config from "../../../config/environment";', 26 | 'second-test.js': 'import config from "my-app/config/environment";', 27 | config: { 28 | 'third-test.js': 'import config from "my-app/config/environment";' 29 | } 30 | } 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/fixtures/config/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: { 3 | 'main.js': 'import config from "../config/environment";', 4 | utils: { 5 | first: { 6 | 'util.js': 'import ConfigUtils from "../config/third/util";', 7 | 'util-unit-test.js': 'import config from "../../../config/environment";' 8 | }, 9 | second: { 10 | 'util.js': 'import config from "../../../config/environment";', 11 | 'util-unit-test.js': 'import config from "my-app/config/environment";' 12 | }, 13 | config: { 14 | third: { 15 | 'util-unit-test.js': 'import config from "my-app/config/environment";', 16 | 'util.js': 'import config from "../../../../config/environment";' 17 | } 18 | } 19 | } 20 | }, 21 | config: { 22 | 'environment.js': '"ENV"', 23 | foo: { 24 | 'baz.sh': 'yolo' 25 | } 26 | }, 27 | tests: { 28 | 'helpers': { 29 | 'resolver.js': 'import config from "../../config/environment";', 30 | 'start-app.js': 'import config from "my-app/config/environment";' 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/fixtures/directory-cleanup/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'app': { 3 | 'routes': { 4 | '.gitkeep': '', 5 | 'post.js': '"post route"' 6 | }, 7 | 'templates': { 8 | '.gitkeep': '', 9 | 'post.hbs': 'post route template' 10 | }, 11 | 'pods': { 12 | 'posts': { 13 | '.gitkeep': '' 14 | } 15 | } 16 | }, 17 | 'tests': { 18 | 'unit': { 19 | '.gitkeep': '' 20 | }, 21 | 'integration': { 22 | '.gitkeep': '' 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/directory-cleanup/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'src': { 3 | 'ui': { 4 | 'routes': { 5 | 'post': { 6 | 'route.js': '"post route"', 7 | 'template.hbs': 'post route template' 8 | } 9 | } 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /test/fixtures/pods-acceptance/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: 'pods' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-acceptance/input.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | 'app': { 5 | 'app.js': '"app.js"', 6 | 'router.js': '"router.js"', 7 | 'index.html': 'index.html contents', 8 | 'pods': { 9 | 'components': { 10 | 'foo-bar': { 11 | 'component.js': '"foo-bar component"', 12 | 'template.hbs': 'foo-bar component template' 13 | } , 14 | 'baz-derp': { 15 | 'component.js': '"baz-derp component"', 16 | 'template.hbs': 'baz-derp template' 17 | }, 18 | 'post-display': { 19 | 'component.js': '"post-display component"', 20 | 'template.hbs': 'post-display component template\n{{post-footer}}' 21 | }, 22 | 'post-footer': { 23 | 'component.js': '"post-footer component"', 24 | 'template.hbs': 'post-footer component template' 25 | }, 26 | }, 27 | 'index': { 28 | 'template.hbs': 'index route template {{main-greeting-text}}', 29 | 'controller.js': '"index controller"', 30 | 'route.js': '"index route"', 31 | }, 32 | 'posts': { 33 | 'index': { 34 | 'route.js': '"posts/index route"', 35 | 'controller.js': '"posts/index controller"', 36 | 'template.hbs': 'posts/index route template', 37 | }, 38 | 'post': { 39 | 'index': { 40 | 'route.js': '"posts/post/index route"', 41 | 'controller.js': '"posts/post/index controller"', 42 | 'template.hbs': 'posts/post/index route template\n{{post-display}}', 43 | }, 44 | 'edit': { 45 | 'route.js': '"posts/post/edit route"', 46 | 'controller.js': '"posts/post/edit controller"', 47 | 'template.hbs': 'posts/post/edit route template' 48 | }, 49 | }, 50 | 'new': { 51 | 'route.js': '"posts/new route"', 52 | 'controller.js': '"posts/new controller"', 53 | 'template.hbs': 'posts/new route template {{show-default-title}}', 54 | }, 55 | 'show': { 56 | 'template.hbs': 'posts/post/show route template', 57 | }, 58 | }, 59 | }, 60 | 'helpers': { 61 | 'i18n.js': stripIndents` 62 | import { helper } from '@ember/component/helper'; 63 | export default helper(i18n); 64 | `, 65 | 'blerg.js': stripIndents` 66 | import { helper } from '@ember/component/helper'; 67 | export default helper(blerg); 68 | `, 69 | 'main-greeting-text.js': stripIndents` 70 | import { helper } from '@ember/component/helper'; 71 | export default helper(mainGreetingText); 72 | `, 73 | 'show-default-title.js': stripIndents` 74 | import { helper } from '@ember/component/helper'; 75 | export default helper(showDefaultTitle); 76 | `, 77 | }, 78 | 'adapters': { 79 | 'application.js': '"application adapter"', 80 | 'post.js': '"post adapter"', 81 | 'comment.js': '"comment adapter"' 82 | }, 83 | 'serializers': { 84 | 'application.js': '"application serializer"', 85 | 'post.js': '"post serializer"', 86 | 'comment.js': '"comment serializer"' 87 | }, 88 | 'models': { 89 | 'post.js': '"post model"', 90 | 'comment.js': '"comment model"', 91 | 'tag.js': '"tag model"' 92 | }, 93 | 'initializers': { 94 | 'blah.js': '"blah initializer"', 95 | 'derp.js': '"derp initializer"' 96 | }, 97 | 'instance-initializers': { 98 | 'blammo.js': '"blammo instance initializer"' 99 | }, 100 | 'transforms': { 101 | 'date.js': '"custom date transform"' 102 | }, 103 | 'mixins': { 104 | 'foo.js': '"foo mixin"' 105 | }, 106 | 'services': { 107 | 'ajax.js': '"ajax service"' 108 | }, 109 | 'validators': { 110 | 'blahzorz.js': '"blahzorz validator"' 111 | } 112 | }, 113 | 'config': { 114 | 'environment.js': '"ENV"', 115 | 'foo': { 116 | 'baz.sh': 'yolo' 117 | } 118 | }, 119 | 'tests': { 120 | 'acceptance': { 121 | 'post-test.js': '"post acceptance test"' 122 | }, 123 | 'unit': { 124 | 'pods': { 125 | 'posts': { 126 | 'index': { 127 | 'route-test.js': '"posts/index unit test"', 128 | }, 129 | }, 130 | }, 131 | 'mixins': { 132 | 'foo-test.js': '"foo mixin unit test"' 133 | }, 134 | 'service': { 135 | 'ajax-test.js': '"ajax service unit test"' 136 | }, 137 | 'validators': { 138 | 'blahzorz-test.js': '"blahzorz validator test"' 139 | } 140 | }, 141 | 'integration': { 142 | 'pods': { 143 | 'components': { 144 | 'post-display': { 145 | 'component-test.js': '"post-display component integration test"', 146 | }, 147 | 'post-footer': { 148 | 'component-test.js': '"post-footer integration test"' 149 | } 150 | }, 151 | 'posts': { 152 | 'index': { 153 | 'route-test.js': '"posts/index integration test"' 154 | } 155 | } 156 | }, 157 | 'helpers': { 158 | 'show-default-title-test.js': '"show-default-title helper integration test"' 159 | } 160 | } 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /test/fixtures/pods-acceptance/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-acceptance/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-custom-name-acceptance/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: 'custom-pods-namespace' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-custom-name-acceptance/input.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | 'app': { 5 | 'app.js': '"app.js"', 6 | 'router.js': '"router.js"', 7 | 'index.html': 'index.html contents', 8 | 'custom-pods-namespace': { 9 | 'components': { 10 | 'foo-bar': { 11 | 'component.js': '"foo-bar component"', 12 | 'template.hbs': 'foo-bar component template' 13 | } , 14 | 'baz-derp': { 15 | 'component.js': '"baz-derp component"', 16 | 'template.hbs': 'baz-derp template' 17 | }, 18 | 'post-display': { 19 | 'component.js': '"post-display component"', 20 | 'template.hbs': 'post-display component template\n{{post-footer}}' 21 | }, 22 | 'post-footer': { 23 | 'component.js': '"post-footer component"', 24 | 'template.hbs': 'post-footer component template' 25 | }, 26 | }, 27 | 'index': { 28 | 'template.hbs': 'index route template {{main-greeting-text}}', 29 | 'controller.js': '"index controller"', 30 | 'route.js': '"index route"', 31 | }, 32 | 'posts': { 33 | 'index': { 34 | 'route.js': '"posts/index route"', 35 | 'controller.js': '"posts/index controller"', 36 | 'template.hbs': 'posts/index route template', 37 | }, 38 | 'post': { 39 | 'index': { 40 | 'route.js': '"posts/post/index route"', 41 | 'controller.js': '"posts/post/index controller"', 42 | 'template.hbs': 'posts/post/index route template\n{{post-display}}', 43 | }, 44 | 'edit': { 45 | 'route.js': '"posts/post/edit route"', 46 | 'controller.js': '"posts/post/edit controller"', 47 | 'template.hbs': 'posts/post/edit route template' 48 | }, 49 | }, 50 | 'new': { 51 | 'route.js': '"posts/new route"', 52 | 'controller.js': '"posts/new controller"', 53 | 'template.hbs': 'posts/new route template {{show-default-title}}', 54 | }, 55 | 'show': { 56 | 'template.hbs': 'posts/post/show route template', 57 | }, 58 | }, 59 | }, 60 | 'helpers': { 61 | 'i18n.js': stripIndents` 62 | import { helper } from '@ember/component/helper'; 63 | export default helper(i18n); 64 | `, 65 | 'blerg.js': stripIndents` 66 | import { helper } from '@ember/component/helper'; 67 | export default helper(blerg); 68 | `, 69 | 'main-greeting-text.js': stripIndents` 70 | import { helper } from '@ember/component/helper'; 71 | export default helper(mainGreetingText); 72 | `, 73 | 'show-default-title.js': stripIndents` 74 | import { helper } from '@ember/component/helper'; 75 | export default helper(showDefaultTitle); 76 | `, 77 | }, 78 | 'adapters': { 79 | 'application.js': '"application adapter"', 80 | 'post.js': '"post adapter"', 81 | 'comment.js': '"comment adapter"' 82 | }, 83 | 'serializers': { 84 | 'application.js': '"application serializer"', 85 | 'post.js': '"post serializer"', 86 | 'comment.js': '"comment serializer"' 87 | }, 88 | 'models': { 89 | 'post.js': '"post model"', 90 | 'comment.js': '"comment model"', 91 | 'tag.js': '"tag model"' 92 | }, 93 | 'initializers': { 94 | 'blah.js': '"blah initializer"', 95 | 'derp.js': '"derp initializer"' 96 | }, 97 | 'instance-initializers': { 98 | 'blammo.js': '"blammo instance initializer"' 99 | }, 100 | 'transforms': { 101 | 'date.js': '"custom date transform"' 102 | }, 103 | 'mixins': { 104 | 'foo.js': '"foo mixin"' 105 | }, 106 | 'services': { 107 | 'ajax.js': '"ajax service"' 108 | }, 109 | 'validators': { 110 | 'blahzorz.js': '"blahzorz validator"' 111 | } 112 | }, 113 | 'config': { 114 | 'environment.js': '"ENV"', 115 | 'foo': { 116 | 'baz.sh': 'yolo' 117 | } 118 | }, 119 | 'tests': { 120 | 'acceptance': { 121 | 'post-test.js': '"post acceptance test"' 122 | }, 123 | 'unit': { 124 | 'custom-pods-namespace': { 125 | 'posts': { 126 | 'index': { 127 | 'route-test.js': '"posts/index unit test"', 128 | }, 129 | }, 130 | }, 131 | 'mixins': { 132 | 'foo-test.js': '"foo mixin unit test"' 133 | }, 134 | 'service': { 135 | 'ajax-test.js': '"ajax service unit test"' 136 | }, 137 | 'validators': { 138 | 'blahzorz-test.js': '"blahzorz validator test"' 139 | } 140 | }, 141 | 'integration': { 142 | 'custom-pods-namespace': { 143 | 'components': { 144 | 'post-display': { 145 | 'component-test.js': '"post-display component integration test"', 146 | }, 147 | 'post-footer': { 148 | 'component-test.js': '"post-footer integration test"' 149 | } 150 | }, 151 | 'posts': { 152 | 'index': { 153 | 'route-test.js': '"posts/index integration test"' 154 | } 155 | } 156 | }, 157 | 'helpers': { 158 | 'show-default-title-test.js': '"show-default-title helper integration test"' 159 | } 160 | } 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /test/fixtures/pods-custom-name-acceptance/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-acceptance/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-private-components/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: 'pods' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-private-components/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | templates: { 4 | components: { 5 | 'x-button.hbs': 'x-button template', 6 | 'x-foo.hbs': 'x-foo template: {{x-bar}}', 7 | 'x-bar.hbs': 'x-bar template' 8 | }, 9 | }, 10 | pods: { 11 | posts: { 12 | 'edit': { 13 | 'template.hbs': '{{x-button}}\n{{#x-button}}\n foo\n{{/x-button}}' 14 | } 15 | } 16 | } 17 | }, 18 | 19 | tests: { 20 | integration: { 21 | components: { 22 | 'x-bar-test.js': '"x-bar component test"' 23 | } 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/fixtures/pods-private-components/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-private-components/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-relative-imports/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: 'pods' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-relative-imports/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | models: { 4 | 'application.js': '//app model', 5 | 'post.js': 'import ApplicationModel from "./application";' 6 | }, 7 | adapters: { 8 | 'application.js': '//app serializer', 9 | 'post.js': 'import ApplicationSerializer from "./application";' 10 | }, 11 | utils: { 12 | 'nested.js': '"nested util"', 13 | 'single.js': '"single util"', 14 | 'derived-nested.js': 'import NestedUtil from "./nested";', 15 | 'derived-single.js': 'import SingleUtil from "./single";' 16 | }, 17 | pods: { 18 | application: { 19 | 'route.js': 'import NestedUtil from "../../utils/nested"; import SingleUtil from "../../utils/single";' 20 | }, 21 | index: { 22 | 'route.js': 'import ApplicationRoute from "../application/route";', 23 | }, 24 | post: { 25 | index: { 26 | 'route.js': 'import ApplicationRoute from "../../application/route";' 27 | } 28 | } 29 | } 30 | }, 31 | tests: { 32 | unit: { 33 | utils: { 34 | 'nested-test.js': '"nested util test"' 35 | }, 36 | routes: { 37 | 'application-test.js': 'import ApplicationRoute from "../../../pods/application/route"; import NestedTest from "../utils/nested-test";' 38 | } 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /test/fixtures/pods-relative-imports/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-relative-imports/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-with-undefined-namespace-acceptance/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: undefined 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-with-undefined-namespace-acceptance/input.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../pods-without-namespace-acceptance/input'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-with-undefined-namespace-acceptance/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-acceptance/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/pods-without-namespace-acceptance/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | podModulePrefix: '' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/pods-without-namespace-acceptance/input.js: -------------------------------------------------------------------------------- 1 | var stripIndents = require('common-tags').stripIndents; 2 | 3 | module.exports = { 4 | 'app': { 5 | 'app.js': '"app.js"', 6 | 'router.js': '"router.js"', 7 | 'index.html': 'index.html contents', 8 | 'components': { 9 | 'foo-bar': { 10 | 'component.js': '"foo-bar component"', 11 | 'template.hbs': 'foo-bar component template' 12 | } , 13 | 'baz-derp': { 14 | 'component.js': '"baz-derp component"', 15 | 'template.hbs': 'baz-derp template' 16 | }, 17 | 'post-display': { 18 | 'component.js': '"post-display component"', 19 | 'template.hbs': 'post-display component template\n{{post-footer}}' 20 | }, 21 | 'post-footer': { 22 | 'component.js': '"post-footer component"', 23 | 'template.hbs': 'post-footer component template' 24 | }, 25 | }, 26 | 'index': { 27 | 'template.hbs': 'index route template {{main-greeting-text}}', 28 | 'controller.js': '"index controller"', 29 | 'route.js': '"index route"', 30 | }, 31 | 'posts': { 32 | 'index': { 33 | 'route.js': '"posts/index route"', 34 | 'controller.js': '"posts/index controller"', 35 | 'template.hbs': 'posts/index route template', 36 | }, 37 | 'post': { 38 | 'index': { 39 | 'route.js': '"posts/post/index route"', 40 | 'controller.js': '"posts/post/index controller"', 41 | 'template.hbs': 'posts/post/index route template\n{{post-display}}', 42 | }, 43 | 'edit': { 44 | 'route.js': '"posts/post/edit route"', 45 | 'controller.js': '"posts/post/edit controller"', 46 | 'template.hbs': 'posts/post/edit route template' 47 | }, 48 | }, 49 | 'new': { 50 | 'route.js': '"posts/new route"', 51 | 'controller.js': '"posts/new controller"', 52 | 'template.hbs': 'posts/new route template {{show-default-title}}', 53 | }, 54 | 'show': { 55 | 'template.hbs': 'posts/post/show route template', 56 | }, 57 | }, 58 | 'helpers': { 59 | 'i18n.js': stripIndents` 60 | import { helper } from '@ember/component/helper'; 61 | export default helper(i18n); 62 | `, 63 | 'blerg.js': stripIndents` 64 | import { helper } from '@ember/component/helper'; 65 | export default helper(blerg); 66 | `, 67 | 'main-greeting-text.js': stripIndents` 68 | import { helper } from '@ember/component/helper'; 69 | export default helper(mainGreetingText); 70 | `, 71 | 'show-default-title.js': stripIndents` 72 | import { helper } from '@ember/component/helper'; 73 | export default helper(showDefaultTitle); 74 | `, 75 | }, 76 | 'post': { 77 | 'adapter.js': '"post adapter"', 78 | 'serializer.js': '"post serializer"', 79 | 'model.js': '"post model"' 80 | }, 81 | 'adapters': { 82 | 'application.js': '"application adapter"', 83 | 'comment.js': '"comment adapter"' 84 | }, 85 | 'serializers': { 86 | 'application.js': '"application serializer"', 87 | 'comment.js': '"comment serializer"' 88 | }, 89 | 'models': { 90 | 'comment.js': '"comment model"', 91 | 'tag.js': '"tag model"' 92 | }, 93 | 'initializers': { 94 | 'blah.js': '"blah initializer"', 95 | 'derp.js': '"derp initializer"' 96 | }, 97 | 'instance-initializers': { 98 | 'blammo.js': '"blammo instance initializer"' 99 | }, 100 | 'transforms': { 101 | 'date.js': '"custom date transform"' 102 | }, 103 | 'mixins': { 104 | 'foo.js': '"foo mixin"' 105 | }, 106 | 'services': { 107 | 'ajax.js': '"ajax service"' 108 | }, 109 | 'validators': { 110 | 'blahzorz.js': '"blahzorz validator"' 111 | } 112 | }, 113 | 'config': { 114 | 'environment.js': '"ENV"', 115 | 'foo': { 116 | 'baz.sh': 'yolo' 117 | } 118 | }, 119 | 'tests': { 120 | 'acceptance': { 121 | 'post-test.js': '"post acceptance test"' 122 | }, 123 | 'unit': { 124 | 'posts': { 125 | 'index': { 126 | 'route-test.js': '"posts/index unit test"', 127 | } 128 | }, 129 | 'mixins': { 130 | 'foo-test.js': '"foo mixin unit test"' 131 | }, 132 | 'service': { 133 | 'ajax-test.js': '"ajax service unit test"' 134 | }, 135 | 'validators': { 136 | 'blahzorz-test.js': '"blahzorz validator test"' 137 | } 138 | }, 139 | 'integration': { 140 | 'components': { 141 | 'post-display': { 142 | 'component-test.js': '"post-display component integration test"', 143 | }, 144 | 'post-footer': { 145 | 'component-test.js': '"post-footer integration test"' 146 | } 147 | }, 148 | 'posts': { 149 | 'index': { 150 | 'route-test.js': '"posts/index integration test"' 151 | } 152 | }, 153 | 'helpers': { 154 | 'show-default-title-test.js': '"show-default-title helper integration test"' 155 | } 156 | } 157 | } 158 | }; 159 | -------------------------------------------------------------------------------- /test/fixtures/pods-without-namespace-acceptance/output.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../classic-acceptance/output'); 2 | -------------------------------------------------------------------------------- /test/fixtures/qunit-module-test-helpers/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | 'app.js': '// app', 4 | 'resolver.js': '// resolver', 5 | adapters: { 6 | '.eslint.js': '{}' 7 | } 8 | }, 9 | config: { 10 | 'environment.js': '"ENV"' 11 | }, 12 | tests: { 13 | '.eslintrc.js': 'module.exports = {};', 14 | '.eslint.js': '{}', 15 | 'test-helper.js': 'import App from "../app";', 16 | helpers: { 17 | 'resolver.js': 'import Resolver from "../../resolver";' 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/fixtures/qunit-module-test-helpers/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | adapters: { 4 | '.eslint.js': '{}' 5 | } 6 | }, 7 | src: { 8 | 'main.js': '// app', 9 | 'resolver.js': '// resolver' 10 | }, 11 | config: { 12 | 'environment.js': '"ENV"' 13 | }, 14 | tests: { 15 | '.eslintrc.js': 'module.exports = {};', 16 | '.eslint.js': '{}', 17 | 'test-helper.js': 'import App from "../src/main";', 18 | 'helpers': { 19 | 'resolver.js': 'import Resolver from "../../src/resolver";' 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/test-helpers/input.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | 'app.js': '// app', 4 | 'resolver.js': '// resolver', 5 | adapters: { 6 | '.eslint.js': '{}' 7 | } 8 | }, 9 | config: { 10 | 'environment.js': '"ENV"' 11 | }, 12 | tests: { 13 | '.eslintrc.js': 'module.exports = {};', 14 | '.eslint.js': '{}', 15 | 'test-helper.js': '{}', 16 | helpers: { 17 | 'resolver.js': 'import Resolver from "../../resolver";', 18 | 'start-app.js': 'import App from "../../app";' 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/fixtures/test-helpers/output.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | adapters: { 4 | '.eslint.js': '{}' 5 | } 6 | }, 7 | src: { 8 | 'main.js': '// app', 9 | 'resolver.js': '// resolver' 10 | }, 11 | config: { 12 | 'environment.js': '"ENV"' 13 | }, 14 | tests: { 15 | '.eslintrc.js': 'module.exports = {};', 16 | '.eslint.js': '{}', 17 | 'test-helper.js': '{}', 18 | 'helpers': { 19 | 'resolver.js': 'import Resolver from "../../src/resolver";', 20 | 'start-app.js': 'import App from "../../src/main";' 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test/logger-test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var assert = require('power-assert'); 3 | var Logger = require('../lib/models/logger'); 4 | 5 | describe('logger', function() { 6 | var logger; 7 | 8 | beforeEach(function() { 9 | logger = new Logger({ 10 | projectRoot: __dirname 11 | }); 12 | }); 13 | 14 | it('tracks moved files', function() { 15 | var source = 'app/helpers/titleize.js'; 16 | var dest = 'src/ui/components/titleize.js'; 17 | 18 | logger.movedFile(source, dest); 19 | 20 | var output = logger.flush(); 21 | 22 | assert(output.indexOf(source) > -1); 23 | assert(output.indexOf(dest) > -1); 24 | }); 25 | 26 | it('uses paths relative to root', function() { 27 | var source = path.resolve(__dirname, 'app/helpers/titleize.js'); 28 | var dest = path.resolve(__dirname, 'src/ui/components/titleize.js'); 29 | 30 | logger.movedFile(source, dest); 31 | 32 | var output = logger.flush(); 33 | 34 | assert(output.indexOf(__dirname) === -1); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ./test/enable-power-assert 2 | --recursive 3 | -------------------------------------------------------------------------------- /test/models/file-info-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('power-assert'); 2 | var Migrator = require('../../lib'); 3 | 4 | describe('file-info model', function() { 5 | describe('destRelativePath', function() { 6 | var engine; 7 | 8 | beforeEach(function() { 9 | engine = new Migrator({ 10 | projectRoot: '.' 11 | }); 12 | 13 | }); 14 | 15 | it('can calculate a destRelativePath where type is the same as collection', function() { 16 | var file = engine.fileInfoFor('app/routes/foo-bar.js'); 17 | 18 | engine.finalizeFileDiscovery(); 19 | 20 | assert(file.destRelativePath === 'src/ui/routes/foo-bar.js'); 21 | }); 22 | 23 | it('can calculate a destRelativePath where the base path is in the pods form', function() { 24 | engine.podModulePrefix = 'pods'; 25 | var file = engine.fileInfoFor('app/pods/foo-bar/route.js'); 26 | 27 | engine.finalizeFileDiscovery(); 28 | 29 | assert(file.destRelativePath === 'src/ui/routes/foo-bar.js'); 30 | }); 31 | 32 | it('can calculate a destRelativePath where type is not the same as collection', function() { 33 | var file = engine.fileInfoFor('app/adapters/foo.js'); 34 | 35 | engine.finalizeFileDiscovery(); 36 | 37 | assert(file.destRelativePath === 'src/data/models/foo/adapter.js'); 38 | }); 39 | 40 | it('uses . instead of /. when only a single item that is the default type is present', function() { 41 | var file = engine.fileInfoFor('app/models/foo.js'); 42 | 43 | engine.finalizeFileDiscovery(); 44 | 45 | assert(file.destRelativePath === 'src/data/models/foo.js'); 46 | }); 47 | 48 | it('uses / instead of .. when multiple items exist for the bucket', function() { 49 | var model = engine.fileInfoFor('app/models/foo.js'); 50 | var adapter = engine.fileInfoFor('app/adapters/foo.js'); 51 | 52 | engine.finalizeFileDiscovery(); 53 | 54 | assert(model.destRelativePath === 'src/data/models/foo/model.js'); 55 | assert(adapter.destRelativePath === 'src/data/models/foo/adapter.js'); 56 | }); 57 | 58 | it('uses / instead of .. when a model and a test exist', function() { 59 | var model = engine.fileInfoFor('app/models/foo.js'); 60 | var test = engine.fileInfoFor('tests/integration/models/foo-test.js'); 61 | 62 | engine.finalizeFileDiscovery(); 63 | 64 | assert(model.destRelativePath === 'src/data/models/foo/model.js'); 65 | assert(test.destRelativePath === 'src/data/models/foo/model-integration-test.js'); 66 | }); 67 | 68 | it('uses / instead of .. when a mixin and a test exist', function() { 69 | var model = engine.fileInfoFor('app/mixins/foo.js'); 70 | var test = engine.fileInfoFor('tests/unit/mixins/foo-test.js'); 71 | 72 | engine.finalizeFileDiscovery(); 73 | 74 | assert(model.destRelativePath === 'src/utils/mixins/foo/mixin.js'); 75 | assert(test.destRelativePath === 'src/utils/mixins/foo/mixin-unit-test.js'); 76 | }); 77 | 78 | it('uses / instead of . when a service and a test exist', function() { 79 | var model = engine.fileInfoFor('app/services/foo.js'); 80 | var test = engine.fileInfoFor('tests/integration/services/foo-test.js'); 81 | 82 | engine.finalizeFileDiscovery(); 83 | 84 | assert(model.destRelativePath === 'src/services/foo/service.js'); 85 | assert(test.destRelativePath === 'src/services/foo/service-integration-test.js'); 86 | }); 87 | 88 | it('uses / instead of . when a util and a test exist', function() { 89 | var util = engine.fileInfoFor('app/utils/foo.js'); 90 | var test = engine.fileInfoFor('tests/unit/utils/foo-test.js'); 91 | 92 | engine.finalizeFileDiscovery(); 93 | 94 | assert(util.destRelativePath === 'src/utils/foo/util.js'); 95 | assert(test.destRelativePath === 'src/utils/foo/util-unit-test.js'); 96 | }); 97 | 98 | describe('private components nesting', function() { 99 | it('detecting private / single use components (template only)', function() { 100 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 101 | var componentTemplate = engine.fileInfoFor('app/templates/components/foo-bar.hbs'); 102 | 103 | routeTemplate.registerRenderableUsage('foo-bar'); 104 | 105 | engine.finalizeFileDiscovery(); 106 | 107 | assert(componentTemplate.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/template.hbs'); 108 | }); 109 | 110 | it('detecting private / single use components (component only)', function() { 111 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 112 | var component = engine.fileInfoFor('app/components/foo-bar.js'); 113 | 114 | routeTemplate.registerRenderableUsage('foo-bar'); 115 | 116 | engine.finalizeFileDiscovery(); 117 | 118 | assert(component.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/component.js'); 119 | }); 120 | 121 | it('pods | detecting private / single use components (component only)', function() { 122 | engine.podModulePrefix = 'pods'; 123 | var routeTemplate = engine.fileInfoFor('app/pods/posts/index/template.hbs'); 124 | var component = engine.fileInfoFor('app/pods/foo-bar/component.js'); 125 | 126 | routeTemplate.registerRenderableUsage('foo-bar'); 127 | 128 | engine.finalizeFileDiscovery(); 129 | 130 | assert(component.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/component.js'); 131 | }); 132 | 133 | it('detecting private / single use helper', function() { 134 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 135 | var helper = engine.fileInfoFor('app/helpers/bar.js'); 136 | 137 | routeTemplate.registerRenderableUsage('bar'); 138 | 139 | engine.finalizeFileDiscovery(); 140 | 141 | assert(helper.destRelativePath === 'src/ui/routes/posts/index/-components/bar.js'); 142 | }); 143 | 144 | it('detecting private / single use helper with a test', function() { 145 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 146 | var helper = engine.fileInfoFor('app/helpers/bar.js'); 147 | var helperTest = engine.fileInfoFor('tests/integration/helpers/bar-test.js'); 148 | 149 | routeTemplate.registerRenderableUsage('bar'); 150 | 151 | engine.finalizeFileDiscovery(); 152 | 153 | assert(helper.destRelativePath === 'src/ui/routes/posts/index/-components/bar/helper.js'); 154 | assert(helperTest.destRelativePath === 'src/ui/routes/posts/index/-components/bar/helper-integration-test.js'); 155 | }); 156 | 157 | it('detecting private / single use components (component only)', function() { 158 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 159 | var component = engine.fileInfoFor('app/components/foo-bar.js'); 160 | var componentTemplate = engine.fileInfoFor('app/templates/components/foo-bar.hbs'); 161 | 162 | routeTemplate.registerRenderableUsage('foo-bar'); 163 | 164 | engine.finalizeFileDiscovery(); 165 | 166 | assert(component.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/component.js'); 167 | assert(componentTemplate.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/template.hbs'); 168 | }); 169 | 170 | it('detecting private / single use components with slashes in component invocation', function() { 171 | var routeTemplate = engine.fileInfoFor('app/templates/posts/post.hbs'); 172 | var component = engine.fileInfoFor('app/components/posts/post/foo-bar.js'); 173 | var componentTemplate = engine.fileInfoFor('app/templates/components/posts/post/foo-bar.hbs'); 174 | 175 | routeTemplate.registerRenderableUsage('posts/post/foo-bar'); 176 | 177 | engine.finalizeFileDiscovery(); 178 | 179 | assert(component.destRelativePath === 'src/ui/routes/posts/post/-components/foo-bar/component.js'); 180 | assert(componentTemplate.destRelativePath === 'src/ui/routes/posts/post/-components/foo-bar/template.hbs'); 181 | }); 182 | 183 | it('detecting private / single use components within other private/single use components', function() { 184 | var routeTemplate = engine.fileInfoFor('app/templates/posts/index.hbs'); 185 | var fooBarComponent = engine.fileInfoFor('app/components/foo-bar.js'); 186 | var fooBarComponentTemplate = engine.fileInfoFor('app/templates/components/foo-bar.hbs'); 187 | var derpHerkComponent = engine.fileInfoFor('app/components/derp-herk.js'); 188 | var derpHerkComponentTemplate = engine.fileInfoFor('app/templates/components/derp-herk.hbs'); 189 | 190 | routeTemplate.registerRenderableUsage('foo-bar'); 191 | fooBarComponentTemplate.registerRenderableUsage('derp-herk'); 192 | 193 | engine.finalizeFileDiscovery(); 194 | 195 | assert(fooBarComponent.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/component.js'); 196 | assert(fooBarComponentTemplate.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/template.hbs'); 197 | assert(derpHerkComponent.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/derp-herk/component.js'); 198 | assert(derpHerkComponentTemplate.destRelativePath === 'src/ui/routes/posts/index/-components/foo-bar/derp-herk/template.hbs'); 199 | }); 200 | }); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launchers: { 3 | Node: { 4 | command: 'mocha --reporter tap', 5 | protocol: 'tap' 6 | } 7 | }, 8 | src_files: [ 9 | 'lib/**/*.js', 10 | 'test/*.js' 11 | ], 12 | 'launch_in_dev': ['Node'] 13 | }; 14 | --------------------------------------------------------------------------------