UNPKG

@nx/docker

Version:

The Nx Plugin for Docker to aid in containerizing projects.

220 lines (219 loc) 9.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNodesV2 = void 0; exports.getProjectNameFromPath = getProjectNameFromPath; const devkit_1 = require("@nx/devkit"); const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes"); const file_hasher_1 = require("nx/src/hasher/file-hasher"); const cache_directory_1 = require("nx/src/utils/cache-directory"); const fs_1 = require("fs"); const path_1 = require("path"); const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs"); const git_utils_1 = require("nx/src/utils/git-utils"); const interpolate_pattern_1 = require("../utils/interpolate-pattern"); function readTargetsCache(cachePath) { return (0, fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {}; } function writeTargetsCache(cachePath, results) { (0, devkit_1.writeJsonFile)(cachePath, results ?? {}); } const dockerfileGlob = '**/Dockerfile'; exports.createNodesV2 = [ dockerfileGlob, async (configFilePaths, options, context) => { const optionsHash = (0, file_hasher_1.hashObject)(options); const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `docker-${optionsHash}.hash`); const targetsCache = readTargetsCache(cachePath); const projectRoots = configFilePaths.map((c) => (0, path_1.dirname)(c)); const normalizedOptions = normalizePluginOptions(options); // TODO(colum): investigate hashing only the dockerfile const hashes = await (0, calculate_hash_for_create_nodes_1.calculateHashesForCreateNodes)(projectRoots, normalizedOptions, context); try { return await (0, devkit_1.createNodesFromFiles)((configFile, _, context, idx) => createNodesInternal(configFile, hashes[idx] + configFile, normalizedOptions, context, targetsCache), configFilePaths, options, context); } finally { writeTargetsCache(cachePath, targetsCache); } }, ]; async function createNodesInternal(configFilePath, hash, normalizedOptions, context, targetsCache) { const projectRoot = (0, path_1.dirname)(configFilePath); targetsCache[hash] ??= await createDockerTargets(projectRoot, normalizedOptions, context); const { targets, metadata } = targetsCache[hash]; return { projects: { [projectRoot]: { root: projectRoot, targets, metadata, }, }, }; } function interpolateDockerTargetOptions(options, projectRoot, imageRef, context) { const commitSha = (0, git_utils_1.getLatestCommitSha)(); const projectName = getProjectName(projectRoot, context.workspaceRoot); const tokens = { projectRoot, projectName, imageRef, currentDate: new Date(), commitSha, shortCommitSha: commitSha ? commitSha.slice(0, 7) : null, }; return (0, interpolate_pattern_1.interpolateObject)(options, tokens); } function getProjectNameFromPath(projectRoot, workspaceRoot) { const root = projectRoot === '.' ? workspaceRoot : projectRoot; const normalized = root .replace(/^[\\/]/, '') .replace(/[\\/\s]+/g, '-') .toLowerCase(); return normalized.length > 128 ? normalized.slice(-128) : normalized; } function getProjectName(projectRoot, workspaceRoot) { const projectJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'project.json'); if ((0, fs_1.existsSync)(projectJsonPath)) { const projectJson = (0, devkit_1.readJsonFile)(projectJsonPath); if (projectJson.name) { return projectJson.name; } } const packageJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'package.json'); if ((0, fs_1.existsSync)(packageJsonPath)) { const packageJson = (0, devkit_1.readJsonFile)(packageJsonPath); if (packageJson.name) { return packageJson.name; } } return getProjectNameFromPath(projectRoot, workspaceRoot); } function buildTargetOptions(interpolatedTarget, projectRoot, imageRef, isRunTarget = false) { const options = { cwd: interpolatedTarget.cwd ?? projectRoot, }; if (isRunTarget) { // Run target doesn't have default args if (interpolatedTarget.args) { options.args = interpolatedTarget.args; } } else { // Build target includes --tag default unless skipDefaultTag is true if (interpolatedTarget.skipDefaultTag) { options.args = interpolatedTarget.args ?? []; } else { options.args = [`--tag ${imageRef}`, ...(interpolatedTarget.args ?? [])]; } } if (interpolatedTarget.env) { options.env = interpolatedTarget.env; } if (interpolatedTarget.envFile) { options.envFile = interpolatedTarget.envFile; } return options; } function buildTargetConfigurations(interpolatedTarget, projectRoot, imageRef, isRunTarget = false) { if (!interpolatedTarget.configurations) { return undefined; } const configurations = {}; for (const [configName, configOptions] of Object.entries(interpolatedTarget.configurations)) { // Each configuration gets the full treatment with defaults // Inherit skipDefaultTag from parent if not explicitly set in config configurations[configName] = buildTargetOptions({ ...configOptions, name: interpolatedTarget.name, skipDefaultTag: configOptions.skipDefaultTag ?? interpolatedTarget.skipDefaultTag, }, projectRoot, imageRef, isRunTarget); } return configurations; } async function createDockerTargets(projectRoot, options, context) { const imageRef = getProjectNameFromPath(projectRoot, devkit_1.workspaceRoot); const interpolatedBuildTarget = interpolateDockerTargetOptions(options.buildTarget, projectRoot, imageRef, context); const interpolatedRunTarget = interpolateDockerTargetOptions(options.runTarget, projectRoot, imageRef, context); const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context); const targets = {}; const metadata = { targetGroups: { ['Docker']: [ interpolatedBuildTarget.name, interpolatedRunTarget.name, 'nx-release-publish', ], }, }; const buildOptions = buildTargetOptions(interpolatedBuildTarget, projectRoot, imageRef, false); const buildConfigurations = buildTargetConfigurations(interpolatedBuildTarget, projectRoot, imageRef, false); targets[interpolatedBuildTarget.name] = { dependsOn: ['build', '^build'], command: `docker build .`, options: buildOptions, ...(buildConfigurations && { configurations: buildConfigurations }), inputs: [ ...('production' in namedInputs ? ['production', '^production'] : ['default', '^default']), ], metadata: { technologies: ['docker'], description: `Run Docker build`, help: { command: `docker build --help`, example: { options: { 'cache-from': 'type=s3,region=eu-west-1,bucket=mybucket .', 'cache-to': 'type=s3,region=eu-west-1,bucket=mybucket .', }, }, }, }, }; const runOptions = buildTargetOptions(interpolatedRunTarget, projectRoot, imageRef, true); const runConfigurations = buildTargetConfigurations(interpolatedRunTarget, projectRoot, imageRef, true); targets[interpolatedRunTarget.name] = { dependsOn: [interpolatedBuildTarget.name], command: `docker run {args} ${imageRef}`, options: runOptions, ...(runConfigurations && { configurations: runConfigurations }), inputs: [ ...('production' in namedInputs ? ['production', '^production'] : ['default', '^default']), ], metadata: { technologies: ['docker'], description: `Run Docker run`, help: { command: `docker run --help`, example: { options: { args: ['-p', '3000:3000'], }, }, }, }, }; targets['nx-release-publish'] = { executor: '@nx/docker:release-publish', }; return { targets, metadata }; } function normalizePluginOptions(options) { const normalizeTarget = (target, defaultName) => { if (typeof target === 'string') { return { name: target }; } if (target && typeof target === 'object') { return { ...target, name: target.name ?? defaultName }; } return { name: defaultName }; }; return { buildTarget: normalizeTarget(options?.buildTarget, 'docker:build'), runTarget: normalizeTarget(options?.runTarget, 'docker:run'), }; }