From 2f7ebf66a29aeb83e135ad09e37208da9e36865e Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 10 Dec 2023 14:23:12 -0600 Subject: [PATCH] feat(docker): generation and hooks --- packages/docker/build/dox/hooks.js | 20 +++++- packages/docker/src/commands.js | 103 +++++------------------------ packages/docker/src/generate.js | 74 +++++++++++++++++++++ packages/docker/src/plugin.js | 33 +++++++++ packages/docker/src/server.js | 7 ++ 5 files changed, 149 insertions(+), 88 deletions(-) create mode 100644 packages/docker/src/generate.js create mode 100644 packages/docker/src/plugin.js diff --git a/packages/docker/build/dox/hooks.js b/packages/docker/build/dox/hooks.js index ddb653b..5b70846 100644 --- a/packages/docker/build/dox/hooks.js +++ b/packages/docker/build/dox/hooks.js @@ -19,5 +19,23 @@ export const hooks = { ports: {3000: 3000}, }, }), -}; + /** + * + * @param {string} dockerfile The content of the Dockerfile. + * + * @returns The new content of the Dockerfile. + */ + '@flecks/docker.Dockerfile': (dockerfile) => ( + dockerfile.replace('DEBUG=*', 'DEBUG=*,-*:silly') + ), + + /** + * + * @param {Object} config The object representing the docker compose configuration. + */ + '@flecks/docker.docker-compose.yml': (config) => { + config.version = '3.1'; + }, + +}; diff --git a/packages/docker/src/commands.js b/packages/docker/src/commands.js index 32dea55..2d60067 100644 --- a/packages/docker/src/commands.js +++ b/packages/docker/src/commands.js @@ -1,10 +1,12 @@ import {spawn} from 'child_process'; import {writeFile} from 'fs/promises'; -import {join, relative} from 'path'; +import {join} from 'path'; import {D} from '@flecks/core'; import {dumpYml} from '@flecks/core/server'; +import {generateComposeConfig, generateDockerFile} from './generate'; + const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; @@ -26,105 +28,32 @@ export default (program, flecks) => { compose, tag, } = opts; - const output = join(FLECKS_CORE_ROOT, 'dist'); - const lines = [ - "version: '3'", - 'services:', - '', - ]; + const dockerFile = generateDockerFile(flecks); + const composeConfig = await generateComposeConfig(flecks); + const composeFile = dumpYml(composeConfig); const id = flecks.get('@flecks/core.id'); - const appServiceName = `${id}_app`; - const services = { - [appServiceName]: { - build: { - context: '..', - dockerfile: 'dist/Dockerfile', - }, - environment: { - FLECKS_ENV_FLECKS_DOCKER_SERVER_enabled: 'false', - }, - volumes: [ - '../node_modules:/var/www/node_modules', - ], - }, - }; - const containers = flecks.invoke('@flecks/docker.containers'); - ( - await Promise.all( - Object.entries(containers) - .map(async ([fleck, config]) => { - Object.entries(await config) - .forEach(([key, config]) => { - services[key] = {image: config.image, environment: {}, ...config.extra}; - }); - return [ - `FLECKS_ENV_${flecks.constructor.environmentalize(fleck)}`, - config, - ]; - }), - ) - ) - // Set environment. - .forEach(([prefix, config]) => { - Object.values(config) - .forEach((config) => { - Object.entries(config.environment || {}) - .forEach(([configService, environment]) => { - Object.entries(environment || {}) - .forEach(([key, value]) => { - const [realKey, realService] = 'app' === configService - ? [`${prefix}_${key}`, appServiceName] - : [key, configService]; - services[realService].environment[realKey] = value; - }); - }); - }); - }); - Object.entries(services) - .forEach(([key, service]) => { - lines.push( - ...dumpYml({[key]: service}) - .split('\n') - .map((line) => ` ${line}`), - ); - }); - const composeFile = join(output, 'docker-compose.yml'); + const output = join(FLECKS_CORE_ROOT, 'dist'); debug('writing %s...', composeFile); await writeFile( + join(output, 'docker-compose.yml'), composeFile, - lines - .map((line) => ('' === line.trim() ? '' : line)) - .join('\n'), ); - const dockerFile = join(output, 'Dockerfile'); debug('writing %s...', dockerFile); await writeFile( + join(output, 'Dockerfile'), dockerFile, - [ - 'FROM node:20', - '', - 'RUN mkdir -p /var/www', - 'WORKDIR /var/www', - 'COPY package.json /var/www', - 'COPY build /var/www/build', - 'COPY dist /var/www/dist', - '', - 'ENV DEBUG=*', - 'ENV NODE_ENV=production', - '', - 'CMD ["node", "./dist/index.js"]', - '', - 'VOLUME /var/www/node_modules', - '', - ].join('\n'), ); /* eslint-disable no-console */ console.log(); console.log('Outputs:'); console.log(); - console.group(); - console.log(relative(FLECKS_CORE_ROOT, composeFile)); - console.log(relative(FLECKS_CORE_ROOT, dockerFile)); + console.group('dist'); + console.group('Dockerfile'); + console.log(dockerFile); + console.groupEnd(); + console.group('docker-compose.yml'); + console.log(composeFile); + console.groupEnd(); console.groupEnd(); console.log(); /* eslint-enable no-console */ diff --git a/packages/docker/src/generate.js b/packages/docker/src/generate.js new file mode 100644 index 0000000..52ddaa1 --- /dev/null +++ b/packages/docker/src/generate.js @@ -0,0 +1,74 @@ +export const generateDockerFile = (flecks) => { + const dockerfile = [ + 'FROM node:20', + '', + 'RUN mkdir -p /var/www', + 'WORKDIR /var/www', + 'COPY package.json /var/www', + 'COPY build /var/www/build', + 'COPY dist /var/www/dist', + '', + 'ENV DEBUG=*', + 'ENV NODE_ENV=production', + '', + 'CMD ["node", "./dist/index.js"]', + '', + 'VOLUME /var/www/node_modules', + '', + ].join('\n'); + return flecks.invokeComposed('@flecks/docker.Dockerfile', dockerfile); +}; + +export const generateComposeConfig = async (flecks) => { + const id = flecks.get('@flecks/core.id'); + const appServiceName = `${id}_app`; + const services = { + [appServiceName]: { + build: { + context: '..', + dockerfile: 'dist/Dockerfile', + }, + environment: { + FLECKS_ENV_FLECKS_DOCKER_SERVER_enabled: 'false', + }, + volumes: [ + '../node_modules:/var/www/node_modules', + ], + }, + }; + const containers = flecks.invoke('@flecks/docker.containers'); + ( + await Promise.all( + Object.entries(containers) + .map(async ([fleck, config]) => { + Object.entries(await config) + .forEach(([key, config]) => { + services[key] = {image: config.image, environment: {}, ...config.extra}; + }); + return [ + `FLECKS_ENV_${flecks.constructor.environmentalize(fleck)}`, + config, + ]; + }), + ) + ) + // Set environment. + .forEach(([prefix, config]) => { + Object.values(config) + .forEach((config) => { + Object.entries(config.environment || {}) + .forEach(([configService, environment]) => { + Object.entries(environment || {}) + .forEach(([key, value]) => { + const [realKey, realService] = 'app' === configService + ? [`${prefix}_${key}`, appServiceName] + : [key, configService]; + services[realService].environment[realKey] = value; + }); + }); + }); + }); + const config = {version: '3', services}; + flecks.invoke('@flecks/docker.docker-compose.yml', config); + return config; +}; diff --git a/packages/docker/src/plugin.js b/packages/docker/src/plugin.js new file mode 100644 index 0000000..b47a4bf --- /dev/null +++ b/packages/docker/src/plugin.js @@ -0,0 +1,33 @@ +import {dumpYml} from '@flecks/core/server'; + +import {generateComposeConfig, generateDockerFile} from './generate'; + +class FlecksDockerOutput { + + constructor(options) { + this.options = options; + } + + // eslint-disable-next-line class-methods-use-this + apply(compiler) { + compiler.hooks.compilation.tap('FlecksDockerOutput', (compilation) => { + compilation.hooks.additionalAssets.tapAsync('FlecksDockerOutput', async (callback) => { + const dockerFile = generateDockerFile(this.options.flecks); + compilation.assets.Dockerfile = { + source: () => dockerFile, + size: () => dockerFile.length, + }; + const composeConfig = await generateComposeConfig(this.options.flecks); + const composeFile = dumpYml(composeConfig); + compilation.assets['docker-compose.yml'] = { + source: () => composeFile, + size: () => composeFile.length, + }; + callback(); + }); + }); + } + +} + +export default FlecksDockerOutput; diff --git a/packages/docker/src/server.js b/packages/docker/src/server.js index b77ac02..c35e35b 100644 --- a/packages/docker/src/server.js +++ b/packages/docker/src/server.js @@ -1,7 +1,14 @@ import commands from './commands'; +import FlecksDockerOutput from './plugin'; import startContainer from './start-container'; export const hooks = { + '@flecks/core.build': (target, config, env, argv, flecks) => { + if ('server' !== target) { + return; + } + config.plugins.push(new FlecksDockerOutput({flecks})); + }, '@flecks/core.config': () => ({ /** * Whether to run docker containers.