diff --git a/.jshintrc b/.jshintrc index c7a8056..5090d44 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,12 +3,13 @@ "esnext": true, "bitwise": true, "camelcase": true, - "curly": true, "eqeqeq": true, + "eqnull": true, "immed": true, "indent": 2, - "latedef": true, + "latedef": "nofunc", "newcap": true, + "nonew": true, "noarg": true, "quotmark": "single", "regexp": true, @@ -16,6 +17,5 @@ "unused": true, "strict": true, "trailing": true, - "smarttabs": true, "maxlen": 80 } diff --git a/acl/index.js b/acl/index.js index 4d0c79e..cad6d79 100644 --- a/acl/index.js +++ b/acl/index.js @@ -2,8 +2,8 @@ var yeoman = require('yeoman-generator'); var async = require('async'); -var workspace = require('loopback-workspace'); -var AclDefinition = workspace.models.AclDefinition; +var wsModels = require('loopback-workspace').models; +var ModelAccessControl = wsModels.ModelAccessControl; var actions = require('../lib/actions'); var helpers = require('../lib/helpers'); @@ -18,6 +18,30 @@ module.exports = yeoman.generators.Base.extend({ loadModels: actions.loadModels, + loadAccessTypeValues: function() { + var done = this.async(); + ModelAccessControl.getAccessTypes(function(err, list) { + this.accessTypeValues = list; + done(err); + }.bind(this)); + }, + + loadRoleValues: function() { + var done = this.async(); + ModelAccessControl.getBuiltinRoles(function(err, list) { + this.roleValues = list; + done(err); + }.bind(this)); + }, + + loadPermissionValues: function() { + var done = this.async(); + ModelAccessControl.getPermissionTypes(function(err, list) { + this.permissionValues = list; + done(err); + }.bind(this)); + }, + askForModel: function() { var done = this.async(); @@ -81,27 +105,27 @@ module.exports = yeoman.generators.Base.extend({ message: 'Select the access type:', type: 'list', default: 'all', - choices: AclDefinition.accessTypeValues, + choices: this.accessTypeValues, }, { name: 'role', message: 'Select the role', type: 'list', default: '$everyone', - choices: AclDefinition.builtinRoleValues, + choices: this.roleValues, }, { name: 'permission', message: 'Select the permission to apply', type: 'list', - choices: AclDefinition.permissionValues, + choices: this.permissionValues, } ]; this.prompt(prompts, function(answers) { this.aclDef = { property: answers.property, accessType: answers.accessType, - principalType: AclDefinition.ROLE, + principalType: 'ROLE', // TODO support all principal types principalId: answers.role, permission: answers.permission }; @@ -117,14 +141,14 @@ module.exports = yeoman.generators.Base.extend({ { where: { name: this.modelName }, limit: 1 } : true /* all models, force refresh */; - this.project.models(filter, function(err, models) { + wsModels.ModelDefinition.find(filter, function(err, models) { if (err) { return done(err); } var firstError = true; async.each(models, function(model, cb) { - model.permissions.create(aclDef, function(err) { + model.accessControls.create(aclDef, function(err) { if (err && firstError) { helpers.reportValidationError(err); firstError = false; diff --git a/app/index.js b/app/index.js index 1f032ca..8809fa5 100644 --- a/app/index.js +++ b/app/index.js @@ -1,10 +1,9 @@ 'use strict'; -var util = require('util'); var yeoman = require('yeoman-generator'); var yosay = require('yosay'); var chalk = require('chalk'); var workspace = require('loopback-workspace'); -var Project = workspace.models.Project; +var Workspace = workspace.models.Workspace; module.exports = yeoman.generators.Base.extend({ constructor: function() { @@ -23,6 +22,7 @@ module.exports = yeoman.generators.Base.extend({ }, injectProjectWriteFile: function() { + /* TODO inject `this.directory` to replace `ncp` in workspace // Modify Project.writeFile use yeoman's write var _projectWriteFile = Project.writeFile; Project.writeFile = function(filepath, content, encoding, cb) { @@ -34,10 +34,29 @@ module.exports = yeoman.generators.Base.extend({ this.on('end', function() { Project.writeFile = _projectWriteFile; }); + */ }, - init: function() { - this.pkg = require('../package.json'); + initWorkspace: function() { + process.env.WORKSPACE_DIR = this.destinationRoot(); + }, + + loadTemplates: function() { + var done = this.async(); + + Workspace.getAvailableTemplates(function(err, list) { + if (err) return done(err); + this.templates = list.map(function(t) { + return { + // TODO - workspace does not provide template details yet + // name: util.format('%s (%s)', t.name, t.description), + // value: t.name + name: t, + value: t + }; + }); + done(); + }.bind(this)); }, askForParameters: function() { @@ -53,23 +72,23 @@ module.exports = yeoman.generators.Base.extend({ message: 'What\'s the name of your application?', default: name }, + /* + TODO: not all templates are projects, some of them are mere components + The only functional project template is 'api-server' at the moment { name: 'template', message: 'What kind of application do you have in mind?', type: 'list', - default: 'mobile', - choices: Project.listTemplates().map(function(t) { - return { - name: util.format('%s (%s)', t.name, t.description), - value: t.name - }; - }), + default: 'api-server', + choices: this.templates } + */ ]; this.prompt(prompts, function(props) { this.appname = props.appname; - this.template = props.template; + //this.template = props.template; + this.template = 'api-server'; done(); }.bind(this)); @@ -78,10 +97,9 @@ module.exports = yeoman.generators.Base.extend({ project: function() { var done = this.async(); - Project.createFromTemplate( - this.destinationRoot(), - this.appname, + Workspace.createFromTemplate( this.template, + this.appname, done ); }, diff --git a/datasource/index.js b/datasource/index.js index fd6e04c..e51d938 100644 --- a/datasource/index.js +++ b/datasource/index.js @@ -2,8 +2,7 @@ var chalk = require('chalk'); var yeoman = require('yeoman-generator'); -var workspace = require('loopback-workspace'); -var Project = workspace.models.Project; +var wsModels = require('loopback-workspace').models; var actions = require('../lib/actions'); var helpers = require('../lib/helpers'); @@ -18,7 +17,7 @@ module.exports = yeoman.generators.NamedBase.extend({ loadConnectors: function() { var done = this.async(); - Project.listAvailableConnectors(function(err, list) { + wsModels.Workspace.listAvailableConnectors(function(err, list) { if (err) { return done(err); } @@ -75,10 +74,11 @@ module.exports = yeoman.generators.NamedBase.extend({ var done = this.async(); var config = { name: this.name, - connector: this.connector + connector: this.connector, + componentName: 'rest' // hard-coded for now }; - this.project.dataSources.create(config, function(err) { + wsModels.DataSourceDefinition.create(config, function(err) { helpers.reportValidationError(err, this.log); return done(err); }.bind(this)); diff --git a/lib/actions.js b/lib/actions.js index 8d925ed..9f36575 100644 --- a/lib/actions.js +++ b/lib/actions.js @@ -1,7 +1,5 @@ 'use strict'; -var util = require('util'); var workspace = require('loopback-workspace'); -var Project = workspace.models.Project; var actions = exports; @@ -11,20 +9,21 @@ var actions = exports; /** * Load the project in `this.destinationRoo()`. - * Set `this.projectDir` and `this.project`. + * Set `this.projectDir`. * @async */ actions.loadProject = function() { - if (this.options.nested && this.options.projectDir && this.options.project) { + if (this.options.nested && this.options.projectDir) { this._externalProject = true; this.projectDir = this.options.projectDir; - this.project = this.options.project; return; } - var done = this.async(); this.projectDir = this.destinationRoot(); + process.env.WORKSPACE_DIR = this.projectDir; + /* TODO - validate the project + var done = this.async(); Project.isValidProjectDir(this.projectDir, function(err, isValid, message) { if (err) { return done(err); @@ -37,41 +36,30 @@ actions.loadProject = function() { message); return done(new Error(msg)); } - - Project.loadFromFiles(this.projectDir, function(err, project) { - if (err) { - return done(err); - } - - this.project = project; - done(); - }.bind(this)); }.bind(this)); + */ }; /** - * Save `this.project`, update all project files. + * Save the current project, update all project files. */ actions.saveProject = function() { if (this._externalProject) { return; } - var done = this.async(); - this.project.saveToFiles(this.projectDir, done); + // no-op in workspace 3.0 }; /** - * Load all models of `this.project`. + * Load all models of the current project. * `this.projectModels` will contain an array of all models (Array.) * `this.modelNames` will contain an array of names (Array.) */ actions.loadModels = function() { var done = this.async(); - this.project.models(function(err, results) { - if (err) { - return done(err); - } + workspace.models.ModelDefinition.all(function(err, results) { + if (err) return done(err); this.projectModels = results; this.modelNames = results.map(function(m) { return m.name; diff --git a/model/index.js b/model/index.js index ccc615d..2546df2 100644 --- a/model/index.js +++ b/model/index.js @@ -1,6 +1,7 @@ 'use strict'; var chalk = require('chalk'); var yeoman = require('yeoman-generator'); +var wsModels = require('loopback-workspace').models; var actions = require('../lib/actions'); var helpers = require('../lib/helpers'); @@ -15,7 +16,8 @@ module.exports = yeoman.generators.NamedBase.extend({ loadDataSources: function() { var done = this.async(); - this.project.dataSources(function(err, results) { + + wsModels.DataSourceDefinition.find(function(err, results) { if (err) { return done(err); } @@ -54,16 +56,30 @@ module.exports = yeoman.generators.NamedBase.extend({ }.bind(this)); }, - model: function() { + modelDefinition: function() { var done = this.async(); var config = { properties: {}, name: this.name, + componentName: wsModels.ConfigFile.ROOT_COMPONENT, public: this.public, + }; + + wsModels.ModelDefinition.create(config, function(err) { + helpers.reportValidationError(err, this.log); + return done(err); + }.bind(this)); + }, + + modelConfiguration: function() { + var done = this.async(); + var config = { + name: this.name, + componentName: 'rest', // hard-coded for now dataSource: this.dataSource }; - this.project.models.create(config, function(err) { + wsModels.ComponentModel.create(config, function(err) { helpers.reportValidationError(err, this.log); return done(err); }.bind(this)); diff --git a/package.json b/package.json index 152647e..e07226a 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,11 @@ "strongloop" ], "dependencies": { - "yeoman-generator": "~0.16.0", - "loopback-workspace": "~2.5.0", - "chalk": "~0.4.0", - "yosay": "^0.1.0", - "async": "^0.9.0" + "async": "^0.9.0", + "chalk": "^0.4.0", + "loopback-workspace": "strongloop/loopback-workspace#3.0", + "yeoman-generator": "^0.16.0", + "yosay": "^0.1.0" }, "peerDependencies": { "yo": ">=1.0.0" diff --git a/property/index.js b/property/index.js index 289779c..a4fb67a 100644 --- a/property/index.js +++ b/property/index.js @@ -3,7 +3,7 @@ var yeoman = require('yeoman-generator'); var chalk = require('chalk'); var workspace = require('loopback-workspace'); -var PropertyDefinition = workspace.models.ModelPropertyDefinition; +var ModelProperty = workspace.models.ModelProperty; var actions = require('../lib/actions'); var helpers = require('../lib/helpers'); @@ -56,7 +56,7 @@ module.exports = yeoman.generators.Base.extend({ var done = this.async(); this.name = this.options.propertyName; - var typeChoices = PropertyDefinition.availableTypes.concat({ + var typeChoices = ModelProperty.availableTypes.concat({ name: '(other)', value: null }); diff --git a/test/acl.test.js b/test/acl.test.js index da2a724..558de5c 100644 --- a/test/acl.test.js +++ b/test/acl.test.js @@ -1,8 +1,8 @@ -/*global describe, beforeEach, it */ +/*global describe, beforeEach, afterEach, it */ 'use strict'; var path = require('path'); var helpers = require('yeoman-generator').test; -var Project = require('loopback-workspace').models.Project; +var wsModels = require('loopback-workspace').models; var SANDBOX = path.resolve(__dirname, 'sandbox'); var fs = require('fs'); var expect = require('must'); @@ -14,13 +14,28 @@ describe('loopback:acl generator', function() { }); beforeEach(function createProject(done) { - Project.createFromTemplate(SANDBOX, 'test-app', 'mobile', done); + common.createDummyProject(SANDBOX, 'test-app', done); + }); + + afterEach(common.resetWorkspace); + + beforeEach(function createCarModel(done) { + var test = this; + wsModels.ModelDefinition.create( + { + name: 'Car', + componentName: '.', + }, + function(err, model) { + test.Model = model; + done(err); + }); }); it('adds an entry to models.json', function(done) { var aclGen = givenAclGenerator(); helpers.mockPrompt(aclGen, { - model: 'user', + model: 'Car', scope: 'all', accessType: '*', role: '$everyone', @@ -28,11 +43,11 @@ describe('loopback:acl generator', function() { }); aclGen.run({}, function() { - var models = readModelsJsonSync(); - var userOpts = models.user.options || {}; - var userAcls = userOpts.acls; + var def = readJsonSync('models/car.json'); + var carAcls = def.acls; - expect(userAcls).to.eql([{ + expect(carAcls).to.eql([{ + id: 1, // TODO fix workspace to not add this extra property accessType: '*', permission: 'AUDIT', principalType: 'ROLE', @@ -49,8 +64,8 @@ describe('loopback:acl generator', function() { return gen; } - function readModelsJsonSync() { - var filepath = path.resolve(SANDBOX, 'models.json'); + function readJsonSync(relativePath) { + var filepath = path.resolve(SANDBOX, relativePath); var content = fs.readFileSync(filepath, 'utf-8'); return JSON.parse(content); } diff --git a/test/app.test.js b/test/app.test.js index bdc5881..3aca181 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -17,17 +17,20 @@ describe('loopback:app generator', function() { it('creates expected files', function(done) { var expected = [ 'package.json', - 'app.js', - 'app.json', - 'datasources.json', - 'models.json', + + 'rest/datasources.json', + 'rest/models.json', + // TODO rest/rest.js + + 'server/config.json', + // TODO server/server.js ]; var gen = givenAppGenerator(); helpers.mockPrompt(gen, { name: 'test-app', - template: 'empty' + template: 'api-server' }); gen.options['skip-install'] = true; diff --git a/test/common.js b/test/common.js index 15ebdb4..189a2cb 100644 --- a/test/common.js +++ b/test/common.js @@ -1,6 +1,8 @@ -/*jshint latedef:nofunc */ 'use strict'; +var async = require('async'); var generators = require('yeoman-generator'); +var workspace = require('loopback-workspace'); +var Workspace = workspace.models.Workspace; var must = require('must'); exports.createGenerator = createGenerator; @@ -28,3 +30,14 @@ if (!must.prototype.members) { }); }; } + +exports.createDummyProject = function(dir, name, done) { + process.env.WORKSPACE_DIR = dir; + Workspace.createFromTemplate('api-server', name, done); +}; + +exports.resetWorkspace = function(done) { + async.each(workspace.models(), function(model, cb) { + model.destroyAll(cb); + }, done); +}; diff --git a/test/datasource.test.js b/test/datasource.test.js index ba2f431..2e60cd6 100644 --- a/test/datasource.test.js +++ b/test/datasource.test.js @@ -1,8 +1,7 @@ -/*global describe, beforeEach, it */ +/*global describe, beforeEach, afterEach, it */ 'use strict'; var path = require('path'); var helpers = require('yeoman-generator').test; -var Project = require('loopback-workspace').models.Project; var SANDBOX = path.resolve(__dirname, 'sandbox'); var fs = require('fs'); var expect = require('must'); @@ -14,18 +13,20 @@ describe('loopback:datasource generator', function() { }); beforeEach(function createProject(done) { - Project.createFromTemplate(SANDBOX, 'test-app', 'mobile', done); + common.createDummyProject(SANDBOX, 'test-app', done); }); - it('adds an entry to datasources.json', function(done) { + afterEach(common.resetWorkspace); + + it('adds an entry to rest/datasources.json', function(done) { var modelGen = givenDataSourceGenerator(['crm']); helpers.mockPrompt(modelGen, { connector: 'mysql' }); - var builtinSources = Object.keys(readDataSourcesJsonSync()); + var builtinSources = Object.keys(readDataSourcesJsonSync('rest')); modelGen.run({}, function() { - var newSources = Object.keys(readDataSourcesJsonSync()); + var newSources = Object.keys(readDataSourcesJsonSync('rest')); var expectedSources = builtinSources.concat(['crm']); expect(newSources).to.have.members(expectedSources); done(); @@ -39,8 +40,8 @@ describe('loopback:datasource generator', function() { return gen; } - function readDataSourcesJsonSync() { - var filepath = path.resolve(SANDBOX, 'datasources.json'); + function readDataSourcesJsonSync(component) { + var filepath = path.resolve(SANDBOX, component || '.', 'datasources.json'); var content = fs.readFileSync(filepath, 'utf-8'); return JSON.parse(content); } diff --git a/test/model.test.js b/test/model.test.js index dd32b70..cab8317 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -1,8 +1,7 @@ -/*global describe, beforeEach, it */ +/*global describe, beforeEach, afterEach, it */ 'use strict'; var path = require('path'); var helpers = require('yeoman-generator').test; -var Project = require('loopback-workspace').models.Project; var SANDBOX = path.resolve(__dirname, 'sandbox'); var fs = require('fs'); var expect = require('must'); @@ -14,19 +13,37 @@ describe('loopback:model generator', function() { }); beforeEach(function createProject(done) { - Project.createFromTemplate(SANDBOX, 'test-app', 'mobile', done); + common.createDummyProject(SANDBOX, 'test-app', done); }); - it('adds an entry to models.json', function(done) { + afterEach(common.resetWorkspace); + + it('creates models/{name}.json', function(done) { + var modelGen = givenModelGenerator(['Product']); + helpers.mockPrompt(modelGen, { + dataSource: 'db', + propertyName: '' + }); + + modelGen.run({}, function() { + var productJson = path.resolve(SANDBOX, 'models/product.json'); + expect(fs.existsSync(productJson), 'file exists'); + var content = JSON.parse(fs.readFileSync(productJson)); + expect(content).to.have.property('name', 'Product'); + done(); + }); + }); + + it('adds an entry to rest/models.json', function(done) { var modelGen = givenModelGenerator(['Product']); helpers.mockPrompt(modelGen, { dataSource: 'db', propertyName: '' }); - var builtinModels = Object.keys(readModelsJsonSync()); + var builtinModels = Object.keys(readModelsJsonSync('rest')); modelGen.run({}, function() { - var newModels = Object.keys(readModelsJsonSync()); + var newModels = Object.keys(readModelsJsonSync('rest')); var expectedModels = builtinModels.concat(['Product']); expect(newModels).to.have.members(expectedModels); done(); @@ -41,8 +58,8 @@ describe('loopback:model generator', function() { return gen; } - function readModelsJsonSync() { - var filepath = path.resolve(SANDBOX, 'models.json'); + function readModelsJsonSync(component) { + var filepath = path.resolve(SANDBOX, component || '.', 'models.json'); var content = fs.readFileSync(filepath, 'utf-8'); return JSON.parse(content); } diff --git a/test/property.test.js b/test/property.test.js index 780f3fd..7c7b33e 100644 --- a/test/property.test.js +++ b/test/property.test.js @@ -2,7 +2,7 @@ 'use strict'; var path = require('path'); var helpers = require('yeoman-generator').test; -var Project = require('loopback-workspace').models.Project; +var wsModels = require('loopback-workspace').models; var SANDBOX = path.resolve(__dirname, 'sandbox'); var fs = require('fs'); var expect = require('must'); @@ -14,23 +14,36 @@ describe('loopback:property generator', function() { }); beforeEach(function createProject(done) { - Project.createFromTemplate(SANDBOX, 'test-app', 'mobile', done); + common.createDummyProject(SANDBOX, 'test-app', done); }); - it('adds an entry to models.json', function(done) { + beforeEach(function createCarModel(done) { + var test = this; + wsModels.ModelDefinition.create( + { + name: 'Car', + componentName: '.', + }, + function(err, model) { + test.Model = model; + done(err); + }); + }); + + it('adds an entry to models/{name}.json', function(done) { var propertyGenerator = givenPropertyGenerator(); helpers.mockPrompt(propertyGenerator, { - model: 'user', + model: 'Car', name: 'isPreferred', type: 'boolean', required: 'true' }); propertyGenerator.run({}, function() { - var models = readModelsJsonSync(); - var userOpts = models.user.properties || {}; - expect(userOpts).to.have.property('isPreferred'); - expect(userOpts.isPreferred).to.eql({ + var definition = readJsonSync('models/car.json'); + var props = definition.properties || {}; + expect(props).to.have.property('isPreferred'); + expect(props.isPreferred).to.eql({ type: 'boolean', required: true }); @@ -45,8 +58,8 @@ describe('loopback:property generator', function() { return gen; } - function readModelsJsonSync() { - var filepath = path.resolve(SANDBOX, 'models.json'); + function readJsonSync(relativePath) { + var filepath = path.resolve(SANDBOX, relativePath); var content = fs.readFileSync(filepath, 'utf-8'); return JSON.parse(content); }