Commit 84422e45 authored by Dan Allen's avatar Dan Allen

Merge issue-11-playbook-component branch into master

resolves #11 playbook component

see merge request antora/antora-direct!11
parents 7b3e3e88 41ef2b53
Pipeline #12427884 passed with stages
in 2 minutes and 50 seconds
// pipeline configuration goes here
// require/define our software components
const buildPlaybook = require('../packages/playbook/lib/playbook-builder')
// ...
// run the pipeline
const playbook = buildPlaybook()
// test
console.log(playbook.site.title)
// code for playbook component goes here
module.exports = {
playbook: {
doc: 'Location of the playbook file.',
format: String,
default: 'site.yml',
env: 'PLAYBOOK',
arg: 'playbook',
},
site: {
url: {
doc:
'The base URL of the published site (optional). Should not include a trailing slash.',
format: String,
default: undefined,
env: 'URL',
arg: 'url',
},
title: {
doc: 'The title of the site (optional).',
format: String,
default: undefined,
arg: 'title',
},
root: {
doc:
'The name of the component to use as the root of the site (optional).',
format: String,
default: undefined,
},
aspect: {
doc:
'The name of the aspect navigation to make available on every page in the site.',
format: String,
default: undefined,
},
nav: {
doc:
'The list of descriptors which define the aspect navigation domains.',
format: Array,
default: undefined,
},
keys: {
google_analytics: {
doc: 'The Google Analytics account key.',
format: String,
default: undefined,
arg: 'google-analytics-key',
},
swiftype: {
doc: 'The key to activate the SwiftType widget.',
format: String,
default: undefined,
arg: 'swiftype-key',
},
},
},
content: {
sources: {
doc: 'The list of git repositories + branch patterns to use.',
format: Array,
default: [],
env: 'CONTENT_SOURCES',
},
},
ui: {
location: {
doc: 'The repository that hosts the UI.',
format: String,
default: undefined,
},
name: {
doc: 'The name of the UI bundle. Defaults to the repository name.',
format: String,
default: undefined,
},
ref: {
doc: 'The reference (or version) of the theme bundle to use.',
format: String,
default: undefined,
},
archive: {
doc:
'A local theme archive. If specified, used in place of the UI bundle from the repository.',
format: String,
default: undefined,
arg: 'ui-archive',
},
skip_cache: {
doc:
'Skip the local bundle cache and always fetch the UI bundle from the repository.',
format: Boolean,
default: false,
arg: 'skip-ui-cache',
},
},
runtime: {
quiet: {
doc: 'Do not write any messages to stdout.',
format: Boolean,
default: false,
arg: 'quiet',
},
silent: {
doc: 'Suppress all messages.',
format: Boolean,
default: false,
arg: 'silent',
},
},
urls: {
htmlExtensionStyle: {
doc:
'Controls how the URL extension for HTML pages is handled (default, drop, or indexify).',
format: ['default', 'drop', 'indexify'],
default: 'default',
arg: 'html-url-extension-style',
},
aspectPageStrategy: {
doc:
'Controls how links to pages in aspect domains are generated (path or query).',
format: String,
default: 'path',
arg: 'aspect-page-url-strategy',
},
},
redirects: {
doc:
'Generate nginx config file containing URL redirects for page aliases.',
format: Boolean,
default: false,
arg: 'redirects',
},
}
const fs = require('fs')
const path = require('path')
const convict = require('convict')
const deepFreeze = require('deep-freeze')
const defaultSchema = require('./config/schema')
const yaml = require('js-yaml')
const cson = require('cson-parser')
function getConvictConfig (customSchema) {
if (customSchema != null) {
return convict(customSchema)
}
return convict(defaultSchema)
}
function loadSpecFile (specPath) {
const specExtname = path.extname(specPath)
const fileContents = fs.readFileSync(specPath, 'utf8')
if (specExtname === '.yml') {
return yaml.safeLoad(fileContents)
}
if (specExtname === '.json') {
return JSON.parse(fileContents)
}
if (specExtname === '.cson') {
return cson.parse(fileContents)
}
throw new Error('Unknown file type')
}
module.exports = (customSchema) => {
const config = getConvictConfig(customSchema)
const specRelativePath = config.get('playbook')
if (specRelativePath == null) {
throw new Error('Playbook spec file cannot be found')
}
let specPath = path.resolve(process.cwd(), specRelativePath)
// assume implicit .yml extension
if (path.extname(specPath) === '') {
specPath += '.yml'
}
const spec = loadSpecFile(specPath)
config.load(spec)
config.validate({ allowed: 'strict' })
const playbook = config.getProperties()
// playbook path property should not leak
delete playbook.playbook
const frozenPlaybook = deepFreeze(playbook)
return frozenPlaybook
}
one:
one: yml-spec-value-one
two: 42
three: false
four:
- name: John
lastname: Lennon
- name: Paul
lastname: McCartney
five: Hello World!
site:
url: https://example.com
title: Example site
one:
one: 'cson-spec-value-one'
two: 42
three: false
four: [
{ name: 'John', lastname: 'Lennon' }
{ name: 'Paul', lastname: 'McCartney' }
]
{
"one": {
"one": "json-spec-value-one"
},
"two": 42,
"three": false,
"four": [
{
"name": "John",
"lastname": "Lennon"
},
{
"name": "Paul",
"lastname": "McCartney"
}
]
}
one:
one: yml-spec-value-one
two: 42
three: false
four:
- name: John
lastname: Lennon
- name: Paul
lastname: McCartney
/* eslint-env mocha */
'use strict'
const { expect } = require('../../../test/test-utils')
const buildPlaybook = require('../lib/playbook-builder')
const path = require('path')
describe('buildPlaybook()', () => {
let originalEnv
let originalArgv
let schema
let expectedPlaybook
beforeEach(() => {
originalArgv = process.argv
originalEnv = process.env
process.argv = ['/path/to/node', '/path/to/script.js']
process.env = {}
schema = {
playbook: {
format: String,
default: null,
env: 'PLAYBOOK',
},
one: {
one: {
format: String,
default: null,
arg: 'oneone',
env: 'ANTORA_ONEONE',
},
two: {
format: String,
default: 'default-value',
},
},
two: {
format: Number,
default: null,
arg: 'two',
env: 'ANTORA_TWO',
},
three: {
format: Boolean,
default: null,
arg: 'three',
env: 'ANTORA_THREE',
},
four: {
format: Array,
default: null,
},
}
expectedPlaybook = {
one: {
two: 'default-value',
},
two: 42,
three: false,
four: [
{ lastname: 'Lennon', name: 'John' },
{ lastname: 'McCartney', name: 'Paul' },
],
}
})
afterEach(() => {
process.argv = originalArgv
process.env = originalEnv
})
const ymlSpec = path.resolve(__dirname, 'fixtures', 'spec-sample.yml')
const extensionLessSpec = path.resolve(__dirname, 'fixtures', 'spec-sample')
const jsonSpec = path.resolve(__dirname, 'fixtures', 'spec-sample.json')
const csonSpec = path.resolve(__dirname, 'fixtures', 'spec-sample.cson')
const iniSpec = path.resolve(__dirname, 'fixtures', 'spec-sample.ini')
const badSpec = path.resolve(__dirname, 'fixtures', 'bad-spec-sample.yml')
const defaultSchemaSpec = path.resolve(
__dirname,
'fixtures',
'default-schema-spec-sample.yml'
)
it('should throw error if no playbook spec file can be loaded', () => {
expect(() => buildPlaybook(schema)).to.throw()
})
it('should load YML playbook spec file', () => {
process.env.PLAYBOOK = ymlSpec
const playbook = buildPlaybook(schema)
expectedPlaybook.one.one = 'yml-spec-value-one'
expect(playbook).to.eql(expectedPlaybook)
})
it('should load YML playbook spec file when no file extension is given', () => {
process.env.PLAYBOOK = extensionLessSpec
const playbook = buildPlaybook(schema)
expectedPlaybook.one.one = 'yml-spec-value-one'
expect(playbook).to.eql(expectedPlaybook)
})
it('should load JSON playbook spec file', () => {
process.env.PLAYBOOK = jsonSpec
const playbook = buildPlaybook(schema)
expectedPlaybook.one.one = 'json-spec-value-one'
expect(playbook).to.eql(expectedPlaybook)
})
it('should load CSON playbook spec file', () => {
process.env.PLAYBOOK = csonSpec
const playbook = buildPlaybook(schema)
expectedPlaybook.one.one = 'cson-spec-value-one'
expect(playbook).to.eql(expectedPlaybook)
})
it('should throw error when loading unknown type file', () => {
process.env.PLAYBOOK = iniSpec
expect(() => buildPlaybook(schema)).to.throw()
})
it('should throw error if spec file is specified but cannot be found', () => {
process.env.PLAYBOOK = 'file/not/found.yml'
expect(() => buildPlaybook(schema)).to.throw()
})
it('should use default value if spec file is not specified', () => {
process.env.PLAYBOOK = ymlSpec
const playbook = buildPlaybook(schema)
expect(playbook.one.two).to.equal('default-value')
})
it('should use env value over spec file value', () => {
process.env.PLAYBOOK = ymlSpec
process.env.ANTORA_ONEONE = 'the-env-value'
const playbook = buildPlaybook(schema)
expect(playbook.one.one).to.equal('the-env-value')
})
it('should use argv value over spec file value or env value', () => {
process.env.PLAYBOOK = ymlSpec
process.argv.push('--oneone', 'the-argv-value')
process.env.ANTORA_ONEONE = 'the-env-value'
const playbook = buildPlaybook(schema)
expect(playbook.one.one).to.equal('the-argv-value')
})
it('should coerce Number values', () => {
process.env.PLAYBOOK = ymlSpec
const playbook = buildPlaybook(schema)
expect(playbook.two).to.equal(42)
})
it('should coerce Number values (via env)', () => {
process.env.PLAYBOOK = ymlSpec
process.env.ANTORA_TWO = '777'
const playbook = buildPlaybook(schema)
expect(playbook.two).to.equal(777)
})
it('should coerce Number values (via argv)', () => {
process.env.PLAYBOOK = ymlSpec
process.argv.push('--two', '777')
const playbook = buildPlaybook(schema)
expect(playbook.two).to.equal(777)
})
it('should coerce Boolean values', () => {
process.env.PLAYBOOK = ymlSpec
const playbook = buildPlaybook(schema)
expect(playbook.three).to.be.false()
})
it('should coerce Boolean values (via env)', () => {
process.env.PLAYBOOK = ymlSpec
process.env.ANTORA_THREE = 'true'
const playbook = buildPlaybook(schema)
expect(playbook.three).to.be.true()
})
it('should coerce Boolean values (via argv)', () => {
process.env.PLAYBOOK = ymlSpec
process.argv.push('--three')
const playbook = buildPlaybook(schema)
expect(playbook.three).to.be.true()
})
it('should throw error when trying to load values not declared in the schema', () => {
process.env.PLAYBOOK = badSpec
expect(() => buildPlaybook(schema)).to.throw()
})
it('should throw error when spec file used values of the wrong format', () => {
process.env.PLAYBOOK = ymlSpec
schema.two.format = String
expect(() => buildPlaybook(schema)).to.throw()
})
it('should return an immutable playbook', () => {
process.env.PLAYBOOK = ymlSpec
const playbook = buildPlaybook(schema)
expect(() => {
playbook.one.two = 'override'
}).to.throw()
})
it('should use default schema if none is specified', () => {
process.env.PLAYBOOK = defaultSchemaSpec
const playbook = buildPlaybook()
expect(playbook.site.url).to.equal('https://example.com')
expect(playbook.site.title).to.equal('Example site')
})
})
......@@ -392,6 +392,10 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
coffee-script@^1.10.0:
version "1.12.7"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
color-convert@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
......@@ -438,6 +442,19 @@ convert-source-map@^1.3.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
convict@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/convict/-/convict-4.0.0.tgz#73507fd0b7370d103bd0d08504dbb6a24f2fc09f"
dependencies:
depd "1.1.0"
json5 "0.5.1"
lodash.clonedeep "4.5.0"
minimist "1.2.0"
moment "2.17.1"
validator "7.0.0"
optionalDependencies:
varify "0.2.0"
core-js@^2.4.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
......@@ -461,6 +478,12 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
shebang-command "^1.2.0"
which "^1.2.9"
cson-parser@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-2.0.0.tgz#636721e322b97c028e8fd18aff5898c0020c2a4d"
dependencies:
coffee-script "^1.10.0"
dateformat@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062"
......@@ -491,6 +514,10 @@ deep-eql@^3.0.0:
dependencies:
type-detect "^4.0.0"
deep-freeze@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
......@@ -519,6 +546,10 @@ del@^2.0.2:
pinkie-promise "^2.0.0"
rimraf "^2.2.8"
depd@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
deprecated@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19"
......@@ -693,6 +724,10 @@ esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
esprima@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9"
esquery@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
......@@ -1489,6 +1524,10 @@ json3@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
json5@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
......@@ -1609,6 +1648,10 @@ lodash._root@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
lodash.clonedeep@4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
......@@ -1831,7 +1874,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
minimist@^1.1.0, minimist@^1.2.0:
minimist@1.2.0, minimist@^1.1.0, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
......@@ -1858,6 +1901,10 @@ mocha@^3.5.3:
mkdirp "0.5.1"
supports-color "3.1.2"
moment@2.17.1:
version "2.17.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
......@@ -2336,6 +2383,12 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
redeyed@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a"
dependencies:
esprima "~3.0.0"
regenerator-runtime@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
......@@ -2687,7 +2740,7 @@ through2@^2.0.0:
readable-stream "^2.1.5"
xtend "~4.0.1"
through@^2.3.6:
through@^2.3.6, through@~2.3.4:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
......@@ -2786,6 +2839,17 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
validator@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-7.0.0.tgz#c74deb8063512fac35547938e6f0b1504a282fd2"
varify@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/varify/-/varify-0.2.0.tgz#191da9fe9dc4cd68d0d14498d4e2a910ff4e6516"
dependencies:
redeyed "~1.0.1"
through "~2.3.4"
vinyl-fs@^0.3.0:
version "0.3.14"
resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment