├── .gitattributes ├── .gitignore ├── .editorconfig ├── .travis.yml ├── .eslintrc.js ├── casper └── index.js ├── app ├── github.js └── index.js ├── LICENSE ├── package.json ├── test └── ghost-spec.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | tmp/ 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - v0.10 5 | - v0.12 6 | - v4 7 | - v5 8 | - v6 9 | 10 | install: 11 | - npm install -g eslint mocha grunt-cli 12 | - npm install 13 | 14 | script: 15 | - eslint "app/*.js" "casper/*.js" "test/*.js" 16 | - npm test 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ 8 | "error", 9 | 4 10 | ], 11 | "linebreak-style": [ 12 | "error", 13 | "unix" 14 | ], 15 | "quotes": [ 16 | "error", 17 | "single" 18 | ], 19 | "semi": [ 20 | "error", 21 | "always" 22 | ] 23 | } 24 | }; -------------------------------------------------------------------------------- /casper/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var generators = require('yeoman-generator'); 5 | 6 | module.exports = generators.Base.extend({ 7 | constructor: function () { 8 | generators.Base.apply(this, arguments); 9 | 10 | this.argument('name', { 11 | type: String, 12 | required: true, 13 | desc: 'Give your new theme a name!' 14 | }); 15 | }, 16 | 17 | writing: function () { 18 | var casperPath = path.join(process.cwd(), 'content', 'themes', 'casper'); 19 | var newThemePath = path.join(process.cwd(), 'content', 'themes', this.name); 20 | 21 | this.fs.copy(casperPath, newThemePath); 22 | }, 23 | 24 | end: function () { 25 | this.log('Successfully generated a new theme \'' + this.name + '\'!'); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /app/github.js: -------------------------------------------------------------------------------- 1 | var github = require('octonode'), 2 | Promise = require('bluebird'), 3 | client = github.client(); 4 | 5 | function getVersions(repoName) { 6 | var repo = client.repo(repoName), 7 | tags = Promise.promisify(repo.tags, {context: repo}); 8 | 9 | // jshint camelcase:false 10 | client.requestDefaults.qs = {per_page: 100}; 11 | // jshint camelcase:true 12 | 13 | return tags; 14 | } 15 | 16 | function getArchive(repoName) { 17 | return function () { 18 | var repo = client.repo(repoName), 19 | archive = Promise.promisify(repo.archive, {context: repo}); 20 | 21 | return archive.apply(repo, arguments); 22 | }; 23 | } 24 | 25 | module.exports = { 26 | ghostVersions: getVersions('TryGhost/Ghost'), 27 | ghostArchive: getArchive('TryGhost/Ghost'), 28 | casperVersions: getVersions('TryGhost/Casper'), 29 | casperArchive: getArchive('TryGhost/Casper') 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2025 Seth Vincent & Ghost Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-ghost", 3 | "version": "0.3.0", 4 | "description": "Generate Ghost blogs and themes using Yeoman.", 5 | "keywords": [ 6 | "yeoman-generator" 7 | ], 8 | "files": [ 9 | "app", 10 | "casper" 11 | ], 12 | "homepage": "https://github.com/sethvincent/generator-ghost", 13 | "bugs": "https://github.com/sethvincent/generator-ghost/issues", 14 | "author": { 15 | "name": "Seth Vincent", 16 | "email": "sethvincent@gmail.com", 17 | "url": "https://github.com/sethvincent" 18 | }, 19 | "license": "MIT", 20 | "main": "generators/app/index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/sethvincent/generator-ghost.git" 24 | }, 25 | "scripts": { 26 | "test": "mocha --no-timeouts" 27 | }, 28 | "dependencies": { 29 | "bluebird": "^3.3.5", 30 | "lodash": "^4.12.0", 31 | "octonode": "^0.7.5", 32 | "semver": "^5.1.0", 33 | "yeoman-generator": "^0.23.3" 34 | }, 35 | "devDependencies": { 36 | "mocha": "^2.4.5", 37 | "yeoman-assert": "^2.2.1", 38 | "yeoman-test": "^1.4.0" 39 | }, 40 | "engines": { 41 | "node": ">=0.10", 42 | "npm": ">=1.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/ghost-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, before, it */ 2 | var path = require('path'), 3 | testHelpers = require('yeoman-test'), 4 | assert = require('yeoman-assert'); 5 | 6 | describe('ghost generator', function () { 7 | var directory; 8 | 9 | before(function(done) { 10 | testHelpers.run(path.join(__dirname, '../app')) 11 | .inTmpDir(function (tmpDir) { 12 | directory = tmpDir; 13 | }) 14 | .withOptions({version: '0.8.0', casperVersion: '1.3.0'}) 15 | .toPromise().then(function () { 16 | done(); 17 | }); 18 | }); 19 | 20 | it('creates correct package.json', function () { 21 | var packageJson = path.join(directory, 'package.json'); 22 | // package.json exists and is correct 23 | assert.file(packageJson); 24 | assert.jsonFileContent(packageJson, {name: 'ghost', version: '0.8.0'}); 25 | }); 26 | 27 | it('correctly downloads casper', function () { 28 | var casperPackage = path.join(directory, 'content/themes/casper/package.json'); 29 | 30 | assert.file(casperPackage); 31 | assert.jsonFileContent(casperPackage, {name: 'Casper', version: '1.3.0'}); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-ghost [![Build Status](https://secure.travis-ci.org/sethvincent/generator-ghost.png?branch=master)](https://travis-ci.org/sethvincent/generator-ghost) 2 | 3 | A generator for [Yeoman](http://yeoman.io). 4 | 5 | 6 | ## Getting Started 7 | 8 | ### What is Yeoman? 9 | 10 | Trick question. It's not a thing. It's this guy: 11 | 12 | ![](http://i.imgur.com/JHaAlBJ.png) 13 | 14 | Basically, he wears a top hat, lives in your computer, and waits for you to tell him what kind of application you wish to create. 15 | 16 | Not every new computer comes with a Yeoman pre-installed. He lives in the [npm](https://npmjs.org) package repository. You only have to ask for him once, then he packs up and moves into your hard drive. *Make sure you clean up, he likes new and shiny things.* 17 | 18 | ``` 19 | $ npm install -g yo 20 | ``` 21 | 22 | Additionally, tnstall the grunt-cli tool: 23 | 24 | ``` 25 | npm install -g grunt-cli 26 | ``` 27 | 28 | ### Yeoman Generators 29 | 30 | Yeoman travels light. He didn't pack any generators when he moved in. You can think of a generator like a plug-in. You get to choose what type of application you wish to create, such as a Backbone application or even a Chrome extension. 31 | 32 | To install generator-ghost from npm, run: 33 | 34 | ``` 35 | $ npm install -g generator-ghost 36 | ``` 37 | 38 | Finally, initiate the generator: 39 | 40 | ``` 41 | $ yo ghost 42 | ``` 43 | 44 | Now, you can run your ghost blog in development like usual: 45 | 46 | ``` 47 | npm start 48 | ``` 49 | 50 | To create a new theme based on Casper, run this subgenerator: 51 | 52 | ``` 53 | yo ghost:theme-copy-casper NAME-OF-NEW-THEME 54 | ``` 55 | 56 | That will copy the Casper theme over to a new folder named NAME-OF-NEW-THEME. 57 | 58 | 59 | ## Todo 60 | 61 | I'd like to have a theme generator that starts a new theme that's even more bare-bones than Casper. 62 | 63 | ## Feedback 64 | Let me know in the repository issue queue if you find any bugs or have ideas for the project: [github.com/sethvincent/generator-ghost/issues](https://github.com/sethvincent/generator-ghost/issues) 65 | 66 | 67 | ### Getting To Know Yeoman 68 | 69 | Yeoman has a heart of gold. He's a person with feelings and opinions, but he's very easy to work with. If you think he's too opinionated, he can be easily convinced. 70 | 71 | If you'd like to get to know Yeoman better and meet some of his friends, [Grunt](http://gruntjs.com) and [Bower](http://bower.io), check out the complete [Getting Started Guide](https://github.com/yeoman/yeoman/wiki/Getting-Started). 72 | 73 | 74 | # Copyright & License 75 | 76 | Copyright (c) 2013-2025 Seth Vincent & Ghost Foundation - Released under the [MIT license](LICENSE). 77 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var generators = require('yeoman-generator'); 3 | var pkg = require('../package'); 4 | var githubUtils = require('./github'); 5 | var semver = require('semver'); 6 | var Promise = require('bluebird'); 7 | 8 | module.exports = generators.Base.extend({ 9 | constructor: function () { 10 | generators.Base.apply(this, arguments); 11 | 12 | this.option('version', { 13 | desc: 'Version of Ghost to install', 14 | type: 'string', 15 | alias: 'v' 16 | }); 17 | 18 | // @TODO: re-enable this when support for the production option is 19 | // added 20 | // this.option('production', { 21 | // desc: 'Whether or not the production install of Ghost should be used (zip file vs. Github repo)', 22 | // type: 'string', 23 | // alias: 'prod', 24 | // default: false 25 | // }); 26 | 27 | this.option('casperVersion', { 28 | desc: 'Version of Casper to install', 29 | type: 'string', 30 | alias: 'cv', 31 | default: 'latest' 32 | }); 33 | }, 34 | 35 | initializing: function () { 36 | var self = this; 37 | 38 | this.properties = _.pick(this.options, ['version', 'casperVersion']); 39 | 40 | this.log('Yeoman Ghost Generator v' + pkg.version); 41 | 42 | return Promise.props({ 43 | ghostVersions: githubUtils.ghostVersions(), 44 | casperVersions: githubUtils.casperVersions() 45 | }).then(function (results) { 46 | self.ghostReleases = _.filter(_.map(results.ghostVersions, 'name'), function (val) { 47 | return semver.valid(val); 48 | }); 49 | 50 | self.casperReleases = _.filter(_.map(results.casperVersions, 'name'), function (val) { 51 | return semver.valid(val); 52 | }); 53 | }); 54 | }, 55 | 56 | prompting: function () { 57 | var version = this.properties.version, 58 | casperVersion = this.properties.casperVersion, 59 | self = this, 60 | prompts = []; 61 | 62 | if (!version || (['latest', 'master', 'stable'].indexOf(version) === -1 && this.ghostReleases.indexOf(version) === -1)) { 63 | prompts.push({ 64 | type: 'list', 65 | name: 'version', 66 | message: 'Version to install', 67 | choices: this.ghostReleases, 68 | default: this.ghostReleases[0] 69 | }); 70 | } 71 | 72 | if (!casperVersion || (['latest', 'master', 'stable'].indexOf(casperVersion) === -1 && this.casperReleases.indexOf(casperVersion) === -1)) { 73 | prompts.push({ 74 | type: 'list', 75 | name: 'casperVersion', 76 | message: 'Casper version to install', 77 | choices: this.casperReleases, 78 | default: this.casperReleases[0] 79 | }); 80 | } 81 | 82 | return this.prompt(prompts).then(function (answers) { 83 | _.assign(self.properties, answers); 84 | }); 85 | }, 86 | 87 | writing: function () { 88 | var extract = Promise.promisify(this.extract, {context: this}), 89 | self = this; 90 | 91 | if (this.properties.version === 'latest') { 92 | this.properties.version = this.ghostReleases[0]; 93 | } 94 | 95 | if (this.properties.casperVersion === 'latest') { 96 | this.properties.casperVersion = this.casperReleases[0]; 97 | } 98 | 99 | this.log('Downloading and extracting Ghost and Casper...'); 100 | 101 | return githubUtils.ghostArchive('tarball', this.properties.version).then(function (url) { 102 | return extract(url, self.destinationRoot(), {strip: 1}); 103 | }).then(function () { 104 | return githubUtils.casperArchive('tarball', self.properties.casperVersion); 105 | }).then(function (casperUrl) { 106 | return extract(casperUrl, self.destinationPath('content', 'themes', 'casper'), {strip: 1}); 107 | }); 108 | }, 109 | 110 | install: function () { 111 | var self = this; 112 | 113 | this.spawnCommandSync('git', ['init']); 114 | 115 | this.npmInstall('', function () { 116 | self.spawnCommand('grunt', ['init']); 117 | }); 118 | } 119 | }); 120 | --------------------------------------------------------------------------------