diff --git a/LICENSE.md b/LICENSE.md
index 1bb5f69..2c5f514 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 Thiago Santos
+Copyright (c) 2017 Gustavo Quinalha
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/bin/seotopper.js b/bin/seotopper.js
new file mode 100644
index 0000000..13ca945
--- /dev/null
+++ b/bin/seotopper.js
@@ -0,0 +1,198 @@
+#!/usr/bin/env node
+
+const inquirer = require('inquirer')
+const seotopper = require('../lib/seotopper')
+
+const questions = [
+ {
+ type: 'input',
+ name: 'title',
+ message: 'What\'s the title of your page',
+ validate: value => {
+ if (value === '') {
+ return 'Please enter a title'
+ }
+ if (value.length > 57) {
+ return 'Please enter a title with less than 57 characters'
+ }
+ return true
+ }
+ },
+ {
+ type: 'input',
+ name: 'description',
+ message: 'What\'s the description of your page',
+ validate: value => {
+ if (value === '') {
+ return 'Please enter a description'
+ }
+ if (value.length > 160) {
+ return 'Please enter a description with less than 160 characters'
+ }
+ return true
+ }
+ },
+ {
+ type: 'input',
+ name: 'author',
+ message: 'Who\'s the author',
+ validate: value => {
+ if (value === '') {
+ return 'Please enter an author\'s name'
+ }
+ return true
+ }
+ },
+ {
+ type: 'input',
+ name: 'image',
+ message: 'What\'s the image of your page',
+ validate: value => {
+ if (value === '') {
+ return 'Please enter an image'
+ }
+ return true
+ }
+ },
+ {
+ type: 'input',
+ name: 'canonical',
+ message: 'What\'s the canonical url of your page',
+ validate: value => {
+ if (value === '') {
+ return 'Please enter a canonical url'
+ }
+ return true
+ }
+ },
+ {
+ type: 'list',
+ name: 'robots',
+ message: 'What you wanna tell to the robots',
+ choices: [
+ 'index/follow',
+ 'noindex/follow',
+ 'index/nofollow',
+ 'noarchive',
+ 'nosnippet',
+ 'noodp',
+ 'notranslate',
+ 'noimageindex',
+ 'none'
+ ]
+ },
+ {
+ type: 'input',
+ name: 'base',
+ message: 'What\'s the base url of your page'
+ },
+ {
+ type: 'input',
+ name: 'sitemap',
+ message: 'What\'s the sitemap of your page'
+ },
+ {
+ type: 'input',
+ name: 'themeColor',
+ message: 'What\'s the theme-color of your page'
+ },
+ {
+ type: 'confirm',
+ name: 'facebook',
+ message: 'Do you wanna seo for facebook'
+ },
+ {
+ type: 'list',
+ name: 'facebookType',
+ message: 'What\'s the type of your page',
+ choices: [
+ 'website',
+ 'blog',
+ 'article',
+ 'activity',
+ 'sport',
+ 'company',
+ 'restaurant',
+ 'hotel',
+ 'cause',
+ 'band',
+ 'government',
+ 'non_profit',
+ 'school',
+ 'university',
+ 'actor',
+ 'athlete',
+ 'city',
+ 'country',
+ 'album',
+ 'book',
+ 'drink',
+ 'game',
+ 'product',
+ 'song',
+ 'movie'
+ ],
+ when: answers => {
+ return answers.facebook
+ }
+ },
+ {
+ type: 'input',
+ name: 'facebookSiteName',
+ message: 'What\'s the name of your page on facebook',
+ when: answers => {
+ return answers.facebook
+ }
+ },
+ {
+ type: 'input',
+ name: 'facebookLocale',
+ message: 'What\'s the locale of your page on facebook',
+ when: answers => {
+ return answers.facebook
+ }
+ },
+ {
+ type: 'input',
+ name: 'facebookId',
+ message: 'What\'s the id of your page on facebook',
+ when: answers => {
+ return answers.facebook
+ }
+ },
+ {
+ type: 'input',
+ name: 'facebookAdmins',
+ message: 'What are the admins of your page on facebook',
+ when: answers => {
+ return answers.facebook
+ }
+ },
+ {
+ type: 'confirm',
+ name: 'twitter',
+ message: 'Do you wanna seo for twitter'
+ },
+ {
+ type: 'list',
+ name: 'twitterCard',
+ message: 'Which twitter do you want',
+ choices: [
+ 'Summary',
+ 'Product',
+ 'Photo',
+ 'Summary Large Image',
+ 'Player',
+ 'App',
+ 'Gallery'
+ ],
+ when: answers => {
+ return answers.twitter
+ }
+ }
+]
+
+inquirer.prompt(questions).then(answers => {
+ const generatedSEO = seotopper(answers)
+ process.stdout.write(generatedSEO)
+})
diff --git a/lib/seotopper.js b/lib/seotopper.js
index 525f778..01743b5 100644
--- a/lib/seotopper.js
+++ b/lib/seotopper.js
@@ -1,15 +1,23 @@
const utils = require('./utils')
-const checkMissingKeys = utils.checkMissingKeys
-const createErrorMessage = utils.createErrorMessage
const requiredProperties = utils.requiredProperties
+const checkMissingKeys = utils.checkMissingKeys
+const createMissingKeysErrorMessage = utils.createMissingKeysErrorMessage
+const checkEmptyKeys = utils.checkEmptyKeys
+const createEmptyKeysErrorMessage = utils.createEmptyKeysErrorMessage
function seotopper(args) {
const keys = Object.keys(args)
const missingKeys = checkMissingKeys(requiredProperties, keys)
if (missingKeys.length > 0) {
- throw new Error(createErrorMessage(missingKeys))
+ throw new Error(createMissingKeysErrorMessage(missingKeys))
+ }
+
+ const emptyKeys = checkEmptyKeys(requiredProperties, keys, args)
+
+ if (emptyKeys.length > 0) {
+ throw new Error(createEmptyKeysErrorMessage(emptyKeys))
}
return `
${args.title}
@@ -35,21 +43,33 @@ function seotopper(args) {
${args.facebook ?
`
-
+
-
-
+
+ ${args.facebookSiteName ?
+ `` :
+ ''
+ }
-
-
- ` :
+ ${args.facebookLocale ?
+ `` :
+ ''
+ }
+ ${args.facebookId ?
+ `` :
+ ''
+ }
+ ${args.facebookAdmins ?
+ `` :
+ ''
+ }` :
''
}
${args.twitter ?
`
-
+
diff --git a/lib/utils.js b/lib/utils.js
index 68836d2..689628b 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -10,15 +10,27 @@ const requiredProperties = [
const checkMissingKeys = (requiredProperties, keys) => requiredProperties
.filter(property => keys.indexOf(property) === -1)
-const createErrorMessage = missingKeys => String(
+const createMissingKeysErrorMessage = missingKeys => String(
'The following ' +
(missingKeys.length > 1 ? 'properties are' : 'property is') +
' required: ' +
- missingKeys.join(', ')
+ missingKeys.sort().join(', ')
)
+const checkEmptyKeys = (requiredProperties, keys, args) => keys
+ .filter(key => requiredProperties.indexOf(key) !== -1)
+ .filter(key => args[key] === '')
+
+const createEmptyKeysErrorMessage = emptyKeys => String(
+ 'The following ' +
+ (emptyKeys.length > 1 ? 'properties' : 'property') +
+ ' cannot be empty: ' +
+ emptyKeys.sort().join(', ')
+)
module.exports = {
checkMissingKeys,
+ checkEmptyKeys,
requiredProperties,
- createErrorMessage
+ createMissingKeysErrorMessage,
+ createEmptyKeysErrorMessage
}
diff --git a/package.json b/package.json
index 7acefb7..f206cd3 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,9 @@
"author": "Gustavo Quinalha",
"keywords": [],
"license": "MIT",
+ "bin": {
+ "seotopper": "bin/seotopper.js"
+ },
"main": "lib/seotopper.js",
"scripts": {
"precommit": "npm test",
@@ -31,7 +34,10 @@
"husky": "^0.13.3",
"xo": "^0.18.1"
},
- "engines" : {
+ "engines": {
"node": ">=4"
+ },
+ "dependencies": {
+ "inquirer": "^3.0.6"
}
}
diff --git a/test/seotopper.test.js b/test/seotopper.test.js
index 46657fb..285d0b7 100644
--- a/test/seotopper.test.js
+++ b/test/seotopper.test.js
@@ -12,16 +12,14 @@ test('main funcionality', t => {
robots: 'index/follow',
themeColor: '#f00',
image: 'https://sua-url.com.br/images/intro.jpg',
- facebook: {
- type: 'website',
- siteName: 'Exemplo',
- locale: 'pt_BR',
- id: '5349',
- admins: '123456789'
- },
- twitter: {
- card: 'summary'
- }
+ facebook: true,
+ facebookType: 'website',
+ facebookSiteName: 'Exemplo',
+ facebookLocale: 'pt_BR',
+ facebookId: '5349',
+ facebookAdmins: '123456789',
+ twitter: true,
+ twitterCard: 'summary'
})
const expected = `Título da minha página
@@ -42,7 +40,7 @@ test('main funcionality', t => {
-
+
@@ -79,16 +77,14 @@ test('base should be optional', t => {
robots: 'index/follow',
themeColor: '#f00',
image: 'https://sua-url.com.br/images/intro.jpg',
- facebook: {
- type: 'website',
- siteName: 'Exemplo',
- locale: 'pt_BR',
- id: '5349',
- admins: '123456789'
- },
- twitter: {
- card: 'summary'
- }
+ facebook: true,
+ facebookType: 'website',
+ facebookSiteName: 'Exemplo',
+ facebookLocale: 'pt_BR',
+ facebookId: '5349',
+ facebookAdmins: '123456789',
+ twitter: true,
+ twitterCard: 'summary'
})
t.notRegex(actual, /rel="base"/)
@@ -103,16 +99,14 @@ test('sitemap should be optional', t => {
robots: 'index/follow',
themeColor: '#f00',
image: 'https://sua-url.com.br/images/intro.jpg',
- facebook: {
- type: 'website',
- siteName: 'Exemplo',
- locale: 'pt_BR',
- id: '5349',
- admins: '123456789'
- },
- twitter: {
- card: 'summary'
- }
+ facebook: true,
+ facebookType: 'website',
+ facebookSiteName: 'Exemplo',
+ facebookLocale: 'pt_BR',
+ facebookId: '5349',
+ facebookAdmins: '123456789',
+ twitter: true,
+ twitterCard: 'summary'
})
t.notRegex(actual, /rel="sitemap"/)
@@ -126,16 +120,14 @@ test('themeColor should be optional', t => {
canonical: 'https://sua-url.com.br',
robots: 'index/follow',
image: 'https://sua-url.com.br/images/intro.jpg',
- facebook: {
- type: 'website',
- siteName: 'Exemplo',
- locale: 'pt_BR',
- id: '5349',
- admins: '123456789'
- },
- twitter: {
- card: 'summary'
- }
+ facebook: true,
+ facebookType: 'website',
+ facebookSiteName: 'Exemplo',
+ facebookLocale: 'pt_BR',
+ facebookId: '5349',
+ facebookAdmins: '123456789',
+ twitter: true,
+ twitterCard: 'summary'
})
t.notRegex(actual, /name="theme-color"/)
@@ -151,9 +143,8 @@ test('facebook should be optional', t => {
canonical: 'https://sua-url.com.br',
robots: 'index/follow',
image: 'https://sua-url.com.br/images/intro.jpg',
- twitter: {
- card: 'summary'
- }
+ twitter: true,
+ twitterCard: 'summary'
})
t.notRegex(actual, /property="og:type"/)
t.notRegex(actual, /property="og:title"/)
@@ -174,13 +165,12 @@ test('twitter should be optional', t => {
canonical: 'https://sua-url.com.br',
robots: 'index/follow',
image: 'https://sua-url.com.br/images/intro.jpg',
- facebook: {
- type: 'website',
- siteName: 'Exemplo',
- locale: 'pt_BR',
- id: '5349',
- admins: '123456789'
- }
+ facebook: true,
+ facebookType: 'website',
+ facebookSiteName: 'Exemplo',
+ facebookLocale: 'pt_BR',
+ facebookId: '5349',
+ facebookAdmins: '123456789'
})
t.notRegex(actual, /name="twitter:card"/)
t.notRegex(actual, /name="twitter:title"/)
@@ -270,3 +260,94 @@ test('should give feedback for more than one required property', t => {
})
}, 'The following properties are required: author, description, title')
})
+
+test('title cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: '',
+ description: 'Descrição da minha página',
+ author: 'Eu',
+ canonical: 'https://sua-url.com.br',
+ robots: 'index/follow',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following property cannot be empty: title')
+})
+
+test('description cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: 'Título da minha página',
+ description: '',
+ author: 'Eu',
+ canonical: 'https://sua-url.com.br',
+ robots: 'index/follow',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following property cannot be empty: description')
+})
+
+test('author cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: 'Título da minha página',
+ description: 'Descrição da minha página',
+ author: '',
+ canonical: 'https://sua-url.com.br',
+ robots: 'index/follow',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following property cannot be empty: author')
+})
+
+test('canonical cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: 'Título da minha página',
+ author: 'Eu',
+ description: 'Descrição da minha página',
+ canonical: '',
+ robots: 'index/follow',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following property cannot be empty: canonical')
+})
+
+test('robots cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: 'Título da minha página',
+ author: 'Eu',
+ description: 'Descrição da minha página',
+ canonical: 'https://sua-url.com.br',
+ robots: '',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following property cannot be empty: robots')
+})
+
+test('image cannot be empty', t => {
+ t.throws(() => {
+ seotopper({
+ title: 'Título da minha página',
+ author: 'Eu',
+ description: 'Descrição da minha página',
+ canonical: 'https://sua-url.com.br',
+ robots: 'index/follow',
+ image: ''
+ })
+ }, 'The following property cannot be empty: image')
+})
+
+test('should give feedback for more than one empty property', t => {
+ t.throws(() => {
+ seotopper({
+ title: '',
+ description: '',
+ author: '',
+ canonical: 'https://sua-url.com.br',
+ robots: 'index/follow',
+ image: 'https://sua-url.com.br/images/intro.jpg'
+ })
+ }, 'The following properties cannot be empty: author, description, title')
+})
diff --git a/test/utils.test.js b/test/utils.test.js
index 9107da2..9adc97f 100644
--- a/test/utils.test.js
+++ b/test/utils.test.js
@@ -2,7 +2,9 @@ const test = require('ava')
const {
requiredProperties,
checkMissingKeys,
- createErrorMessage
+ createMissingKeysErrorMessage,
+ checkEmptyKeys,
+ createEmptyKeysErrorMessage
} = require('../lib/utils')
test('requiredProperties should be an array', t => {
@@ -18,16 +20,44 @@ test('checkMissingKeys', t => {
t.deepEqual(actual, expected, 'should return the missing keys')
})
-test('createErrorMessage missing one key', t => {
- const actual = createErrorMessage(['title'])
+test('createMissingKeysErrorMessage missing one key', t => {
+ const actual = createMissingKeysErrorMessage(['title'])
const expected = 'The following property is required: title'
t.is(actual, expected)
})
-test('createErrorMessage missign more than one key', t => {
- const actual = createErrorMessage(['title', 'author'])
- const expected = 'The following properties are required: title, author'
+test('createMissingKeysErrorMessage missing more than one key', t => {
+ const actual = createMissingKeysErrorMessage(['title', 'author'])
+ const expected = 'The following properties are required: author, title'
+
+ t.is(actual, expected)
+})
+
+test('checkEmptyKeys', t => {
+ const actual = checkEmptyKeys(
+ ['title', 'author'],
+ ['title', 'author'],
+ {
+ title: '',
+ author: 'me'
+ }
+ )
+ const expected = ['title']
+
+ t.deepEqual(actual, expected)
+})
+
+test('createEmptyKeysErrorMessage with one empty key', t => {
+ const actual = createEmptyKeysErrorMessage(['title'])
+ const expected = 'The following property cannot be empty: title'
+
+ t.is(actual, expected)
+})
+
+test('createEmptyKeysErrorMessage with more than one empty key', t => {
+ const actual = createEmptyKeysErrorMessage(['title', 'author'])
+ const expected = 'The following properties cannot be empty: author, title'
t.is(actual, expected)
})