Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add route command's blueprint #1542

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions addon/ng2/blueprints/guard/files/__path__/__name__.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import {
Can<%= hook %>,
ActivatedRouteSnapShot,
RouterStateSnapShot,
Router
} from '@angular/router';

@Injectable()
export class <%= classifiedModuleName %> implements Can<%= hook %> {
constructor(private router: Router) {}

can<%= hook %>(<%= route %>: ActivatedRouteSnapShot, state: RouterStateSnapShot) {

}
}
99 changes: 99 additions & 0 deletions addon/ng2/blueprints/guard/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const SilentError = require('silent-error');
const path = require('path');
const util = require('../../utilities/route-utils');
var dynamicPathParser = require('../../utilities/dynamic-path-parser');

module.exports = {
description: 'Generate guard',

availableOptions: [
{ name: 'can-activate', type: Boolean, default: false, aliases: ['ca'] },
{ name: 'can-deactivate', type: Boolean, default: false, aliases: ['cd'] },
{ name: 'can-activate-child', type: Boolean, default: false, aliases: ['cach'] },
{ name: 'route', type: String },
{ name: 'name', type: String }
],

normalizeEntityName: function(entityName) {
var parsedPath = dynamicPathParser(this.project, entityName);
this.dynamicPath = parsedPath;
this.guardPath = entityName;
this.entityName = path.parse(entityName).name.split('.')[0];
return this.entityName;
},

locals: function(options) {
var activate = options.canActivate;
var deactivate = options.canDeactivate;
var activateChild = options.canActivateChild;
// make sure only one option is selected
if (activate + deactivate + activateChild !== 1) {
throw new SilentError('Please choose only one of the aliases ca, cd, cach');
}
this.hook = activate ? 'Activate' : ( deactivate ? 'Deactivate' : 'ActivateChild');
var customVariables = {
hook: this.hook,
route: activate || deactivate ? 'route' : 'childRoute'
};
if (options.name) {
customVariables['classifiedModuleName'] = options.name;
}
return customVariables;
},

fileMapTokens: function() {
return {
__name__: () => this.entityName,
__path__: () => this.dynamicPath.dir
}
},

beforeInstall: function() {
this.importPath = this.guardPath[0] === path.sep ?
path.resolve(path.join(this.project.root, 'src', 'app', this.guardPath)) :
path.join(process.env.PWD, this.guardPath);
if (this.importPath.indexOf('src' + path.sep + 'app') === -1) {
throw new SilentError('Guard must be within app');
}

},
// TODO: resolve import path and disallow duplicate guards
afterInstall: function(options) {
return this._locals(options)
.then(names => {
var mainFile = path.join(this.project.root, 'src', 'main.ts');
var routesFile = path.join(this.project.root, 'src', 'routes.ts');
var importPath = '.' + this.importPath.replace(path.join(this.project.root, 'src'), '');

var imports = {};
var toInsert = {};
var importedClass = names.classifiedModuleName;
imports[importedClass] = [ `${importPath}.service` ];
var route = this.guardPath.split('/').filter(n => n !== '').join('/') // strip outer slashes
toInsert[route] = [ `can${this.hook}`, `[ ${importedClass} ]` ];
var updateRoutesFile = util.addItemsToRouteProperties(routesFile, toInsert);
updateRoutesFile.concat(util.insertImport(routesFile, importedClass, importPath));

return util.applyChanges(updateRoutesFile)
.then(() => util.applyChanges(util.bootstrapItem(mainFile, imports, importedClass)));
});
}
}


// .then(function() {
// return ng(['generate', 'guard', '/test-route', '--ca'])
// }).then(function() {
// var mainFileContent = fs.readFileSync(mainFile, 'utf8');
// var routesFileContent = fs.readFileSync(routesFile, 'utf8');
// expect(mainFileContent).to.contain('import { TestRoute } from \'./app/test-route/test-route.service.ts\'');
// if (!isMobileTest()){
// expect(mainFileContent).to.contain('bootstrap(AppComponent, [ provideRouter(routes), TestRoute ])');
// } else {
// expect(mainFileContent).to.contain('bootstrap(AppComponent, [ APP_SHELL_RUNTIME_PROVIDERS, provideRouter(routes), TestRoute ])');
// }

// expect(routesFileContent).to.contain('import { TestRoute } from \'./app/test-route/test-route.service.ts\'');
// expect(routesFileContent).to.contain('[\n { path: \'test-route\', component: TestRouteComponent: canActivate: [TestRoute] }\n ]')
// })
// })
2 changes: 2 additions & 0 deletions addon/ng2/blueprints/routes/files/__path__/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export default [];
99 changes: 99 additions & 0 deletions addon/ng2/blueprints/routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const Blueprint = require('ember-cli/lib/models/blueprint');
const getFiles = Blueprint.prototype.files;
const path = require('path');
const fs = require('fs');
var chalk = require('chalk');
const util = require('../../utilities/route-utils');
const SilentError = require('silent-error');

