@arc-fusion/cli
Version:
CLI for running Arc Fusion on your local machine
187 lines (163 loc) • 5.71 kB
JavaScript
const fs = require('fs')
const path = require('path')
const yamljs = require('yamljs')
const spawn = require('./spawn')
const {
exec,
mkdirp,
touch,
writeFile,
gentleCopy
} = require('./promises')
const {
getEnvVariables,
createDockerVolume,
deleteDockerVolume
} = require('./local')
const {
ADMIN_RELEASE,
FUSION_RELEASE,
FUSION_ROOT,
PROJECT_ROOT,
REPO_NAME,
BOOTSTRAP_ROOT,
CACHE_VOLUME_NAME
} = require('../environment')
async function dockerList (op) {
const { stdout: processes } = await exec(`docker ${op}`)
return processes
.split('\n')
.slice(1)
}
function generateFilename (name) {
if (!name) return ''
return name.replace(/^\.*/, '.').replace(/\.+$/, '')
}
function dockerComposeOp (op) {
const cwd = PROJECT_ROOT
return async function (name, options = {}) {
const { compose } = options
const command = compose === 'v2' ? 'docker' : 'docker-compose'
const args = ['-f', path.join(PROJECT_ROOT, '.fusion', `docker-compose${generateFilename(name)}.yml`)].concat(op || [])
return spawn(
command,
compose === 'v2' ? ['compose'].concat(...args) : args,
{
cwd,
env: {
...process.env,
COMPOSE_PROJECT_NAME: `fusion${name ? `_${name}` : ''}`
},
stdio: 'inherit'
}
)
}
}
const daemon = dockerComposeOp(['up', '-d'])
async function down (name, options) {
return dockerComposeOp(['down', '-v'])(name, options)
}
async function build (name, options) {
// don't pull images if we are linking the fusion repo locally (it'll use the cached one if you already pulled it before)
const { FUSION_REPO } = await getEnvVariables(PROJECT_ROOT)
const useImages = name || !FUSION_REPO
// `docker-compose pull` updates all services that are image-based
if (useImages) await dockerComposeOp('pull')(name, options)
// need to build engine first if we are not using an image and it is the default start
if (!useImages && !name) await dockerComposeOp(['build', '--progress=plain', 'engine'])(name, options)
// `docker-compose build --pull` updates the base FROM image for all services that are built
return dockerComposeOp(useImages ? ['build', '--pull'] : ['build', '--progress=plain'])(name, options)
}
async function generate (name, options = {}) {
await touch(path.join(PROJECT_ROOT, '.env'))
await mkdirp(path.join(PROJECT_ROOT, '.fusion'))
await gentleCopy(path.join(BOOTSTRAP_ROOT, '.dockerignore'), path.join(PROJECT_ROOT, '.dockerignore'))
const props = {
ADMIN_RELEASE,
FUSION_ROOT,
PROJECT_ROOT,
REPO_NAME,
CACHE_VOLUME_NAME,
...options
}
const is20 = /^(2\.0\.|2\.0$)/i.test(FUSION_RELEASE)
const dockerCompose = await require(`../../templates/${is20 ? '2.0/' : ''}docker-compose${generateFilename(name)}.yml`)(props)
return writeFile(path.join(PROJECT_ROOT, '.fusion', `docker-compose${generateFilename(name)}.yml`), yamljs.stringify(dockerCompose, 10, 2))
}
function cleanupVerifyArtifacts () {
const fusionDir = path.join(PROJECT_ROOT, '.fusion')
const verifyDir = path.join(fusionDir, 'verify')
const composePath = path.join(fusionDir, 'docker-compose.verify.yml')
const dockerfilePath = path.join(fusionDir, 'verify.Dockerfile')
return spawn('docker', ['run', '--rm', '-v', `${verifyDir}:/cleanup`, 'alpine', 'sh', '-c', 'rm -rf /cleanup/* /cleanup/.[!.]*'], { stdio: 'inherit' })
.then(() => {
if (fs.existsSync(verifyDir)) fs.rmdirSync(verifyDir)
if (fs.existsSync(composePath)) fs.unlinkSync(composePath)
if (fs.existsSync(dockerfilePath)) fs.unlinkSync(dockerfilePath)
console.log('\x1b[32mCleaned up verify artifacts')
})
}
async function run (name, options) {
try {
await generate(name, options)
await down(name, options)
await build(name, options)
return await start(name, options)
// } catch (e) {
} finally {
await down(name, options)
if (name === 'verify' && !options.keepArtifacts) {
await cleanupVerifyArtifacts()
}
}
}
async function start (name, options = {}) {
const { rebuild, verbose } = options
const operation = ['up']
if (name) {
operation.push('--abort-on-container-exit')
operation.push(`--exit-code-from=${name}`)
operation.push('--remove-orphans')
} else {
// create an external cache volume so we can persist webpack caching across runs
if (rebuild) await deleteDockerVolume(CACHE_VOLUME_NAME)
await createDockerVolume(CACHE_VOLUME_NAME)
// don't output stdio from service dependencies
if (!verbose) {
operation.push('--no-attach=admin')
operation.push('--no-attach=content-cache')
operation.push('--no-attach=cache-proxy')
operation.push('--no-attach=data')
operation.push('--no-attach=fusion-cli-api')
operation.push('--no-attach=origin')
operation.push('--no-attach=resolver')
operation.push('--no-attach=themes')
}
}
return dockerComposeOp(operation, options)(name, options)
}
const stop = dockerComposeOp('stop')
/**
* getDockerImage checks the COMPILER_IMAGE and COMPILER_RELEASE environment variables
* and generates the Docker image to be used for the webpack container
* @returns [String] DockerHub image to be used as the base for the webpack container
*/
const getDockerImage = () => {
const compilerRelease = process.env.COMPILER_RELEASE || 'latest'
const dockerImage = `washpost/pagebuilder-compiler:${compilerRelease}`
return (process.env.COMPILER_IMAGE)
? dockerImage
: 'washpost/fusion-engine:$FUSION_RELEASE'
}
module.exports = {
build,
daemon,
dockerList,
down,
generate,
getDockerImage,
run,
start,
stop
}