├── .gitignore ├── History.md ├── LICENSE.txt ├── README.md ├── example ├── README.md ├── index.js └── package.json ├── index.js ├── package.json └── packer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.2 / 2015-04-10 3 | ================== 4 | 5 | * Fixed security bug with path browsing 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Auth0, Inc. (http://auth0.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Configured sample generator 2 | 3 | **WARNING - THIS PROJECT IS NO LONGER MAINTAINED!!!** 4 | 5 | 6 | Generates sample projects from a Github repository with some added user configuration. This package lets you create tailored samples for each of your users that have everything configured to just start using them. 7 | 8 | ## Key Features 9 | 10 | * Create a sample zip file with added configuration 11 | * Sample project can be a sub folder of a Github repository 12 | * Entirely customizable so that it fits yoour company's configuration format 13 | * Makes your users happier ;) 14 | 15 | ## Installation 16 | 17 | ````bash 18 | npm install configurated-sample-generator --save 19 | ```` 20 | 21 | ## Usage 22 | 23 | ### Basic usage 24 | 25 | First, we'll configure and create the Packer. It'll state how we'll configure the sample project we downloads from Github. 26 | 27 | ````js 28 | var PackerConfigurer = require('configurated-sample-generator'); 29 | 30 | module.exports = new PackerConfigurer({ 31 | // This is the Github organization 32 | organization: 'auth0' 33 | }) 34 | .setInformationLoader(function(context) { 35 | // Here we'd go to the database and search for some configurations 36 | // based on some parameter we got from the request (accesable by context) 37 | return db.getSecret(context.clientId).then(function(secret) { 38 | context.secret = secret; 39 | return context; 40 | }); 41 | }) 42 | // This is for Server side samples. Creates a .env file 43 | .addTypeProcesor('server', PackerConfigurer.envFileCreator(function(context) { 44 | return { 45 | // This we got from the request 46 | 'CLIENT_ID': context.clientId, 47 | // This we loaded from DB in the information loader 48 | 'CLIENT_SECRET': context.secret, 49 | }; 50 | })) 51 | // This is for a .Net project for examle. It'll replace the `{CLIENT_ID}` from the 52 | // app.config for the real value we got 53 | .addTypeProcesor('searchAndReplaceConfig', PackerConfigurer.fileReplacer(function(context, contents) { 54 | return contents 55 | .replace('{CLIENT_ID}', context.clientId) 56 | .replace('{CLIENT_SECRET}', context.secret) 57 | })) 58 | .getPacker(); 59 | ```` 60 | 61 | Now, we import the packer and serve the ZIP file from an URL 62 | 63 | ````js 64 | // This is the file above 65 | var Packer = require('./packer'); 66 | 67 | app.get('/download-server-sample', function(req, res) { 68 | 69 | var packer = new Packer({ 70 | // Name of the Github repository 71 | repository: 'company-examples', 72 | // Github branch 73 | gitBranch: 'master', 74 | // Path to the example if it's not the entire repository 75 | examplePath: 'examples/server-sample', 76 | // This matches the types from the file above 77 | // We could have used searchAndReplaceConfig 78 | sampleType: 'server', 79 | // Add information to the `context` object 80 | contextInformation: { 81 | clientId: req.query.clientId 82 | } 83 | }); 84 | 85 | // Downloads the Github repo 86 | // Adds needed configuration file 87 | // Returns a stream as a promise 88 | packer.create() 89 | .then(function(stream) { 90 | res.writeHead('200', { 91 | 'Content-Type': 'application/zip', 92 | 'Content-disposition': 'attachment; filename=' + req.params.repo + '-sample.zip' 93 | }); 94 | res.end(stream,'binary'); 95 | }) 96 | .catch(function(error) { 97 | res.send(400, error); 98 | }); 99 | }); 100 | ```` 101 | 102 | ## API 103 | 104 | ### PackerConfigurer 105 | 106 | This is the `index.js` object. 107 | 108 | #### new PackerConfigurer(options) 109 | 110 | **All constructor parameters are optionals**. They can be specified later on in the [`Packer`](https://github.com/auth0/configurated-sample-generator#packer). 111 | 112 | * `organization`: Name of the Github organization. 113 | * `repository`: Name of the Github repository. 114 | * `gitBranch`: Name of the git branch to use 115 | * `examplePath`: Path to the example if not the root folder 116 | * `sampleType`: Sample type to use. Check [Type processors](https://github.com/auth0/configurated-sample-generator#addtypeprocesorname-function) section 117 | * `configurationFilePath`: Path of the file to be used to configure. This will be used if we're going to replace the content of a file instead of actually creating a new one. 118 | 119 | #### setInformationLoader(loaderFunction) 120 | 121 | The loaderFunction will load user configuration values from somewhere and add them to the context. The `loaderFunction` receives the context as a parameter and returns either a `context` or a promise of a `context` with all the new fields added. 122 | 123 | ````js 124 | .setInformationLoader(function(context) { 125 | return db.getSecret(context.clientId).then(function(secret) { 126 | context.secret = secret; 127 | return context; 128 | }); 129 | }) 130 | ```` 131 | 132 | #### addTypeProcesor(name, function) 133 | 134 | This adds a new Type Processor. A type processor is a function that will create or modify an existing file from the example with user configuration values that were loaded with the [Information Loader](https://github.com/auth0/configurated-sample-generator#setinformationloaderloaderfunction). The function sent as a parameter receives the `context` as a parameter and returns either the `context` itself or a promise of a `context`. It can leverage the `context.examplePath` and `context.configurationFilePath` to know where to create the configuration file or which file to modify. 135 | 136 | For example: 137 | 138 | ````js 139 | function myConfigurator(context) { 140 | var fileContent = 'This is user content ' + context.someVariable; 141 | return fs.writeFileAsync(path.join(context.examplePath, 'file.config'), fileContent, {flags: 'w'}).then(function() { 142 | debug("File created"); 143 | return context; 144 | }); 145 | }; 146 | ```` 147 | 148 | This library comes with 3 helper TypeProcessor creators which will work for most cases 149 | 150 | ##### fileWriter 151 | 152 | Creates a new file in the example folder. Receives a `context` as a parameter and returns an object with the `name` and `content` fields for the file name and file contents respectively 153 | 154 | ````js 155 | .addTypeProcesor('myType', PackerConfigurer.fileWriter(function(context) { 156 | return { 157 | name: 'app.config', 158 | content: 'This is user content ' + context.someVariable 159 | } 160 | })); 161 | ```` 162 | 163 | ##### envFileCreator 164 | 165 | Creates a new `.env`. Returns a key value object with the configurations to put in the `.env` file 166 | 167 | ````js 168 | .addTypeProcesor('myType', PackerConfigurer.envFileCreator(function(context) { 169 | return { 170 | myKey: context.keyValue 171 | } 172 | })); 173 | ```` 174 | 175 | ##### fileReplacer 176 | 177 | Replaces the content of an existing configuration file. It receives the `context` and the content of the file specified in the `configurationFilePath` variable and returns the new content of the file. 178 | 179 | ````js 180 | .addTypeProcesor('searchAndReplaceConfig', PackerConfigurer.fileReplacer(function(context, contents) { 181 | return contents 182 | .replace('{CLIENT_ID}', context.clientId) 183 | .replace('{CLIENT_SECRET}', context.secret) 184 | })) 185 | ```` 186 | 187 | #### getPacker 188 | 189 | Returns the `Packer` prototype to be used to create the sample projects 190 | 191 | ### Packer 192 | 193 | #### new Packer(options) 194 | 195 | The packer constructor can receive the same options as the [PackerConfigurer constructor](https://github.com/auth0/configurated-sample-generator#new-packerconfigureroptions). Besides that, it can receive an extra `contextInformation` property with any information that the `context` object should be bootstrapped with. 196 | 197 | #### create 198 | 199 | It returns a promise of an buffer of the Zip file with the sample. It can be used as follows for example: 200 | 201 | 202 | ````js 203 | packer.create() 204 | .then(function(stream) { 205 | debug("Got the stream OK"); 206 | res.writeHead('200', { 207 | 'Content-Type': 'application/zip', 208 | 'Content-disposition': 'attachment; filename=' + req.params.repo + '-sample.zip' 209 | }); 210 | res.end(stream,'binary'); 211 | }) 212 | .catch(function(error) { 213 | debug('There is an error', error); 214 | res.send(400, error); 215 | }); 216 | ```` 217 | 218 | ## Usages 219 | 220 | This project is used throughout to generate samples for the [Auth0](https://auth0.com) documentation. For example, if you go to [AngularJS documentation](https://auth0.com/docs/client-platforms/angularjs) logged in, you'll see the sample is downloaded with your Auth0 keys already configured. 221 | 222 | ## Examples 223 | 224 | In the [example folder](https://github.com/auth0/configurated-sample-generator/tree/master/example) :). 225 | 226 | ## Issue Reporting 227 | 228 | If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 229 | 230 | ## Author 231 | 232 | [Auth0](auth0.com) 233 | 234 | ## License 235 | 236 | This project is licensed under the MIT license. See the [LICENSE](LICENSE.txt) file for more info. 237 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This is an example of usage of `configurated-sample-generator`. 4 | 5 | Just run `npm i` + `node index.js` and go to the following url: [http://localhost:3000/auth0/configured-sample-generator/master/create-package?path=example/&configId=23](http://localhost:3000/auth0/configured-sample-generator/master/create-package?path=example/&configId=23). 6 | 7 | This will download this example as a ZIP file with an added `.env` file which will have the `configId` set to 23 since we put that in the URL and one other configuration file we "got from the DB". 8 | 9 | Enjoy! 10 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var logger = require('morgan'); 3 | var http = require('http'); 4 | var debug = require('debug')('server'); 5 | var PackerConfigurer = require('configurated-sample-generator'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var app = express(); 9 | 10 | // Prettify HTML 11 | app.locals.pretty = true; 12 | 13 | app.use(logger('dev')); 14 | 15 | 16 | 17 | // Request body parsing middleware should be above methodOverride 18 | app.use(bodyParser.urlencoded()); 19 | app.use(bodyParser.json()); 20 | 21 | // Packer configuration 22 | var Packer = new PackerConfigurer() 23 | .setInformationLoader(function(context) { 24 | context.otherConfigId = 'other configuration id from db'; 25 | return context; 26 | }) 27 | .addTypeProcesor('server', PackerConfigurer.envFileCreator(function(context) { 28 | return { 29 | 'CONFIG_ID': context.configId, 30 | 'OTHER_CONFIG_ID': context.otherConfigId 31 | }; 32 | })) 33 | .getPacker(); 34 | 35 | app.get('/:org/:repo/:branch/create-package', function(req, res) { 36 | 37 | var packer = new Packer({ 38 | organization: req.params.org, 39 | repository: req.params.repo, 40 | gitBranch: req.params.branch, 41 | examplePath: req.query.path, 42 | sampleType: 'server', 43 | configurationFilePath: req.query.filePath, 44 | contextInformation: { 45 | configId: req.query.configId 46 | } 47 | }); 48 | 49 | packer.create() 50 | .then(function(stream) { 51 | debug("Got the stream OK"); 52 | res.writeHead('200', { 53 | 'Content-Type': 'application/zip', 54 | 'Content-disposition': 'attachment; filename=' + req.params.repo + '-sample.zip' 55 | }); 56 | res.end(stream,'binary'); 57 | }) 58 | .catch(function(error) { 59 | debug('There is an error', error); 60 | res.send(400, error); 61 | }); 62 | }); 63 | 64 | http.createServer(app).listen(3000, function (err) { 65 | console.log('listening in http://localhost:3000'); 66 | }); 67 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth0-package-creator", 3 | "version": "0.3.0", 4 | "description": "Auth0 Package creator with custom info", 5 | "repository": "git://github.com/auth0/auth0-package-creator", 6 | "author": "Martin Gontovnikas", 7 | "license": "ISC", 8 | "dependencies": { 9 | "bluebird": "^3.3.3", 10 | "body-parser": "^1.4.3", 11 | "configurated-sample-generator": "file:../", 12 | "debug": "^2.2.0", 13 | "express": "^4.4.5", 14 | "lodash": "^4.6.1", 15 | "morgan": "^1.1.1" 16 | }, 17 | "engines": { 18 | "node": "4.x" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | Bluebird = require('bluebird'), 3 | path = require('path'), 4 | debug = require('debug')('configurated-sample-generator'), 5 | fs = Bluebird.promisifyAll(require('fs')), 6 | packer = require('./packer'), 7 | util = require('util'); 8 | 9 | function PackerConfigurer(options) { 10 | options = options || {}; 11 | this.organization = options.organization; 12 | this.types = { 13 | noop: _.identity 14 | }; 15 | this.infoLoader = _.identity; 16 | } 17 | 18 | PackerConfigurer.prototype.addTypeProcesor = function(name, fn) { 19 | this.types[name] = fn; 20 | return this; 21 | } 22 | 23 | PackerConfigurer.prototype.setInformationLoader = function(fn) { 24 | this.infoLoader = fn; 25 | return this; 26 | } 27 | 28 | PackerConfigurer.prototype.getPacker = function() { 29 | return _.partial(packer, this); 30 | } 31 | 32 | PackerConfigurer.fileWriter = function(fn) { 33 | return function fileWriter(context) { 34 | var fileDesc = fn(context); 35 | return fs.writeFileAsync(path.join(context.configurationFilePath || context.examplePath, fileDesc.name), fileDesc.content, {flags: 'w'}).then(function() { 36 | debug("File created"); 37 | return context; 38 | }); 39 | }; 40 | }; 41 | 42 | 43 | PackerConfigurer.envFileCreator = function(fn) { 44 | return PackerConfigurer.fileWriter(function(context) { 45 | var env = _.map(fn(context), function(value, key) { 46 | return util.format('%s=%s', key, value); 47 | }).join(' \n'); 48 | return { 49 | name: '.env', 50 | content: env + '\n' 51 | }; 52 | }); 53 | }; 54 | 55 | 56 | PackerConfigurer.fileReplacer = function(fn) { 57 | return function searchAndReplaceConfig(context) { 58 | return fs.readFileAsync(context.configurationFilePath) 59 | .then(function(contents) { 60 | debug("Reading content of existing file to replace"); 61 | var finalContent = fn(context, contents.toString()); 62 | 63 | debug("Content replaced"); 64 | return fs.writeFileAsync(context.configurationFilePath, finalContent, {flags: 'w'}) 65 | .then(function() { 66 | debug("New content written"); 67 | return context; 68 | }); 69 | }); 70 | }; 71 | } 72 | 73 | module.exports = PackerConfigurer; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configurated-sample-generator", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "description": "Generates sample projects from a Github repository with some added user configuration", 6 | "repository": "git://github.com/auth0/configurated-sample-generator", 7 | "author": "Martin Gontovnikas ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "archiver": "^0.21.0", 11 | "bluebird": "^3.3.3", 12 | "debug": "^2.2.0", 13 | "lodash": "^4.6.1", 14 | "request": "^2.37.0", 15 | "stream-to-promise": "^1.1.0", 16 | "tar": "^2.2.1", 17 | "temp": "^0.8.0" 18 | }, 19 | "engines": { 20 | "node": "4.x" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packer.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | fs = Promise.promisifyAll(require('fs')), 3 | util = require('util'), 4 | tar = require('tar'), 5 | request = require('request'), 6 | zlib = require('zlib'), 7 | _ = require('lodash'), 8 | debug = require('debug')('configurated-sample-generator'), 9 | temp = Promise.promisifyAll(require('temp')), 10 | path = require('path'), 11 | streamToPromise = require('stream-to-promise'), 12 | archiver = require('archiver'); 13 | 14 | // Initializations 15 | temp.track(); 16 | 17 | function Packer(configuration, options) { 18 | this.validate(options); 19 | this.organization = options.organization || configuration.organization; 20 | this.repo = options.repository || configuration.repository; 21 | this.branch = options.gitBranch || configuration.gitBranch; 22 | this.path = options.examplePath || configuration.examplePath || '.'; 23 | this.type = options.sampleType || configuration.sampleType || 'noop' ; 24 | this.filePath = options.configurationFilePath || configuration.configurationFilePath || this.path; 25 | this.contextInformation = options.contextInformation; 26 | this.config = configuration; 27 | } 28 | 29 | Packer.prototype.validate = function(options) { 30 | if (options.examplePath) { 31 | if (options.examplePath.indexOf('.') === 0 || 32 | options.examplePath.indexOf('/') === 0 || 33 | options.examplePath.indexOf('..') > -1) { 34 | throw new Error("The path you have entered is invalid"); 35 | } 36 | } 37 | } 38 | 39 | Packer.prototype.create = function() { 40 | debug("Calling create function"); 41 | return this.download() 42 | .then(this.config.infoLoader.bind(this.cofig)) 43 | .then(this.config.types[this.type].bind(this.config)) 44 | .then(this.zip.bind(this)) 45 | .finally(function() { 46 | temp.cleanup(); 47 | }); 48 | }; 49 | 50 | Packer.GITHUB_URL = 'https://github.com/%s/%s/archive/%s.tar.gz'; 51 | 52 | Packer.prototype.download = function() { 53 | var _this = this; 54 | var url = util.format(Packer.GITHUB_URL, this.organization, this.repo, this.branch); 55 | 56 | debug("Downloading file from URL", url); 57 | 58 | return temp.mkdirAsync('githubdownload') 59 | .then(function(dirPath) { 60 | debug("Temp dir created", dirPath); 61 | var examplePath = path.join(dirPath, _this.repo + '-' + _this.branch, _this.path); 62 | var filePath = path.join(dirPath, _this.repo + '-' + _this.branch, _this.filePath); 63 | 64 | return streamToPromise( 65 | request(url) 66 | .pipe(zlib.Unzip()) 67 | .pipe(tar.Extract({path: dirPath}))) 68 | .then(function() { 69 | debug("Converted download stream to promise"); 70 | return _.extend({ 71 | examplePath: examplePath, 72 | configurationFilePath: filePath 73 | }, _this.contextInformation); 74 | 75 | }); 76 | }); 77 | }; 78 | 79 | 80 | Packer.prototype.zip = function(context) { 81 | debug("Zipping file"); 82 | 83 | var archive = archiver('zip'); 84 | 85 | archive.bulk([ 86 | { expand: true, cwd: context.examplePath, src: ['**'], dot: true } 87 | ]); 88 | 89 | archive.finalize(); 90 | 91 | return streamToPromise(archive) 92 | .then(function(data) { 93 | debug("Zip created"); 94 | return data; 95 | }); 96 | }; 97 | 98 | module.exports = Packer; 99 | --------------------------------------------------------------------------------