module.exports = {
description: 'Generates a route and template',

availableOptions: [
{ name: 'default', type: Boolean, default: false },
{ name: 'route', type: String },
{ name: 'parent', type: String, default: '' },
{ name: 'outlet', type: Boolean, default: false },
{ name: 'with-component', type: Boolean, default: false, aliases: ['wc'] } // isn't functional yet
],

beforeInstall: function(options){
this._locals(options)
.then(names => {
var directory = this.newRoutePath[0] === path.sep ?
path.resolve(path.join(this.project.root, 'src', 'app', this.newRoutePath)):
path.resolve(process.env.PWD, this.newRoutePath);
// get route to be used in routes.ts
var route = directory.replace(path.join(this.project.root, 'src', 'app'), '');
var parsedRoute = path.parse(route);
var routeName = parsedRoute.name.split('.')[0];
// take care of the cases /**/home/home.component.ts vs /**/home
route = routeName === path.parse(parsedRoute.dir).name ?
path.parse(route).dir : `${parsedRoute.dir}/${routeName}`;
// setup options needed for adding path to routes.ts
this.pathOptions = {
isDefault: options.default,
route: options.route || route,
parent: options.parent,
outlet: options.outlet,
component: `${names.classifiedModuleName}Component`,
dasherizedName: names.dasherizedModuleName,
mainFile: path.join(this.project.root, 'src/main.ts'),
routesFile: path.join(this.project.root, 'src/routes.ts')
};
});
},

files: function() {
var fileList = getFiles.call(this);
if (this.project && fs.existsSync(path.join(this.project.root, 'src/routes.ts'))) {
return [];
}
return fileList;
},

fileMapTokens: function() {
return {
__path__: () => 'src'
};
},

normalizeEntityName: function(entityName) {
this.newRoutePath = entityName
entityName = path.parse(entityName).name;
if (!entityName) {
throw new SilentError('Please provide new route\'s name');
}
return entityName.split('.')[0];
},

afterInstall: function() {
return Promise.resolve().then(() => {
// confirm componentFile and presence of export of the component in componentFile
var component = this.pathOptions.component;
var file = util.resolveComponentPath(this.project.root, process.env.PWD, this.newRoutePath);
if (!util.confirmComponentExport(file, component)) {
throw new SilentError(`Please add export for '${component}' to '${file}'`);
}
return Promise.resolve();
}).then(() => {
// update routes in routes.ts
return util.applyChanges(util.addPathToRoutes(this.pathOptions.routesFile, this.pathOptions));
}).then(() => {
// add import to routes.ts in main.ts
return util.applyChanges(
[util.insertImport(this.pathOptions.mainFile, 'routes', './routes', true)]);
}).then(() => {
// bootstrap routes
var routes = { 'provideRouter': ['@angular/router'] };
var toBootstrap = 'provideRouter(routes)';
return util.applyChanges(util.bootstrapItem(this.pathOptions.mainFile, routes, toBootstrap));
}).catch(e => {
if (e.message.indexOf('Did not bootstrap') !== -1) {
this._writeStatusToUI(chalk.yellow, e.message, '');
} else {
throw new SilentError(e.message);
}
});
}
}
14 changes: 11 additions & 3 deletions addon/ng2/utilities/route-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ export function bootstrapItem(mainFile, imports: {[key: string]: [string, boolea
// get ExpressionStatements from the top level syntaxList of the sourceFile
let bootstrapNodes = rootNode.getChildAt(0).getChildren().filter(node => {
// get bootstrap expressions
return node.kind === ts.SyntaxKind.ExpressionStatement &&
node.getChildAt(0).getChildAt(0).text.toLowerCase() === 'bootstrap';
return node.kind === ts.SyntaxKind.ExpressionStatement && node.getChildAt(0) &&
node.getChildAt(0).getChildAt(0) && node.getChildAt(0).getChildAt(0).text
&& node.getChildAt(0).getChildAt(0).text.toLowerCase() === 'bootstrap';
});
if (bootstrapNodes.length !== 1) {
throw new Error(`Did not bootstrap provideRouter in ${mainFile}` +
' because of multiple or no bootstrap calls');
' because of multiple or no traditional bootstrap calls');
}
let bootstrapNode = bootstrapNodes[0].getChildAt(0);
let isBootstraped = findNodes(bootstrapNode, ts.SyntaxKind.SyntaxList) // get bootstrapped items
Expand Down Expand Up @@ -520,3 +521,10 @@ function getValueForKey(objectLiteralNode: ts.TypeNode.ObjectLiteralExpression,
function getRootNode(file: string) {
return ts.createSourceFile(file, fs.readFileSync(file).toString(), ts.ScriptTarget.ES6, true);
}

function printAll(node, d = 0) {
let text = node.text ? `-----> ${node.text}` : '';
console.log(new Array(d).join('####'), ts.SyntaxKind[node.kind], text);
d++;
node.getChildren().forEach(_ => printAll(_, d));
}
Binary file added angular-cli-1.0.0-beta.9.tgz
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/acceptance/route-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('route utils', () => {
.then(() => nru.bootstrapItem(mainFile, routes, toBootstrap))
.catch(e =>
expect(e.message).to.equal('Did not bootstrap provideRouter in' +
' tmp/main.ts because of multiple or no bootstrap calls')
' tmp/main.ts because of multiple or no traditional bootstrap calls')
);
});
it('configures correctly if bootstrap or provide router is not at top level', () => {
Expand Down
16 changes: 16 additions & 0 deletions tests/e2e/e2e_workflow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ describe('Basic end-to-end Workflow', function () {
});
});

it('creates routes.ts, configures main.ts, and adds routes', function() {
return ng(['generate', 'component', 'test-route']).then(function() {
return ng(['generate', 'routes', `${path.sep}test-route`]).then(function () {
var routesFile = path.join(process.cwd(), 'src', 'routes.ts');
var mainFile = path.join(process.cwd(), 'src', 'main.ts');
expect(existsSync(routesFile)).to.be.truthy;
var mainFileContent = fs.readFileSync(mainFile, 'utf8');
var routesFileContent = fs.readFileSync(routesFile, 'utf8');

expect(mainFileContent).to.contain('import routes from \'./routes\';');
expect(routesFileContent).to.contain('import { TestRouteComponent } from \'./app/test-route/test-route.component\';');
expect(routesFileContent).to.contain('[\n { path: \'test-route\', component: TestRouteComponent }\n]');
});
});
});

xit('Perform `ng test` after adding a route', function () {
this.timeout(420000);

Expand Down