Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4988b8683 |
@@ -124,6 +124,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Add a docker build target"
|
||||
},
|
||||
"skipDockerPlugin": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the @nx/docker plugin and use the legacy docker build target instead.",
|
||||
"default": false
|
||||
},
|
||||
"useProjectJson": {
|
||||
"type": "boolean",
|
||||
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
"outputPath": {
|
||||
"description": "The output path for the node application",
|
||||
"type": "string"
|
||||
},
|
||||
"skipDockerPlugin": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the @nx/docker plugin and use the legacy docker build target instead.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
|
||||
@@ -35,7 +35,11 @@
|
||||
"./package.json": "./package.json",
|
||||
"./generators.json": "./generators.json",
|
||||
"./executors.json": "./executors.json",
|
||||
"./migrations.json": "./migrations.json"
|
||||
"./migrations.json": "./migrations.json",
|
||||
"./generators": {
|
||||
"types": "./src/generators/index.d.ts",
|
||||
"default": "./src/generators/index.js"
|
||||
}
|
||||
},
|
||||
"nx-migrations": {
|
||||
"migrations": "./migrations.json"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { initGenerator } from './init/init';
|
||||
@@ -33,6 +33,7 @@
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0",
|
||||
"@nx/devkit": "workspace:*",
|
||||
"@nx/docker": "workspace:*",
|
||||
"@nx/jest": "workspace:*",
|
||||
"@nx/js": "workspace:*",
|
||||
"@nx/eslint": "workspace:*",
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
normalizeOptions,
|
||||
NormalizedSchema,
|
||||
} from './lib';
|
||||
// @ts-ignore-next-line
|
||||
import { initGenerator as dockerInitGenerator } from '@nx/docker/generators';
|
||||
|
||||
function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
|
||||
if (options.isUsingTsSolutionConfig) {
|
||||
@@ -97,6 +99,7 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
|
||||
...options,
|
||||
project: options.name,
|
||||
skipFormat: true,
|
||||
skipDockerPlugin: options.skipDockerPlugin,
|
||||
});
|
||||
tasks.push(dockerTask);
|
||||
}
|
||||
@@ -210,10 +213,20 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
|
||||
}
|
||||
|
||||
if (options.docker) {
|
||||
// Initialize @nx/docker plugin if not skipping
|
||||
if (!options.skipDockerPlugin) {
|
||||
const dockerInitTask = await dockerInitGenerator(tree, {
|
||||
skipFormat: true,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
});
|
||||
tasks.push(dockerInitTask);
|
||||
}
|
||||
|
||||
const dockerTask = await setupDockerGenerator(tree, {
|
||||
...options,
|
||||
project: options.name,
|
||||
skipFormat: true,
|
||||
skipDockerPlugin: options.skipDockerPlugin,
|
||||
});
|
||||
|
||||
tasks.push(dockerTask);
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface Schema {
|
||||
port?: number;
|
||||
rootProject?: boolean;
|
||||
docker?: boolean;
|
||||
skipDockerPlugin?: boolean;
|
||||
isNest?: boolean;
|
||||
addPlugin?: boolean;
|
||||
useTsSolution?: boolean;
|
||||
|
||||
@@ -124,6 +124,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Add a docker build target"
|
||||
},
|
||||
"skipDockerPlugin": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the @nx/docker plugin and use the legacy docker build target instead.",
|
||||
"default": false
|
||||
},
|
||||
"useProjectJson": {
|
||||
"type": "boolean",
|
||||
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# This file is generated by Nx.
|
||||
#
|
||||
#<% if (skipDockerPlugin) { %>
|
||||
# Build the docker image with `npx nx docker-build <%= project %>`.
|
||||
# Tip: Modify "docker-build" options in project.json to change docker build args.
|
||||
#
|
||||
#<% } else { %>
|
||||
# Build the docker image with `npx nx docker:build <%= project %>`.
|
||||
# Tip: Modify "docker:build" options in project.json to change docker build args.
|
||||
#<% } %>
|
||||
# Run the container with `docker run -p 3000:3000 -t <%= sanitizedProjectName %>`.
|
||||
FROM docker.io/node:lts-alpine
|
||||
|
||||
@@ -15,7 +18,7 @@ RUN addgroup --system <%= sanitizedProjectName %> && \
|
||||
adduser --system -G <%= sanitizedProjectName %> <%= sanitizedProjectName %>
|
||||
|
||||
COPY <%= buildLocation %> <%= sanitizedProjectName %>/
|
||||
COPY <%= projectPath %>/package.json <%= sanitizedProjectName %>/
|
||||
<% if (skipDockerPlugin) { %>COPY <%= projectPath %>/package.json <%= sanitizedProjectName %>/<% } else { %>COPY package.json <%= sanitizedProjectName %>/<% } %>
|
||||
RUN chown -R <%= sanitizedProjectName %>:<%= sanitizedProjectName %> .
|
||||
|
||||
# You can remove this install step if you build with `--bundle` option.
|
||||
|
||||
@@ -4,4 +4,5 @@ export interface SetUpDockerOptions {
|
||||
buildTarget?: string;
|
||||
skipFormat?: boolean;
|
||||
outputPath: string;
|
||||
skipDockerPlugin?: boolean;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"outputPath": {
|
||||
"description": "The output path for the node application",
|
||||
"type": "string"
|
||||
},
|
||||
"skipDockerPlugin": {
|
||||
"type": "boolean",
|
||||
"description": "Skip the @nx/docker plugin and use the legacy docker build target instead.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,31 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { applicationGenerator } from '../application/application';
|
||||
import { setupDockerGenerator } from './setup-docker';
|
||||
|
||||
// Mock the @nx/docker package
|
||||
jest.mock('@nx/devkit', () => {
|
||||
const actualDevkit = jest.requireActual('@nx/devkit');
|
||||
return {
|
||||
...actualDevkit,
|
||||
ensurePackage: jest.fn((pkg, version) => {
|
||||
if (pkg === '@nx/docker') {
|
||||
return {
|
||||
initGenerator: jest.fn(async (tree, options) => {
|
||||
// Mock the @nx/docker init generator
|
||||
const nxJson = actualDevkit.readNxJson(tree);
|
||||
if (!nxJson.plugins) {
|
||||
nxJson.plugins = [];
|
||||
}
|
||||
nxJson.plugins.push('@nx/docker');
|
||||
actualDevkit.updateNxJson(tree, nxJson);
|
||||
return () => {};
|
||||
}),
|
||||
};
|
||||
}
|
||||
return actualDevkit.ensurePackage(pkg, version);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('setupDockerGenerator', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(async () => {
|
||||
@@ -20,6 +45,7 @@ describe('setupDockerGenerator', () => {
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: true, // Use legacy mode for this test
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
@@ -48,6 +74,7 @@ describe('setupDockerGenerator', () => {
|
||||
directory: '.',
|
||||
framework: 'fastify',
|
||||
docker: true,
|
||||
skipDockerPlugin: true, // Use legacy mode for this test
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
@@ -67,6 +94,106 @@ describe('setupDockerGenerator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('skipDockerPlugin', () => {
|
||||
it('should create docker-build target when skipDockerPlugin is true', async () => {
|
||||
const projectName = 'api-with-legacy-docker';
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: projectName,
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: true,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, projectName);
|
||||
const dockerFile = tree.read(`${projectName}/Dockerfile`, 'utf8');
|
||||
|
||||
expect(tree.exists(`${projectName}/Dockerfile`)).toBeTruthy();
|
||||
expect(dockerFile).toContain(`COPY dist/${projectName} ${projectName}/`);
|
||||
expect(dockerFile).toContain(
|
||||
`COPY ${projectName}/package.json ${projectName}/`
|
||||
);
|
||||
expect(dockerFile).toContain(
|
||||
'Build the docker image with `npx nx docker-build'
|
||||
);
|
||||
expect(project.targets).toEqual(
|
||||
expect.objectContaining({
|
||||
'docker-build': {
|
||||
dependsOn: ['build'],
|
||||
command: `docker build -f ${projectName}/Dockerfile . -t ${projectName}`,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not create docker-build target when skipDockerPlugin is false', async () => {
|
||||
const projectName = 'api-with-plugin-docker';
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: projectName,
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: false,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, projectName);
|
||||
const dockerFile = tree.read(`${projectName}/Dockerfile`, 'utf8');
|
||||
|
||||
expect(tree.exists(`${projectName}/Dockerfile`)).toBeTruthy();
|
||||
expect(dockerFile).toContain(`COPY dist ${projectName}/`);
|
||||
expect(dockerFile).toContain(`COPY package.json ${projectName}/`);
|
||||
expect(dockerFile).toContain(
|
||||
'Build the docker image with `npx nx docker:build'
|
||||
);
|
||||
expect(project.targets?.['docker-build']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use project-relative paths when skipDockerPlugin is false', async () => {
|
||||
const projectName = 'nested-api';
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: `apps/${projectName}`,
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: false,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
const dockerFile = tree.read(`apps/${projectName}/Dockerfile`, 'utf8');
|
||||
|
||||
expect(dockerFile).toContain(`COPY dist nested-api/`);
|
||||
expect(dockerFile).toContain(`COPY package.json nested-api/`);
|
||||
expect(dockerFile).not.toContain(`apps/${projectName}`);
|
||||
});
|
||||
|
||||
it('should use workspace-relative paths when skipDockerPlugin is true', async () => {
|
||||
const projectName = 'nested-api-legacy';
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
directory: `apps/${projectName}`,
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: true,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
const dockerFile = tree.read(`apps/${projectName}/Dockerfile`, 'utf8');
|
||||
|
||||
expect(dockerFile).toContain(
|
||||
`COPY dist/apps/${projectName} nested-api-legacy/`
|
||||
);
|
||||
expect(dockerFile).toContain(
|
||||
`COPY apps/${projectName}/package.json nested-api-legacy/`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('project name sanitization', () => {
|
||||
it('should sanitize project names with special characters for Docker commands', async () => {
|
||||
const projectName = '@myorg/my-app';
|
||||
@@ -82,6 +209,7 @@ describe('setupDockerGenerator', () => {
|
||||
await setupDockerGenerator(tree, {
|
||||
project: projectName,
|
||||
outputPath: 'dist/myorg/my-app',
|
||||
skipDockerPlugin: true,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, projectName);
|
||||
@@ -135,6 +263,7 @@ describe('setupDockerGenerator', () => {
|
||||
await setupDockerGenerator(tree, {
|
||||
project: projectName,
|
||||
outputPath: 'dist/basic-app',
|
||||
skipDockerPlugin: true,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, projectName);
|
||||
@@ -183,6 +312,7 @@ describe('setupDockerGenerator', () => {
|
||||
framework: 'express',
|
||||
e2eTestRunner: 'none',
|
||||
docker: true,
|
||||
skipDockerPlugin: true, // Use legacy mode for this test
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
@@ -237,6 +367,7 @@ describe('setupDockerGenerator', () => {
|
||||
await setupDockerGenerator(tree, {
|
||||
project: projectName,
|
||||
outputPath: 'dist/scope/my-app',
|
||||
skipDockerPlugin: true,
|
||||
});
|
||||
|
||||
const dockerfileContent = tree.read('Dockerfile', 'utf8');
|
||||
|
||||
@@ -22,6 +22,7 @@ function normalizeOptions(
|
||||
project: setupOptions.project ?? readNxJson(tree).defaultProject,
|
||||
targetName: setupOptions.targetName ?? 'docker-build',
|
||||
buildTarget: setupOptions.buildTarget ?? 'build',
|
||||
skipDockerPlugin: setupOptions.skipDockerPlugin ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,17 +52,40 @@ function addDocker(tree: Tree, options: SetUpDockerOptions) {
|
||||
}
|
||||
|
||||
const sanitizedProjectName = sanitizeProjectName(options.project);
|
||||
const finalOutputPath = options.outputPath ?? outputPath;
|
||||
|
||||
// Calculate build location based on skipDockerPlugin flag
|
||||
let buildLocation: string;
|
||||
if (options.skipDockerPlugin) {
|
||||
// Legacy mode: use workspace-relative paths
|
||||
buildLocation = finalOutputPath;
|
||||
} else {
|
||||
// New mode: use project-relative paths
|
||||
// Remove the project root prefix from the output path
|
||||
const projectRootWithSlash = projectConfig.root + '/';
|
||||
buildLocation = finalOutputPath.startsWith(projectRootWithSlash)
|
||||
? finalOutputPath.substring(projectRootWithSlash.length)
|
||||
: finalOutputPath.startsWith(projectConfig.root)
|
||||
? finalOutputPath.substring(projectConfig.root.length)
|
||||
: 'dist';
|
||||
}
|
||||
|
||||
generateFiles(tree, join(__dirname, './files'), projectConfig.root, {
|
||||
tmpl: '',
|
||||
buildLocation: options.outputPath ?? outputPath,
|
||||
buildLocation,
|
||||
project: options.project,
|
||||
projectPath: projectConfig.root,
|
||||
sanitizedProjectName,
|
||||
skipDockerPlugin: options.skipDockerPlugin,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProjectConfig(tree: Tree, options: SetUpDockerOptions) {
|
||||
// Only create custom docker-build target if skipDockerPlugin is true
|
||||
if (!options.skipDockerPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
let projectConfig = readProjectConfiguration(tree, options.project);
|
||||
|
||||
// Use sanitized project name for Docker image tag
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"include": [],
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../docker"
|
||||
},
|
||||
{
|
||||
"path": "../eslint"
|
||||
},
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
],
|
||||
"include": ["**/*.ts", "**/*.json"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../docker/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../eslint/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
Generated
+3
@@ -2820,6 +2820,9 @@ importers:
|
||||
'@nx/devkit':
|
||||
specifier: workspace:*
|
||||
version: link:../devkit
|
||||
'@nx/docker':
|
||||
specifier: workspace:*
|
||||
version: link:../docker
|
||||
'@nx/eslint':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint
|
||||
|
||||
Reference in New Issue
Block a user