From e275534a84ec14652b47ca8942a5cec55248282a Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Mon, 3 Jul 2023 14:20:49 -0300 Subject: [PATCH] feat(nx-python): automatically activate shared virtual environment (#138) * feat(nx-python): automatically activate shared virtual environment Changes all the `@nxlv/python` executors to first verify if the shared virtual environment is available in the workspace, if yes, activate before running the commands, also adds a new `@nxlv/python:run-commands` executors to wrap the `nx:run-commands` and activate the virtual environment before running the commands, if you are using `@nxlv/python` version 16> you can use this following command to migrate all your `nx:run-commands` to `@nxlv/python:run-commands`: `npx nx migrate @nxlv/python` re #132 --- packages/nx-python/.eslintrc.json | 7 +- packages/nx-python/README.md | 28 +++- packages/nx-python/executors.json | 5 + packages/nx-python/migrations.json | 10 ++ packages/nx-python/package.json | 3 + packages/nx-python/project.json | 8 +- .../src/executors/add/executor.spec.ts | 27 +++- .../nx-python/src/executors/add/executor.ts | 2 + .../src/executors/build/executor.spec.ts | 33 ++++- .../nx-python/src/executors/build/executor.ts | 7 +- .../src/executors/flake8/executor.spec.ts | 19 ++- .../src/executors/flake8/executor.ts | 7 +- .../src/executors/install/executor.spec.ts | 10 +- .../src/executors/remove/executor.spec.ts | 19 ++- .../src/executors/remove/executor.ts | 2 + .../executors/run-commands/executor.spec.ts | 38 +++++ .../src/executors/run-commands/executor.ts | 13 ++ .../src/executors/run-commands/schema.json | 140 ++++++++++++++++++ .../src/executors/sls-deploy/executor.spec.ts | 14 ++ .../src/executors/sls-deploy/executor.ts | 2 + .../executors/sls-package/executor.spec.ts | 13 ++ .../src/executors/sls-package/executor.ts | 2 + .../src/executors/tox/executor.spec.ts | 18 ++- .../nx-python/src/executors/tox/executor.ts | 7 +- .../src/executors/update/executor.spec.ts | 23 ++- .../src/executors/update/executor.ts | 2 + .../src/executors/utils/poetry.spec.ts | 109 ++++++++++++-- .../nx-python/src/executors/utils/poetry.ts | 24 ++- .../__snapshots__/generator.spec.ts.snap | 41 +++++ .../files/pyproject.toml | 5 + .../migrate-to-shared-venv/generator.spec.ts | 31 ++++ .../migrate-to-shared-venv/schema.d.ts | 1 + .../migrate-to-shared-venv/schema.json | 5 + .../__snapshots__/generator.spec.ts.snap | 56 +++---- .../generators/poetry-project/generator.ts | 4 +- .../__snapshots__/generator.spec.ts.snap | 42 +++--- .../src/generators/project/generator.ts | 6 +- .../nx-python/src/graph/dependency-graph.ts | 3 + .../replace-nx-run-commands.spec.ts | 60 ++++++++ .../update-16-1-0/replace-nx-run-commands.ts | 27 ++++ 40 files changed, 763 insertions(+), 110 deletions(-) create mode 100644 packages/nx-python/migrations.json create mode 100644 packages/nx-python/src/executors/run-commands/executor.spec.ts create mode 100644 packages/nx-python/src/executors/run-commands/executor.ts create mode 100644 packages/nx-python/src/executors/run-commands/schema.json create mode 100644 packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.spec.ts create mode 100644 packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.ts diff --git a/packages/nx-python/.eslintrc.json b/packages/nx-python/.eslintrc.json index 4cf7f34..2d016be 100644 --- a/packages/nx-python/.eslintrc.json +++ b/packages/nx-python/.eslintrc.json @@ -15,7 +15,12 @@ "rules": {} }, { - "files": ["./package.json", "./generators.json", "./executors.json"], + "files": [ + "./package.json", + "./generators.json", + "./executors.json", + "./migrations.json" + ], "parser": "jsonc-eslint-parser", "rules": { "@nx/nx-plugin-checks": "error" diff --git a/packages/nx-python/README.md b/packages/nx-python/README.md index 42883b5..fabb2ea 100644 --- a/packages/nx-python/README.md +++ b/packages/nx-python/README.md @@ -84,9 +84,10 @@ npx nx generate @nxlv/python:migrate-to-shared-venv **Options**: -| Option | Type | Description | Required | Default | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------ | -------- | ------- | -| `--moveDevDependencies` | `boolean` | Specifies if migration moves the dev dependencies from the projects to the root `pyproject.toml` | `true` | `true` | +| Option | Type | Description | Required | Default | +| ----------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | +| `--moveDevDependencies` | `boolean` | Specifies if migration moves the dev dependencies from the projects to the root `pyproject.toml` | `true` | `true` | +| `--autoActivate` | `boolean` | Adds the `autoActivate` config in the root `pyproject.toml`, this flag is used to auto-activate the venv when the `@nxlv/python` executors are called | `true` | `true` | After the migration is completed, the workspace does not have the `pyproject.toml` in the root directory, and all the local projects are referencing the root `pyproject.toml` file. @@ -495,3 +496,24 @@ The `@nxlv/python:install` handles the `poetry install` command for a project. | `--cacheDir` | `string` | Custom poetry install cache directory | `false` | | | `--verbose` | `boolean` | Use verbose mode in the install `poetry install -vv` | `false` | `false` | | `--debug` | `boolean` | Use debug mode in the install `poetry install -vvv` | `false` | `false` | + +#### run-commands (same as `nx:run-commands`) + +The `@nxlv/python:run-commands` wraps the `nx:run-commands` default Nx executor and if the `autoActivate` option is set to `true` in the root `pyproject.toml` file, it will verify the the virtual environment is not activated, if no, it will activate the virtual environment before running the commands. + +> NOTE: This executor only changes the default `nx:run-commands` if the workspace is configured to use the Shared virtual environment mode and the `autoActivate` option is set to `true` in the root `pyproject.toml` file. +> NOTE: The `autoActivate` option is set to `false` by default. + +root `pyproject.toml` + +```toml +... +[tool.nx] +autoActivate = true + +... +``` + +The options and behavior are the same as the `nx:run-commands` executor. + +[See the Nx documentation for more information](https://nx.dev/packages/nx/executors/run-commands) diff --git a/packages/nx-python/executors.json b/packages/nx-python/executors.json index fff73c2..0c1cc4f 100644 --- a/packages/nx-python/executors.json +++ b/packages/nx-python/executors.json @@ -45,6 +45,11 @@ "implementation": "./src/executors/sls-package/executor", "schema": "./src/executors/sls-package/schema.json", "description": "Serverless Package Wrapper Executor" + }, + "run-commands": { + "implementation": "./src/executors/run-commands/executor", + "schema": "./src/executors/run-commands/schema.json", + "description": "Python Venv Run Commands Executor" } } } diff --git a/packages/nx-python/migrations.json b/packages/nx-python/migrations.json new file mode 100644 index 0000000..04d8048 --- /dev/null +++ b/packages/nx-python/migrations.json @@ -0,0 +1,10 @@ +{ + "generators": { + "16-1-0-replace-nx-run-commands": { + "version": "16.1.0", + "description": "Migrate all nx:run-commands to @nxlv/python:run-commands", + "cli": "nx", + "implementation": "./src/migrations/update-16-1-0/replace-nx-run-commands" + } + } +} diff --git a/packages/nx-python/package.json b/packages/nx-python/package.json index b4f4759..af00563 100644 --- a/packages/nx-python/package.json +++ b/packages/nx-python/package.json @@ -13,5 +13,8 @@ }, "peerDependencies": { "@nx/devkit": "^16.0.0" + }, + "nx-migrations": { + "migrations": "./migrations.json" } } diff --git a/packages/nx-python/project.json b/packages/nx-python/project.json index 760ac1f..3bda6af 100644 --- a/packages/nx-python/project.json +++ b/packages/nx-python/project.json @@ -32,6 +32,11 @@ "input": "./packages/nx-python", "glob": "executors.json", "output": "." + }, + { + "input": "./packages/nx-python", + "glob": "migrations.json", + "output": "." } ] } @@ -44,7 +49,8 @@ "packages/nx-python/**/*.ts", "packages/nx-python/generators.json", "packages/nx-python/executors.json", - "packages/nx-python/package.json" + "packages/nx-python/package.json", + "packages/nx-python/migrations.json" ] } }, diff --git a/packages/nx-python/src/executors/add/executor.spec.ts b/packages/nx-python/src/executors/add/executor.spec.ts index 88cca94..670543b 100644 --- a/packages/nx-python/src/executors/add/executor.spec.ts +++ b/packages/nx-python/src/executors/add/executor.spec.ts @@ -8,17 +8,19 @@ import dedent from 'string-dedent'; describe('Add Executor', () => { let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; beforeAll(() => { console.log(chalk`init chalk`); }); beforeEach(() => { - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -54,6 +56,7 @@ describe('Add Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -100,6 +103,7 @@ describe('Add Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['add', 'numpy'], { cwd: 'apps/app', shell: false, @@ -151,6 +155,7 @@ describe('Add Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'poetry', ['add', 'numpy', '--group', 'dev'], @@ -206,6 +211,7 @@ describe('Add Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'poetry', ['add', 'numpy', '--extras=dev'], @@ -256,6 +262,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -302,6 +309,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['add', 'numpy'], { cwd: 'apps/app', shell: false, @@ -402,6 +410,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(4); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -538,6 +547,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(4); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -632,6 +642,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -697,6 +708,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -766,6 +778,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -836,6 +849,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -901,6 +915,7 @@ version = "1.0.0" const output = await executor(options, context); expect(output.success).toBe(true); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -964,6 +979,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'poetry', ['add', 'numpy', '--group', 'dev'], @@ -1024,6 +1040,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, 'poetry', diff --git a/packages/nx-python/src/executors/add/executor.ts b/packages/nx-python/src/executors/add/executor.ts index fe6d78e..d099b1f 100644 --- a/packages/nx-python/src/executors/add/executor.ts +++ b/packages/nx-python/src/executors/add/executor.ts @@ -5,6 +5,7 @@ import { updateDependencyTree } from '../../dependency/update-dependency'; import { existsSync } from 'fs-extra'; import path from 'path'; import { + activateVenv, addLocalProjectToPoetryProject, checkPoetryExecutable, getLocalDependencyConfig, @@ -19,6 +20,7 @@ export default async function executor( const workspaceRoot = context.root; process.chdir(workspaceRoot); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); const projectConfig = context.workspace.projects[context.projectName]; const rootPyprojectToml = existsSync('pyproject.toml'); diff --git a/packages/nx-python/src/executors/build/executor.spec.ts b/packages/nx-python/src/executors/build/executor.spec.ts index 2247201..f41e051 100644 --- a/packages/nx-python/src/executors/build/executor.spec.ts +++ b/packages/nx-python/src/executors/build/executor.spec.ts @@ -15,6 +15,7 @@ import dedent from 'string-dedent'; describe('Build Executor', () => { let buildPath = null; let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; beforeAll(() => { console.log(chalk`init chalk`); @@ -23,11 +24,15 @@ describe('Build Executor', () => { beforeEach(() => { uuidMock.mockReturnValue('abc'); buildPath = join(tmpdir(), 'nx-python', 'build', 'abc'); - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); + spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -68,6 +73,7 @@ describe('Build Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -104,6 +110,7 @@ describe('Build Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -248,6 +255,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(existsSync(`${buildPath}/dep1`)).toBeTruthy(); @@ -364,6 +372,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['build'], { @@ -475,6 +484,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['build'], { @@ -585,6 +595,7 @@ describe('Build Executor', () => { expect(output.success).toBe(true); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['build'], { @@ -684,6 +695,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(false); }); @@ -825,6 +837,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(existsSync(`${buildPath}/dep1`)).toBeTruthy(); @@ -995,6 +1008,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(true); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); @@ -1130,6 +1144,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); expect(existsSync(`${buildPath}/dist/app.fake`)).toBeTruthy(); @@ -1224,6 +1239,7 @@ describe('Build Executor', () => { }, }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(false); }); @@ -1293,6 +1309,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(existsSync(buildPath)).not.toBeTruthy(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['build'], { cwd: buildPath, @@ -1357,6 +1374,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(false); }); }); @@ -1433,6 +1451,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(false); expect(existsSync(buildPath)).toBeTruthy(); }); @@ -1511,6 +1530,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(true); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); @@ -1623,6 +1643,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(true); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); @@ -1767,6 +1788,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(true); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); @@ -2012,6 +2034,7 @@ describe('Build Executor', () => { }); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(output.success).toBe(true); expect(existsSync(buildPath)).toBeTruthy(); expect(existsSync(`${buildPath}/app`)).toBeTruthy(); diff --git a/packages/nx-python/src/executors/build/executor.ts b/packages/nx-python/src/executors/build/executor.ts index a11af1d..efe274c 100644 --- a/packages/nx-python/src/executors/build/executor.ts +++ b/packages/nx-python/src/executors/build/executor.ts @@ -18,7 +18,11 @@ import { tmpdir } from 'os'; import { v4 as uuid } from 'uuid'; import chalk from 'chalk'; import { Logger } from '../utils/logger'; -import { checkPoetryExecutable, runPoetry } from '../utils/poetry'; +import { + activateVenv, + checkPoetryExecutable, + runPoetry, +} from '../utils/poetry'; import { LockedDependencyResolver, ProjectDependencyResolver, @@ -35,6 +39,7 @@ export default async function executor( const workspaceRoot = context.root; process.chdir(workspaceRoot); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); if ( options.lockedVersions === true && diff --git a/packages/nx-python/src/executors/flake8/executor.spec.ts b/packages/nx-python/src/executors/flake8/executor.spec.ts index 0626ad0..6d9821a 100644 --- a/packages/nx-python/src/executors/flake8/executor.spec.ts +++ b/packages/nx-python/src/executors/flake8/executor.spec.ts @@ -11,14 +11,18 @@ import { mkdirsSync, writeFileSync } from 'fs-extra'; describe('Flake8 Executor', () => { let tmppath = null; let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; beforeEach(() => { tmppath = join(tmpdir(), 'nx-python', 'flake8', uuid()); - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); + spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -58,6 +62,7 @@ describe('Flake8 Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -91,6 +96,7 @@ describe('Flake8 Executor', () => { } ); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(output.success).toBe(true); }); @@ -125,6 +131,7 @@ describe('Flake8 Executor', () => { } ); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(output.success).toBe(true); }); @@ -157,6 +164,7 @@ describe('Flake8 Executor', () => { } ); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(output.success).toBe(false); }); @@ -193,6 +201,7 @@ describe('Flake8 Executor', () => { } ); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(output.success).toBe(false); }); diff --git a/packages/nx-python/src/executors/flake8/executor.ts b/packages/nx-python/src/executors/flake8/executor.ts index edc24df..0b4c033 100644 --- a/packages/nx-python/src/executors/flake8/executor.ts +++ b/packages/nx-python/src/executors/flake8/executor.ts @@ -4,7 +4,11 @@ import { Logger } from '../utils/logger'; import { Flake8ExecutorSchema } from './schema'; import path from 'path'; import { mkdirsSync, existsSync, readFileSync, rmSync } from 'fs-extra'; -import { checkPoetryExecutable, runPoetry } from '../utils/poetry'; +import { + activateVenv, + checkPoetryExecutable, + runPoetry, +} from '../utils/poetry'; const logger = new Logger(); @@ -16,6 +20,7 @@ export default async function executor( const workspaceRoot = context.root; process.chdir(workspaceRoot); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); logger.info( chalk`\n {bold Running flake8 linting on project {bgBlue ${context.projectName} }...}\n` diff --git a/packages/nx-python/src/executors/install/executor.spec.ts b/packages/nx-python/src/executors/install/executor.spec.ts index 3ba5bd4..a53b94a 100644 --- a/packages/nx-python/src/executors/install/executor.spec.ts +++ b/packages/nx-python/src/executors/install/executor.spec.ts @@ -5,6 +5,7 @@ import path from 'path'; describe('Install Executor', () => { let checkPoetryExecutableMock: jest.SpyInstance; + const context = { cwd: '', root: '.', @@ -23,11 +24,10 @@ describe('Install Executor', () => { }; beforeEach(() => { - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + spawnSyncMock.mockReturnValue({ status: 0 }); }); diff --git a/packages/nx-python/src/executors/remove/executor.spec.ts b/packages/nx-python/src/executors/remove/executor.spec.ts index 38e6093..b87c71b 100644 --- a/packages/nx-python/src/executors/remove/executor.spec.ts +++ b/packages/nx-python/src/executors/remove/executor.spec.ts @@ -7,6 +7,7 @@ import dedent from 'string-dedent'; describe('Delete Executor', () => { let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; let getPoetryVersionMock: jest.SpyInstance; beforeAll(() => { @@ -14,14 +15,15 @@ describe('Delete Executor', () => { }); beforeEach(() => { - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); getPoetryVersionMock = jest .spyOn(poetryUtils, 'getPoetryVersion') .mockResolvedValue('1.5.0'); - checkPoetryExecutableMock.mockResolvedValue(undefined); + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -57,6 +59,7 @@ describe('Delete Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -147,6 +150,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(3); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -269,6 +273,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(4); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -351,6 +356,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -406,6 +412,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -468,6 +475,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, 'poetry', @@ -540,6 +548,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, 'poetry', diff --git a/packages/nx-python/src/executors/remove/executor.ts b/packages/nx-python/src/executors/remove/executor.ts index d9a27c2..bf7f18a 100644 --- a/packages/nx-python/src/executors/remove/executor.ts +++ b/packages/nx-python/src/executors/remove/executor.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { existsSync } from 'fs-extra'; import { updateDependencyTree } from '../../dependency/update-dependency'; import { + activateVenv, checkPoetryExecutable, getLocalDependencyConfig, getPoetryVersion, @@ -19,6 +20,7 @@ export default async function executor( const workspaceRoot = context.root; process.chdir(workspaceRoot); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); const rootPyprojectToml = existsSync('pyproject.toml'); const projectConfig = context.workspace.projects[context.projectName]; diff --git a/packages/nx-python/src/executors/run-commands/executor.spec.ts b/packages/nx-python/src/executors/run-commands/executor.spec.ts new file mode 100644 index 0000000..84b9793 --- /dev/null +++ b/packages/nx-python/src/executors/run-commands/executor.spec.ts @@ -0,0 +1,38 @@ +jest.mock('nx/src/executors/run-commands/run-commands.impl'); +jest.mock('../utils/poetry'); + +import executor from './executor'; + +describe('run commands executor', () => { + const context = { + cwd: '', + root: '.', + isVerbose: false, + projectName: 'app', + workspace: { + npmScope: 'nxlv', + version: 2, + projects: { + app: { + root: 'apps/app', + targets: {}, + }, + }, + }, + }; + + it('should activate the venv and call the base executor', async () => { + const options = { + command: 'test', + __unparsed__: [], + }; + await executor(options, context); + + expect((await import('../utils/poetry')).activateVenv).toHaveBeenCalledWith( + context.root + ); + expect( + (await import('nx/src/executors/run-commands/run-commands.impl')).default + ).toHaveBeenCalledWith(options, context); + }); +}); diff --git a/packages/nx-python/src/executors/run-commands/executor.ts b/packages/nx-python/src/executors/run-commands/executor.ts new file mode 100644 index 0000000..d108088 --- /dev/null +++ b/packages/nx-python/src/executors/run-commands/executor.ts @@ -0,0 +1,13 @@ +import { ExecutorContext } from '@nx/devkit'; +import baseExecutor, { + RunCommandsOptions, +} from 'nx/src/executors/run-commands/run-commands.impl'; +import { activateVenv } from '../utils/poetry'; + +export default async function executor( + options: RunCommandsOptions, + context: ExecutorContext +) { + activateVenv(context.root); + return baseExecutor(options, context); +} diff --git a/packages/nx-python/src/executors/run-commands/schema.json b/packages/nx-python/src/executors/run-commands/schema.json new file mode 100644 index 0000000..11a3089 --- /dev/null +++ b/packages/nx-python/src/executors/run-commands/schema.json @@ -0,0 +1,140 @@ +{ + "version": 2, + "title": "Run Commands", + "description": "Run any custom commands with Nx.", + "type": "object", + "cli": "nx", + "outputCapture": "pipe", + "presets": [ + { + "name": "Arguments forwarding", + "keys": ["commands"] + }, + { + "name": "Custom done conditions", + "keys": ["commands", "readyWhen"] + }, + { + "name": "Setting the cwd", + "keys": ["commands", "cwd"] + } + ], + "properties": { + "commands": { + "type": "array", + "description": "Commands to run in child process.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to run in child process." + }, + "forwardAllArgs": { + "type": "boolean", + "description": "Whether arguments should be forwarded when interpolation is not present." + }, + "prefix": { + "type": "string", + "description": "Prefix in front of every line out of the output" + }, + "color": { + "type": "string", + "description": "Color of the output", + "enum": [ + "black", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "white" + ] + }, + "bgColor": { + "type": "string", + "description": "Background color of the output", + "enum": [ + "bgBlack", + "bgRed", + "bgGreen", + "bgYellow", + "bgBlue", + "bgMagenta", + "bgCyan", + "bgWhite" + ] + }, + "description": { + "type": "string", + "description": "An optional description useful for inline documentation purposes. It is not used as part of the execution of the command." + } + }, + "additionalProperties": false, + "required": ["command"] + }, + { + "type": "string" + } + ] + }, + "x-priority": "important" + }, + "command": { + "type": "string", + "description": "Command to run in child process.", + "x-priority": "important" + }, + "parallel": { + "type": "boolean", + "description": "Run commands in parallel.", + "default": true, + "x-priority": "important" + }, + "readyWhen": { + "type": "string", + "description": "String to appear in `stdout` or `stderr` that indicates that the task is done. When running multiple commands, this option can only be used when `parallel` is set to `true`. If not specified, the task is done when all the child processes complete." + }, + "args": { + "type": "string", + "description": "Extra arguments. You can pass them as follows: nx run project:target --args='--wait=100'. You can then use {args.wait} syntax to interpolate them in the workspace config file. See example [above](#chaining-commands-interpolating-args-and-setting-the-cwd)" + }, + "envFile": { + "type": "string", + "description": "You may specify a custom .env file path." + }, + "color": { + "type": "boolean", + "description": "Use colors when showing output of command.", + "default": false + }, + "cwd": { + "type": "string", + "description": "Current working directory of the commands. If it's not specified the commands will run in the workspace root, if a relative path is specified the commands will run in that path relative to the workspace root and if it's an absolute path the commands will run in that path." + }, + "__unparsed__": { + "hidden": true, + "type": "array", + "items": { + "type": "string" + }, + "$default": { + "$source": "unparsed" + }, + "x-priority": "internal" + } + }, + "additionalProperties": true, + "oneOf": [ + { + "required": ["commands"] + }, + { + "required": ["command"] + } + ], + "examplesFile": "../../../docs/run-commands-examples.md" +} diff --git a/packages/nx-python/src/executors/sls-deploy/executor.spec.ts b/packages/nx-python/src/executors/sls-deploy/executor.spec.ts index 4b1e4e1..d188704 100644 --- a/packages/nx-python/src/executors/sls-deploy/executor.spec.ts +++ b/packages/nx-python/src/executors/sls-deploy/executor.spec.ts @@ -1,9 +1,12 @@ import chalk from 'chalk'; +import * as poetryUtils from '../utils/poetry'; import { spawnSyncMock } from '../../utils/mocks/cross-spawn.mock'; import executor from './executor'; import fsMock from 'mock-fs'; describe('Serverless Framework Deploy Executor', () => { + let activateVenvMock: jest.SpyInstance; + const context = { cwd: '', root: '.', @@ -25,6 +28,12 @@ describe('Serverless Framework Deploy Executor', () => { console.log(chalk`init chalk`); }); + beforeEach(() => { + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); + }); + afterEach(() => { fsMock.restore(); jest.resetAllMocks(); @@ -39,6 +48,7 @@ describe('Serverless Framework Deploy Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -56,6 +66,7 @@ describe('Serverless Framework Deploy Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -76,6 +87,7 @@ describe('Serverless Framework Deploy Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'npx', ['sls', 'deploy', '--stage', 'dev'], @@ -104,6 +116,7 @@ describe('Serverless Framework Deploy Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'npx', ['sls', 'deploy', '--stage', 'dev'], @@ -132,6 +145,7 @@ describe('Serverless Framework Deploy Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'npx', ['sls', 'deploy', '--stage', 'dev', '--verbose', '--force'], diff --git a/packages/nx-python/src/executors/sls-deploy/executor.ts b/packages/nx-python/src/executors/sls-deploy/executor.ts index 29a3259..d2f8be1 100644 --- a/packages/nx-python/src/executors/sls-deploy/executor.ts +++ b/packages/nx-python/src/executors/sls-deploy/executor.ts @@ -5,6 +5,7 @@ import { Logger } from '../utils/logger'; import { ExecutorSchema } from './schema'; import path from 'path'; import { existsSync, readdirSync, writeFileSync, removeSync } from 'fs-extra'; +import { activateVenv } from '../utils/poetry'; const logger = new Logger(); @@ -14,6 +15,7 @@ export default async function executor( ) { const workspaceRoot = context.root; process.chdir(workspaceRoot); + activateVenv(workspaceRoot); const projectConfig = context.workspace.projects[context.projectName]; const cwd = projectConfig.root; diff --git a/packages/nx-python/src/executors/sls-package/executor.spec.ts b/packages/nx-python/src/executors/sls-package/executor.spec.ts index 6105b42..692d799 100644 --- a/packages/nx-python/src/executors/sls-package/executor.spec.ts +++ b/packages/nx-python/src/executors/sls-package/executor.spec.ts @@ -1,9 +1,12 @@ import chalk from 'chalk'; +import * as poetryUtils from '../utils/poetry'; import { spawnSyncMock } from '../../utils/mocks/cross-spawn.mock'; import executor from './executor'; import fsMock from 'mock-fs'; describe('Serverless Framework Package Executor', () => { + let activateVenvMock: jest.SpyInstance; + const context = { cwd: '', root: '.', @@ -21,6 +24,12 @@ describe('Serverless Framework Package Executor', () => { }, }; + beforeEach(() => { + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); + }); + beforeAll(() => { console.log(chalk`init chalk`); }); @@ -37,6 +46,7 @@ describe('Serverless Framework Package Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -52,6 +62,7 @@ describe('Serverless Framework Package Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -70,6 +81,7 @@ describe('Serverless Framework Package Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'npx', ['sls', 'package', '--stage', 'dev'], @@ -96,6 +108,7 @@ describe('Serverless Framework Package Executor', () => { }, context ); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'npx', ['sls', 'package', '--stage', 'dev'], diff --git a/packages/nx-python/src/executors/sls-package/executor.ts b/packages/nx-python/src/executors/sls-package/executor.ts index cf4af45..6e251ca 100644 --- a/packages/nx-python/src/executors/sls-package/executor.ts +++ b/packages/nx-python/src/executors/sls-package/executor.ts @@ -5,6 +5,7 @@ import { Logger } from '../utils/logger'; import { ExecutorSchema } from './schema'; import path from 'path'; import { existsSync, readdirSync, writeFileSync, removeSync } from 'fs-extra'; +import { activateVenv } from '../utils/poetry'; const logger = new Logger(); @@ -14,6 +15,7 @@ export default async function executor( ) { const workspaceRoot = context.root; process.chdir(workspaceRoot); + activateVenv(workspaceRoot); const projectConfig = context.workspace.projects[context.projectName]; const cwd = projectConfig.root; diff --git a/packages/nx-python/src/executors/tox/executor.spec.ts b/packages/nx-python/src/executors/tox/executor.spec.ts index a6e36de..6b53955 100644 --- a/packages/nx-python/src/executors/tox/executor.spec.ts +++ b/packages/nx-python/src/executors/tox/executor.spec.ts @@ -18,6 +18,7 @@ const options: ToxExecutorSchema = { describe('Tox Executor', () => { let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; const context = { cwd: '.', @@ -41,11 +42,12 @@ describe('Tox Executor', () => { }); beforeEach(() => { - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -76,6 +78,7 @@ describe('Tox Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).not.toHaveBeenCalled(); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); @@ -92,6 +95,7 @@ describe('Tox Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).toBeCalledWith( { silent: options.silent, @@ -134,6 +138,7 @@ describe('Tox Executor', () => { ); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).toBeCalledWith( { silent: options.silent, @@ -165,6 +170,7 @@ describe('Tox Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).toBeCalledWith( { silent: options.silent, @@ -188,6 +194,7 @@ describe('Tox Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).toBeCalledWith( { silent: options.silent, @@ -215,6 +222,7 @@ describe('Tox Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(buildExecutorMock).toBeCalledWith( { silent: options.silent, diff --git a/packages/nx-python/src/executors/tox/executor.ts b/packages/nx-python/src/executors/tox/executor.ts index 2f73fbe..03004c5 100644 --- a/packages/nx-python/src/executors/tox/executor.ts +++ b/packages/nx-python/src/executors/tox/executor.ts @@ -5,7 +5,11 @@ import path from 'path'; import chalk from 'chalk'; import { Logger } from '../utils/logger'; import { readdirSync, existsSync } from 'fs-extra'; -import { checkPoetryExecutable, runPoetry } from '../utils/poetry'; +import { + activateVenv, + checkPoetryExecutable, + runPoetry, +} from '../utils/poetry'; const logger = new Logger(); @@ -17,6 +21,7 @@ export default async function executor( process.chdir(workspaceRoot); logger.setOptions(options); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); const projectConfig = context.workspace.projects[context.projectName]; const distFolder = path.join(projectConfig.root, 'dist'); diff --git a/packages/nx-python/src/executors/update/executor.spec.ts b/packages/nx-python/src/executors/update/executor.spec.ts index 76bd34a..1c167e4 100644 --- a/packages/nx-python/src/executors/update/executor.spec.ts +++ b/packages/nx-python/src/executors/update/executor.spec.ts @@ -8,17 +8,19 @@ import dedent from 'string-dedent'; describe('Update Executor', () => { let checkPoetryExecutableMock: jest.SpyInstance; + let activateVenvMock: jest.SpyInstance; beforeAll(() => { console.log(chalk`init chalk`); }); beforeEach(() => { - checkPoetryExecutableMock = jest.spyOn( - poetryUtils, - 'checkPoetryExecutable' - ); - checkPoetryExecutableMock.mockResolvedValue(undefined); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + activateVenvMock = jest + .spyOn(poetryUtils, 'activateVenv') + .mockReturnValue(undefined); spawnSyncMock.mockReturnValue({ status: 0 }); }); @@ -54,6 +56,7 @@ describe('Update Executor', () => { const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -96,6 +99,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['update', 'numpy'], { cwd: 'apps/app', shell: false, @@ -142,6 +146,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).not.toHaveBeenCalled(); expect(output.success).toBe(false); }); @@ -188,6 +193,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['update', 'numpy'], { cwd: 'apps/app', shell: false, @@ -285,6 +291,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(4); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -379,6 +386,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -447,6 +455,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(1); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, @@ -511,6 +520,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith( 'poetry', ['update', 'numpy', '--group', 'dev'], @@ -560,6 +570,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['update'], { cwd: 'apps/app', shell: false, @@ -616,6 +627,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, 'poetry', @@ -739,6 +751,7 @@ version = "1.0.0" const output = await executor(options, context); expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(activateVenvMock).toHaveBeenCalledWith('.'); expect(spawnSyncMock).toHaveBeenCalledTimes(5); expect(spawnSyncMock).toHaveBeenNthCalledWith( 1, diff --git a/packages/nx-python/src/executors/update/executor.ts b/packages/nx-python/src/executors/update/executor.ts index 890770e..c9c90bc 100644 --- a/packages/nx-python/src/executors/update/executor.ts +++ b/packages/nx-python/src/executors/update/executor.ts @@ -2,6 +2,7 @@ import { ExecutorContext, ProjectConfiguration } from '@nx/devkit'; import chalk from 'chalk'; import { UpdateExecutorSchema } from './schema'; import { + activateVenv, checkPoetryExecutable, getLocalDependencyConfig, getProjectTomlPath, @@ -20,6 +21,7 @@ export default async function executor( process.chdir(workspaceRoot); try { + activateVenv(workspaceRoot); await checkPoetryExecutable(); const projectConfig = context.workspace.projects[context.projectName]; const rootPyprojectToml = existsSync('pyproject.toml'); diff --git a/packages/nx-python/src/executors/utils/poetry.spec.ts b/packages/nx-python/src/executors/utils/poetry.spec.ts index c1ef98a..76e6fdf 100644 --- a/packages/nx-python/src/executors/utils/poetry.spec.ts +++ b/packages/nx-python/src/executors/utils/poetry.spec.ts @@ -1,4 +1,5 @@ import { spawnSyncMock } from '../../utils/mocks/cross-spawn.mock'; +import fsMock from 'mock-fs'; const commandExistsMock = jest.fn(); jest.mock('command-exists', () => { @@ -8,20 +9,32 @@ jest.mock('command-exists', () => { }; }); -import { checkPoetryExecutable, runPoetry, getPoetryVersion } from './poetry'; +import * as poetryUtils from './poetry'; +import dedent from 'string-dedent'; +import chalk from 'chalk'; +import path from 'path'; describe('Poetry Utils', () => { + beforeAll(() => { + console.log(chalk`init chalk`); + }); + + afterEach(() => { + fsMock.restore(); + jest.resetAllMocks(); + }); + describe('Check Poetry Executable', () => { it('should check if poetry exists', () => { commandExistsMock.mockResolvedValue(undefined); - expect(checkPoetryExecutable()).resolves.toBeUndefined(); + expect(poetryUtils.checkPoetryExecutable()).resolves.toBeUndefined(); }); it('should throw an exeception when poetry is not installed', () => { commandExistsMock.mockRejectedValue(null); - expect(checkPoetryExecutable()).rejects.toThrowError( + expect(poetryUtils.checkPoetryExecutable()).rejects.toThrowError( 'Poetry is not installed. Please install Poetry before running this command.' ); }); @@ -37,7 +50,7 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 0, }); - runPoetry(['install'], { cwd: '.' }); + poetryUtils.runPoetry(['install'], { cwd: '.' }); expect(consoleLogSpy).toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { cwd: '.', @@ -51,7 +64,7 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 0, }); - runPoetry(['install'], { cwd: '/path' }); + poetryUtils.runPoetry(['install'], { cwd: '/path' }); expect(consoleLogSpy).toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { cwd: '/path', @@ -65,7 +78,7 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 0, }); - runPoetry(['install'], { log: false }); + poetryUtils.runPoetry(['install'], { log: false }); expect(consoleLogSpy).not.toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { shell: false, @@ -78,7 +91,7 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 0, }); - runPoetry(['install']); + poetryUtils.runPoetry(['install'], undefined); expect(consoleLogSpy).toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { shell: false, @@ -91,7 +104,7 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 1, }); - runPoetry(['install'], { cwd: '.', error: false }); + poetryUtils.runPoetry(['install'], { cwd: '.', error: false }); expect(consoleLogSpy).toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { cwd: '.', @@ -105,7 +118,9 @@ describe('Poetry Utils', () => { spawnSyncMock.mockReturnValue({ status: 1, }); - expect(() => runPoetry(['install'], { cwd: '.' })).toThrowError(); + expect(() => + poetryUtils.runPoetry(['install'], { cwd: '.' }) + ).toThrowError(); expect(consoleLogSpy).toHaveBeenCalled(); expect(spawnSyncMock).toHaveBeenCalledWith('poetry', ['install'], { cwd: '.', @@ -122,7 +137,7 @@ describe('Poetry Utils', () => { stdout: Buffer.from('Poetry (version 1.5.0)'), }); - const version = await getPoetryVersion(); + const version = await poetryUtils.getPoetryVersion(); expect(version).toEqual('1.5.0'); @@ -131,7 +146,7 @@ describe('Poetry Utils', () => { stdout: Buffer.from('\n\nSomething else\n\nPoetry (version 1.2.2)'), }); - const version2 = await getPoetryVersion(); + const version2 = await poetryUtils.getPoetryVersion(); expect(version2).toEqual('1.2.2'); }); @@ -142,7 +157,77 @@ describe('Poetry Utils', () => { error: true, }); - await expect(getPoetryVersion()).rejects.toThrowError(); + await expect(poetryUtils.getPoetryVersion()).rejects.toThrowError(); + }); + }); + + describe('Activate Venv', () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + it('should not activate venv when it is already activated', () => { + process.env.VIRTUAL_ENV = 'venv'; + + poetryUtils.activateVenv('.'); + + expect(process.env).toStrictEqual({ + ...originalEnv, + VIRTUAL_ENV: 'venv', + }); + }); + + it('should not activate venv when the root pyproject.toml does not exists', () => { + delete process.env.VIRTUAL_ENV; + + poetryUtils.activateVenv('.'); + + expect(process.env).toEqual(originalEnv); + }); + + it('should not activate venv when the root pyproject.toml exists and the autoActivate property is not defined', () => { + delete process.env.VIRTUAL_ENV; + + fsMock({ + 'pyproject.toml': dedent` + [tool.poetry] + name = "app" + version = "1.0.0" + [tool.poetry.dependencies] + python = "^3.8" + `, + }); + + poetryUtils.activateVenv('.'); + + expect(process.env).toEqual(originalEnv); + }); + + it('should activate venv when the root pyproject.toml exists and the autoActivate property is defined', () => { + delete process.env.VIRTUAL_ENV; + + fsMock({ + 'pyproject.toml': dedent` + [tool.nx] + autoActivate = true + + [tool.poetry] + name = "app" + version = "1.0.0" + [tool.poetry.dependencies] + python = "^3.8" + `, + }); + + poetryUtils.activateVenv('.'); + + expect(process.env).toEqual({ + ...originalEnv, + VIRTUAL_ENV: path.resolve('.venv'), + PATH: `${path.resolve('.venv')}/bin:${originalEnv.PATH}`, + }); }); }); }); diff --git a/packages/nx-python/src/executors/utils/poetry.ts b/packages/nx-python/src/executors/utils/poetry.ts index 4182d47..8a2d0ef 100644 --- a/packages/nx-python/src/executors/utils/poetry.ts +++ b/packages/nx-python/src/executors/utils/poetry.ts @@ -2,7 +2,7 @@ import { ExecutorContext, ProjectConfiguration } from '@nx/devkit'; import chalk from 'chalk'; import spawn from 'cross-spawn'; import path from 'path'; -import toml from '@iarna/toml'; +import toml, { parse } from '@iarna/toml'; import fs from 'fs'; import { PyprojectToml } from '../../graph/dependency-graph'; import commandExists from 'command-exists'; @@ -139,3 +139,25 @@ export function runPoetry( ); } } + +export function activateVenv(workspaceRoot: string) { + if (!process.env.VIRTUAL_ENV) { + const rootPyproject = path.join(workspaceRoot, 'pyproject.toml'); + + if (fs.existsSync(rootPyproject)) { + const rootConfig = parse( + fs.readFileSync(rootPyproject, 'utf-8') + ) as PyprojectToml; + const autoActivate = rootConfig.tool.nx?.autoActivate ?? false; + if (autoActivate) { + console.log( + chalk`\n{bold shared virtual environment detected and not activated, activating...}\n\n` + ); + const virtualEnv = path.resolve(workspaceRoot, '.venv'); + process.env.VIRTUAL_ENV = virtualEnv; + process.env.PATH = `${virtualEnv}/bin:${process.env.PATH}`; + delete process.env.PYTHONHOME; + } + } + } +} diff --git a/packages/nx-python/src/generators/migrate-to-shared-venv/__snapshots__/generator.spec.ts.snap b/packages/nx-python/src/generators/migrate-to-shared-venv/__snapshots__/generator.spec.ts.snap index 6ca4b47..f28ac20 100644 --- a/packages/nx-python/src/generators/migrate-to-shared-venv/__snapshots__/generator.spec.ts.snap +++ b/packages/nx-python/src/generators/migrate-to-shared-venv/__snapshots__/generator.spec.ts.snap @@ -134,3 +134,44 @@ exports[`nx-python migrate-shared-venv generator should migrate an isolate venv "3.8.11 " `; + +exports[`nx-python migrate-shared-venv generator should migrate an isolate venv to shared venv with auto activate enabled 1`] = ` +"[tool.nx] +autoActivate = true + +[tool.poetry] +name = "@nxlv/nx-plugins" +version = "1.0.0" +description = "" +authors = [ ] +license = "Proprietary" +readme = "README.md" + + [tool.poetry.dependencies] + python = ">=3.8,<3.10" + + [tool.poetry.dependencies.proj1] + path = "apps/proj1" + develop = true + +[tool.poetry.group.dev.dependencies] +flake8 = "4.0.1" +flake8-isort = "4.1.1" +flake8-print = "5.0.0" +flake8-pytest-style = "1.6.0" +flake8-docstrings = "1.6.0" +flake8-type-checking = "2.0.6" +autopep8 = "1.5.7" +pytest = "7.1.2" +pytest-env = "0.6.2" +pytest-cov = "3.0.0" +pytest-html = "3.1.1" +pytest-sugar = "0.9.5" +tomli = "1.2.2" +tox = "3.23.1" + +[build-system] +requires = [ "poetry-core==1.1.0" ] +build-backend = "poetry.core.masonry.api" +" +`; diff --git a/packages/nx-python/src/generators/migrate-to-shared-venv/files/pyproject.toml b/packages/nx-python/src/generators/migrate-to-shared-venv/files/pyproject.toml index 06d02f5..8b67102 100644 --- a/packages/nx-python/src/generators/migrate-to-shared-venv/files/pyproject.toml +++ b/packages/nx-python/src/generators/migrate-to-shared-venv/files/pyproject.toml @@ -1,3 +1,8 @@ +<%if (autoActivate) { -%> +[tool.nx] +autoActivate = true + +<% } -%> [tool.poetry] name = "<%= packageName %>" version = "1.0.0" diff --git a/packages/nx-python/src/generators/migrate-to-shared-venv/generator.spec.ts b/packages/nx-python/src/generators/migrate-to-shared-venv/generator.spec.ts index aeef0ce..2b8ab57 100644 --- a/packages/nx-python/src/generators/migrate-to-shared-venv/generator.spec.ts +++ b/packages/nx-python/src/generators/migrate-to-shared-venv/generator.spec.ts @@ -29,6 +29,7 @@ describe('nx-python migrate-shared-venv generator', () => { moveDevDependencies: true, pyenvPythonVersion: '3.8.11', pyprojectPythonDependency: '>=3.8,<3.10', + autoActivate: false, }) ).rejects.toThrow('poetry not found'); @@ -55,6 +56,7 @@ describe('nx-python migrate-shared-venv generator', () => { moveDevDependencies: true, pyenvPythonVersion: '3.8.11', pyprojectPythonDependency: '>=3.8,<3.10', + autoActivate: false, }); task(); @@ -96,6 +98,7 @@ describe('nx-python migrate-shared-venv generator', () => { moveDevDependencies: true, pyenvPythonVersion: '3.8.11', pyprojectPythonDependency: '>=3.8,<3.10', + autoActivate: false, }); task(); @@ -116,4 +119,32 @@ describe('nx-python migrate-shared-venv generator', () => { stdio: 'inherit', }); }); + + it('should migrate an isolate venv to shared venv with auto activate enabled', async () => { + await projectGenerator(appTree, { + name: 'proj1', + type: 'application', + publishable: true, + customSource: false, + addDevDependencies: true, + moduleName: 'proj1', + packageName: 'proj1', + buildLockedVersions: true, + buildBundleLocalDependencies: true, + pyenvPythonVersion: '3.8.11', + pyprojectPythonDependency: '>=3.8,<3.10', + toxEnvlist: 'py38', + }); + + const task = await generator(appTree, { + moveDevDependencies: true, + pyenvPythonVersion: '3.8.11', + pyprojectPythonDependency: '>=3.8,<3.10', + autoActivate: true, + }); + task(); + + expect(checkPoetryExecutableMock).toHaveBeenCalled(); + expect(appTree.read('pyproject.toml', 'utf-8')).toMatchSnapshot(); + }); }); diff --git a/packages/nx-python/src/generators/migrate-to-shared-venv/schema.d.ts b/packages/nx-python/src/generators/migrate-to-shared-venv/schema.d.ts index c223b0e..db129e9 100644 --- a/packages/nx-python/src/generators/migrate-to-shared-venv/schema.d.ts +++ b/packages/nx-python/src/generators/migrate-to-shared-venv/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { moveDevDependencies: boolean; pyprojectPythonDependency: string; pyenvPythonVersion: string; + autoActivate: boolean; } diff --git a/packages/nx-python/src/generators/migrate-to-shared-venv/schema.json b/packages/nx-python/src/generators/migrate-to-shared-venv/schema.json index bd6740c..cb49a39 100644 --- a/packages/nx-python/src/generators/migrate-to-shared-venv/schema.json +++ b/packages/nx-python/src/generators/migrate-to-shared-venv/schema.json @@ -18,6 +18,11 @@ "type": "string", "description": "Pyenv .python-version content", "default": "3.9.5" + }, + "autoActivate": { + "type": "boolean", + "description": "Specifies if the root pyproject toml should be automatically activated when running @nxlv/python executors", + "default": true } }, "required": [] diff --git a/packages/nx-python/src/generators/poetry-project/__snapshots__/generator.spec.ts.snap b/packages/nx-python/src/generators/poetry-project/__snapshots__/generator.spec.ts.snap index ae68ace..e34a913 100644 --- a/packages/nx-python/src/generators/poetry-project/__snapshots__/generator.spec.ts.snap +++ b/packages/nx-python/src/generators/poetry-project/__snapshots__/generator.spec.ts.snap @@ -36,7 +36,7 @@ exports[`application generator custom template dir should run successfully with }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -115,7 +115,7 @@ exports[`application generator individual package should run successfully minima }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -212,7 +212,7 @@ exports[`application generator individual package should run successfully minima }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "libs/test", @@ -309,7 +309,7 @@ exports[`application generator individual package should run successfully minima }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/subdir/test", @@ -409,7 +409,7 @@ exports[`application generator individual package should run successfully minima }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -515,7 +515,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -640,7 +640,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -651,7 +651,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -801,7 +801,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -812,7 +812,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -962,7 +962,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -973,7 +973,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1123,7 +1123,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1134,7 +1134,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1284,7 +1284,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1295,7 +1295,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1445,7 +1445,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1456,7 +1456,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1593,7 +1593,7 @@ exports[`application generator individual package should run successfully with f ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1604,7 +1604,7 @@ exports[`application generator individual package should run successfully with f "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1741,7 +1741,7 @@ exports[`application generator individual package should run successfully with l ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1752,7 +1752,7 @@ exports[`application generator individual package should run successfully with l "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1947,7 +1947,7 @@ exports[`application generator individual package should run successfully with l ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1958,7 +1958,7 @@ exports[`application generator individual package should run successfully with l "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -2145,7 +2145,7 @@ exports[`application generator shared virtual environment should run successfull }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -2262,7 +2262,7 @@ exports[`application generator shared virtual environment should run successfull }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -2379,7 +2379,7 @@ exports[`application generator shared virtual environment should run successfull }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -2497,7 +2497,7 @@ exports[`application generator shared virtual environment should run successfull }, }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", diff --git a/packages/nx-python/src/generators/poetry-project/generator.ts b/packages/nx-python/src/generators/poetry-project/generator.ts index d647961..a4cd262 100644 --- a/packages/nx-python/src/generators/poetry-project/generator.ts +++ b/packages/nx-python/src/generators/poetry-project/generator.ts @@ -344,7 +344,7 @@ export default async function ( const targets: ProjectConfiguration['targets'] = { lock: { - executor: 'nx:run-commands', + executor: '@nxlv/python:run-commands', options: { command: 'poetry lock --no-update', cwd: normalizedOptions.projectRoot, @@ -398,7 +398,7 @@ export default async function ( if (options.unitTestRunner === 'pytest') { targets.test = { - executor: 'nx:run-commands', + executor: '@nxlv/python:run-commands', outputs: [ `{workspaceRoot}/reports/${normalizedOptions.projectRoot}/unittests`, `{workspaceRoot}/coverage/${normalizedOptions.projectRoot}`, diff --git a/packages/nx-python/src/generators/project/__snapshots__/generator.spec.ts.snap b/packages/nx-python/src/generators/project/__snapshots__/generator.spec.ts.snap index 90a0a95..cbbd0f1 100644 --- a/packages/nx-python/src/generators/project/__snapshots__/generator.spec.ts.snap +++ b/packages/nx-python/src/generators/project/__snapshots__/generator.spec.ts.snap @@ -26,7 +26,7 @@ exports[`nx-python project generator should generate a python project into a dif ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p shared_test --render-toc > docs/source/api.md", "cwd": "apps/shared/test", @@ -52,7 +52,7 @@ exports[`nx-python project generator should generate a python project into a dif ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/shared/test", @@ -63,7 +63,7 @@ exports[`nx-python project generator should generate a python project into a dif "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/shared/test", @@ -303,7 +303,7 @@ exports[`nx-python project generator should generate a python project with tags ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p test --render-toc > docs/source/api.md", "cwd": "apps/test", @@ -329,7 +329,7 @@ exports[`nx-python project generator should generate a python project with tags ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -340,7 +340,7 @@ exports[`nx-python project generator should generate a python project with tags "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -395,7 +395,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p test --render-toc > docs/source/api.md", "cwd": "libs/test", @@ -421,7 +421,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "libs/test", @@ -432,7 +432,7 @@ exports[`nx-python project generator should successfully generate a python libra "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "libs/test", @@ -596,7 +596,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p test --render-toc > docs/source/api.md", "cwd": "libs/test", @@ -622,7 +622,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "libs/test", @@ -633,7 +633,7 @@ exports[`nx-python project generator should successfully generate a python libra "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "libs/test", @@ -813,7 +813,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p test --render-toc > docs/source/api.md", "cwd": "libs/test", @@ -839,7 +839,7 @@ exports[`nx-python project generator should successfully generate a python libra ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "libs/test", @@ -850,7 +850,7 @@ exports[`nx-python project generator should successfully generate a python libra "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "libs/test", @@ -922,7 +922,7 @@ exports[`nx-python project generator should successfully generate a python proje ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p test --render-toc > docs/source/api.md", "cwd": "apps/test", @@ -948,7 +948,7 @@ exports[`nx-python project generator should successfully generate a python proje ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -959,7 +959,7 @@ exports[`nx-python project generator should successfully generate a python proje "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", @@ -1123,7 +1123,7 @@ exports[`nx-python project generator should successfully generate a python proje ], }, "docs": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "pydoc-markdown -p mymodule --render-toc > docs/source/api.md", "cwd": "apps/test", @@ -1149,7 +1149,7 @@ exports[`nx-python project generator should successfully generate a python proje ], }, "lock": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry lock --no-update", "cwd": "apps/test", @@ -1160,7 +1160,7 @@ exports[`nx-python project generator should successfully generate a python proje "options": {}, }, "test": { - "executor": "nx:run-commands", + "executor": "@nxlv/python:run-commands", "options": { "command": "poetry run pytest tests/", "cwd": "apps/test", diff --git a/packages/nx-python/src/generators/project/generator.ts b/packages/nx-python/src/generators/project/generator.ts index 397becf..3f43528 100644 --- a/packages/nx-python/src/generators/project/generator.ts +++ b/packages/nx-python/src/generators/project/generator.ts @@ -140,14 +140,14 @@ async function generator(host: Tree, options: Schema) { sourceRoot: `${normalizedOptions.projectRoot}/${normalizedOptions.moduleName}`, targets: { docs: { - executor: 'nx:run-commands', + executor: '@nxlv/python:run-commands', options: { command: `pydoc-markdown -p ${normalizedOptions.moduleName} --render-toc > docs/source/api.md`, cwd: normalizedOptions.projectRoot, }, }, lock: { - executor: 'nx:run-commands', + executor: '@nxlv/python:run-commands', options: { command: 'poetry lock --no-update', cwd: normalizedOptions.projectRoot, @@ -196,7 +196,7 @@ async function generator(host: Tree, options: Schema) { }, }, test: { - executor: 'nx:run-commands', + executor: '@nxlv/python:run-commands', outputs: [ `{workspaceRoot}/reports/${normalizedOptions.projectRoot}/unittests`, `{workspaceRoot}/coverage/${normalizedOptions.projectRoot}`, diff --git a/packages/nx-python/src/graph/dependency-graph.ts b/packages/nx-python/src/graph/dependency-graph.ts index 4ccbb23..0ebf84a 100644 --- a/packages/nx-python/src/graph/dependency-graph.ts +++ b/packages/nx-python/src/graph/dependency-graph.ts @@ -40,6 +40,9 @@ export type PyprojectTomlSource = { export type PyprojectToml = { tool?: { + nx?: { + autoActivate?: boolean; + }; poetry?: { name: string; version: string; diff --git a/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.spec.ts b/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.spec.ts new file mode 100644 index 0000000..8ccdcfc --- /dev/null +++ b/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.spec.ts @@ -0,0 +1,60 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { + Tree, + readProjectConfiguration, + updateProjectConfiguration, +} from '@nx/devkit'; +import generator from '../../generators/poetry-project/generator'; +import * as poetryUtils from '../../executors/utils/poetry'; + +import update from './replace-nx-run-commands'; + +describe('16-1-0-replace-nx-run-commands migration', () => { + let tree: Tree; + let checkPoetryExecutableMock: jest.SpyInstance; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + checkPoetryExecutableMock = jest + .spyOn(poetryUtils, 'checkPoetryExecutable') + .mockResolvedValue(undefined); + }); + + it('should run successfully', async () => { + await generator(tree, { + name: 'test', + projectType: 'application', + pyprojectPythonDependency: '', + pyenvPythonVersion: '', + publishable: false, + buildLockedVersions: false, + buildBundleLocalDependencies: false, + linter: 'none', + unitTestRunner: 'pytest', + rootPyprojectDependencyGroup: 'main', + unitTestHtmlReport: false, + unitTestJUnitReport: false, + codeCoverage: false, + codeCoverageHtmlReport: false, + codeCoverageXmlReport: false, + }); + + const projectConfig = readProjectConfiguration(tree, 'test'); + projectConfig.targets.lock.executor = 'nx:run-commands'; + projectConfig.targets.test.executor = 'nx:run-commands'; + + updateProjectConfiguration(tree, 'test', projectConfig); + + await update(tree); + + const updatedProjectConfig = readProjectConfiguration(tree, 'test'); + + expect(updatedProjectConfig.targets.lock.executor).toEqual( + '@nxlv/python:run-commands' + ); + expect(updatedProjectConfig.targets.test.executor).toEqual( + '@nxlv/python:run-commands' + ); + expect(checkPoetryExecutableMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.ts b/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.ts new file mode 100644 index 0000000..6457fd1 --- /dev/null +++ b/packages/nx-python/src/migrations/update-16-1-0/replace-nx-run-commands.ts @@ -0,0 +1,27 @@ +import { + Tree, + getProjects, + readProjectConfiguration, + updateProjectConfiguration, +} from '@nx/devkit'; +import path from 'path'; + +export default function update(host: Tree) { + for (const [projectName, data] of getProjects(host)) { + const projectTomlPath = path.join(data.root, 'pyproject.toml'); + const projectConfigPath = path.join(data.root, 'project.json'); + + if (host.exists(projectTomlPath) && host.exists(projectConfigPath)) { + const projectConfig = readProjectConfiguration(host, projectName); + + if ('lock' in projectConfig.targets) { + projectConfig.targets.lock.executor = '@nxlv/python:run-commands'; + } + if ('test' in projectConfig.targets) { + projectConfig.targets.test.executor = '@nxlv/python:run-commands'; + } + + updateProjectConfiguration(host, projectName, projectConfig); + } + } +}