UNPKG

@arc-fusion/cli

Version:

CLI for running Arc Fusion on your local machine

424 lines (396 loc) 14.5 kB
/* eslint-disable no-template-curly-in-string */ 'use strict' const os = require('os') const path = require('path') const semver = require('semver') const webpackDockerfile = require('./webpack.Dockerfile') const getLinkedModules = require('./links') const { getEnvVariables, getBlockVariables, getLinkedThemeBlocks, getLinkedEngineSdkBlock, getLinkedCssFrameworkBlock, getLinkedThemeComponentsBlock, getLinkedCacheProxyRepo, getLinkedEngineRepo, getLinkedOriginRepo, getLinkedResolverRepo, getLinkedFusionVersion, getLinkedFusionEngineRepo } = require('../bin/utils/local') const { mkdirp, writeFile } = require('../bin/utils/promises') const { MONGO_VERSION } = require('../bin/environment') const isWindows = /^win/i.test(os.platform()) module.exports = async ({ ADMIN_RELEASE, PROJECT_ROOT, REPO_NAME, CACHE_VOLUME_NAME, admin = true, rebuild, links, production }) => { const environment = { DB_NAME: `\${DB_NAME:-${REPO_NAME}}`, MONGO_URL: `mongodb://data:27017/\${DB_NAME:-${REPO_NAME}}`, PB_MONGODB_URI: 'mongodb://data:27017', NODE_ENV: 'development', ENVIRONMENT: 'localhost', CONTEXT_PATH: '${CONTEXT_PATH:-pf}', DEBUG: '${DEBUG:-arc.fusion.render.error}', HTTP_ENGINE: 'http://engine:8080', HTTP_RESOLVER: 'http://resolver:8080' } const networks = [ 'fusion' ] // get all modules linked via npm link const linkedModules = await getLinkedModules(PROJECT_ROOT) || {} const moduleLinks = Object.keys(linkedModules) .map((linkedModule) => `${linkedModules[linkedModule]}:/opt/engine/bundle/linked_modules/${linkedModule}:ro,cached`) // check for local repos we need to link const env = await getEnvVariables(PROJECT_ROOT) const blocksEnv = await getBlockVariables(PROJECT_ROOT) if (blocksEnv) { const linkedBlocks = await getLinkedThemeBlocks(PROJECT_ROOT, env.THEMES_BLOCKS_REPO, blocksEnv, links, production) || [] if (linkedBlocks.length) moduleLinks.push(...linkedBlocks) const themeComponents = getLinkedThemeComponentsBlock(env.THEME_COMPONENTS_REPO, blocksEnv) if (themeComponents) moduleLinks.push(themeComponents) if (blocksEnv.engineSDK) { const engineSdk = getLinkedEngineSdkBlock(env.ENGINE_SDK_REPO, blocksEnv) if (engineSdk) moduleLinks.push(engineSdk) } if (blocksEnv.cssFramework) { const cssFramework = getLinkedCssFrameworkBlock(env.CSS_FRAMEWORK_REPO, blocksEnv) if (cssFramework) moduleLinks.push(cssFramework) } } // These functions will get the path to the linked Pagebuilder repos. // `fusionLinks` will remain as we separate out more of rest of fusion const cacheProxyLinks = getLinkedCacheProxyRepo(env.CACHEPROXY_SOURCE_DIR) || {} if (env.FUSION_REPO && env.ENGINE_SOURCE_DIR) { console.error('Linking the fusion and engine repos at the same time is not supported. Please unlink one of them.') } const engineLinks = { ...getLinkedFusionEngineRepo(env.FUSION_REPO), ...getLinkedEngineRepo(env.ENGINE_SOURCE_DIR) } const originLinks = getLinkedOriginRepo(env.ORIGIN_SOURCE_DIR) || {} const resolverLinks = getLinkedResolverRepo(env.RESOLVER_SOURCE_DIR) || {} // filter out duplicates from links const uniqueLinks = [...new Set(moduleLinks)] // If the bundle has a fusion version tagged, check whether it is 3.2.7 or higher, since // Redis is only supported there. Error out and notify the user to use higher Fusion version if (env.FUSION_RELEASE && semver.satisfies(semver.coerce(env.FUSION_RELEASE), '< 3.2.7')) { console.log('Checking Fusion Release Version') return console.error('This version of the CLI only supports Fusion engine versions 3.2.7 and higher. Please use later images') } else if (engineLinks && engineLinks.engine) { console.log('Checking Linked Fusion Version') const fusionVersion = getLinkedFusionVersion(env.FUSION_REPO) if (fusionVersion !== 'latest' && semver.satisfies(semver.coerce(fusionVersion), '< 3.2.7')) { return console.error('This version of the CLI only supports Fusion engine versions 3.2.7 and higher. Please use later images') } } // Otherwise, no tags exist, meaning it's using the latest image. All latest images will support redis // the node_modules volume trick to hide host files from the container causes an error if the directory does not exist await mkdirp(path.join(PROJECT_ROOT, 'node_modules')) await writeFile( path.join(PROJECT_ROOT, '.fusion', 'webpack.Dockerfile'), await webpackDockerfile( { PROJECT_ROOT, rebuild, localEngine: !!Object.keys(engineLinks).length, themesVersion: env.BLOCK_DIST_TAG, cssVersion: env.CSS_DIST_TAG, sdkVersion: env.ENGINE_SDK_DIST_TAG, componentsVersion: env.THEME_COMPONENTS_DIST_TAG, npmLegacyPeerDeps: env.NPM_LEGACY_PEER_DEPS }) ) return { networks: { fusion: { driver: 'bridge', external: false, internal: false } }, secrets: { github_token: { environment: 'GITHUB_TOKEN' } }, services: { data: { ...(env.MONGO_SOURCE_DIR && { build: { context: path.resolve(env.MONGO_SOURCE_DIR) } }), image: `washpost/mongo-vandelay:${env.MONGO_VERSION || MONGO_VERSION}`, container_name: 'fusion-data', environment: { ...environment, RESTORE: 'true', SYNC: (isWindows) ? 'true' : '' }, networks: { fusion: { aliases: [ 'db', 'database' ] } }, ports: [ '27017:27017' ], volumes: [ '../data/db:/data/db:rw', '../data/dumps:/data/dumps:rw', '../data/restore:/data/restore:rw' ] }, 'content-cache': { // memcached service runs from an image, so there is no Dockerfile image: 'memcached', // max size is 10MB command: 'memcached -I 10m', container_name: 'fusion-content-cache', networks }, 'cache-proxy': { ...(cacheProxyLinks.cacheProxy && { build: { context: path.resolve(env.CACHEPROXY_SOURCE_DIR), secrets: ['github_token'] } }), image: cacheProxyLinks.cacheProxy ? 'local-washpost/fusion-cache-proxy:latest' : 'washpost/fusion-cache-proxy:${CACHE_PROXY_RELEASE:-latest}', container_name: 'fusion-cache-proxy', depends_on: [ 'content-cache' ], networks, ports: cacheProxyLinks.cacheProxy ? ['9030:8080'] : [], environment: { CACHE_PROXY_CREDENTIALS: 'localhost:password', CACHE_NODES: 'content-cache:11211', WATCH: cacheProxyLinks.cacheProxy ? 'true' : null }, ...(cacheProxyLinks.cacheProxy && { volumes: cacheProxyLinks.cacheProxy }) }, webpack: { build: { context: '..', dockerfile: './.fusion/webpack.Dockerfile', args: { FUSION_RELEASE: '${FUSION_RELEASE:-latest}', NODE_VERSION: env.ENGINE_NODE_VERSION }, secrets: ['github_token'] }, restart: 'unless-stopped', command: 'watch', container_name: 'fusion-webpack', env_file: [ '../.env' ], environment: { ...environment, NODE_OPTIONS: `--max-old-space-size=${env.WEBPACK_HEAP_SIZE || '4096'}`, WATCH: engineLinks.engine ? 'true' : null, POLL: null, // because we import ALL variables from .env, ignore PORT PORT: '8080' }, network_mode: 'none', volumes: [ '..:/opt/engine/bundle/src:ro,cached', // hide the bundle's node_modules from docker '/opt/engine/bundle/src/node_modules', // because this volume is created on docker-compose up, it will copy the existing data from the image 'shared:/opt/engine/bundle/node_modules:rw,delegated', // this is external so make sure we override the existing data in the image with the data we have `${CACHE_VOLUME_NAME}:/opt/engine/node_modules/.cache:rw,nocopy,delegated`, '../.fusion/babel:/opt/engine/bundle/babel:rw,cached', '../.fusion/build:/opt/engine/bundle/build:rw,cached', '../.fusion/dist:/opt/engine/bundle/dist:rw,cached', '../.fusion/generated:/opt/engine/bundle/generated:rw,cached', ...(engineLinks.engine || []), ...uniqueLinks ] }, engine: { ...(engineLinks.engine && { build: { context: env.FUSION_REPO ? path.resolve(env.FUSION_REPO, 'engine') : path.resolve(env.ENGINE_SOURCE_DIR), args: { NODE_VERSION: env.ENGINE_NODE_VERSION }, secrets: ['github_token'] } }), image: 'washpost/fusion-engine:${FUSION_RELEASE:-latest}', restart: 'unless-stopped', container_name: engineLinks.engine ? 'fusion-engine-local' : 'fusion-engine', depends_on: [ 'cache-proxy', 'data' ], env_file: [ '../.env' ], environment: { ...environment, NODE_OPTIONS: `--max-old-space-size=${env.ENGINE_HEAP_SIZE || '2048'}`, CACHE_PROXY_URL: 'http://localhost:password@cache-proxy:8080/cache', CACHE_PREFIX: '0803', // because we import ALL variables from .env, ignore PORT PORT: '8080', SLS_RELOAD_HANDLER: `${env.SLS_RELOAD_HANDLER || 'true'}` }, networks, ports: engineLinks.engine ? ['9010:8080', '9229:9229'] : [], volumes: [ '..:/opt/engine/bundle/src:ro,cached', // hide the bundle's node_modules from docker '/opt/engine/bundle/src/node_modules', // read-only and nocopy ensures the data will be shared from the webpack container 'shared:/opt/engine/bundle/node_modules:ro,nocopy,delegated', '../.fusion/babel:/opt/engine/bundle/babel:ro,cached', '../.fusion/build:/opt/engine/bundle/build:ro,cached', // need to be able to write skeleton scripts '../.fusion/dist:/opt/engine/bundle/dist:rw,cached', ...(engineLinks.engine || []), ...uniqueLinks ] }, resolver: { ...(resolverLinks.resolver && { build: { context: path.resolve(env.RESOLVER_SOURCE_DIR), secrets: ['github_token'], args: { NODE_VERSION: env.RESOLVER_NODE_VERSION } } }), image: 'washpost/fusion-resolver:${RESOLVER_RELEASE:-latest}', container_name: resolverLinks.resolver ? 'fusion-resolver-local' : 'fusion-resolver', depends_on: [ 'data', 'engine' ], environment: { ...environment, RESOLVE_FROM_DB: 'true', // should only be 'true' for local dev environments TRAILING_SLASH_RULE: null, // Options are FORCE, DROP, or NOOP WATCH: resolverLinks.resolver ? 'true' : null }, networks, ports: resolverLinks.resolver ? ['9020:8080', '9021:9229'] : [], ...(resolverLinks.resolver && { volumes: resolverLinks.resolver }) }, ...( (admin) ? { admin: { image: `washpost/pb-editor-api:\${PB_RELEASE:-${ADMIN_RELEASE}}`, container_name: 'fusion-admin', depends_on: [ 'data' ], env_file: [ '../.env' ], environment: { ...environment, KAFKA_HOSTS: '', PORT: 8888 }, networks, ports: [] } } : {} ), 'fusion-cli-api': { ...(env.FUSION_CLI_API_REPO && { build: path.resolve(env.FUSION_CLI_API_REPO), volumes: [ `${path.resolve(env.FUSION_CLI_API_REPO)}:/app` ] }), image: 'washpost/fusion-cli-api:${CLI_API_RELEASE:-production}', container_name: 'fusion-cli-api', env_file: ['../.env'], environment: { ...environment }, networks, ports: [ '${PORT:-80}:8080' ] }, origin: { ...(originLinks.origin && { build: { context: path.resolve(env.ORIGIN_SOURCE_DIR), secrets: ['github_token'], args: { NODE_VERSION: env.ORIGIN_NODE_VERSION } } }), image: 'washpost/fusion-origin:${ORIGIN_RELEASE:-latest}', container_name: 'fusion-origin', depends_on: [ 'engine', 'resolver' ], environment: { ...environment, ...( (admin) ? { PB_ADMIN: 'http://admin:8888', PB_EDITOR: 'http://${PB_EDITOR:-beta}.pb-admin.aws.arc.pub' } : {} ), ...(originLinks.origin && { NODE_INSPECT: 'true', WATCH: 'true' }), DEFAULT_ARC_SITE: null, IS_ADMIN: 'true' }, networks, ports: originLinks.origin ? ['8081:8081', '9001:9229'] : [], volumes: [ '../mocks:/opt/origin/mocks:ro', '../resources:/opt/origin/resources:ro', '../.fusion/dist:/opt/origin/dist:ro,cached', ...(originLinks.origin || []) ] }, themes: { image: 'pagebuilderteam/arc-themes-stylebuilder:latest', env_file: [ '../.env' ], environment: { ...environment, STYLES_PATH: '/opt/engine/bundle/node_modules', STYLES_WRITE_PATH: '/opt/origin', FEATURE_PACK_PATH: '/opt/engine/bundle/src' }, volumes: [ 'shared:/opt/engine/bundle/node_modules:ro,nocopy,delegated', '..:/opt/engine/bundle/src:ro,cached', '../site-styles:/opt/origin/:rw', ...uniqueLinks ] } }, volumes: { shared: null, [CACHE_VOLUME_NAME]: { external: true } } } }