├── .gitignore ├── app ├── templates │ ├── gitignore │ ├── gitattributes │ ├── travis.yml │ ├── index.js │ ├── editorconfig │ ├── test.js │ ├── readme.md │ ├── _package.json │ ├── license │ └── info.plist ├── utils.js └── index.js ├── .gitattributes ├── .travis.yml ├── .editorconfig ├── test ├── test-generate-uuid.js └── test.js ├── readme.md ├── license └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /app/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '4' 5 | -------------------------------------------------------------------------------- /app/templates/travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '4' 5 | -------------------------------------------------------------------------------- /app/templates/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const alfy = require('alfy'); 3 | 4 | alfy.output([ 5 | { 6 | title: 'Unicorn', 7 | subtitle: alfy.input 8 | } 9 | ]); 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /app/templates/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import alfyTest from 'alfy-test'; 3 | 4 | test(async t => { 5 | const alfy = alfyTest(); 6 | const result = await alfy('Rainbow'); 7 | 8 | t.deepEqual(result, [ 9 | { 10 | title: 'Unicorn', 11 | subtitle: 'Rainbow' 12 | } 13 | ]); 14 | }); 15 | -------------------------------------------------------------------------------- /test/test-generate-uuid.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {generateUuid} from '../app/utils'; 3 | 4 | test('remembers UUIDs by key', t => { 5 | const foo1 = generateUuid('foo'); 6 | const foo2 = generateUuid('foo'); 7 | const bar = generateUuid('bar'); 8 | 9 | t.is(foo1, foo2); 10 | t.not(foo1, bar); 11 | }); 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # generator-alfred [![Build Status](https://travis-ci.org/SamVerschueren/generator-alfred.svg?branch=master)](https://travis-ci.org/SamVerschueren/generator-alfred) 2 | 3 | > Scaffold out an [Alfred](https://www.alfredapp.com/) workflow 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --global generator-alfred 10 | ``` 11 | 12 | 13 | ## Usage 14 | 15 | With [yo](https://github.com/yeoman/yo): 16 | 17 | ``` 18 | $ yo alfred 19 | ``` 20 | 21 | 22 | ## License 23 | 24 | MIT © [Sam Verschueren](https://github.com/SamVerschueren) 25 | -------------------------------------------------------------------------------- /app/templates/readme.md: -------------------------------------------------------------------------------- 1 | # <%= repoName %> [![Build Status](https://travis-ci.org/<%= githubUsername %>/<%= repoName %>.svg?branch=master)](https://travis-ci.org/<%= githubUsername %>/<%= repoName %>) 2 | 3 | > <%= moduleDescription %> 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --global <%= moduleName %> 10 | ``` 11 | 12 | *Requires [Node.js](https://nodejs.org) 4+ and the Alfred [Powerpack](https://www.alfredapp.com/powerpack/).* 13 | 14 | 15 | ## Usage 16 | 17 | In Alfred, type `<%= alfredKeyword %>`, Enter, and your query. 18 | 19 | 20 | ## License 21 | 22 | MIT © [<%= name %>](<%= website %>) 23 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= moduleName %>", 3 | "version": "0.0.0", 4 | "description": "<%= moduleDescription %>", 5 | "license": "MIT", 6 | "repository": "<%= githubUsername %>/<%= repoName %>", 7 | "author": { 8 | "name": "<%= name %>", 9 | "email": "<%= email %>", 10 | "url": "<%= humanizedWebsite %>" 11 | }, 12 | "engines": { 13 | "node": ">=4" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava", 17 | "postinstall": "alfy-init", 18 | "preuninstall": "alfy-cleanup" 19 | }, 20 | "files": [ 21 | "index.js", 22 | "icon.png", 23 | "info.plist" 24 | ], 25 | "keywords": [ 26 | "alfred", 27 | "workflow", 28 | "alfy" 29 | ], 30 | "dependencies": { 31 | "alfy": "^0.6.0" 32 | }, 33 | "devDependencies": { 34 | "alfy-test": "^0.3.0", 35 | "ava": "^0.18.0", 36 | "xo": "^0.17.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const url = require('url'); 3 | const uuid = require('uuid'); 4 | const _s = require('underscore.string'); 5 | const isScoped = require('is-scoped'); 6 | 7 | const uuids = new Map(); 8 | 9 | exports.generateUuid = key => { 10 | if (key && uuids.has(key)) { 11 | return uuids.get(key); 12 | } 13 | 14 | const id = uuid.v4().toUpperCase(); 15 | 16 | if (key) { 17 | uuids.set(key, id); 18 | } 19 | 20 | return id; 21 | }; 22 | 23 | exports.bundleId = props => { 24 | const parsed = url.parse(props.website); 25 | 26 | if (parsed.hostname === 'github.com') { 27 | return `com.${props.githubUsername.toLowerCase()}.${props.alfredName}`; 28 | } 29 | 30 | // Reverse hostname 31 | const parts = parsed.hostname.split('.'); 32 | return `${parts[1]}.${parts[0]}.${props.alfredName}`; 33 | }; 34 | 35 | exports.repoName = name => isScoped(name) ? name.split('/')[1] : name; 36 | 37 | exports.slugifyPackageName = name => isScoped(name) ? name : _s.slugify(name); 38 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import test from 'ava'; 3 | import helpers from 'yeoman-test'; 4 | import assert from 'yeoman-assert'; 5 | import pify from 'pify'; 6 | import tempfile from 'tempfile'; 7 | 8 | test.beforeEach(async t => { 9 | await pify(helpers.testDirectory)(tempfile()); 10 | t.context.generator = helpers.createGenerator('alfred', [path.join(__dirname, '../app')], null, {skipInstall: true}); 11 | }); 12 | 13 | test.serial('generates expected files', async t => { 14 | const generator = t.context.generator; 15 | 16 | helpers.mockPrompt(generator, { 17 | moduleName: 'test', 18 | githubUsername: 'test', 19 | website: 'http://test.com' 20 | }); 21 | 22 | await pify(generator.run.bind(generator))(); 23 | 24 | assert.file([ 25 | '.editorconfig', 26 | '.git', 27 | '.gitattributes', 28 | '.gitignore', 29 | '.travis.yml', 30 | 'index.js', 31 | 'license', 32 | 'package.json', 33 | 'readme.md', 34 | 'test.js', 35 | 'info.plist' 36 | ]); 37 | 38 | assert.noFile('cli.js'); 39 | }); 40 | -------------------------------------------------------------------------------- /app/templates/license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) <%= name %> <<%= email %>> (<%= humanizedWebsite %>) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sam Verschueren (github.com/SamVerschueren) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-alfred", 3 | "version": "0.5.0", 4 | "description": "Scaffold out an Alfred workflow", 5 | "license": "MIT", 6 | "repository": "SamVerschueren/generator-alfred", 7 | "author": { 8 | "name": "Sam Verschueren", 9 | "email": "sam.verschueren@gmail.com", 10 | "url": "github.com/SamVerschueren" 11 | }, 12 | "maintainers": [ 13 | { 14 | "name": "Sindre Sorhus", 15 | "email": "sindresorhus@gmail.com", 16 | "url": "sindresorhus.com" 17 | } 18 | ], 19 | "engines": { 20 | "node": ">=4" 21 | }, 22 | "scripts": { 23 | "test": "xo && ava" 24 | }, 25 | "files": [ 26 | "app" 27 | ], 28 | "keywords": [ 29 | "yeoman-generator", 30 | "plugin", 31 | "boilerplate", 32 | "template", 33 | "scaffold", 34 | "alfred", 35 | "alfy", 36 | "workflow", 37 | "init" 38 | ], 39 | "dependencies": { 40 | "humanize-url": "^1.0.1", 41 | "is-scoped": "^1.0.0", 42 | "normalize-url": "^1.6.0", 43 | "superb": "^1.3.0", 44 | "underscore.string": "^3.3.4", 45 | "uuid": "^3.0.1", 46 | "yeoman-generator": "^1.1.0" 47 | }, 48 | "devDependencies": { 49 | "ava": "*", 50 | "pify": "^2.3.0", 51 | "tempfile": "^1.1.1", 52 | "xo": "*", 53 | "yeoman-assert": "^2.2.1", 54 | "yeoman-test": "^1.4.0" 55 | }, 56 | "ava": { 57 | "failWithoutAssertions": false 58 | }, 59 | "xo": { 60 | "ignores": [ 61 | "app/templates/**" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/templates/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | <%= alfredName %> 7 | bundleid 8 | <%= alfredBundleId %> 9 | category 10 | <%= alfredCategory %> 11 | connections 12 | 13 | <%= uuid('uid') %> 14 | 15 | 16 | destinationuid 17 | <%= uuid('destinationuid') %> 18 | modifiers 19 | 0 20 | modifiersubtext 21 | 22 | vitoclose 23 | 24 | 25 | 26 | 27 | disabled 28 | 29 | objects 30 | 31 | 32 | config 33 | 34 | browser 35 | 36 | spaces 37 | 38 | url 39 | {query} 40 | utf8 41 | 42 | 43 | type 44 | alfred.workflow.action.openurl 45 | uid 46 | <%= uuid('destinationuid') %> 47 | version 48 | 1 49 | 50 | 51 | config 52 | 53 | alfredfiltersresults 54 | 55 | argumenttype 56 | 0 57 | escaping 58 | 102 59 | keyword 60 | <%= alfredKeyword %> 61 | queuedelaycustom 62 | 3 63 | queuedelayimmediatelyinitially 64 | 65 | queuedelaymode 66 | 0 67 | queuemode 68 | 2 69 | runningsubtext 70 | Searching... 71 | script 72 | ./node_modules/.bin/run-node index.js "$1" 73 | scriptargtype 74 | 1 75 | scriptfile 76 | index.js 77 | subtext 78 | 79 | title 80 | <%= alfredTitle %> 81 | type 82 | 0 83 | withspace 84 | 85 | 86 | type 87 | alfred.workflow.input.scriptfilter 88 | uid 89 | <%= uuid('uid') %> 90 | version 91 | 2 92 | 93 | 94 | readme 95 | 96 | uidata 97 | 98 | <%= uuid('destinationuid') %> 99 | 100 | xpos 101 | 150 102 | ypos 103 | 10 104 | 105 | <%= uuid('uid') %> 106 | 107 | xpos 108 | 10 109 | ypos 110 | 10 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const superb = require('superb'); 3 | const normalizeUrl = require('normalize-url'); 4 | const humanizeUrl = require('humanize-url'); 5 | const Generator = require('yeoman-generator'); 6 | const _s = require('underscore.string'); 7 | const utils = require('./utils'); 8 | 9 | module.exports = class extends Generator { 10 | init() { 11 | return this.prompt([ 12 | { 13 | name: 'moduleName', 14 | message: 'What do you want to name your module?', 15 | default: _s.slugify(this.appname), 16 | filter: x => { 17 | let name = utils.slugifyPackageName(x); 18 | 19 | if (!name.startsWith('alfred-')) { 20 | name = `alfred-${name}`; 21 | } 22 | 23 | return name; 24 | } 25 | }, 26 | { 27 | name: 'moduleDescription', 28 | message: 'What is your module description?', 29 | default: `My ${superb()} module` 30 | }, 31 | { 32 | name: 'alfredKeyword', 33 | message: 'What is the Alfred activation keyword?', 34 | default: props => props.moduleName.replace(/^alfred-/, '') 35 | }, 36 | { 37 | name: 'alfredTitle', 38 | message: 'What is the Alfred title?', 39 | default: props => props.moduleDescription 40 | }, 41 | { 42 | name: 'alfredCategory', 43 | message: 'What is the Alfred category?', 44 | type: 'list', 45 | default: 'Uncategorised', 46 | choices: [ 47 | 'Tools', 48 | 'Internet', 49 | 'Productivity', 50 | 'Uncategorised' 51 | ] 52 | }, 53 | { 54 | name: 'githubUsername', 55 | message: 'What is your GitHub username?', 56 | store: true, 57 | validate: x => x.length > 0 ? true : 'You have to provide a username' 58 | }, 59 | { 60 | name: 'website', 61 | message: 'What is the URL of your website?', 62 | store: true, 63 | validate: x => x.length > 0 ? true : 'You have to provide a website URL', 64 | filter: x => normalizeUrl(x) 65 | } 66 | ]).then(props => { 67 | props.alfredName = props.moduleName.replace(/^alfred-/, ''); 68 | 69 | const tpl = { 70 | moduleName: props.moduleName, 71 | moduleDescription: props.moduleDescription, 72 | alfredName: props.alfredName, 73 | alfredBundleId: utils.bundleId(props), 74 | alfredCategory: props.alfredCategory, 75 | alfredKeyword: props.alfredKeyword, 76 | alfredTitle: props.alfredTitle, 77 | githubUsername: this.options.org || props.githubUsername, 78 | repoName: utils.repoName(props.moduleName), 79 | name: this.user.git.name(), 80 | email: this.user.git.email(), 81 | website: props.website, 82 | humanizedWebsite: humanizeUrl(props.website), 83 | uuid: utils.generateUuid 84 | }; 85 | 86 | const mv = (from, to) => { 87 | this.fs.move(this.destinationPath(from), this.destinationPath(to)); 88 | }; 89 | 90 | this.fs.copyTpl([ 91 | `${this.templatePath()}/**` 92 | ], this.destinationPath(), tpl); 93 | 94 | mv('editorconfig', '.editorconfig'); 95 | mv('gitattributes', '.gitattributes'); 96 | mv('gitignore', '.gitignore'); 97 | mv('travis.yml', '.travis.yml'); 98 | mv('_package.json', 'package.json'); 99 | }); 100 | } 101 | 102 | git() { 103 | this.spawnCommandSync('git', ['init']); 104 | } 105 | 106 | install() { 107 | this.npmInstall(null, {ignoreScripts: true}); 108 | } 109 | }; 110 | --------------------------------------------------------------------------------