From 0e018e620eb2f9b050db8b74c48690f3671a014f Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Fri, 20 Jan 2023 15:26:39 -0700 Subject: [PATCH] feat(node): add docker as a build target (#14475) --- docs/generated/manifests/menus.json | 8 ++ docs/generated/manifests/packages.json | 9 ++ docs/generated/packages-metadata.json | 9 ++ .../packages/node/generators/application.json | 4 + .../node/generators/setup-docker.json | 36 ++++++++ packages/node/generators.json | 11 +++ .../src/generators/application/application.ts | 10 +++ .../src/generators/application/schema.d.ts | 1 + .../src/generators/application/schema.json | 4 + .../setup-docker/files/Dockerfile__tmpl__ | 11 +++ .../src/generators/setup-docker/schema.d.ts | 6 ++ .../src/generators/setup-docker/schema.json | 25 ++++++ .../setup-docker/setup-docker.spec.ts | 60 +++++++++++++ .../generators/setup-docker/setup-docker.ts | 88 +++++++++++++++++++ 14 files changed, 282 insertions(+) create mode 100644 docs/generated/packages/node/generators/setup-docker.json create mode 100644 packages/node/src/generators/setup-docker/files/Dockerfile__tmpl__ create mode 100644 packages/node/src/generators/setup-docker/schema.d.ts create mode 100644 packages/node/src/generators/setup-docker/schema.json create mode 100644 packages/node/src/generators/setup-docker/setup-docker.spec.ts create mode 100644 packages/node/src/generators/setup-docker/setup-docker.ts diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 8f5ac428da..2ca79e220a 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -5015,6 +5015,14 @@ "children": [], "isExternal": false, "disableCollapsible": false + }, + { + "id": "setup-docker", + "path": "/packages/node/generators/setup-docker", + "name": "setup-docker", + "children": [], + "isExternal": false, + "disableCollapsible": false } ], "isExternal": false, diff --git a/docs/generated/manifests/packages.json b/docs/generated/manifests/packages.json index 81197f0a08..69d914c5b8 100644 --- a/docs/generated/manifests/packages.json +++ b/docs/generated/manifests/packages.json @@ -1554,6 +1554,15 @@ "originalFilePath": "/packages/node/src/generators/library/schema.json", "path": "/packages/node/generators/library", "type": "generator" + }, + "/packages/node/generators/setup-docker": { + "description": "Set up Docker configuration for a project.", + "file": "generated/packages/node/generators/setup-docker.json", + "hidden": false, + "name": "setup-docker", + "originalFilePath": "/packages/node/src/generators/setup-docker/schema.json", + "path": "/packages/node/generators/setup-docker", + "type": "generator" } }, "path": "/packages/node" diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index 1f1d3bce6a..9c734ac782 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1531,6 +1531,15 @@ "originalFilePath": "/packages/node/src/generators/library/schema.json", "path": "node/generators/library", "type": "generator" + }, + { + "description": "Set up Docker configuration for a project.", + "file": "generated/packages/node/generators/setup-docker.json", + "hidden": false, + "name": "setup-docker", + "originalFilePath": "/packages/node/src/generators/setup-docker/schema.json", + "path": "node/generators/setup-docker", + "type": "generator" } ], "githubRoot": "https://github.com/nrwl/nx/blob/master", diff --git a/docs/generated/packages/node/generators/application.json b/docs/generated/packages/node/generators/application.json index 1384e97cdf..0dd6d1ee16 100644 --- a/docs/generated/packages/node/generators/application.json +++ b/docs/generated/packages/node/generators/application.json @@ -110,6 +110,10 @@ "enum": ["jest", "none"], "description": "Test runner to use for end to end (e2e) tests", "default": "jest" + }, + "docker": { + "type": "boolean", + "description": "Add a docker build target" } }, "required": [], diff --git a/docs/generated/packages/node/generators/setup-docker.json b/docs/generated/packages/node/generators/setup-docker.json new file mode 100644 index 0000000000..28de91d33a --- /dev/null +++ b/docs/generated/packages/node/generators/setup-docker.json @@ -0,0 +1,36 @@ +{ + "name": "setup-docker", + "factory": "./src/generators/setup-docker/setup-docker", + "schema": { + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "SchematicsNxSetupDocker", + "title": "Nx Node Docker Options Schema", + "description": "Nx Node Docker Options Schema.", + "type": "object", + "properties": { + "projectName": { + "description": "The name of the project", + "$default": { "$source": "argv", "index": 0 }, + "type": "string" + }, + "targetName": { + "description": "The name of the target to create", + "type": "string", + "default": "docker-build" + }, + "buildTargetName": { + "description": "The name of the build target", + "type": "string", + "default": "build" + } + }, + "presets": [] + }, + "description": "Set up Docker configuration for a project.", + "hidden": false, + "implementation": "/packages/node/src/generators/setup-docker/setup-docker.ts", + "aliases": [], + "path": "/packages/node/src/generators/setup-docker/schema.json", + "type": "generator" +} diff --git a/packages/node/generators.json b/packages/node/generators.json index 77bc307983..6512a1c90a 100644 --- a/packages/node/generators.json +++ b/packages/node/generators.json @@ -23,6 +23,12 @@ "aliases": ["lib"], "x-type": "library", "description": "Create a node library." + }, + "setup-docker": { + "factory": "./src/generators/setup-docker/setup-docker", + "schema": "./src/generators/setup-docker/schema.json", + "description": "Set up Docker configuration for a project.", + "hidden": false } }, "schematics": { @@ -46,6 +52,11 @@ "aliases": ["lib"], "x-type": "library", "description": "Create a node library." + }, + "setup-docker": { + "factory": "./src/generators/setup-docker/setup-docker#setupDockerGenerator", + "schema": "./src/generators/setup-docker/schema.json", + "description": "Set up Docker configuration for a project." } } } diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 8ac6ab2c50..a242881e73 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -44,6 +44,7 @@ import { import * as shared from '@nrwl/workspace/src/utils/create-ts-config'; import { e2eProjectGenerator } from '../e2e-project/e2e-project'; +import { setupDockerGenerator } from '../setup-docker/setup-docker'; export interface NormalizedSchema extends Schema { appProjectRoot: string; @@ -374,6 +375,15 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { addProxy(tree, options); } + if (options.docker) { + const dockerTask = await setupDockerGenerator(tree, { + ...options, + projectName: options.name, + }); + + tasks.push(dockerTask); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/node/src/generators/application/schema.d.ts b/packages/node/src/generators/application/schema.d.ts index d84dc2abcf..a2de327066 100644 --- a/packages/node/src/generators/application/schema.d.ts +++ b/packages/node/src/generators/application/schema.d.ts @@ -19,6 +19,7 @@ export interface Schema { framework?: NodeJsFrameWorks; port?: number; rootProject?: boolean; + docker?: boolean; } export type NodeJsFrameWorks = diff --git a/packages/node/src/generators/application/schema.json b/packages/node/src/generators/application/schema.json index 1c6603344e..3bfda4f5e7 100644 --- a/packages/node/src/generators/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -110,6 +110,10 @@ "enum": ["jest", "none"], "description": "Test runner to use for end to end (e2e) tests", "default": "jest" + }, + "docker": { + "type": "boolean", + "description": "Add a docker build target" } }, "required": [] diff --git a/packages/node/src/generators/setup-docker/files/Dockerfile__tmpl__ b/packages/node/src/generators/setup-docker/files/Dockerfile__tmpl__ new file mode 100644 index 0000000000..30b670a33b --- /dev/null +++ b/packages/node/src/generators/setup-docker/files/Dockerfile__tmpl__ @@ -0,0 +1,11 @@ +FROM docker.io/node:lts-alpine + +WORKDIR /app + +RUN addgroup --system <%= projectName %> && \ + adduser --system -G <%= projectName %> <%= projectName %> + +COPY <%= buildLocation %> dist +RUN chown -R <%= projectName %>:<%= projectName %> . + +CMD [ "node", "dist" ] \ No newline at end of file diff --git a/packages/node/src/generators/setup-docker/schema.d.ts b/packages/node/src/generators/setup-docker/schema.d.ts new file mode 100644 index 0000000000..8a7a88e6fe --- /dev/null +++ b/packages/node/src/generators/setup-docker/schema.d.ts @@ -0,0 +1,6 @@ +export interface SetUpDockerOptions { + projectName?: string; + targetName?: string; + buildTarget?: string; + skipFormat?: boolean; +} diff --git a/packages/node/src/generators/setup-docker/schema.json b/packages/node/src/generators/setup-docker/schema.json new file mode 100644 index 0000000000..3495fad64b --- /dev/null +++ b/packages/node/src/generators/setup-docker/schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/schema", + "cli": "nx", + "$id": "SchematicsNxSetupDocker", + "title": "Nx Node Docker Options Schema", + "description": "Nx Node Docker Options Schema.", + "type": "object", + "properties": { + "projectName": { + "description": "The name of the project", + "$default": { "$source": "argv", "index": 0 }, + "type": "string" + }, + "targetName": { + "description": "The name of the target to create", + "type": "string", + "default": "docker-build" + }, + "buildTargetName": { + "description": "The name of the build target", + "type": "string", + "default": "build" + } + } +} diff --git a/packages/node/src/generators/setup-docker/setup-docker.spec.ts b/packages/node/src/generators/setup-docker/setup-docker.spec.ts new file mode 100644 index 0000000000..83927822b5 --- /dev/null +++ b/packages/node/src/generators/setup-docker/setup-docker.spec.ts @@ -0,0 +1,60 @@ +import { readProjectConfiguration, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { applicationGenerator } from '../application/application'; +describe('setupDockerGenerator', () => { + let tree: Tree; + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + }); + + describe('integrated', () => { + it('should create docker assets when --docker is passed', async () => { + await applicationGenerator(tree, { + name: 'api', + framework: 'express', + e2eTestRunner: 'none', + docker: true, + }); + + const project = readProjectConfiguration(tree, 'api'); + + expect(tree.exists('api/Dockerfile')).toBeTruthy(); + expect(project.targets).toEqual( + expect.objectContaining({ + 'docker-build': { + dependsOn: ['build'], + executor: 'nx:run-commands', + options: { + commands: ['docker build -f api/Dockerfile .'], + }, + }, + }) + ); + }); + }); + + describe('standalone', () => { + it('should create docker assets when --docker is passed', async () => { + await applicationGenerator(tree, { + name: 'api', + framework: 'fastify', + rootProject: true, + docker: true, + }); + + const project = readProjectConfiguration(tree, 'api'); + expect(tree.exists('Dockerfile')).toBeTruthy(); + expect(project.targets).toEqual( + expect.objectContaining({ + 'docker-build': { + dependsOn: ['build'], + executor: 'nx:run-commands', + options: { + commands: ['docker build -f Dockerfile .'], + }, + }, + }) + ); + }); + }); +}); diff --git a/packages/node/src/generators/setup-docker/setup-docker.ts b/packages/node/src/generators/setup-docker/setup-docker.ts new file mode 100644 index 0000000000..60bf05a7ad --- /dev/null +++ b/packages/node/src/generators/setup-docker/setup-docker.ts @@ -0,0 +1,88 @@ +import { + addDependenciesToPackageJson, + convertNxGenerator, + formatFiles, + generateFiles, + GeneratorCallback, + joinPathFragments, + logger, + readNxJson, + readProjectConfiguration, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { SetUpDockerOptions } from './schema'; + +function normalizeOptions( + tree: Tree, + setupOptions: SetUpDockerOptions +): SetUpDockerOptions { + return { + ...setupOptions, + projectName: setupOptions.projectName ?? readNxJson(tree).defaultProject, + targetName: setupOptions.targetName ?? 'docker-build', + buildTarget: setupOptions.buildTarget ?? 'build', + }; +} + +function addDocker(tree: Tree, options: SetUpDockerOptions) { + const project = readProjectConfiguration(tree, options.projectName); + if (!project || !options.targetName) { + return; + } + + if (tree.exists(joinPathFragments(project.root, 'DockerFile'))) { + logger.info( + `Skipping setup since a Dockerfile already exists inside ${project.root}` + ); + } else { + const outputPath = + project.targets[`${options.buildTarget}`]?.options.outputPath; + generateFiles(tree, joinPathFragments(__dirname, './files'), project.root, { + tmpl: '', + app: project.sourceRoot, + buildLocation: outputPath, + projectName: options.projectName, + }); + } +} + +export function updateProjectConfig(tree: Tree, options: SetUpDockerOptions) { + let projectConfig = readProjectConfiguration(tree, options.projectName); + + projectConfig.targets[`${options.targetName}`] = { + dependsOn: [`${options.buildTarget}`], + executor: 'nx:run-commands', + options: { + commands: [ + `docker build -f ${joinPathFragments( + projectConfig.root, + 'Dockerfile' + )} .`, + ], + }, + }; + + updateProjectConfiguration(tree, options.projectName, projectConfig); +} + +export async function setupDockerGenerator( + tree: Tree, + setupOptions: SetUpDockerOptions +) { + const tasks: GeneratorCallback[] = []; + const options = normalizeOptions(tree, setupOptions); + // Should check if the node project exists + addDocker(tree, options); + updateProjectConfig(tree, options); + + if (!options.skipFormat) { + await formatFiles(tree); + } + + return runTasksInSerial(...tasks); +} + +export default setupDockerGenerator; +export const setupDockerSchematic = convertNxGenerator(setupDockerGenerator);